diff --git a/.github/workflows/build-clang-doxy.yml b/.github/workflows/build-clang-doxy.yml index 084aca223..3bd827a02 100644 --- a/.github/workflows/build-clang-doxy.yml +++ b/.github/workflows/build-clang-doxy.yml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Brent Rubell for Adafruit Industries, 2023 +# SPDX-FileCopyrightText: Brent Rubell for Adafruit Industries, 2023-2024 # # SPDX-License-Identifier: MIT name: WipperSnapper Build CI diff --git a/.github/workflows/release-callee.yml b/.github/workflows/release-callee.yml index 730812bd3..8399709ea 100644 --- a/.github/workflows/release-callee.yml +++ b/.github/workflows/release-callee.yml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Brent Rubell for Adafruit Industries, 2022 +# SPDX-FileCopyrightText: Brent Rubell for Adafruit Industries, 2022-2024 # # SPDX-License-Identifier: MIT name: WipperSnapper Release Callee diff --git a/.github/workflows/release-caller.yml b/.github/workflows/release-caller.yml index 91839b1e9..231ab93bf 100644 --- a/.github/workflows/release-caller.yml +++ b/.github/workflows/release-caller.yml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Brent Rubell for Adafruit Industries, 2022 +# SPDX-FileCopyrightText: Brent Rubell for Adafruit Industries, 2022 - 2024 # # SPDX-License-Identifier: MIT name: WipperSnapper Release Workflow diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml new file mode 100644 index 000000000..015c8a479 --- /dev/null +++ b/.github/workflows/run-tests.yml @@ -0,0 +1,54 @@ +# SPDX-FileCopyrightText: Brent Rubell for Adafruit Industries, 2024 +# +# SPDX-License-Identifier: MIT +name: WipperSnapper Tests + +on: + pull_request: + +permissions: + checks: write + pull-requests: write + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + test: + - test_offline + fail-fast: false + steps: + - name: Check out repository code + uses: actions/checkout@v3 + + - name: Check out repository code + uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.13' + + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install -r tests/requirements.txt + + - name: Install Wokwi CI server + uses: wokwi/wokwi-ci-server-action@v1 + + - name: Install Wokwi CLI + run: curl -L https://wokwi.com/ci/install.sh | sh + + - name: Test on Wokwi + run: pytest tests/${{ matrix.test }}.py --junitxml=report.xml -v + env: + WOKWI_CLI_TOKEN: ${{ secrets.WOKWI_CLI_TOKEN }} + + - name: Publish Test Results + uses: EnricoMi/publish-unit-test-result-action@v2 + if: always() + with: + files: | + /home/runner/work/Adafruit_Wippersnapper_Arduino/Adafruit_Wippersnapper_Arduino/report.xml \ No newline at end of file diff --git a/.gitignore b/.gitignore index b22a8d404..864ae0611 100644 --- a/.gitignore +++ b/.gitignore @@ -39,13 +39,19 @@ html/* # VSCode artifacts .vscode/* src/.vscode/settings.json - .DS_STORE - examples/Wippersnapper_demo/build/ +# Virtual environment directories +.venv/ +venv/ + +# Python artifacts +tests/__pycache__/ + # Platformio artifacts .pio/ -# Secrets +# These sometimes contain credentials, don't commit them! +src/Wippersnapper_demo_wokwi.ino data/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 65076780b..40454cefb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,7 +3,7 @@ "limits": "c", "type_traits": "c" }, - "C_Cpp.dimInactiveRegions": true, + "C_Cpp.dimInactiveRegions": false, "dotnet.defaultSolution": "disable", "cmake.configureOnOpen": false } \ No newline at end of file diff --git a/Doxyfile b/Doxyfile index c05228416..91d657f55 100644 --- a/Doxyfile +++ b/Doxyfile @@ -1,4 +1,4 @@ -# Doxyfile 1.9.1 +# Doxyfile 1.9.8 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. @@ -12,6 +12,16 @@ # For lists, items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (\" \"). +# +# Note: +# +# Use doxygen to compare the used configuration file with the template +# configuration file: +# doxygen -x [configFile] +# Use doxygen to compare the used configuration file with the template +# configuration file without replacing the environment variables or CMake type +# replacement variables: +# doxygen -x_noenv [configFile] #--------------------------------------------------------------------------- # Project related configuration options @@ -60,16 +70,28 @@ PROJECT_LOGO = OUTPUT_DIRECTORY = -# 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 +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 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. +# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to +# control the number of sub-directories. # The default value is: NO. CREATE_SUBDIRS = NO +# Controls the number of sub-directories that will be created when +# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every +# level increment doubles the number of directories, resulting in 4096 +# directories at level 8 which is the default and also the maximum value. The +# sub-directories are organized in 2 levels, the first level always has a fixed +# number of 16 directories. +# Minimum value: 0, maximum value: 8, default value: 8. +# This tag requires that the tag CREATE_SUBDIRS is set to YES. + +CREATE_SUBDIRS_LEVEL = 8 + # If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII # characters to appear in the names of generated files. If set to NO, non-ASCII # characters will be escaped, for example _xE3_x81_x84 will be used for Unicode @@ -81,14 +103,14 @@ ALLOW_UNICODE_NAMES = 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. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, +# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English +# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, +# Hindi, 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 @@ -189,6 +211,16 @@ SHORT_NAMES = NO JAVADOC_AUTOBRIEF = NO +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = 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 @@ -209,6 +241,14 @@ QT_AUTOBRIEF = NO MULTILINE_CPP_IS_BRIEF = NO +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + # 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. @@ -232,16 +272,16 @@ TAB_SIZE = 4 # the documentation. An alias has the form: # name=value # For example adding -# "sideeffect=@par Side Effects:\n" +# "sideeffect=@par Side Effects:^^" # 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 (in the resulting output). You can put ^^ in the value part of an -# alias to insert a newline as if a physical newline was in the original file. -# When you need a literal { or } or , in the value part of an alias you have to -# escape them by means of a backslash (\), this can lead to conflicts with the -# commands \{ and \} for these it is advised to use the version @{ and @} or use -# a double escape (\\{ and \\}) +# "Side Effects:". Note that you cannot put \n's in the value part of an alias +# to insert newlines (in the resulting output). You can put ^^ in the value part +# of an alias to insert a newline as if a physical newline was in the original +# file. When you need a literal { or } or , in the value part of an alias you +# have to escape them by means of a backslash (\), this can lead to conflicts +# with the commands \{ and \} for these it is advised to use the version @{ and +# @} or use a double escape (\\{ and \\}) ALIASES = @@ -273,13 +313,21 @@ OPTIMIZE_FOR_FORTRAN = NO OPTIMIZE_OUTPUT_VHDL = NO +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = 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, -# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL, -# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, +# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: # FortranFree, unknown formatted Fortran: Fortran. In the later case the parser # tries to guess whether the code is fixed or free formatted code, this is the # default for Fortran type files). For instance to make doxygen treat .inc files @@ -315,6 +363,17 @@ MARKDOWN_SUPPORT = YES TOC_INCLUDE_HEADINGS = 0 +# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to +# generate identifiers for the Markdown headings. Note: Every identifier is +# unique. +# Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a +# sequence number starting at 0 and GITHUB use the lower case version of title +# with any whitespace replaced by '-' and punctuation characters removed. +# The default value is: DOXYGEN. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +MARKDOWN_ID_STYLE = DOXYGEN + # 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 putting a % sign in front of the word or @@ -426,6 +485,27 @@ TYPEDEF_HIDES_STRUCT = NO LOOKUP_CACHE_SIZE = 0 +# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which effectively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + +# If the TIMESTAMP tag is set different from NO then each generated page will +# contain the date or date and time when the page was generated. Setting this to +# NO can help when comparing the output of multiple runs. +# Possible values are: YES, NO, DATETIME and DATE. +# The default value is: NO. + +TIMESTAMP = NO + #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- @@ -446,6 +526,12 @@ EXTRACT_ALL = NO EXTRACT_PRIVATE = NO +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = NO + # 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. @@ -483,6 +569,13 @@ EXTRACT_LOCAL_METHODS = NO EXTRACT_ANON_NSPACES = NO +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + # 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 @@ -494,7 +587,8 @@ 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. +# will also hide undocumented C++ concepts if enabled. This option has no effect +# if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_CLASSES = NO @@ -525,14 +619,15 @@ INTERNAL_DOCS = NO # filesystem is case sensitive (i.e. it supports files in the same directory # whose names only differ in casing), the option must be set to YES to properly # deal with such files in case they appear in the input. For filesystems that -# are not case sensitive the option should be be set to NO to properly deal with +# are not case sensitive the option should be set to NO to properly deal with # output files written for symbols that only differ in casing, such as for two # classes, one named CLASS and the other named Class, and to also support # references to files without having to specify the exact matching casing. On # Windows (including Cygwin) and MacOS, users should typically set this option # to NO, whereas on Linux or other Unix flavors it should typically be set to # YES. -# The default value is: system dependent. +# Possible values are: SYSTEM, NO and YES. +# The default value is: SYSTEM. CASE_SENSE_NAMES = NO @@ -550,6 +645,12 @@ HIDE_SCOPE_NAMES = NO HIDE_COMPOUND_REFERENCE= NO +# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class +# will show which file needs to be included to use the class. +# The default value is: YES. + +SHOW_HEADERFILE = YES + # 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. @@ -707,7 +808,8 @@ FILE_VERSION_FILTER = # 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. +# will be used as the name of the layout file. See also section "Changing the +# layout of pages" for information. # # 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 @@ -753,27 +855,50 @@ WARNINGS = 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. +# potential errors in the documentation, such as documenting some parameters in +# a documented function twice, or documenting parameters that don't exist or +# using markup commands wrongly. # The default value is: YES. WARN_IF_DOC_ERROR = YES +# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete +# function parameter documentation. If set to NO, doxygen will accept that some +# parameters have no documentation without warning. +# The default value is: YES. + +WARN_IF_INCOMPLETE_DOC = 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. If -# EXTRACT_ALL is set to YES then this flag will automatically be disabled. +# value. If set to NO, doxygen will only warn about wrong parameter +# documentation, but not about the absence of documentation. If EXTRACT_ALL is +# set to YES then this flag will automatically be disabled. See also +# WARN_IF_INCOMPLETE_DOC # The default value is: NO. WARN_NO_PARAMDOC = YES +# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about +# undocumented enumeration values. If set to NO, doxygen will accept +# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: NO. + +WARN_IF_UNDOC_ENUM_VAL = NO + # If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when # a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS # then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but # at the end of the doxygen process doxygen will return with a non-zero status. -# Possible values are: NO, YES and FAIL_ON_WARNINGS. +# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then doxygen behaves +# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined doxygen will not +# write the warning messages in between other messages but write them at the end +# of a run, in case a WARN_LOGFILE is defined the warning messages will be +# besides being in the defined file also be shown at the end of a run, unless +# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case +# the behavior will remain as with the setting FAIL_ON_WARNINGS. +# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. # The default value is: NO. WARN_AS_ERROR = NO @@ -784,13 +909,27 @@ WARN_AS_ERROR = NO # 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) +# See also: WARN_LINE_FORMAT # The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" +# In the $text part of the WARN_FORMAT command it is possible that a reference +# to a more specific place is given. To make it easier to jump to this place +# (outside of doxygen) the user can define a custom "cut" / "paste" string. +# Example: +# WARN_LINE_FORMAT = "'vi $file +$line'" +# See also: WARN_FORMAT +# The default value is: at line $line of file $file. + +WARN_LINE_FORMAT = "at line $line of file $file" + # 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). +# error (stderr). In case the file specified cannot be opened for writing the +# warning and error messages are written to standard error. When as file - is +# specified the warning and error messages are written to standard output +# (stdout). WARN_LOGFILE = @@ -811,10 +950,21 @@ INPUT = # libiconv (or the iconv built into libc) for the transcoding. See the libiconv # documentation (see: # https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# See also: INPUT_FILE_ENCODING # The default value is: UTF-8. INPUT_ENCODING = UTF-8 +# This tag can be used to specify the character encoding of the source files +# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify +# character encoding on a per file pattern basis. Doxygen will compare the file +# name with each pattern and apply the encoding instead of the default +# INPUT_ENCODING) if there is a match. The character encodings are a list of the +# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding +# "INPUT_ENCODING" for further information on supported encodings. + +INPUT_FILE_ENCODING = + # 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. @@ -826,12 +976,12 @@ INPUT_ENCODING = UTF-8 # Note the list of default checked file patterns might differ from the list of # default file extension mappings. # -# 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 (to be provided as doxygen C comment), -# *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl, -# *.ucf, *.qsf and *.ice. +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm, +# *.cpp, *.cppm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, +# *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, *.php, +# *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be +# provided as doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.c \ *.cc \ @@ -863,7 +1013,8 @@ EXCLUDE = EXCLUDE \ src/nanopb \ src/provisioning/tinyusb/fatfs \ src/pb.h \ - src/provisioning + src/provisioning \ + src/protos # 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 @@ -885,10 +1036,7 @@ EXCLUDE_PATTERNS = *.md # (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/* +# ANamespace::AClass, ANamespace::*Test EXCLUDE_SYMBOLS = @@ -933,6 +1081,11 @@ IMAGE_PATH = # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. # +# Note that doxygen will use the data processed and written to standard output +# for further processing, therefore nothing else, like debug statements or used +# commands (so in case of a Windows batch file always use @echo OFF), should be +# written to standard output. +# # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by doxygen. @@ -974,6 +1127,15 @@ FILTER_SOURCE_PATTERNS = USE_MDFILE_AS_MAINPAGE = +# The Fortran standard specifies that for fixed formatted Fortran code all +# characters from position 72 are to be considered as comment. A common +# extension is to allow longer lines before the automatic comment starts. The +# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can +# be processed before the automatic comment starts. +# Minimum value: 7, maximum value: 10000, default value: 72. + +FORTRAN_COMMENT_AFTER = 72 + #--------------------------------------------------------------------------- # Configuration options related to source browsing #--------------------------------------------------------------------------- @@ -1071,10 +1233,11 @@ VERBATIM_HEADERS = YES ALPHABETICAL_INDEX = YES -# 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. +# 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. The IGNORE_PREFIX +# tag works for classes, function and member names. The entity will be placed in +# the alphabetical list under the first letter of the entity name that remains +# after removing the prefix. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = @@ -1153,7 +1316,12 @@ HTML_STYLESHEET = # Doxygen will copy the style sheet files to the output directory. # Note: The order of the extra style sheet files is of importance (e.g. the last # style sheet in the list overrules the setting of the previous ones in the -# list). For an example see the documentation. +# list). +# Note: Since the styling of scrollbars can currently not be overruled in +# Webkit/Chromium, the styling will be left out of the default doxygen.css if +# one or more extra stylesheets have been specified. So if scrollbar +# customization is desired it has to be added explicitly. For an example see the +# documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_STYLESHEET = @@ -1168,9 +1336,22 @@ HTML_EXTRA_STYLESHEET = HTML_EXTRA_FILES = +# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output +# should be rendered with a dark or light theme. +# Possible values are: LIGHT always generate light mode output, DARK always +# generate dark mode output, AUTO_LIGHT automatically set the mode according to +# the user preference, use light mode if no preference is set (the default), +# AUTO_DARK automatically set the mode according to the user preference, use +# dark mode if no preference is set and TOGGLE allow to user to switch between +# light and dark mode via a button. +# The default value is: AUTO_LIGHT. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE = AUTO_LIGHT + # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to -# this color. Hue is specified as an angle on a colorwheel, see +# this color. Hue is specified as an angle on a color-wheel, see # https://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. @@ -1180,7 +1361,7 @@ HTML_EXTRA_FILES = 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 +# in the HTML output. For a value of 0 the output will use gray-scales 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. @@ -1198,14 +1379,16 @@ HTML_COLORSTYLE_SAT = 100 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 YES can help to show when doxygen was last run and thus if the -# documentation is up to date. -# The default value is: NO. +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_TIMESTAMP = NO +HTML_DYNAMIC_MENUS = 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 @@ -1215,6 +1398,13 @@ HTML_TIMESTAMP = NO HTML_DYNAMIC_SECTIONS = NO +# If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be +# dynamically folded and expanded in the generated HTML source code. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_CODE_FOLDING = 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 @@ -1251,6 +1441,13 @@ GENERATE_DOCSET = NO DOCSET_FEEDNAME = "Doxygen generated docs" +# This tag determines the URL 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. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDURL = + # 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. @@ -1276,8 +1473,12 @@ 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: -# https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows. +# on Windows. In the beginning of 2021 Microsoft took the original page, with +# a.o. the download links, offline the HTML help workshop was already many years +# in maintenance mode). You can download the HTML help workshop from the web +# archives at Installation executable (see: +# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo +# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). # # 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 @@ -1334,6 +1535,16 @@ BINARY_TOC = NO TOC_EXPAND = NO +# The SITEMAP_URL tag is used to specify the full URL of the place where the +# generated documentation will be placed on the server by the user during the +# deployment of the documentation. The generated sitemap is called sitemap.xml +# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL +# is specified no sitemap is generated. For information about the sitemap +# protocol see https://www.sitemaps.org +# This tag requires that the tag GENERATE_HTML is set to YES. + +SITEMAP_URL = + # 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 @@ -1436,16 +1647,28 @@ DISABLE_INDEX = NO # 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 style sheets (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. +# further fine tune the look of the index (see "Fine-tuning the output"). 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 +# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the +# FULL_SIDEBAR option determines if the side bar is limited to only the treeview +# area (value NO) or if it should extend to the full height of the window (value +# YES). Setting this to YES gives a layout similar to +# https://docs.readthedocs.io with more room for contents, but less room for the +# project logo, title, and description. If either GENERATE_TREEVIEW or +# DISABLE_INDEX is set to NO, this option has no effect. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FULL_SIDEBAR = 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. # @@ -1470,6 +1693,24 @@ TREEVIEW_WIDTH = 250 EXT_LINKS_IN_WINDOW = NO +# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email +# addresses. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +OBFUSCATE_EMAILS = YES + +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + # 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 @@ -1479,16 +1720,11 @@ EXT_LINKS_IN_WINDOW = NO FORMULA_FONTSIZE = 10 -# Use the FORMULA_TRANSPARENT 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. +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. -FORMULA_TRANSPARENT = YES +FORMULA_MACROFILE = # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see # https://www.mathjax.org) which uses client side JavaScript for the rendering @@ -1501,11 +1737,29 @@ FORMULA_TRANSPARENT = YES USE_MATHJAX = NO +# With MATHJAX_VERSION it is possible to specify the MathJax version to be used. +# Note that the different versions of MathJax have different requirements with +# regards to the different settings, so it is possible that also other MathJax +# settings have to be changed when switching between the different MathJax +# versions. +# Possible values are: MathJax_2 and MathJax_3. +# The default value is: MathJax_2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_VERSION = MathJax_2 + # 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/v2.7-latest/output.html) for more details. +# the MathJax output. For more details about the output format see MathJax +# version 2 (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 +# (see: +# http://docs.mathjax.org/en/latest/web/components/output.html). # Possible values are: HTML-CSS (which is slower, but has the best -# compatibility), NativeMML (i.e. MathML) and SVG. +# compatibility. This is the name for Mathjax version 2, for MathJax version 3 +# this will be translated into chtml), NativeMML (i.e. MathML. Only supported +# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This +# is the name for Mathjax version 3, for MathJax version 2 this will be +# translated into HTML-CSS) and SVG. # The default value is: HTML-CSS. # This tag requires that the tag USE_MATHJAX is set to YES. @@ -1518,15 +1772,21 @@ MATHJAX_FORMAT = HTML-CSS # 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 https://www.mathjax.org before deployment. -# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2. +# MathJax from https://www.mathjax.org before deployment. The default value is: +# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 +# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 # 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 +# for MathJax version 2 (see +# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# For example for MathJax version 3 (see +# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): +# MATHJAX_EXTENSIONS = ams # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_EXTENSIONS = @@ -1667,6 +1927,16 @@ LATEX_CMD_NAME = latex MAKEINDEX_CMD_NAME = makeindex +# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to +# generate index for LaTeX. In case there is no backslash (\) as first character +# it will be automatically added in the LaTeX code. +# Note: This tag is used in the generated output file (.tex). +# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat. +# The default value is: makeindex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_MAKEINDEX_CMD = makeindex + # If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX # documents. This may be useful for small projects and may help to save some # trees in general. @@ -1696,29 +1966,31 @@ PAPER_TYPE = a4 EXTRA_PACKAGES = -# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the -# generated LaTeX document. The header should contain everything until the first -# chapter. If it is left blank doxygen will generate a standard header. See -# section "Doxygen usage" for information on how to let doxygen write the -# default header to a separate file. +# The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for +# the generated LaTeX document. The header should contain everything until the +# first chapter. If it is left blank doxygen will generate a standard header. It +# is highly recommended to start with a default header using +# doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty +# and then modify the file new_header.tex. See also section "Doxygen usage" for +# information on how to generate the default header that doxygen normally uses. # -# Note: Only use a user-defined header if you know what you are doing! The -# following commands have a special meaning inside the header: $title, -# $datetime, $date, $doxygenversion, $projectname, $projectnumber, -# $projectbrief, $projectlogo. Doxygen will replace $title with the empty -# string, for the replacement values of the other commands the user is referred -# to HTML_HEADER. +# Note: Only use a user-defined header if you know what you are doing! +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. The following +# commands have a special meaning inside the header (and footer): For a +# description of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_HEADER = -# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the -# generated LaTeX document. The footer should contain everything after the last -# chapter. If it is left blank doxygen will generate a standard footer. See +# The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for +# the generated LaTeX document. The footer should contain everything after the +# last chapter. If it is left blank doxygen will generate a standard footer. See # LATEX_HEADER for more information on how to generate a default footer and what -# special commands can be used inside the footer. -# -# Note: Only use a user-defined footer if you know what you are doing! +# 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. Note: Only use a user-defined footer if you know what you are +# doing! # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_FOOTER = @@ -1761,10 +2033,16 @@ PDF_HYPERLINKS = YES USE_PDFLATEX = YES -# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode -# command to the generated LaTeX files. This will instruct LaTeX to keep running -# if errors occur, instead of asking the user for help. This option is also used -# when generating formulas in HTML. +# The LATEX_BATCHMODE tag signals the behavior of LaTeX in case of an error. +# Possible values are: NO same as ERROR_STOP, YES same as BATCH, BATCH In batch +# mode nothing is printed on the terminal, errors are scrolled as if is +# hit at every error; missing files that TeX tries to input or request from +# keyboard input (\read on a not open input stream) cause the job to abort, +# NON_STOP In nonstop mode the diagnostic message will appear on the terminal, +# but there is no possibility of user interaction just like in batch mode, +# SCROLL In scroll mode, TeX will stop only for missing files to input or if +# keyboard input is necessary and ERROR_STOP In errorstop mode, TeX will stop at +# each error, asking for user intervention. # The default value is: NO. # This tag requires that the tag GENERATE_LATEX is set to YES. @@ -1777,16 +2055,6 @@ LATEX_BATCHMODE = NO LATEX_HIDE_INDICES = NO -# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source -# code with syntax highlighting in the LaTeX output. -# -# Note that which sources are shown also depends on other settings such as -# SOURCE_BROWSER. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_SOURCE_CODE = NO - # The LATEX_BIB_STYLE tag can be used to specify the style to use for the # bibliography, e.g. plainnat, or ieeetr. See # https://en.wikipedia.org/wiki/BibTeX and \cite for more info. @@ -1795,13 +2063,13 @@ LATEX_SOURCE_CODE = NO LATEX_BIB_STYLE = plain -# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated -# 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: NO. +# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) +# path from which the emoji images will be read. If a relative path is entered, +# it will be relative to the LATEX_OUTPUT directory. If left blank the +# LATEX_OUTPUT directory will be used. # This tag requires that the tag GENERATE_LATEX is set to YES. -LATEX_TIMESTAMP = NO +LATEX_EMOJI_DIRECTORY = #--------------------------------------------------------------------------- # Configuration options related to the RTF output @@ -1859,16 +2127,6 @@ RTF_STYLESHEET_FILE = RTF_EXTENSIONS_FILE = -# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code -# with syntax highlighting in the RTF output. -# -# Note that which sources are shown also depends on other settings such as -# SOURCE_BROWSER. -# The default value is: NO. -# This tag requires that the tag GENERATE_RTF is set to YES. - -RTF_SOURCE_CODE = NO - #--------------------------------------------------------------------------- # Configuration options related to the man page output #--------------------------------------------------------------------------- @@ -1940,6 +2198,13 @@ XML_OUTPUT = xml XML_PROGRAMLISTING = YES +# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include +# namespace members in file scope as well, matching the HTML output. +# The default value is: NO. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_NS_MEMB_FILE_SCOPE = NO + #--------------------------------------------------------------------------- # Configuration options related to the DOCBOOK output #--------------------------------------------------------------------------- @@ -1958,27 +2223,44 @@ GENERATE_DOCBOOK = NO DOCBOOK_OUTPUT = docbook -# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the -# program listings (including syntax highlighting and cross-referencing -# information) to the DOCBOOK output. Note that enabling this will significantly -# increase the size of the DOCBOOK output. -# The default value is: NO. -# This tag requires that the tag GENERATE_DOCBOOK is set to YES. - -DOCBOOK_PROGRAMLISTING = NO - #--------------------------------------------------------------------------- # Configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an -# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures +# AutoGen Definitions (see https://autogen.sourceforge.net/) file that captures # the structure of the code including all documentation. Note that this feature # is still experimental and incomplete at the moment. # The default value is: NO. GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# Configuration options related to Sqlite3 output +#--------------------------------------------------------------------------- + +# If the GENERATE_SQLITE3 tag is set to YES doxygen will generate a Sqlite3 +# database with symbols found by doxygen stored in tables. +# The default value is: NO. + +GENERATE_SQLITE3 = NO + +# The SQLITE3_OUTPUT tag is used to specify where the Sqlite3 database 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: sqlite3. +# This tag requires that the tag GENERATE_SQLITE3 is set to YES. + +SQLITE3_OUTPUT = sqlite3 + +# The SQLITE3_OVERWRITE_DB tag is set to YES, the existing doxygen_sqlite3.db +# database file will be recreated with each doxygen run. If set to NO, doxygen +# will warn if an a database file is already found and not modify it. +# The default value is: YES. +# This tag requires that the tag GENERATE_SQLITE3 is set to YES. + +SQLITE3_RECREATE_DB = YES + #--------------------------------------------------------------------------- # Configuration options related to the Perl module output #--------------------------------------------------------------------------- @@ -2053,7 +2335,8 @@ SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by the -# preprocessor. +# preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of +# RECURSIVE has no effect here. # This tag requires that the tag SEARCH_INCLUDES is set to YES. INCLUDE_PATH = @@ -2074,7 +2357,9 @@ INCLUDE_FILE_PATTERNS = # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = __cdecl=, ARDUINO_ARCH_ESP32=1, ARDUINO_FUNHOUSE_ESP32S2=1 +PREDEFINED = "__cdecl=," \ + "ARDUINO_ARCH_ESP32=1," \ + ARDUINO_FUNHOUSE_ESP32S2=1 # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # tag can be used to specify a list of macro names that should be expanded. The @@ -2120,15 +2405,15 @@ TAGFILES = GENERATE_TAGFILE = -# If the ALLEXTERNALS tag is set to YES, all external class will be listed in -# the class index. If set to NO, only the inherited external classes will be -# listed. +# If the ALLEXTERNALS tag is set to YES, all external classes and namespaces +# will be listed in the class and namespace index. If set to NO, only the +# inherited external classes will be listed. # The default value is: NO. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed -# in the modules index. If set to NO, only the current project's groups will be +# in the topic index. If set to NO, only the current project's groups will be # listed. # The default value is: YES. @@ -2142,25 +2427,9 @@ EXTERNAL_GROUPS = YES EXTERNAL_PAGES = YES #--------------------------------------------------------------------------- -# Configuration options related to the dot tool +# Configuration options related to diagram generator tools #--------------------------------------------------------------------------- -# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram -# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to -# NO turns the diagrams off. Note that this option also works with HAVE_DOT -# disabled, but it is recommended to install and use dot, since it yields more -# powerful graphs. -# The default value is: YES. - -CLASS_DIAGRAMS = YES - -# You can include diagrams made with dia in doxygen documentation. Doxygen will -# then run dia to produce the diagram and insert it in the documentation. The -# DIA_PATH tag allows you to specify the directory where the dia binary resides. -# If left empty dia is assumed to be found in the default search path. - -DIA_PATH = - # If set to YES the inheritance and collaboration graphs will hide inheritance # and usage relations if the target is undocumented or is not a class. # The default value is: YES. @@ -2169,7 +2438,7 @@ HIDE_UNDOC_RELATIONS = YES # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz (see: -# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent +# https://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent # Bell Labs. The other options in this section have no effect if this option is # set to NO # The default value is: NO. @@ -2186,49 +2455,73 @@ HAVE_DOT = NO DOT_NUM_THREADS = 0 -# When you want a differently looking font in the dot files that doxygen -# generates you can specify the font name using DOT_FONTNAME. You need to make -# sure dot is able to find the font, which can be done by putting it in a -# standard location or by setting the DOTFONTPATH environment variable or by -# setting DOT_FONTPATH to the directory containing the font. -# The default value is: Helvetica. +# DOT_COMMON_ATTR is common attributes for nodes, edges and labels of +# subgraphs. When you want a differently looking font in the dot files that +# doxygen generates you can specify fontname, fontcolor and fontsize attributes. +# For details please see Node, +# Edge and Graph Attributes specification You need to make sure dot is able +# to find the font, which can be done by putting it in a standard location or by +# setting the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. Default graphviz fontsize is 14. +# The default value is: fontname=Helvetica,fontsize=10. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_FONTNAME = Helvetica +DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10" -# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of -# dot graphs. -# Minimum value: 4, maximum value: 24, default value: 10. +# DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can +# add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. Complete documentation about +# arrows shapes. +# The default value is: labelfontname=Helvetica,labelfontsize=10. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_FONTSIZE = 10 +DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10" -# By default doxygen will tell dot to use the default font as specified with -# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set -# the path where dot can find it using this tag. +# DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes +# around nodes set 'shape=plain' or 'shape=plaintext' Shapes specification +# The default value is: shape=box,height=0.2,width=0.4. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4" + +# You can set the path where dot can find font specified with fontname in +# DOT_COMMON_ATTR and others dot attributes. # This tag requires that the tag HAVE_DOT is set to YES. DOT_FONTPATH = -# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for -# each documented class showing the direct and indirect inheritance relations. -# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO. +# If the CLASS_GRAPH tag is set to YES or GRAPH or BUILTIN then doxygen will +# generate a graph for each documented class showing the direct and indirect +# inheritance relations. In case the CLASS_GRAPH tag is set to YES or GRAPH and +# HAVE_DOT is enabled as well, then dot will be used to draw the graph. In case +# the CLASS_GRAPH tag is set to YES and HAVE_DOT is disabled or if the +# CLASS_GRAPH tag is set to BUILTIN, then the built-in generator will be used. +# If the CLASS_GRAPH tag is set to TEXT the direct and indirect inheritance +# relations will be shown as texts / links. +# Possible values are: NO, YES, TEXT, GRAPH and BUILTIN. # The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. CLASS_GRAPH = YES # If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a # graph for each documented class showing the direct and indirect implementation # dependencies (inheritance, containment, and class references variables) of the -# class with other documented classes. +# class with other documented classes. Explicit enabling a collaboration graph, +# when COLLABORATION_GRAPH is set to NO, can be accomplished by means of the +# command \collaborationgraph. Disabling a collaboration graph can be +# accomplished by means of the command \hidecollaborationgraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for -# groups, showing the direct groups dependencies. +# groups, showing the direct groups dependencies. Explicit enabling a group +# dependency graph, when GROUP_GRAPHS is set to NO, can be accomplished by means +# of the command \groupgraph. Disabling a directory graph can be accomplished by +# means of the command \hidegroupgraph. See also the chapter Grouping in the +# manual. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2255,6 +2548,28 @@ UML_LOOK = NO UML_LIMIT_NUM_FIELDS = 10 +# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and +# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS +# tag is set to YES, doxygen will add type and arguments for attributes and +# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen +# will not generate fields with class member information in the UML graphs. The +# class diagrams will look similar to the default class diagrams but using UML +# notation for the relationships. +# Possible values are: NO, YES and NONE. +# The default value is: NO. +# This tag requires that the tag UML_LOOK is set to YES. + +DOT_UML_DETAILS = NO + +# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters +# to display on a single line. If the actual line length exceeds this threshold +# significantly it will wrapped across multiple lines. Some heuristics are apply +# to avoid ugly line breaks. +# Minimum value: 0, maximum value: 1000, default value: 17. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_WRAP_THRESHOLD = 17 + # If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and # collaboration graphs will show the relations between templates and their # instances. @@ -2266,7 +2581,9 @@ TEMPLATE_RELATIONS = NO # If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to # YES then doxygen will generate a graph for each documented file showing the # direct and indirect include dependencies of the file with other documented -# files. +# files. Explicit enabling an include graph, when INCLUDE_GRAPH is is set to NO, +# can be accomplished by means of the command \includegraph. Disabling an +# include graph can be accomplished by means of the command \hideincludegraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2275,7 +2592,10 @@ INCLUDE_GRAPH = YES # If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are # set to YES then doxygen will generate a graph for each documented file showing # the direct and indirect include dependencies of the file with other documented -# files. +# files. Explicit enabling an included by graph, when INCLUDED_BY_GRAPH is set +# to NO, can be accomplished by means of the command \includedbygraph. Disabling +# an included by graph can be accomplished by means of the command +# \hideincludedbygraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2315,16 +2635,26 @@ GRAPHICAL_HIERARCHY = YES # If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the # dependencies a directory has on other directories in a graphical way. The # dependency relations are determined by the #include relations between the -# files in the directories. +# files in the directories. Explicit enabling a directory graph, when +# DIRECTORY_GRAPH is set to NO, can be accomplished by means of the command +# \directorygraph. Disabling a directory graph can be accomplished by means of +# the command \hidedirectorygraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. DIRECTORY_GRAPH = YES +# The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels +# of child directories generated in directory dependency graphs by dot. +# Minimum value: 1, maximum value: 25, default value: 1. +# This tag requires that the tag DIRECTORY_GRAPH is set to YES. + +DIR_GRAPH_MAX_DEPTH = 1 + # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. For an explanation of the image formats see the section # output formats in the documentation of the dot tool (Graphviz (see: -# http://www.graphviz.org/)). +# https://www.graphviz.org/)). # Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order # to make the SVG files visible in IE 9+ (other browsers do not have this # requirement). @@ -2361,11 +2691,12 @@ DOT_PATH = DOTFILE_DIRS = -# The MSCFILE_DIRS tag can be used to specify one or more directories that -# contain msc files that are included in the documentation (see the \mscfile -# command). +# You can include diagrams made with dia in doxygen documentation. Doxygen will +# then run dia to produce the diagram and insert it in the documentation. The +# DIA_PATH tag allows you to specify the directory where the dia binary resides. +# If left empty dia is assumed to be found in the default search path. -MSCFILE_DIRS = +DIA_PATH = # The DIAFILE_DIRS tag can be used to specify one or more directories that # contain dia files that are included in the documentation (see the \diafile @@ -2374,10 +2705,10 @@ MSCFILE_DIRS = DIAFILE_DIRS = # When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the -# path where java can find the plantuml.jar file. If left blank, it is assumed -# PlantUML is not used or called during a preprocessing step. Doxygen will -# generate a warning when it encounters a \startuml command in this case and -# will not generate output for the diagram. +# path where java can find the plantuml.jar file or to the filename of jar file +# to be used. If left blank, it is assumed PlantUML is not used or called during +# a preprocessing step. Doxygen will generate a warning when it encounters a +# \startuml command in this case and will not generate output for the diagram. PLANTUML_JAR_PATH = @@ -2415,18 +2746,6 @@ DOT_GRAPH_MAX_NODES = 50 MAX_DOT_GRAPH_DEPTH = 0 -# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent -# background. This is disabled by default, because dot on Windows does not seem -# to support this out of the box. -# -# Warning: Depending on the platform used, enabling this option may lead to -# badly anti-aliased labels on the edges of a graph (i.e. they become hard to -# read). -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_TRANSPARENT = NO - # Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) support @@ -2439,6 +2758,8 @@ DOT_MULTI_TARGETS = NO # If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page # explaining the meaning of the various boxes and arrows in the dot generated # graphs. +# Note: This tag requires that UML_LOOK isn't set, i.e. the doxygen internal +# graphical representation for inheritance and collaboration diagrams is used. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2447,8 +2768,24 @@ GENERATE_LEGEND = YES # If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate # files that are used to generate the various graphs. # -# Note: This setting is not only used for dot files but also for msc and -# plantuml temporary files. +# Note: This setting is not only used for dot files but also for msc temporary +# files. # The default value is: YES. DOT_CLEANUP = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. If the MSCGEN_TOOL tag is left empty (the default), then doxygen will +# use a built-in version of mscgen tool to produce the charts. Alternatively, +# the MSCGEN_TOOL tag can also specify the name an external tool. For instance, +# specifying prog as the value, doxygen will call the tool as prog -T +# -o . The external tool should support +# output file formats "png", "eps", "svg", and "ismap". + +MSCGEN_TOOL = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the \mscfile +# command). + +MSCFILE_DIRS = diff --git a/LICENSE b/LICENSE index bee399aa1..9bd427e0e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020-2021 Adafruit Industries +Copyright (c) 2020-2024 Adafruit Industries Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index d72345c81..5044a03b2 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,9 @@ Adafruit.io WipperSnapper is a firmware designed to turn any Wi-Fi capable board into an Internet-of-Things (IoT) device. No code required! -WipperSnapper works with multiple microcontroller architectures such as ESP8266, ESP32, ESP32-S2, ESP32-C3, RP2040, and ATSAMD51. +WipperSnapper works with [multiple microcontroller architectures](https://github.com/adafruit/Adafruit_Wippersnapper_Arduino/?tab=readme-ov-file#supported-platforms) and development boards. It is designed to be easily extensible to support new sensors, inputs, and outputs. -You will need a **free** [Adafruit IO](https://io.adafruit.com) account to use WipperSnapper. - -**NOTE: WipperSnapper firmware is in beta** and is actively being developed. Please [report bugs via the issues page on this repository](https://github.com/adafruit/Adafruit_Wippersnapper_Arduino/issues/new?assignees=&labels=&projects=&template=bug_report.md&title=). +A **free** [Adafruit IO](https://io.adafruit.com) account is required to use WipperSnapper. # Get Started [Learn how to install and use WipperSnapper by following this guide on the Adafruit Learning System - QuickStart: Adafruit IO WipperSnapper](https://learn.adafruit.com/quickstart-adafruit-io-wippersnapper). @@ -24,7 +22,7 @@ Pre-compiled binaries and UF2 files for supported hardware are provided on the [ |Platform| MCU(s) | |--|--| -|[ESP32-x](https://github.com/espressif/arduino-esp32)| ESP32, ESP32-S2, ESP32-S3, ESP32-C3 | +|[ESP32-x](https://github.com/espressif/arduino-esp32)| ESP32, ESP32-S2, ESP32-S3, ESP32-C3, ESP32-C6 | |[ESP8266](https://github.com/esp8266/Arduino)| ESP8266 | |[RP2040](https://github.com/earlephilhower/arduino-pico)| RP2040 MCU w/WiFi (i.e: Pico W) | |[ATSAMD](https://github.com/adafruit/ArduinoCore-samd/)| SAMD51 MCU w/separate WiFi Co-Processor (i.e: Adafruit "AirLift")| diff --git a/examples/Wippersnapper_demo/Wippersnapper_demo.ino b/examples/Wippersnapper_demo/Wippersnapper_demo.ino index 489a3d96c..6a378dcac 100644 --- a/examples/Wippersnapper_demo/Wippersnapper_demo.ino +++ b/examples/Wippersnapper_demo/Wippersnapper_demo.ino @@ -24,12 +24,12 @@ void setup() { wipper.provision(); Serial.begin(115200); - //while (!Serial) delay(10); + while (!Serial) delay(10); wipper.connect(); } void loop() { - wipper.run(); + //wipper.run(); } \ No newline at end of file diff --git a/library.properties b/library.properties index 00e4afd2d..85989c3b6 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=Adafruit WipperSnapper -version=1.0.0-beta.93 +version=1.0.0 author=Adafruit maintainer=Adafruit sentence=Arduino application for Adafruit.io WipperSnapper diff --git a/partitions-4MB.csv b/partitions-4MB.csv new file mode 100644 index 000000000..f3112a23b --- /dev/null +++ b/partitions-4MB.csv @@ -0,0 +1,11 @@ +# ESP-IDF Partition Table +# Name, Type, SubType, Offset, Size, Flags +# bootloader.bin,, 0x1000, 32K +# partition table, 0x8000, 4K + +nvs, data, nvs, 0x9000, 20K, +otadata, data, ota, 0xe000, 8K, +ota_0, app, ota_0, 0x10000, 1408K, +ota_1, app, ota_1, 0x170000, 1408K, +uf2, app, factory,0x2d0000, 256K, +ffat, data, fat, 0x310000, 960K, diff --git a/platformio.ini b/platformio.ini index dd4acafc2..4c637ac4c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -14,12 +14,13 @@ default_envs = adafruit_feather_esp32s3_tft, adafruit_magtag29_esp32s2, adafruit [env] framework = arduino monitor_speed = 115200 +extra_scripts = upload_no_build.py lib_compat_mode = strict lib_deps = adafruit/Adafruit Zero DMA Library - https://github.com/adafruit/Adafruit_TinyUSB_Arduino.git - adafruit/Adafruit NeoPixel + adafruit/Adafruit TinyUSB Library adafruit/Adafruit SPIFlash + adafruit/Adafruit NeoPixel adafruit/Adafruit DotStar adafruit/ENS160 - Adafruit Fork adafruit/Adafruit SleepyDog Library @@ -71,13 +72,22 @@ lib_deps = adafruit/Adafruit TouchScreen adafruit/Adafruit MQTT Library bblanchon/ArduinoJson + https://github.com/adafruit/SdFat.git https://github.com/pstolarz/OneWireNg.git - https://github.com/milesburton/Arduino-Temperature-Control-Library.git https://github.com/Sensirion/arduino-sht.git https://github.com/Sensirion/arduino-i2c-scd4x.git https://github.com/Sensirion/arduino-i2c-sen5x.git https://github.com/adafruit/WiFiNINA.git https://github.com/Starmbi/hp_BH1750.git + https://github.com/adafruit/RTClib.git + +; Common build environment for ESP32 platform +[common:esp32] +;platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.03/platform-espressif32.zip +;platform = https://github.com/pioarduino/platform-espressif32#develop +platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.07/platform-espressif32.zip +lib_ignore = WiFiNINA, WiFi101 + ;;;;;;;;;;; FunHouse / LVGL Boards ;;;;;;;;;;;;;; https://github.com/adafruit/Adafruit_HX8357_Library.git https://github.com/adafruit/Adafruit_ILI9341.git @@ -95,6 +105,7 @@ platform = https://github.com/pioarduino/platform-espressif32/releases/download/ ; platform = https://github.com/pioarduino/platform-espressif32#develop lib_ignore = WiFiNINA, WiFi101, OneWire monitor_filters = esp32_exception_decoder, time +; upload_speed = 921600 ; Common build environment for ESP8266 platform [common:esp8266] @@ -129,6 +140,18 @@ lib_compat_mode = soft ; can be strict once pio detects SleepyDog on RP2040 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Individual Board Definitions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ESP32-x Boards ; +; extra_scripts = post:pio_mergefs.py + +; Espressif ESP32 Dev Module +; for Wokwi Simulator and Testing +; NoFS +[env:esp32dev] +extends = common:esp32 +board = esp32dev +board_build.partitions = default_8MB.csv +build_flags = -DARDUINO_ESP32_DEV +board_build.filesystem = littlefs +upload_speed = 921600 ; Adafruit ESP32 Feather [env:featheresp32] @@ -189,7 +212,10 @@ board_build.partitions = min_spiffs.csv extends = common:esp32 board = featheresp32-s2 build_flags = -DARDUINO_ADAFRUIT_FEATHER_ESP32S2 -DBOARD_HAS_PSRAM +;board_build.partitions = tinyuf2-partitions-4MB-noota.csv board_build.partitions = tinyuf2-partitions-4MB.csv +;build_type = debug +monitor_filters = esp32_exception_decoder extra_scripts = pre:rename_usb_config.py ; Adafruit Feather ESP32-S2 TFT @@ -205,7 +231,8 @@ extra_scripts = pre:rename_usb_config.py extends = common:esp32 board = adafruit_feather_esp32s2_reversetft build_flags = -DARDUINO_ADAFRUIT_FEATHER_ESP32S2_REVTFT -DBOARD_HAS_PSRAM -board_build.partitions = tinyuf2-partitions-4MB.csv +;board_build.partitions = tinyuf2-partitions-4MB.csv +board_build.partitions = tinyuf2-partitions-4MB-noota.csv extra_scripts = pre:rename_usb_config.py ; Adafruit Feather ESP32-S3 2MB PSRAM @@ -214,7 +241,8 @@ extends = common:esp32 board = adafruit_feather_esp32s3 build_flags = -DARDUINO_ADAFRUIT_FEATHER_ESP32S3 -DBOARD_HAS_PSRAM ;set partition to tinyuf2-partitions-4MB.csv as of idf 5.1 -board_build.partitions = tinyuf2-partitions-4MB.csv +; board_build.partitions = tinyuf2-partitions-4MB.csv +board_build.partitions = tinyuf2-partitions-4MB-noota.csv extra_scripts = pre:rename_usb_config.py ; Adafruit Feather ESP32-S3 NO PSRAM @@ -241,7 +269,7 @@ extends = common:esp32 board = adafruit_feather_esp32s3_reversetft build_flags = -DARDUINO_ADAFRUIT_FEATHER_ESP32S3_REVTFT -DBOARD_HAS_PSRAM ;set partition to tinyuf2-partitions-4MB.csv as of idf 5.1 -board_build.partitions = tinyuf2-partitions-4MB.csv +board_build.partitions = tinyuf2-partitions-4MB-noota.csv extra_scripts = pre:rename_usb_config.py ; Adafruit Magtag ESP32-S2 diff --git a/src/Wippersnapper.cpp b/src/Wippersnapper.cpp index a4c190825..e34d893a8 100644 --- a/src/Wippersnapper.cpp +++ b/src/Wippersnapper.cpp @@ -66,9 +66,6 @@ Wippersnapper::Wippersnapper() { // UART WS._uartComponent = new ws_uart(); - - // DallasSemi (OneWire) - WS._ds18x20Component = new ws_ds18x20(); }; /**************************************************************************/ @@ -1319,43 +1316,6 @@ void cbPWMMsg(char *data, uint16_t len) { bool cbDecodeDs18x20Msg(pb_istream_t *stream, const pb_field_t *field, void **arg) { (void)arg; // marking unused parameter to avoid compiler warning - if (field->tag == - wippersnapper_signal_v1_Ds18x20Request_req_ds18x20_init_tag) { - WS_DEBUG_PRINTLN("[Message Type] Init. DS Sensor"); - // Attempt to decode contents of DS18x20 message - wippersnapper_ds18x20_v1_Ds18x20InitRequest msgDS18xInitReq = - wippersnapper_ds18x20_v1_Ds18x20InitRequest_init_zero; - - if (!ws_pb_decode(stream, - wippersnapper_ds18x20_v1_Ds18x20InitRequest_fields, - &msgDS18xInitReq)) { - WS_DEBUG_PRINTLN("ERROR: Could not decode " - "wippersnapper_ds18x20_v1_Ds18x20InitRequest"); - return false; // fail out if we can't decode the request - } - WS_DEBUG_PRINT("Adding DS18x20 Component..."); - if (!WS._ds18x20Component->addDS18x20(&msgDS18xInitReq)) - return false; - WS_DEBUG_PRINTLN("Added!"); - } else if (field->tag == - wippersnapper_signal_v1_Ds18x20Request_req_ds18x20_deinit_tag) { - WS_DEBUG_PRINTLN("[Message Type] De-init. DS Sensor"); - // Attempt to decode contents of message - wippersnapper_ds18x20_v1_Ds18x20DeInitRequest msgDS18xDeInitReq = - wippersnapper_ds18x20_v1_Ds18x20DeInitRequest_init_zero; - if (!ws_pb_decode(stream, - wippersnapper_ds18x20_v1_Ds18x20DeInitRequest_fields, - &msgDS18xDeInitReq)) { - WS_DEBUG_PRINTLN("ERROR: Could not decode " - "wippersnapper_ds18x20_v1_Ds18x20DeInitRequest"); - return false; // fail out if we can't decode the request - } - // exec. deinit request - WS._ds18x20Component->deleteDS18x20(&msgDS18xDeInitReq); - } else { - WS_DEBUG_PRINTLN("ERROR: DS Message type not found!"); - return false; - } return true; } @@ -2876,13 +2836,9 @@ ws_status_t Wippersnapper::run() { WS._i2cPort0->update(); WS.feedWDT(); - // Process DS18x20 sensor events - WS._ds18x20Component->update(); - WS.feedWDT(); - // Process UART sensor events WS._uartComponent->update(); WS.feedWDT(); return WS_NET_CONNECTED; // TODO: Make this funcn void! -} +} \ No newline at end of file diff --git a/src/Wippersnapper.h b/src/Wippersnapper.h index 0c418eab2..10f6f3530 100644 --- a/src/Wippersnapper.h +++ b/src/Wippersnapper.h @@ -39,74 +39,11 @@ // Wippersnapper API Helpers #include "Wippersnapper_Boards.h" #include "components/statusLED/Wippersnapper_StatusLED.h" +#include "helpers/ws_helper_macros.h" +#include "helpers/ws_helper_status.h" +#include "helpers/ws_helper_topics.h" #include "provisioning/ConfigJson.h" -#define WS_DEBUG ///< Define to enable debugging to serial terminal -#define WS_PRINTER Serial ///< Where debug messages will be printed - -// Define actual debug output functions when necessary. -#ifdef WS_DEBUG -#define WS_DEBUG_PRINT(...) \ - { WS_PRINTER.print(__VA_ARGS__); } ///< Prints debug output. -#define WS_DEBUG_PRINTLN(...) \ - { WS_PRINTER.println(__VA_ARGS__); } ///< Prints line from debug output. -#define WS_DEBUG_PRINTHEX(...) \ - { WS_PRINTER.print(__VA_ARGS__, HEX); } ///< Prints debug output. -#else -#define WS_DEBUG_PRINT(...) \ - {} ///< Prints debug output -#define WS_DEBUG_PRINTLN(...) \ - {} ///< Prints line from debug output. -#endif - -#define WS_DELAY_WITH_WDT(timeout) \ - { \ - unsigned long start = millis(); \ - while (millis() - start < timeout) { \ - delay(10); \ - yield(); \ - feedWDT(); \ - if (millis() < start) { \ - start = millis(); /* if rollover */ \ - } \ - } \ - } ///< Delay function - -/**************************************************************************/ -/*! - @brief Retry a function until a condition is met or a timeout is reached. - @param func - The function to retry. - @param result_type - The type of the result of the function. - @param result_var - The variable to store the last result of the function. - @param condition - The condition to check the result against. - @param timeout - The maximum time to retry the function. - @param interval - The time to wait between retries. - @param ... - The arguments to pass to the function. -*/ -/**************************************************************************/ -#define RETRY_FUNCTION_UNTIL_TIMEOUT(func, result_type, result_var, condition, \ - timeout, interval, ...) \ - { \ - unsigned long startTime = millis(); \ - while (millis() - startTime < timeout) { \ - result_type result_var = func(__VA_ARGS__); \ - if (condition(result_var)) { \ - break; \ - } \ - if (startTime > millis()) { \ - startTime = millis(); /* if rollover */ \ - } \ - WS_DELAY_WITH_WDT(interval); \ - } \ - } ///< Retry a function until a condition is met or a timeout is reached. - // Wippersnapper pb helpers #include @@ -127,7 +64,6 @@ #include "display/ws_display_ui_helper.h" #endif -#include "components/ds18x20/ws_ds18x20.h" #include "components/pixels/ws_pixels.h" #include "components/pwm/ws_pwm.h" #include "components/servo/ws_servo.h" @@ -144,76 +80,6 @@ #define WS_VERSION \ "1.0.0-beta.93" ///< WipperSnapper app. version (semver-formatted) -// Reserved Adafruit IO MQTT topics -#define TOPIC_IO_THROTTLE "/throttle" ///< Adafruit IO Throttle MQTT Topic -#define TOPIC_IO_ERRORS "/errors" ///< Adafruit IO Error MQTT Topic - -// Reserved Wippersnapper topics -#define TOPIC_WS "/wprsnpr/" ///< WipperSnapper topic -#define TOPIC_INFO "/info/" ///< Registration sub-topic -#define TOPIC_SIGNALS "/signals/" ///< Signals sub-topic -#define TOPIC_I2C "/i2c" ///< I2C sub-topic -#define MQTT_TOPIC_PIXELS_DEVICE \ - "/signals/device/pixel" ///< Pixels device->broker topic -#define MQTT_TOPIC_PIXELS_BROKER \ - "/signals/broker/pixel" ///< Pixels broker->device topic - -/** Defines the Adafruit IO connection status */ -typedef enum { - WS_IDLE = 0, // Waiting for connection establishement - WS_NET_DISCONNECTED = 1, // Network disconnected - WS_DISCONNECTED = 2, // Disconnected from Adafruit IO - WS_FINGERPRINT_UNKOWN = 3, // Unknown WS_SSL_FINGERPRINT - - WS_NET_CONNECT_FAILED = 10, // Failed to connect to network - WS_CONNECT_FAILED = 11, // Failed to connect to Adafruit IO - WS_FINGERPRINT_INVALID = 12, // Unknown WS_SSL_FINGERPRINT - WS_AUTH_FAILED = 13, // Invalid Adafruit IO login credentials provided. - WS_SSID_INVALID = - 14, // SSID is "" or otherwise invalid, connection not attempted - - WS_NET_CONNECTED = 20, // Connected to Adafruit IO - WS_CONNECTED = 21, // Connected to network - WS_CONNECTED_INSECURE = 22, // Insecurely (non-SSL) connected to network - WS_FINGERPRINT_UNSUPPORTED = 23, // Unsupported WS_SSL_FINGERPRINT - WS_FINGERPRINT_VALID = 24, // Valid WS_SSL_FINGERPRINT - WS_BOARD_DESC_INVALID = 25, // Unable to send board description - WS_BOARD_RESYNC_FAILED = 26 // Board sync failure -} ws_status_t; - -/** Defines the Adafruit IO MQTT broker's connection return codes */ -typedef enum { - WS_MQTT_CONNECTED = 0, // Connected - WS_MQTT_INVALID_PROTOCOL = 1, // Invalid mqtt protocol - WS_MQTT_INVALID_CID = 2, // Client id rejected - WS_MQTT_SERVICE_UNAVALIABLE = 3, // Malformed user/pass - WS_MQTT_INVALID_USER_PASS = 4, // Unauthorized access to resource - WS_MQTT_UNAUTHORIZED = 5, // MQTT service unavailable - WS_MQTT_THROTTLED = 6, // Account throttled - WS_MQTT_BANNED = 7 // Account banned -} ws_mqtt_status_t; - -/** Defines the Wippersnapper client's hardware registration status */ -typedef enum { - WS_BOARD_DEF_IDLE, - WS_BOARD_DEF_SEND_FAILED, - WS_BOARD_DEF_SENT, - WS_BOARD_DEF_OK, - WS_BOARD_DEF_INVALID, - WS_BOARD_DEF_UNSPECIFIED -} ws_board_status_t; - -/** Defines the Wippersnapper client's network status */ -typedef enum { - FSM_NET_IDLE, - FSM_NET_CONNECTED, - FSM_MQTT_CONNECTED, - FSM_NET_CHECK_MQTT, - FSM_NET_CHECK_NETWORK, - FSM_NET_ESTABLISH_NETWORK, - FSM_NET_ESTABLISH_MQTT, -} fsm_net_t; - #define WS_WDT_TIMEOUT 60000 ///< WDT timeout #define WS_MAX_ALT_WIFI_NETWORKS 3 ///< Maximum number of alternative networks /* MQTT Configuration */ @@ -237,7 +103,6 @@ class ws_ledc; class WipperSnapper_Component_I2C; class ws_servo; class ws_pwm; -class ws_ds18x20; class ws_pixels; class ws_uart; @@ -360,7 +225,6 @@ class Wippersnapper { ws_pixels *_ws_pixelsComponent; ///< ptr to instance of ws_pixels class ws_pwm *_pwmComponent; ///< Instance of pwm class ws_servo *_servoComponent; ///< Instance of servo class - ws_ds18x20 *_ds18x20Component; ///< Instance of DS18x20 class ws_uart *_uartComponent; ///< Instance of UART class // TODO: does this really need to be global? @@ -495,4 +359,4 @@ class Wippersnapper { }; extern Wippersnapper WS; ///< Global member variable for callbacks -#endif // ADAFRUIT_WIPPERSNAPPER_H +#endif // ADAFRUIT_WIPPERSNAPPER_H \ No newline at end of file diff --git a/src/Wippersnapper_Boards.h b/src/Wippersnapper_Boards.h index 37e3db870..d2fbd7287 100644 --- a/src/Wippersnapper_Boards.h +++ b/src/Wippersnapper_Boards.h @@ -71,6 +71,7 @@ #define USE_STATUS_NEOPIXEL #define STATUS_NEOPIXEL_PIN 33 #define STATUS_NEOPIXEL_NUM 1 +#define SD_CS_PIN 33 #define USE_PSRAM ///< Board has PSRAM, use it for dynamic memory allocation #elif defined(ARDUINO_ADAFRUIT_FEATHER_ESP32S2_TFT) #define BOARD_ID "feather-esp32s2-tft" @@ -79,6 +80,7 @@ #define STATUS_NEOPIXEL_PIN 33 #define STATUS_NEOPIXEL_NUM 1 #define PIN_I2C_POWER_INVERTED 7 +#define SD_CS_PIN 33 #define USE_PSRAM ///< Board has PSRAM, use it for dynamic memory allocation #elif defined(ARDUINO_ADAFRUIT_FEATHER_ESP32S2_REVTFT) #define BOARD_ID "feather-esp32s2-reverse-tft" @@ -86,6 +88,7 @@ #define USE_STATUS_NEOPIXEL #define STATUS_NEOPIXEL_PIN PIN_NEOPIXEL #define STATUS_NEOPIXEL_NUM NEOPIXEL_NUM +#define SD_CS_PIN 33 #define USE_PSRAM ///< Board has PSRAM, use it for dynamic memory allocation #elif defined(ARDUINO_ADAFRUIT_FEATHER_ESP32S3_NOPSRAM) #define BOARD_ID "feather-esp32s3" @@ -93,12 +96,14 @@ #define USE_STATUS_NEOPIXEL #define STATUS_NEOPIXEL_PIN PIN_NEOPIXEL #define STATUS_NEOPIXEL_NUM NEOPIXEL_NUM +#define SD_CS_PIN 33 #elif defined(ARDUINO_ADAFRUIT_FEATHER_ESP32S3) #define BOARD_ID "feather-esp32s3-4mbflash-2mbpsram" #define USE_TINYUSB #define USE_STATUS_NEOPIXEL #define STATUS_NEOPIXEL_PIN PIN_NEOPIXEL #define STATUS_NEOPIXEL_NUM NEOPIXEL_NUM +#define SD_CS_PIN 33 #define USE_PSRAM ///< Board has PSRAM, use it for dynamic memory allocation #elif defined(ARDUINO_ADAFRUIT_FEATHER_ESP32S3_TFT) #define BOARD_ID "feather-esp32s3-tft" @@ -106,6 +111,7 @@ #define USE_STATUS_NEOPIXEL #define STATUS_NEOPIXEL_PIN PIN_NEOPIXEL #define STATUS_NEOPIXEL_NUM NEOPIXEL_NUM +#define SD_CS_PIN 33 #define USE_PSRAM ///< Board has PSRAM, use it for dynamic memory allocation #elif defined(ARDUINO_ADAFRUIT_FEATHER_ESP32S3_REVTFT) #define BOARD_ID "feather-esp32s3-reverse-tft" @@ -113,6 +119,7 @@ #define USE_STATUS_NEOPIXEL #define STATUS_NEOPIXEL_PIN PIN_NEOPIXEL #define STATUS_NEOPIXEL_NUM NEOPIXEL_NUM +#define SD_CS_PIN 33 #define USE_PSRAM ///< Board has PSRAM, use it for dynamic memory allocation #elif defined(ARDUINO_ADAFRUIT_QTPY_ESP32S2) #define BOARD_ID "qtpy-esp32s2" @@ -120,6 +127,7 @@ #define USE_STATUS_NEOPIXEL #define STATUS_NEOPIXEL_PIN PIN_NEOPIXEL #define STATUS_NEOPIXEL_NUM 1 +#define SD_CS_PIN 33 #define USE_PSRAM ///< Board has PSRAM, use it for dynamic memory allocation #elif defined(ARDUINO_ADAFRUIT_QTPY_ESP32S3_NOPSRAM) #define BOARD_ID "qtpy-esp32s3" @@ -127,12 +135,14 @@ #define USE_STATUS_NEOPIXEL #define STATUS_NEOPIXEL_PIN PIN_NEOPIXEL #define STATUS_NEOPIXEL_NUM 1 +#define SD_CS_PIN 33 #elif defined(ARDUINO_ADAFRUIT_QTPY_ESP32S3_N4R2) #define BOARD_ID "qtpy-esp32s3-n4r2" #define USE_TINYUSB #define USE_STATUS_NEOPIXEL #define STATUS_NEOPIXEL_PIN PIN_NEOPIXEL #define STATUS_NEOPIXEL_NUM 1 +#define SD_CS_PIN 33 #define USE_PSRAM ///< Board has PSRAM, use it for dynamic memory allocation #elif defined(ARDUINO_ADAFRUIT_QTPY_ESP32C3) #define BOARD_ID "qtpy-esp32c3" @@ -145,30 +155,33 @@ #define USE_LITTLEFS #define USE_STATUS_LED #define STATUS_LED_PIN 0 +#define SD_CS_PIN 15 #elif defined(ARDUINO_ADAFRUIT_ITSYBITSY_ESP32) #define BOARD_ID "itsybitsy-esp32" #define USE_LITTLEFS #define USE_STATUS_NEOPIXEL #define STATUS_NEOPIXEL_PIN PIN_NEOPIXEL #define STATUS_NEOPIXEL_NUM 1 +#define SD_CS_PIN 33 #define USE_PSRAM ///< Board has PSRAM, use it for dynamic memory allocation #elif defined(ARDUINO_FEATHER_ESP32) #define BOARD_ID "feather-esp32" #define USE_LITTLEFS #define USE_STATUS_LED #define STATUS_LED_PIN 13 -#elif defined(ARDUINO_ADAFRUIT_FEATHER_ESP32C6) -#define BOARD_ID "feather-esp32c6" -#define USE_LITTLEFS -#define USE_STATUS_NEOPIXEL -#define STATUS_NEOPIXEL_PIN PIN_NEOPIXEL -#define STATUS_NEOPIXEL_NUM 1 +#define SD_CS_PIN 33 +#elif defined(ARDUINO_ESP32_DEV) || defined(ESP32_DEV) +#define BOARD_ID "feather-esp32" +#define USE_STATUS_LED +#define STATUS_LED_PIN 13 +#define SD_CS_PIN 15 #elif defined(ARDUINO_ADAFRUIT_FEATHER_ESP32_V2) #define BOARD_ID "feather-esp32-v2" #define USE_LITTLEFS #define USE_STATUS_NEOPIXEL #define STATUS_NEOPIXEL_PIN PIN_NEOPIXEL #define STATUS_NEOPIXEL_NUM 1 +#define SD_CS_PIN 33 #define USE_PSRAM ///< Board has PSRAM, use it for dynamic memory allocation #elif defined(ARDUINO_ADAFRUIT_QTPY_ESP32_PICO) #define BOARD_ID "qtpy-esp32" @@ -176,6 +189,7 @@ #define USE_STATUS_NEOPIXEL #define STATUS_NEOPIXEL_PIN PIN_NEOPIXEL #define STATUS_NEOPIXEL_NUM 1 +#define SD_CS_PIN 33 #define USE_PSRAM ///< Board has PSRAM, use it for dynamic memory allocation #elif defined(ARDUINO_SAMD_NANO_33_IOT) #define BOARD_ID "nano-33-iot" @@ -190,6 +204,7 @@ #define USE_TINYUSB #define USE_STATUS_LED #define STATUS_LED_PIN 32 +#define SD_CS_PIN 17 #else #warning "Board type not identified within Wippersnapper_Boards.h!" #endif diff --git a/src/Wippersnapper_V2.cpp b/src/Wippersnapper_V2.cpp new file mode 100644 index 000000000..566c2b562 --- /dev/null +++ b/src/Wippersnapper_V2.cpp @@ -0,0 +1,1298 @@ +/*! + * @file Wippersnapper_v2.cpp + * + * @mainpage Adafruit Wippersnapper Wrapper + * + * @section intro_sec Introduction + * + * This is the documentation for Adafruit's Wippersnapper wrapper for the + * Arduino platform. It is designed specifically to work with the + * Adafruit IO+ Wippersnapper IoT platform. + * + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * @section dependencies Dependencies + * + * This library depends on Adafruit_Sensor being + * present on your system. Please make sure you have installed the latest + * version before using this library. + * + * @section author Author + * + * Copyright (c) Brent Rubell 2020-2023 for Adafruit Industries. + * + * @section license License + * + * BSD license, all text here must be included in any redistribution. + * + */ + +#include "Wippersnapper_V2.h" + +Wippersnapper_V2 WsV2; + +Wippersnapper_V2::Wippersnapper_V2() { + // TODO: Scope out how much of this we can remove + // and what should be here (if we are wrong!) + _mqttV2 = 0; // MQTT Client object + + // Reserved MQTT Topics + _topicError = 0; + _topicThrottle = 0; + _subscribeError = 0; + _subscribeThrottle = 0; + + // Init. component classes + // LEDC (ESP32-ONLY) +#ifdef ARDUINO_ARCH_ESP32 + WsV2._ledcV2 = new ws_ledc(); +#endif + + // PWM (Arch-specific implementations) +#ifdef ARDUINO_ARCH_ESP32 + WsV2._pwmComponentV2 = new ws_pwm(WsV2._ledcV2); +#else + WsV2._pwmComponentV2 = new ws_pwm(); +#endif + + // Servo + WsV2._servoComponentV2 = new ws_servo(); + + // UART + WsV2._uartComponentV2 = new ws_uart(); + + // Initialize model classes + WsV2.sensorModel = new SensorModel(); + + // SD Card + WsV2._sdCardV2 = new ws_sdcard(); + + // Initialize controller classes + WsV2.digital_io_controller = new DigitalIOController(); + WsV2.analogio_controller = new AnalogIOController(); + WsV2._ds18x20_controller = new DS18X20Controller(); +}; + +/**************************************************************************/ +/*! + @brief Wippersnapper_V2 destructor +*/ +/**************************************************************************/ +Wippersnapper_V2::~Wippersnapper_V2() {} + +/**************************************************************************/ +/*! + @brief Provisions a WipperSnapper device with its network + configuration and Adafruit IO credentials. +*/ +/**************************************************************************/ +void Wippersnapper_V2::provisionV2() { + // Obtain device's MAC address + getMacAddrV2(); + + // Initialize the status LED for signaling FS errors + initStatusLED(); + + // If an SD card is inserted and mounted, we do not need to provision + // wifi/io credentials + if (WsV2._sdCardV2->mode_offline == true) + return; + +// Initialize the filesystem +#ifdef USE_TINYUSB + _fileSystemV2 = new Wippersnapper_FS_V2(); +#elif defined(USE_LITTLEFS) + _littleFSV2 = new WipperSnapper_LittleFS(); +#endif + +#ifdef USE_DISPLAY + // Initialize the display + displayConfig config; + WsV2._fileSystemV2->parseDisplayConfig(config); + WsV2._display = new ws_display_driver(config); + // Begin display + if (!WsV2._display->begin()) { + WS_DEBUG_PRINTLN("Unable to enable display driver and LVGL"); + haltErrorV2("Unable to enable display driver, please check the json " + "configuration!"); + } + + WsV2._display->enableLogging(); + releaseStatusLED(); // don't use status LED if we are using the display + // UI Setup + WsV2._ui_helper = new ws_display_ui_helper(WsV2._display); + WsV2._ui_helper->set_bg_black(); + WsV2._ui_helper->show_scr_load(); + WsV2._ui_helper->set_label_status("Validating Credentials..."); +#endif + +#ifdef USE_TINYUSB + _fileSystemV2->parseSecrets(); +#elif defined(USE_LITTLEFS) + _littleFSV2->parseSecrets(); +#else + check_valid_ssidV2(); // non-fs-backed, sets global credentials within network + // iface +#endif + // Set the status pixel's brightness + setStatusLEDBrightness(WsV2._configV2.status_pixel_brightness); + // Set device's wireless credentials + set_ssid_passV2(); + +#ifdef USE_DISPLAY + WsV2._ui_helper->set_label_status(""); + WsV2._ui_helper->set_load_bar_icon_complete(loadBarIconFile); +#endif +} + +/**************************************************************************/ +/*! + @brief Disconnects from Adafruit IO+ Wippersnapper_V2. +*/ +/**************************************************************************/ +void Wippersnapper_V2::disconnectV2() { _disconnectV2(); } + +// Concrete class definition for abstract classes + +/****************************************************************************/ +/*! + @brief Connects to wireless network. +*/ +/****************************************************************************/ +void Wippersnapper_V2::_connectV2() { + WS_DEBUG_PRINTLN("Wippersnapper_V2::_connectV2"); + WS_DEBUG_PRINTLN("ERROR: Please define a network interface!"); +} + +/****************************************************************************/ +/*! + @brief Disconnect Wippersnapper MQTT session and network. +*/ +/****************************************************************************/ +void Wippersnapper_V2::_disconnectV2() { + WS_DEBUG_PRINTLN("WIppersnapper_V2::_disconnectV2"); + WS_DEBUG_PRINTLN("ERROR: Please define a network interface!"); +} + +/****************************************************************************/ +/*! + @brief Sets the network interface's unique identifer, typically the + MAC address. +*/ +/****************************************************************************/ +void Wippersnapper_V2::getMacAddrV2() { + WS_DEBUG_PRINTLN("Wippersnapper_V2::getMacAddrV2"); + WS_DEBUG_PRINTLN("ERROR: Please define a network interface!"); +} + +/****************************************************************************/ +/*! + @brief Gets the network's RSSI. + @return int32_t RSSI value, 0 to 255, in dB +*/ +/****************************************************************************/ +int32_t Wippersnapper_V2::getRSSIV2() { + WS_DEBUG_PRINTLN("Wiippersnapper_V2::getRSSIV2"); + WS_DEBUG_PRINTLN("ERROR: Please define a network interface!"); + return 0; +} + +/****************************************************************************/ +/*! + @brief Sets up the MQTT client session. + @param clientID + A unique client identifier string. +*/ +/****************************************************************************/ +void Wippersnapper_V2::setupMQTTClientV2(const char * /*clientID*/) { + WS_DEBUG_PRINTLN("Wippersnapper_V2::setupMQTTClientV2"); + WS_DEBUG_PRINTLN("ERROR: Please define a network interface!"); +} + +/****************************************************************************/ +/*! + @brief Returns the network's connection status + @returns Network status as ws_status_t. +*/ +/****************************************************************************/ +ws_status_t Wippersnapper_V2::networkStatusV2() { + WS_DEBUG_PRINTLN("Wippersnapper_V2::networkStatusV2"); + WS_DEBUG_PRINTLN("ERROR: Please define a network interface!"); + return WS_IDLE; +} + +/****************************************************************************/ +/*! + @brief Sets the device's wireless network credentials. + @param ssid + Your wireless network's SSID + @param ssidPassword + Your wireless network's password. +*/ +/****************************************************************************/ +void Wippersnapper_V2::set_ssid_passV2(const char * /*ssid*/, + const char * /*ssidPassword*/) { + WS_DEBUG_PRINTLN("Wippersnapper_V2::set_ssid_passV2"); + WS_DEBUG_PRINTLN("ERROR: Please define a network interface!"); +} + +/****************************************************************************/ +/*! + @brief Sets the device's wireless network credentials from the + secrets.json configuration file. +*/ +/****************************************************************************/ +void Wippersnapper_V2::set_ssid_passV2() { + WS_DEBUG_PRINTLN("Wippersnapper_V2::set_ssid_passV2"); + WS_DEBUG_PRINTLN("ERROR: Please define a network interface!"); +} + +/***********************************************************/ +/*! +@brief Performs a scan of local WiFi networks. +@returns True if `_network_ssid` is found, False otherwise. +*/ +/***********************************************************/ +bool Wippersnapper_V2::check_valid_ssidV2() { + WS_DEBUG_PRINTLN("Wippersnapper_V2::check_valid_ssidV2"); + WS_DEBUG_PRINTLN("ERROR: Please define a network interface!"); + return false; +} + +/****************************************************************************/ +/*! + @brief Configures the device's Adafruit IO credentials. This method + should be used only if filesystem-backed provisioning is + not avaliable. +*/ +/****************************************************************************/ +void Wippersnapper_V2::set_user_keyV2() { + WS_DEBUG_PRINTLN("Wippersnapper_V2::set_user_keyV2"); + WS_DEBUG_PRINTLN("ERROR: Please define a network interface!"); +} + +/****************************************************************************/ +/*! + @brief Handles a Checkin Response message and initializes the + device's GPIO classes. + @param stream + Incoming data stream from buffer. + @returns True if Checkin Response decoded and parsed successfully, + False otherwise. +*/ +/****************************************************************************/ +bool handleCheckinResponse(pb_istream_t *stream) { + // Decode the Checkin Response message + if (!WsV2.CheckInModel->DecodeCheckinResponse(stream)) { + WS_DEBUG_PRINTLN("ERROR: Unable to decode Checkin Response message"); + return false; + } + + // Parse the response message + WsV2.CheckInModel->ParseCheckinResponse(); + + // Validate the checkin response message + if (WsV2.CheckInModel->getCheckinResponse() != + wippersnapper_checkin_CheckinResponse_Response_RESPONSE_OK) { + WS_DEBUG_PRINTLN("ERROR: CheckinResponse not RESPONSE_OK, backing out!"); + return false; + } + + // Configure GPIO classes based on checkin response message + WsV2.digital_io_controller->SetMaxDigitalPins( + WsV2.CheckInModel->getTotalGPIOPins()); + + WsV2.analogio_controller->SetRefVoltage( + WsV2.CheckInModel->getReferenceVoltage()); + WsV2.analogio_controller->SetTotalAnalogPins( + WsV2.CheckInModel->getTotalAnalogPins()); + + // set glob flag so we don't keep the polling loop open + WsV2.got_checkin_response = true; + return true; +} + +// Decoders // + +/******************************************************************************************/ +/*! + @brief Decodes a BrokerToDevice message and executes the asscoiated + callback. + @param stream + Incoming data stream from buffer. + @param field + Protobuf message's tag type. + @param arg + Optional arguments from decoder calling function. + @returns True if decoded and executed successfully, False otherwise. +*/ +/******************************************************************************************/ +bool cbDecodeBrokerToDevice(pb_istream_t *stream, const pb_field_t *field, + void **arg) { + (void)arg; // marking unused parameters to avoid compiler warning + + switch (field->tag) { + case wippersnapper_signal_BrokerToDevice_checkin_response_tag: + WS_DEBUG_PRINTLN("-> Checkin Response Message Type"); + WS_DEBUG_PRINT("Handling Checkin Response..."); + if (!handleCheckinResponse(stream)) { + WS_DEBUG_PRINTLN("Failure handling Checkin Response!"); + return false; + } + WS_DEBUG_PRINTLN("Handled!"); + break; + case wippersnapper_signal_BrokerToDevice_digitalio_add_tag: + WS_DEBUG_PRINTLN("-> DigitalIO Add Message Type"); + if (!WsV2.digital_io_controller->Handle_DigitalIO_Add(stream)) { + WS_DEBUG_PRINTLN("ERROR: Unable to add digitalio pin!"); + return false; + } + break; + case wippersnapper_signal_BrokerToDevice_digitalio_remove_tag: + WS_DEBUG_PRINTLN("-> DigitalIO Remove Message Type"); + if (!WsV2.digital_io_controller->Handle_DigitalIO_Remove(stream)) { + WS_DEBUG_PRINTLN("ERROR: Unable to remove digitalio pin!"); + return false; + } + break; + case wippersnapper_signal_BrokerToDevice_digitalio_write_tag: + WS_DEBUG_PRINTLN("-> DigitalIO Write Message Type"); + if (!WsV2.digital_io_controller->Handle_DigitalIO_Write(stream)) { + WS_DEBUG_PRINTLN("ERROR: Unable to write to digitalio pin!"); + return false; + } + break; + case wippersnapper_signal_BrokerToDevice_analogio_add_tag: + WS_DEBUG_PRINTLN("-> AnalogIO Add Message Type"); + if (!WsV2.analogio_controller->Handle_AnalogIOAdd(stream)) { + WS_DEBUG_PRINTLN("ERROR: Unable to add analogio pin!"); + return false; + } + break; + case wippersnapper_signal_BrokerToDevice_analogio_remove_tag: + WS_DEBUG_PRINTLN("-> AnalogIO Remove Message Type"); + if (!WsV2.analogio_controller->Handle_AnalogIORemove(stream)) { + WS_DEBUG_PRINTLN("ERROR: Unable to remove analogio pin!"); + return false; + } + break; + case wippersnapper_signal_BrokerToDevice_ds18x20_add_tag: + WS_DEBUG_PRINTLN("-> DS18X20 Add Message Type"); + if (!WsV2._ds18x20_controller->Handle_Ds18x20Add(stream)) { + WS_DEBUG_PRINTLN("ERROR: Unable to add DS18X20 sensor!"); + return false; + } + break; + case wippersnapper_signal_BrokerToDevice_ds18x20_remove_tag: + WS_DEBUG_PRINTLN("-> DS18X20 Remove Message Type"); + if (!WsV2._ds18x20_controller->Handle_Ds18x20Remove(stream)) { + WS_DEBUG_PRINTLN("ERROR: Unable to remove DS18X20 sensor!"); + return false; + } + break; + default: + WS_DEBUG_PRINTLN("ERROR: BrokerToDevice message type not found!"); + return false; + } + + // once this is returned, pb_dec_submessage() + // decodes the submessage contents. + return true; +} + +/**************************************************************************/ +/*! + @brief Called when client receives a message published across the + Adafruit IO MQTT /ws-b2d/ "signal topic". + @param data + Data (payload) from MQTT broker. + @param len + Length of data received from MQTT broker. +*/ +/**************************************************************************/ +void cbBrokerToDevice(char *data, uint16_t len) { + WS_DEBUG_PRINTLN("=> New B2D message!"); + wippersnapper_signal_BrokerToDevice msg_signal = + wippersnapper_signal_BrokerToDevice_init_default; + + // Configure the payload callback + msg_signal.cb_payload.funcs.decode = cbDecodeBrokerToDevice; + + // Decode msg_signal + WS_DEBUG_PRINTLN("Creating input stream..."); + pb_istream_t istream = pb_istream_from_buffer((uint8_t *)data, len); + WS_DEBUG_PRINTLN("Decoding BrokerToDevice message..."); + if (!pb_decode(&istream, wippersnapper_signal_BrokerToDevice_fields, + &msg_signal)) { + WS_DEBUG_PRINTLN("ERROR: Unable to decode BrokerToDevice message!"); + return; + } + WS_DEBUG_PRINTLN("Decoded BrokerToDevice message!"); +} + +void callDecodeB2D() { + WS_DEBUG_PRINTLN("\n[App] Parsing Offline Configuration Messages..."); + WS_DEBUG_PRINT("[debug] Total Messages: "); + WS_DEBUG_PRINTLN(WsV2._sharedConfigBuffers.size()); + + for (size_t i = 0; i < WsV2._sharedConfigBuffers.size(); i++) { + wippersnapper_signal_BrokerToDevice msg_signal = + wippersnapper_signal_BrokerToDevice_init_default; + // Configure the payload callback + msg_signal.cb_payload.funcs.decode = cbDecodeBrokerToDevice; + // TDOO: This can be cleaned up, get the buffer + const std::vector &buffer = WsV2._sharedConfigBuffers[i]; + WS_DEBUG_PRINTLN("Creating istream..."); + pb_istream_t istream = pb_istream_from_buffer(buffer.data(), buffer.size()); + // Decode the message + WS_DEBUG_PRINT("Decoding BrokerToDevice message #"); + WS_DEBUG_PRINTLN(i); + if (!pb_decode(&istream, wippersnapper_signal_BrokerToDevice_fields, + &msg_signal)) { + WS_DEBUG_PRINTLN("ERROR: Unable to decode BrokerToDevice message!"); + continue; // Skip this message and move on! + } + WS_DEBUG_PRINTLN("Decoded BrokerToDevice message!"); + } +} + +/**************************************************************************/ +/*! + @brief Called when client receives a message published across the + Adafruit IO MQTT /error special topic. + @param errorData + Data from MQTT broker. + @param len + Length of data received from MQTT broker. +*/ +/**************************************************************************/ +void cbErrorTopicV2(char *errorData, uint16_t len) { + (void)len; // marking unused parameter to avoid compiler warning + WS_DEBUG_PRINT("IO Ban Error: "); + WS_DEBUG_PRINTLN(errorData); + // Disconnect client from broker + WS_DEBUG_PRINT("Disconnecting from MQTT.."); + if (!WsV2._mqttV2->disconnect()) { + WS_DEBUG_PRINTLN("ERROR: Unable to disconnect from MQTT broker!"); + } + +#ifdef USE_DISPLAY + WsV2._ui_helper->show_scr_error("IO Ban Error", errorData); +#endif + + // WDT reset + WsV2.haltErrorV2("IO MQTT Ban Error"); +} + +/**************************************************************************/ +/*! + @brief Called when client receives a message published across the + Adafruit IO MQTT /throttle special topic. Delays until + throttle is released. + @param throttleData + Throttle message from Adafruit IO. + @param len + Length of data received from MQTT broker. +*/ +/**************************************************************************/ +void cbThrottleTopicV2(char *throttleData, uint16_t len) { + (void)len; // marking unused parameter to avoid compiler warning + WS_DEBUG_PRINT("IO Throttle Error: "); + WS_DEBUG_PRINTLN(throttleData); + char *throttleMessage; + // Parse out # of seconds from message buffer + throttleMessage = strtok(throttleData, ","); + throttleMessage = strtok(NULL, " "); + // Convert from seconds to to millis + int throttleDuration = atoi(throttleMessage) * 1000; + + WS_DEBUG_PRINT("Device is throttled for "); + WS_DEBUG_PRINT(throttleDuration); + WS_DEBUG_PRINTLN("ms and blocking command execution."); + +#ifdef USE_DISPLAY + char buffer[100]; + snprintf( + buffer, 100, + "[IO ERROR] Device is throttled for %d mS and blocking execution..\n.", + throttleDuration); + WsV2._ui_helper->add_text_to_terminal(buffer); +#endif + + // If throttle duration is less than the keepalive interval, delay for the + // full keepalive interval + if (throttleDuration < WS_KEEPALIVE_INTERVAL_MS) { + delay(WS_KEEPALIVE_INTERVAL_MS); + } else { + // round to nearest millis to prevent delaying for less time than req'd. + float throttleLoops = ceil(throttleDuration / WS_KEEPALIVE_INTERVAL_MS); + // block the run() loop + while (throttleLoops > 0) { + delay(WS_KEEPALIVE_INTERVAL_MS); + WsV2.feedWDTV2(); + WsV2._mqttV2->ping(); + throttleLoops--; + } + } + WS_DEBUG_PRINTLN("Device is un-throttled, resumed command execution"); +#ifdef USE_DISPLAY + WsV2._ui_helper->add_text_to_terminal( + "[IO] Device is un-throttled, resuming...\n"); +#endif +} + +/**************************************************************************/ +/*! + @brief Attempts to generate unique device identifier. + @returns True if device identifier generated successfully, + False otherwise. +*/ +/**************************************************************************/ +bool Wippersnapper_V2::generateDeviceUIDV2() { + // Generate device unique identifier + // Set machine_name + WsV2._boardIdV2 = BOARD_ID; + // Move the top 3 bytes from the UID + for (int i = 5; i > 2; i--) { + WsV2._macAddrV2[6 - 1 - i] = WsV2._macAddrV2[i]; + } + snprintf(WsV2.sUIDV2, sizeof(WsV2.sUIDV2), "%02d%02d%02d", WsV2._macAddrV2[0], + WsV2._macAddrV2[1], WsV2._macAddrV2[2]); + // Conversion to match integer UID sent by createMsgCheckinRequest() + itoa(atoi(WsV2.sUIDV2), WsV2.sUIDV2, 10); + + // Calculate the length of device and UID strings + WS_DEBUG_PRINTLN("Calculating device UID length..."); + size_t lenBoardId = strlen(WsV2._boardIdV2); + size_t lenUID = strlen(WsV2.sUIDV2); + size_t lenIOWipper = strlen("io-wipper-"); + size_t lenDeviceUID = lenBoardId + lenUID + lenIOWipper + 1; + + // Attempt to allocate memory for the _device_uid + WS_DEBUG_PRINTLN("Allocating memory for device UID"); +#ifdef USE_PSRAM + _device_uidV2 = (char *)ps_malloc(sizeof(char) * lenDeviceUID); +#else + _device_uidV2 = (char *)malloc(sizeof(char) * lenDeviceUID); +#endif + + // Check if memory allocation was successful + if (_device_uidV2 == NULL) { + WS_DEBUG_PRINTLN("ERROR: Unable to create device uid, Malloc failure"); + return false; + } + + // Create the device identifier + snprintf(_device_uidV2, lenDeviceUID, "io-wipper-%s%s", WsV2._boardIdV2, + WsV2.sUIDV2); + WS_DEBUG_PRINT("Device UID: "); + WS_DEBUG_PRINTLN(_device_uidV2); + + return true; +} + +/**************************************************************************/ +/*! + @brief Generates device-specific Wippersnapper control topics and + subscribes to them. + @returns True if memory for control topics allocated successfully, + False otherwise. +*/ +/**************************************************************************/ +bool Wippersnapper_V2::generateWSTopicsV2() { + WS_DEBUG_PRINTLN("Pre-calculating topic lengths..."); + // Calculate length of strings that are are dynamic within the secrets file + size_t lenUser = strlen(WsV2._configV2.aio_user); + size_t lenBoardId = strlen(_device_uidV2); + // Calculate length of static strings + size_t lenTopicX2x = strlen("/ws-x2x/"); + size_t lenTopicErrorStr = strlen("/errors/"); + size_t lenTopicThrottleStr = strlen("/throttle/"); + // Calculate length of complete topic strings + // NOTE: We are using "+2" to account for the null terminator and the "/" at + // the end of the topic + size_t lenTopicB2d = lenUser + lenTopicX2x + lenBoardId + 2; + size_t lenTopicD2b = lenUser + lenTopicX2x + lenBoardId + 2; + size_t lenTopicError = lenUser + lenTopicErrorStr + 2; + size_t lenTopicThrottle = lenUser + lenTopicThrottleStr + 2; + + // Attempt to allocate memory for the broker-to-device topic +#ifdef USE_PSRAM + WsV2._topicB2d = (char *)ps_malloc(sizeof(char) * lenTopicB2d); +#else + WsV2._topicB2d = (char *)malloc(sizeof(char) * lenTopicB2d); +#endif + // Check if memory allocation was successful + if (WsV2._topicB2d == NULL) + return false; + // Build the broker-to-device topic + snprintf(WsV2._topicB2d, lenTopicB2d, "%s/ws-b2d/%s/", + WsV2._configV2.aio_user, _device_uidV2); + WS_DEBUG_PRINT("Broker-to-device topic: "); + WS_DEBUG_PRINTLN(WsV2._topicB2d); + // Subscribe to broker-to-device topic + _subscribeB2d = new Adafruit_MQTT_Subscribe(WsV2._mqttV2, WsV2._topicB2d, 1); + WsV2._mqttV2->subscribe(_subscribeB2d); + _subscribeB2d->setCallback(cbBrokerToDevice); + + // Create global device to broker topic + // Attempt to allocate memory for the broker-to-device topic +#ifdef USE_PSRAM + WsV2._topicD2b = (char *)ps_malloc(sizeof(char) * lenTopicD2b); +#else + WsV2._topicD2b = (char *)malloc(sizeof(char) * lenTopicD2b); +#endif + // Check if memory allocation was successful + if (WsV2._topicD2b == NULL) + return false; + // Build the broker-to-device topic + snprintf(WsV2._topicD2b, lenTopicD2b, "%s/ws-d2b/%s/", + WsV2._configV2.aio_user, _device_uidV2); + WS_DEBUG_PRINT("Device-to-broker topic: "); + WS_DEBUG_PRINTLN(WsV2._topicD2b); + + // Attempt to allocate memory for the error topic +#ifdef USE_PSRAM + WsV2._topicError = (char *)ps_malloc(sizeof(char) * lenTopicError); +#else + WsV2._topicError = (char *)malloc(sizeof(char) * lenTopicError); +#endif + // Check if memory allocation was successful + if (WsV2._topicError == NULL) + return false; + // Build the error topic + snprintf(WsV2._topicError, lenTopicError, "%s/%s/", WsV2._configV2.aio_user, + "errors"); + WS_DEBUG_PRINT("Error topic: "); + WS_DEBUG_PRINTLN(WsV2._topicError); + // Subscribe to the error topic + _subscribeError = new Adafruit_MQTT_Subscribe(WsV2._mqttV2, WsV2._topicError); + WsV2._mqttV2->subscribe(_subscribeError); + // TODO: Implement the error topic callback + _subscribeError->setCallback(cbErrorTopicV2); + +// Attempt to allocate memory for the error topic +#ifdef USE_PSRAM + WsV2._topicThrottle = (char *)ps_malloc(sizeof(char) * lenTopicThrottle); +#else + WsV2._topicThrottle = (char *)malloc(sizeof(char) * lenTopicThrottle); +#endif + // Check if memory allocation was successful + if (WsV2._topicThrottle == NULL) + return false; + // Build the throttle topic + snprintf(WsV2._topicThrottle, lenTopicThrottle, "%s/%s/", + WsV2._configV2.aio_user, "throttle"); + WS_DEBUG_PRINT("Throttle topic: "); + WS_DEBUG_PRINTLN(WsV2._topicThrottle); + // Subscribe to throttle topic + _subscribeThrottle = + new Adafruit_MQTT_Subscribe(WsV2._mqttV2, WsV2._topicThrottle); + WsV2._mqttV2->subscribe(_subscribeThrottle); + _subscribeThrottle->setCallback(cbThrottleTopicV2); + + return true; +} + +/**************************************************************************/ +/*! + @brief Writes an error message to the serial and the filesystem, + blinks WS_LED_STATUS_ERROR_RUNTIME pattern and hangs. + @param error + The error message to write to the serial and filesystem. +*/ +/**************************************************************************/ +void Wippersnapper_V2::errorWriteHangV2(String error) { + // Print error + WS_DEBUG_PRINTLN(error); +#ifdef USE_TINYUSB + _fileSystemV2->writeToBootOut(error.c_str()); + TinyUSBDevice.attach(); + delay(500); +#endif + // Signal and hang forever + while (1) { + WS_DEBUG_PRINTLN("ERROR: Halted execution"); + WS_DEBUG_PRINTLN(error.c_str()); + WsV2.feedWDTV2(); + statusLEDBlink(WS_LED_STATUS_ERROR_RUNTIME); + delay(1000); + } +} + +/**************************************************************************/ +/*! + @brief Checks network and MQTT connectivity. Handles network + re-connection and mqtt re-establishment. +*/ +/**************************************************************************/ +void Wippersnapper_V2::runNetFSMV2() { + WsV2.feedWDTV2(); + // Initial state + fsm_net_t fsmNetwork; + fsmNetwork = FSM_NET_CHECK_MQTT; + int maxAttempts; + while (fsmNetwork != FSM_NET_CONNECTED) { + switch (fsmNetwork) { + case FSM_NET_CHECK_MQTT: + if (WsV2._mqttV2->connected()) { + // WS_DEBUG_PRINTLN("Connected to Adafruit IO!"); + fsmNetwork = FSM_NET_CONNECTED; + return; + } + fsmNetwork = FSM_NET_CHECK_NETWORK; + break; + case FSM_NET_CHECK_NETWORK: + if (networkStatusV2() == WS_NET_CONNECTED) { + WS_DEBUG_PRINTLN("Connected to WiFi!"); +#ifdef USE_DISPLAY + if (WsV2._ui_helper->getLoadingState()) + WsV2._ui_helper->set_load_bar_icon_complete(loadBarIconWifi); +#endif + fsmNetwork = FSM_NET_ESTABLISH_MQTT; + break; + } + fsmNetwork = FSM_NET_ESTABLISH_NETWORK; + break; + case FSM_NET_ESTABLISH_NETWORK: + WS_DEBUG_PRINTLN("Establishing network connection..."); + WS_PRINTER.flush(); +#ifdef USE_DISPLAY + if (WsV2._ui_helper->getLoadingState()) + WsV2._ui_helper->set_label_status("Connecting to WiFi..."); +#endif + // Perform a WiFi scan and check if SSID within + // secrets.json is within the scanned SSIDs + WS_DEBUG_PRINT("Performing a WiFi scan for SSID..."); + if (!check_valid_ssidV2()) { +#ifdef USE_DISPLAY + WsV2._ui_helper->show_scr_error("ERROR", + "Unable to find WiFi network listed in " + "the secrets file. Rebooting soon..."); +#endif + haltErrorV2("ERROR: Unable to find WiFi network, rebooting soon...", + WS_LED_STATUS_WIFI_CONNECTING); + } + // Attempt to connect to wireless network + maxAttempts = 5; + while (maxAttempts > 0) { + // blink before we connect + statusLEDBlink(WS_LED_STATUS_WIFI_CONNECTING); + feedWDTV2(); + // attempt to connect + WS_DEBUG_PRINT("Connecting to WiFi (attempt #"); + WS_DEBUG_PRINT(5 - maxAttempts); + WS_DEBUG_PRINTLN(")"); + WS_PRINTER.flush(); + feedWDTV2(); + _connectV2(); + feedWDTV2(); + // did we connect? + if (networkStatusV2() == WS_NET_CONNECTED) + break; + maxAttempts--; + } + // Validate connection + if (networkStatusV2() != WS_NET_CONNECTED) { + WS_DEBUG_PRINTLN("ERROR: Unable to connect to WiFi!"); +#ifdef USE_DISPLAY + WsV2._ui_helper->show_scr_error( + "CONNECTION ERROR", + "Unable to connect to WiFi Network. Please check that you entered " + "the WiFi credentials correctly. Rebooting in 5 seconds..."); +#endif + haltErrorV2("ERROR: Unable to connect to WiFi, rebooting soon...", + WS_LED_STATUS_WIFI_CONNECTING); + } + + fsmNetwork = FSM_NET_CHECK_NETWORK; + break; + case FSM_NET_ESTABLISH_MQTT: +#ifdef USE_DISPLAY + if (WsV2._ui_helper->getLoadingState()) + WsV2._ui_helper->set_label_status("Connecting to IO..."); +#endif + WsV2._mqttV2->setKeepAliveInterval(WS_KEEPALIVE_INTERVAL_MS / 1000); + // Attempt to connect + maxAttempts = 5; + while (maxAttempts > 0) { + WS_DEBUG_PRINT("Connecting to AIO MQTT (attempt #"); + WS_DEBUG_PRINT(5 - maxAttempts); + WS_DEBUG_PRINTLN(")"); + WS_PRINTER.flush(); + WS_DEBUG_PRINT("WiFi Status: "); + WS_DEBUG_PRINTLN(networkStatusV2()); + WS_PRINTER.flush(); + feedWDTV2(); + statusLEDBlink(WS_LED_STATUS_MQTT_CONNECTING); + feedWDTV2(); + int8_t mqttRC = WsV2._mqttV2->connect(); + feedWDTV2(); + if (mqttRC == WS_MQTT_CONNECTED) { + fsmNetwork = FSM_NET_CHECK_MQTT; + break; + } + WS_DEBUG_PRINT("MQTT Connection Error: "); + WS_DEBUG_PRINTLN(mqttRC); + WS_DEBUG_PRINTLN(WsV2._mqttV2->connectErrorString(mqttRC)); + WS_DEBUG_PRINTLN( + "Unable to connect to Adafruit IO MQTT, retrying in 3 seconds..."); + delay(3000); + maxAttempts--; + } + if (fsmNetwork != FSM_NET_CHECK_MQTT) { +#ifdef USE_DISPLAY + WsV2._ui_helper->show_scr_error( + "CONNECTION ERROR", + "Unable to connect to Adafruit.io. If you are repeatedly having " + "this issue, please check that your IO Username and IO Key are set " + "correctly in the secrets file. This device will reboot in 5 " + "seconds..."); +#endif + haltErrorV2( + "ERROR: Unable to connect to Adafruit.IO MQTT, rebooting soon...", + WS_LED_STATUS_MQTT_CONNECTING); + } + break; + default: + break; + } + } +} + +/**************************************************************************/ +/*! + @brief Prints an error to the serial and halts the hardware until + the WDT bites. + @param error + The desired error to print to serial. + @param ledStatusColor + The desired color to blink. +*/ +/**************************************************************************/ +void Wippersnapper_V2::haltErrorV2(String error, + ws_led_status_t ledStatusColor) { + for (;;) { + WS_DEBUG_PRINT("ERROR [WDT RESET]: "); + WS_DEBUG_PRINTLN(error); + // let the WDT fail out and reset! + statusLEDSolid(ledStatusColor); +#ifndef ARDUINO_ARCH_ESP8266 + delay(1000); +#else + // Calls to delay() and yield() feed the ESP8266's + // hardware and software watchdog timers, delayMicroseconds does not. + delayMicroseconds(1000000); +#endif + } +} + +/**************************************************************************/ +/*! + @brief Publishes a signal message to the broker. + @param which_payload + The type of signal payload to publish. + @param payload + The payload to publish. + @returns True if the signal message published successfully, + False otherwise. +*/ +/**************************************************************************/ +bool Wippersnapper_V2::PublishSignal(pb_size_t which_payload, void *payload) { + +#ifdef DEBUG_PROFILE + unsigned long total_start_time = micros(); +#endif + + size_t szMessageBuf; + wippersnapper_signal_DeviceToBroker MsgSignal = + wippersnapper_signal_DeviceToBroker_init_default; + + // Fill generic signal payload with the payload from the args. + WS_DEBUG_PRINT("Signal Payload Type: "); + switch (which_payload) { + case wippersnapper_signal_DeviceToBroker_checkin_request_tag: + WS_DEBUG_PRINTLN("CheckinRequest"); + MsgSignal.which_payload = + wippersnapper_signal_DeviceToBroker_checkin_request_tag; + MsgSignal.payload.checkin_request = + *(wippersnapper_checkin_CheckinRequest *)payload; + break; + case wippersnapper_signal_DeviceToBroker_digitalio_event_tag: + WS_DEBUG_PRINTLN("DigitalIOEvent"); + MsgSignal.which_payload = + wippersnapper_signal_DeviceToBroker_digitalio_event_tag; + MsgSignal.payload.digitalio_event = + *(wippersnapper_digitalio_DigitalIOEvent *)payload; + break; + case wippersnapper_signal_DeviceToBroker_analogio_event_tag: + WS_DEBUG_PRINTLN("AnalogIOEvent"); + MsgSignal.which_payload = + wippersnapper_signal_DeviceToBroker_analogio_event_tag; + MsgSignal.payload.analogio_event = + *(wippersnapper_analogio_AnalogIOEvent *)payload; + break; + case wippersnapper_signal_DeviceToBroker_ds18x20_added_tag: + WS_DEBUG_PRINTLN("DS18X20Added"); + MsgSignal.which_payload = + wippersnapper_signal_DeviceToBroker_ds18x20_added_tag; + MsgSignal.payload.ds18x20_added = + *(wippersnapper_ds18x20_Ds18x20Added *)payload; + break; + case wippersnapper_signal_DeviceToBroker_ds18x20_event_tag: + WS_DEBUG_PRINTLN("DS18X20Event"); + MsgSignal.which_payload = + wippersnapper_signal_DeviceToBroker_ds18x20_event_tag; + MsgSignal.payload.ds18x20_event = + *(wippersnapper_ds18x20_Ds18x20Event *)payload; + break; + default: + WS_DEBUG_PRINTLN("ERROR: Invalid signal payload type, bailing out!"); + return false; + } + + // Get the encoded size of the signal message + if (!pb_get_encoded_size(&szMessageBuf, + wippersnapper_signal_DeviceToBroker_fields, + &MsgSignal)) { + WS_DEBUG_PRINTLN( + "ERROR: Unable to get encoded size of signal message, bailing out!"); + return false; + } + + // Size the message buffer to fit the encoded signal message + uint8_t msgBuf[szMessageBuf]; + + // Encode the signal message + WS_DEBUG_PRINT("Encoding signal message..."); + pb_ostream_t stream = pb_ostream_from_buffer(msgBuf, szMessageBuf); + if (!ws_pb_encode(&stream, wippersnapper_signal_DeviceToBroker_fields, + &MsgSignal)) { + WS_DEBUG_PRINTLN( + "ERROR: Unable to encode d2b signal message, bailing out!"); + return false; + } + WS_DEBUG_PRINTLN("Encoded!"); + + // Check that we are still connected + runNetFSMV2(); + WsV2.feedWDTV2(); + +#ifdef DEBUG_PROFILE + unsigned long publish_start_time = micros(); +#endif + + // Attempt to publish the signal message to the broker + WS_DEBUG_PRINT("Publishing signal message to broker..."); +#ifdef DEBUG_PROFILE + WS_DEBUG_PRINT("Message buffer size: "); + WS_DEBUG_PRINTLN(szMessageBuf); +#endif + if (!WsV2._mqttV2->publish(WsV2._topicD2b, msgBuf, szMessageBuf, 1)) { + WS_DEBUG_PRINTLN("ERROR: Failed to publish signal message to broker!"); + return false; + } + WS_DEBUG_PRINTLN("Published!"); + +#ifdef DEBUG_PROFILE + unsigned long publish_end_time = micros(); + WS_DEBUG_PRINT("Publishing time: "); + WS_DEBUG_PRINTLN(publish_end_time - publish_start_time); + unsigned long total_end_time = micros(); + WS_DEBUG_PRINT("Total PublishSignal() execution time: "); + WS_DEBUG_PRINTLN(total_end_time - total_start_time); +#endif + + return true; +} + +/**************************************************************************/ +/*! + @brief Creates, fills, encodes and publishes a checkin request + message to the broker. + @returns True if the Checkin request message published successfully, + False otherwise. +*/ +/**************************************************************************/ +bool Wippersnapper_V2::CreateCheckinRequest() { + WS_DEBUG_PRINT("Creating the CheckinRequest message..."); + WsV2.CheckInModel = new CheckinModel(); + WsV2.CheckInModel->CreateCheckinRequest(WsV2.sUIDV2, WS_VERSION); + WS_DEBUG_PRINTLN("Created!"); + + WS_DEBUG_PRINT("Encoding the CheckinRequest message..."); + if (!WsV2.CheckInModel->EncodeCheckinRequest()) + return false; + WS_DEBUG_PRINTLN("Encoded!"); + + WS_DEBUG_PRINT("Publishing Checkin Request..."); + if (!PublishSignal(wippersnapper_signal_DeviceToBroker_checkin_request_tag, + WsV2.CheckInModel->getCheckinRequest())) + return false; + WS_DEBUG_PRINTLN("Published!"); + + return true; +} + +/**************************************************************************/ +/*! + @brief Polls for and handles the checkin response + message from the broker. +*/ +/**************************************************************************/ +void Wippersnapper_V2::PollCheckinResponse() { + WsV2.got_checkin_response = false; + WS_DEBUG_PRINTLN("Waiting for checkin response..."); + // If we don't get a response within WS_WDT_TIMEOUT seconds, the WDT + // will expire and reset the device + while (!WsV2.got_checkin_response) { + pingBrokerV2(); // Pinging the broker to keep the connection alive + WsV2._mqttV2->processPackets(10); // Process incoming packets + } + WS_DEBUG_PRINTLN("Completed checkin process!"); +} + +/**************************************************************************/ +/*! + @brief Pings the MQTT broker within the keepalive interval + to keep the connection alive. Blinks the keepalive LED + every STATUS_LED_KAT_BLINK_TIME milliseconds. +*/ +/**************************************************************************/ +void Wippersnapper_V2::pingBrokerV2() { + // ping within keepalive-10% to keep connection open + if (millis() > (_prv_pingV2 + (WS_KEEPALIVE_INTERVAL_MS - + (WS_KEEPALIVE_INTERVAL_MS * 0.10)))) { + WS_DEBUG_PRINT("Sending MQTT PING: "); + if (WsV2._mqttV2->ping()) { + WS_DEBUG_PRINTLN("SUCCESS!"); + } else { + WS_DEBUG_PRINTLN("FAILURE! Running network FSM..."); + WsV2._mqttV2->disconnect(); + runNetFSMV2(); + } + _prv_pingV2 = millis(); + WS_DEBUG_PRINT("WiFi RSSI: "); + WS_DEBUG_PRINTLN(getRSSIV2()); + } + // blink status LED every STATUS_LED_KAT_BLINK_TIME millis + if (millis() > (_prvKATBlinkV2 + STATUS_LED_KAT_BLINK_TIME)) { + WS_DEBUG_PRINTLN("STATUS LED BLINK KAT"); +#ifdef USE_DISPLAY + WsV2._ui_helper->add_text_to_terminal("[NET] Sent KeepAlive ping!\n"); +#endif + statusLEDBlink(WS_LED_STATUS_KAT); + _prvKATBlinkV2 = millis(); + } +} + +/********************************************************/ +/*! + @brief Feeds the WDT to prevent hardware reset. +*/ +/*******************************************************/ +void Wippersnapper_V2::feedWDTV2() { Watchdog.reset(); } + +/********************************************************/ +/*! + @brief Enables the watchdog timer. + @param timeoutMS + The desired amount of time to elapse before + the WDT executes. +*/ +/*******************************************************/ +void Wippersnapper_V2::enableWDTV2(int timeoutMS) { +#ifndef ARDUINO_ARCH_RP2040 + Watchdog.disable(); +#endif + if (Watchdog.enable(timeoutMS) == 0) { + WsV2.haltErrorV2("WDT initialization failure!"); + } +} + +/********************************************************/ +/*! + @brief Process all incoming packets from the + Adafruit IO MQTT broker. Handles network + connectivity. +*/ +/*******************************************************/ +void Wippersnapper_V2::processPacketsV2() { + // runNetFSMV2(); // NOTE: Removed for now, causes error with virtual + // _connectV2 method when caused with WsV2 object in another file. + WsV2.feedWDTV2(); + // Process all incoming packets from Wippersnapper_V2 MQTT Broker + WsV2._mqttV2->processPackets(10); +} + +/**************************************************************************/ +/*! + @brief Prints information about the WsV2 device to the serial monitor. +*/ +/**************************************************************************/ +void printDeviceInfoV2() { + WS_DEBUG_PRINTLN("-------Device Information-------"); + WS_DEBUG_PRINT("Firmware Version: "); + WS_DEBUG_PRINTLN(WS_VERSION); + WS_DEBUG_PRINTLN("API: Version 2"); + WS_DEBUG_PRINT("Board ID: "); + WS_DEBUG_PRINTLN(BOARD_ID); + WS_DEBUG_PRINT("Adafruit.io User: "); + WS_DEBUG_PRINTLN(WsV2._configV2.aio_user); + WS_DEBUG_PRINT("WiFi Network: "); + WS_DEBUG_PRINTLN(WsV2._configV2.network.ssid); + + char sMAC[18] = {0}; + sprintf(sMAC, "%02X:%02X:%02X:%02X:%02X:%02X", WsV2._macAddrV2[0], + WsV2._macAddrV2[1], WsV2._macAddrV2[2], WsV2._macAddrV2[3], + WsV2._macAddrV2[4], WsV2._macAddrV2[5]); + WS_DEBUG_PRINT("MAC Address: "); + WS_DEBUG_PRINTLN(sMAC); + WS_DEBUG_PRINTLN("-------------------------------"); + +// (ESP32-Only) Print reason why device was reset +#ifdef ARDUINO_ARCH_ESP32 + esp_reset_reason_t r = esp_reset_reason(); + WS_DEBUG_PRINT("ESP Reset Reason: "); + WS_DEBUG_PRINTLN(resetReasonName(r)); +#endif +} + +/**************************************************************************/ +/*! + @brief Connects to Adafruit IO+ Wippersnapper_V2 broker. +*/ +/**************************************************************************/ +void Wippersnapper_V2::connectV2() { + WS_DEBUG_PRINTLN("Adafruit.io WipperSnapper"); + + // Dump device info to the serial monitor + printDeviceInfoV2(); + + // enable global WDT + WsV2.enableWDTV2(WS_WDT_TIMEOUT); + + // Generate device identifier + WS_DEBUG_PRINTLN("Generating device UID..."); + if (!generateDeviceUIDV2()) { + haltErrorV2("Unable to generate Device UID"); + } + WS_DEBUG_PRINTLN("Device UID generated successfully!"); + + // If we are running in offline mode, we skip the network setup + // and MQTT connection process and jump to the offline device config process + // NOTE: After this, bail out of this function and run the app loop!!! + if (WsV2._sdCardV2->mode_offline == true) { + WS_DEBUG_PRINTLN("[Offline] Running device configuration..."); + // Enable logging + // TODO: Change func. name to ConfigureLogging + WS_DEBUG_PRINTLN("[Offline] Enabling logging..."); + WsV2._sdCardV2->EnableLogging(); +// Wait for incoming JSON string from Serial +#ifdef OFFLINE_MODE_DEBUG + if (!WsV2._sdCardV2->waitForSerialConfig()) + haltErrorV2("Unable to validate incoming JSON file"); +#endif + // Parse the JSON file + if (!WsV2._sdCardV2->parseConfigFile()) + haltErrorV2("Failed to parse incoming JSON file"); + WS_DEBUG_PRINT("[Offline] Attempting to configure hardware..."); + // Mock the "check in" process + // Configure GPIO classes based on checkin response message + // TODO: Add checkin pins/data to the JSON string, we are hardcoding here + WsV2.digital_io_controller->SetMaxDigitalPins(10); + WsV2.analogio_controller->SetRefVoltage(3.3); + WsV2.analogio_controller->SetTotalAnalogPins(4); + // Call the signal decoder to parse the incoming JSON string + // TODO: We need a way to repeat this call better + callDecodeB2D(); + WS_DEBUG_PRINTLN( + "[Offline] Hardware configured, skipping network setup..."); + return; + } else { + WS_DEBUG_PRINTLN("Running in online mode..."); + } + + // Configures an Adafruit Arduino MQTT object + WS_DEBUG_PRINTLN("Setting up MQTT client..."); + setupMQTTClientV2(_device_uidV2); + WS_DEBUG_PRINTLN("Set up MQTT client successfully!"); + + WS_DEBUG_PRINTLN("Generating device's MQTT topics..."); + if (!generateWSTopicsV2()) { + haltErrorV2("Unable to allocate space for MQTT topics"); + } + WS_DEBUG_PRINTLN("Generated device's MQTT topics successfully!"); + + // Connect to Network + WS_DEBUG_PRINTLN("Running Network FSM..."); + // Run the network fsm + runNetFSMV2(); + WsV2.feedWDTV2(); + +#ifdef USE_DISPLAY + WsV2._ui_helper->set_load_bar_icon_complete(loadBarIconCloud); + WsV2._ui_helper->set_label_status("Sending device info..."); +#endif + + WS_DEBUG_PRINTLN("Performing checkin handshake..."); + // Publish the checkin request + if (!CreateCheckinRequest()) { + haltErrorV2("Unable to publish checkin request"); + } + // Handle the checkin response + PollCheckinResponse(); + + // Set the status LED to green to indicate successful configuration + setStatusLEDColor(0x00A300, WS.status_pixel_brightness); + delay(100); + // Set the status LED to off during app runtime + setStatusLEDColor(0x000000, WS.status_pixel_brightness); + +// switch to monitor screen +#ifdef USE_DISPLAY + WS_DEBUG_PRINTLN("Clearing loading screen..."); + WsV2._ui_helper->clear_scr_load(); + WS_DEBUG_PRINTLN("building monitor screen..."); + WsV2._ui_helper->build_scr_monitor(); +#endif + WS_DEBUG_PRINTLN("Running app loop..."); +} + +/**************************************************************************/ +/*! + @brief Processes incoming commands and handles network connection. + @returns Network status, as ws_status_t. +*/ +/**************************************************************************/ +ws_status_t Wippersnapper_V2::runV2() { + if (!WsV2._sdCardV2->mode_offline) { + // Handle networking functions + runNetFSMV2(); + WsV2.feedWDTV2(); + pingBrokerV2(); + // Process all incoming packets from Wippersnapper_V2 MQTT Broker + WsV2._mqttV2->processPackets(10); + } + + // Process all digital events + WsV2.digital_io_controller->Update(); + + // Process all analog inputs + WsV2.analogio_controller->update(); + + // Process all DS18x20 sensor events + WsV2._ds18x20_controller->update(); + + // TODO: Process I2C sensor events + + // TODO: Process DS18x20 sensor events + + // TODO: Process UART sensor events + + return WS_NET_CONNECTED; // TODO: Make this funcn void! +} diff --git a/src/Wippersnapper_V2.h b/src/Wippersnapper_V2.h new file mode 100644 index 000000000..81f20e2b6 --- /dev/null +++ b/src/Wippersnapper_V2.h @@ -0,0 +1,292 @@ +/*! + * @file Wippersnapper_V2.h + * + * This is the documentation for Adafruit's Wippersnapper firmware for the + * Arduino platform. It is designed specifically to work with + * Adafruit IO Wippersnapper IoT platform. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2020-2024 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ + +#ifndef WIPPERSNAPPER_V2_H +#define WIPPERSNAPPER_V2_H + +// Debug Flags +#define OFFLINE_MODE_DEBUG 1 ///< Debug flag for offline mode (read via serial) +// #DEBUG_PROFILE 1 ///< Enable debug output for function profiling + +// Cpp STD +#include + +// Nanopb dependencies +#include +#include +#include +#include +#include + +// Include Signal Proto +#include "protos/checkin.pb.h" +#include "protos/digitalio.pb.h" +#include "protos/ds18x20.pb.h" +#include "protos/signal.pb.h" + +// External libraries +#include "Adafruit_MQTT.h" // MQTT Client +#include "Adafruit_SleepyDog.h" // Watchdog +#include "Arduino.h" // Wiring +#include // SPI + +// Wippersnapper API Helpers +#include "Wippersnapper_Boards.h" +#include "components/statusLED/Wippersnapper_StatusLED.h" +#include "helpers/ws_helper_status.h" +#ifdef ARDUINO_ARCH_ESP32 +#include "helpers/ws_helper_esp.h" +#endif + +// Components (API v2) +#include "components/analogio/controller.h" +#include "components/checkin/model.h" +#include "components/digitalIO/controller.h" +#include "components/ds18x20/controller.h" +#include "components/sensor/model.h" + +// Components (API v1) +#include "components/analogIO/Wippersnapper_AnalogIO.h" +#include "components/i2c/WipperSnapper_I2C.h" +#include "components/pixels/ws_pixels.h" +#include "components/pwm/ws_pwm.h" +#include "components/servo/ws_servo.h" +#include "components/uart/ws_uart.h" +#ifdef ARDUINO_ARCH_ESP32 +#include "components/ledc/ws_ledc.h" +#endif +// Display +#ifdef USE_DISPLAY +#include "display/ws_display_driver.h" +#include "display/ws_display_ui_helper.h" +#endif + +#include "provisioning/ConfigJson.h" +#include "provisioning/sdcard/ws_sdcard.h" +#if defined(USE_TINYUSB) +#include "provisioning/tinyusb/Wippersnapper_FS_V2.h" +#endif +#if defined(USE_LITTLEFS) +#include "provisioning/littlefs/WipperSnapper_LittleFS.h" +#endif + +#define WS_VERSION \ + "2.0.0-alpha.1" ///< WipperSnapper app. version (semver-formatted) + +#define WS_WDT_TIMEOUT 60000 ///< WDT timeout +#define WS_MAX_ALT_WIFI_NETWORKS 3 ///< Maximum number of alternative networks +/* MQTT Configuration */ +#define WS_KEEPALIVE_INTERVAL_MS \ + 5000 ///< Session keepalive interval time, in milliseconds + +// Forward declarations (API v1) +class Wippersnapper_AnalogIO; +class Wippersnapper_FS_V2; +class WipperSnapper_LittleFS; +class ws_sdcard; +#ifdef USE_DISPLAY +class ws_display_driver; +class ws_display_ui_helper; +#endif +#ifdef ARDUINO_ARCH_ESP32 +class ws_ledc; +#endif +class WipperSnapper_Component_I2C; +class ws_servo; +class ws_pwm; +class ws_pixels; +class ws_uart; + +// Forward declarations (API v2) +class CheckinModel; +class SensorModel; +class DigitalIOController; +class AnalogIOController; +class DS18X20Controller; + +/**************************************************************************/ +/*! + @brief Class that provides storage and functions for the Adafruit IO + Wippersnapper interface. +*/ +/**************************************************************************/ +class Wippersnapper_V2 { +public: + Wippersnapper_V2(); + virtual ~Wippersnapper_V2(); + + void provisionV2(); + + // Global flags for the status led + bool + lockStatusNeoPixelV2; ///< True if status LED is using the status neopixel + bool lockStatusDotStarV2; ///< True if status LED is using the status dotstar + bool lockStatusLEDV2; ///< True if status LED is using the built-in LED + float status_pixel_brightnessV2 = + STATUS_PIXEL_BRIGHTNESS_DEFAULT; ///< Global status pixel's brightness + ///< (from 0.0 to 1.0) + + virtual void set_user_keyV2(); + virtual void set_ssid_passV2(const char *ssid, const char *ssidPassword); + virtual void set_ssid_passV2(); + virtual bool check_valid_ssidV2(); + + virtual void _connectV2(); + virtual void _disconnectV2(); + void connectV2(); + void disconnectV2(); + + virtual void getMacAddrV2(); + virtual int32_t getRSSIV2(); + virtual void setupMQTTClientV2(const char *clientID); + + virtual ws_status_t networkStatusV2(); + + // Generators for device UID and MQTT topics + bool generateDeviceUIDV2(); + bool generateWSTopicsV2(); + + // High-level MQTT Publish + bool PublishSignal(pb_size_t which_payload, void *payload); + + // Checkin API + bool CreateCheckinRequest(); + void PollCheckinResponse(); + + // run() loop + ws_status_t runV2(); + void processPacketsV2(); + + // Networking helpers + void pingBrokerV2(); + void runNetFSMV2(); + + // WDT helpers + void enableWDTV2(int timeoutMS = 0); + void feedWDTV2(); + + // Error handling helpers + void + haltErrorV2(String error, + ws_led_status_t ledStatusColor = WS_LED_STATUS_ERROR_RUNTIME); + void errorWriteHangV2(String error); + + bool _is_offline_mode; ///< Global flag for if the device is in offline mode + + // I2C + // TODO: Audit all of this! + std::vector + i2cComponentsV2; ///< Vector containing all I2C components + WipperSnapper_Component_I2C *_i2cPort0V2 = + NULL; ///< WipperSnapper I2C Component for I2C port #0 + WipperSnapper_Component_I2C *_i2cPort1V2 = + NULL; ///< WipperSnapper I2C Component for I2C port #1 + bool _isI2CPort0InitV2 = + false; ///< True if I2C port 0 has been initialized, False otherwise. + bool _isI2CPort1InitV2 = + false; ///< True if I2C port 1 has been initialized, False otherwise. + + // TODO: Do we need this? + ws_board_status_t _boardStatusV2 = + WS_BOARD_DEF_IDLE; ///< Hardware's registration status + + // TODO: We really should look at making these static definitions, not dynamic + // to free up space on the heap + Wippersnapper_AnalogIO *_analogIOV2; ///< Instance of analog io class + Wippersnapper_FS_V2 *_fileSystemV2; ///< Instance of Filesystem (native USB) + WipperSnapper_LittleFS + *_littleFSV2; ///< Instance of LittleFS Filesystem (non-native USB) + ws_sdcard *_sdCardV2; ///< Instance of SD card class +#ifdef USE_DISPLAY + ws_display_driver *_displayV2 = nullptr; ///< Instance of display driver class + ws_display_ui_helper *_ui_helperV2 = + nullptr; ///< Instance of display UI helper class +#endif + ws_pixels *_ws_pixelsComponentV2; ///< ptr to instance of ws_pixels class + ws_pwm *_pwmComponentV2; ///< Instance of pwm class + ws_servo *_servoComponentV2; ///< Instance of servo class + ws_uart *_uartComponentV2; ///< Instance of UART class + + // API v2 Components + CheckinModel *CheckInModel; ///< Instance of CheckinModel class + SensorModel *sensorModel; ///< Instance of SensorModel class + DigitalIOController + *digital_io_controller; ///< Instance of DigitalIO controller class + AnalogIOController *analogio_controller; ///< Instance of AnalogIO controller + DS18X20Controller *_ds18x20_controller; ///< Instance of DS18X20 controller + + // TODO: does this really need to be global? + uint8_t _macAddrV2[6]; /*!< Unique network iface identifier */ + char sUIDV2[13]; /*!< Unique network iface identifier */ + const char *_boardIdV2; /*!< Adafruit IO+ board string */ + Adafruit_MQTT *_mqttV2; /*!< Reference to Adafruit_MQTT, _mqtt. */ + + // TODO: Audit this, does it need to be here? + secretsConfig _configV2; /*!< Wippersnapper secrets.json as a struct. */ + networkConfig _multiNetworksV2[3]; /*!< Wippersnapper networks as structs. */ + bool _isWiFiMultiV2 = false; /*!< True if multiple networks are defined. */ + + // TODO: Does this need to be within this class? + int32_t totalDigitalPinsV2; /*!< Total number of digital-input capable pins */ + + // TODO: Do these need to be here or can they sit within their function? + char *throttleMessageV2; /*!< Pointer to throttle message data. */ + int throttleTimeV2; /*!< Total amount of time to throttle the device, in + milliseconds. */ + +// enable LEDC if esp32 +#ifdef ARDUINO_ARCH_ESP32 + ws_ledc *_ledcV2 = nullptr; ///< Pointer to LEDC object +#endif + bool got_checkin_response; ///< True if a checkin response was received, False + ///< otherwise. + std::vector> + _sharedConfigBuffers; ///< Shared JSON config buffers for offline mode +private: + void _initV2(); + + // MQTT topics + char *_topicB2d; + char *_topicD2b; + char *_topicError; + char *_topicThrottle; + + // Adafruit_MQTT Subscription objects + Adafruit_MQTT_Subscribe *_subscribeB2d; + Adafruit_MQTT_Subscribe *_subscribeError; + Adafruit_MQTT_Subscribe *_subscribeThrottle; + +protected: + ws_status_t _statusV2 = WS_IDLE; ///< Wippersnapper status + + uint32_t _last_mqtt_connectV2 = 0; /*!< Previous time when client connected to + Adafruit IO, in milliseconds. */ + uint32_t _prv_pingV2 = 0; /*!< Previous time when client pinged Adafruit IO's + MQTT broker, in milliseconds. */ + uint32_t _prvKATBlinkV2 = 0; /*!< Previous time when client pinged Adafruit + IO's MQTT broker, in milliseconds. */ + + // Device information + const char *_deviceIdV2; /*!< Adafruit IO+ device identifier string */ + char *_device_uidV2; /*!< Unique device identifier */ + + wippersnapper_signal_v1_CreateSignalRequest + _outgoingSignalMsgV2; /*!< Outgoing signal message from device */ +}; +extern Wippersnapper_V2 WsV2; ///< Global member variable for callbacks + +#endif // WIPPERSNAPPER_V2_H diff --git a/src/Wippersnapper_demo.ino b/src/Wippersnapper_demo_regular.ino.skip similarity index 54% rename from src/Wippersnapper_demo.ino rename to src/Wippersnapper_demo_regular.ino.skip index 4327de9e4..3cd5697b1 100644 --- a/src/Wippersnapper_demo.ino +++ b/src/Wippersnapper_demo_regular.ino.skip @@ -13,23 +13,31 @@ // // All text above must be included in any redistribution. -#include "Wippersnapper_Networking.h" -Wippersnapper_WiFi wipper; +#include "ws_manager.h" +//#include "Wippersnapper_Networking.h" +//Wippersnapper_WiFi wipper; +Wippersnapper_Manager manager; // Enable debug output for beta builds #define WS_DEBUG +// Pin to check for API version +#define API_PIN 0 + void setup() { - // Provisioning must occur prior to serial init. - wipper.provision(); + // NOTE: Provisioning must occur prior to serial init. + manager.checkAPIVersion(API_PIN); + manager.provision(); Serial.begin(115200); - // while (!Serial) delay(10); - - wipper.connect(); + while(!Serial) {;} + Serial.println("Adafruit Wippersnapper API Manager Demo"); + Serial.print("Running Wippersnapper API Version: "); + Serial.println(manager.getAPIVersion()); + manager.connect(); } void loop() { - wipper.run(); + manager.run(); } \ No newline at end of file diff --git a/src/Wippersnapper_demo_wokwi.ino b/src/Wippersnapper_demo_wokwi.ino new file mode 100644 index 000000000..87b50f301 --- /dev/null +++ b/src/Wippersnapper_demo_wokwi.ino @@ -0,0 +1,46 @@ +// Adafruit IO WipperSnapper Beta, Wokwi Test Sketch +// +// ***NOTICE*** +// This sketch is not intended to be uploaded to a physical device +// This sketch is for testing Wokwi-CLI and Wokwi-VSCode +// +// Brent Rubell for Adafruit Industries, 2024 +// +// All text above must be included in any redistribution. + +/************************ Adafruit IO Config *******************************/ +// Visit io.adafruit.com if you need to create an account, +// or if you need your Adafruit IO key. +#define IO_USERNAME "brubell" +#define IO_KEY "YOUR_AIO_KEY" +/**************************** WiFi Config ***********************************/ +#define WIFI_SSID "Wokwi-GUEST" +#define WIFI_PASS "" +// Enable debug output for beta builds +#define WS_DEBUG +// Pin to check for API version +#define API_PIN 0 + +#include "ws_manager.h" +//#include "Wippersnapper_Networking.h" +//Wippersnapper_WiFi wipper; +Wippersnapper_Manager manager; +Wippersnapper_WiFiV2 wipper(IO_USERNAME, IO_KEY, WIFI_SSID, WIFI_PASS, "io.adafruit.com", 8883); + + +void setup() { + // NOTE: Provisioning must occur prior to serial init. + manager.checkAPIVersion(API_PIN); + manager.provision(); + + Serial.begin(115200); + // while (!Serial) delay(10); + Serial.println("Adafruit Wippersnapper API Manager Demo"); + Serial.print("Running Wippersnapper API Version: "); + Serial.println(manager.getAPIVersion()); + manager.connect(); +} + +void loop() { + manager.run(); +} \ No newline at end of file diff --git a/src/components/analogIO/controller.cpp b/src/components/analogIO/controller.cpp new file mode 100644 index 000000000..6c163a755 --- /dev/null +++ b/src/components/analogIO/controller.cpp @@ -0,0 +1,295 @@ +/*! + * @file controller.cpp + * + * Controller for the analogio.proto API + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2024 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#include "controller.h" + +/***********************************************************************/ +/*! + @brief AnalogIO controller constructor +*/ +/***********************************************************************/ +AnalogIOController::AnalogIOController() { + _analogio_hardware = new AnalogIOHardware(); + _analogio_model = new AnalogIOModel(); + _analogio_hardware->SetResolution(16); // Default to 16-bit resolution + SetRefVoltage(3.3); // Default to 3.3V +} + +/***********************************************************************/ +/*! + @brief AnalogIO controller destructor +*/ +/***********************************************************************/ +AnalogIOController::~AnalogIOController() { + delete _analogio_hardware; + delete _analogio_model; +} + +/***********************************************************************/ +/*! + @brief Set the reference voltage for the analog pins + @param voltage + The reference voltage. +*/ +/***********************************************************************/ +void AnalogIOController::SetRefVoltage(float voltage) { + // To set the reference voltage, we call into the hardware + _analogio_hardware->SetReferenceVoltage(voltage); +} + +/***********************************************************************/ +/*! + @brief Set the total number of analog pins present on the hardware. + @param total_pins + The hardware's total number of analog pins. +*/ +/***********************************************************************/ +void AnalogIOController::SetTotalAnalogPins(uint8_t total_pins) { + _total_analogio_pins = total_pins; +} + +/***********************************************************************/ +/*! + @brief Handles an AnalogIOAdd message from the broker and adds a + new analog pin to the controller. + @param stream + The nanopb input stream. + @return True if the pin was successfully added, False otherwise. +*/ +/***********************************************************************/ +bool AnalogIOController::Handle_AnalogIOAdd(pb_istream_t *stream) { + // Attempt to decode the incoming message into an AnalogIOAdd object + if (!_analogio_model->DecodeAnalogIOAdd(stream)) { + WS_DEBUG_PRINTLN("[analogio] ERROR: Unable to decode Add message"); + return false; + } + + // Get the pin name + uint8_t pin_name = atoi(_analogio_model->GetAnalogIOAddMsg()->pin_name + 1); + + // Create a new analogioPin object + // TODO: Replicate this within the digitalio controller, much cleaner way to + // assign! + analogioPin new_pin = { + .name = pin_name, + .period = long(_analogio_model->GetAnalogIOAddMsg()->period) * 1000, + .prv_period = 0, + .read_mode = _analogio_model->GetAnalogIOAddMsg()->read_mode}; + + // Initialize the pin + _analogio_hardware->InitPin(pin_name); + + // Print out the pin's details + WS_DEBUG_PRINTLN("[analogio] Added new pin:"); + WS_DEBUG_PRINT("Pin Name: "); + WS_DEBUG_PRINTLN(new_pin.name); + WS_DEBUG_PRINT("Period: "); + WS_DEBUG_PRINTLN(new_pin.period); + WS_DEBUG_PRINT("Read Mode: "); + WS_DEBUG_PRINTLN(new_pin.read_mode); + + // Add the new pin to the vector + _analogio_pins.push_back(new_pin); + + return true; +} + +/***************************************************************************/ +/*! + @brief Handles an AnalogIORemove message from the broker and removes + the requested analog pin from the controller. + @param stream + The nanopb input stream. + @return True if the pin was successfully removed, False otherwise. +*/ +/***************************************************************************/ +bool AnalogIOController::Handle_AnalogIORemove(pb_istream_t *stream) { + // Attempt to decode the incoming message into an AnalogIORemove object + if (!_analogio_model->DecodeAnalogIORemove(stream)) { + WS_DEBUG_PRINTLN("ERROR: Unable to decode AnalogIORemove message"); + return false; + } + + // Get the pin name + int pin_name = atoi(_analogio_model->GetAnalogIORemoveMsg()->pin_name + 1); + + // Remove the pin from the hardware + _analogio_hardware->DeinitPin(pin_name); + + // Remove the pin from the vector + // TODO: Refactor this out? TODO: Make this better?? + for (int i = 0; i < _analogio_pins.size(); i++) { + if (_analogio_pins[i].name == pin_name) { + _analogio_pins.erase(_analogio_pins.begin() + i); + break; + } + } + return true; +} + +/***************************************************************************/ +/*! + @brief Checks if a pin's periodic timer has expired. + @param pin + The requested pin to check. + @param cur_time + The current time (called from millis()). + @return True if the pin's period has expired, False otherwise. +*/ +/***************************************************************************/ +bool AnalogIOController::IsPinTimerExpired(analogioPin *pin, ulong cur_time) { + return cur_time - pin->prv_period > pin->period; +} + +/***************************************************************************/ +/*! + @brief Encodes and publishes an AnalogIOEvent message to the broker. + @param pin + The pin to encode and publish. + @param value + The pin's value. + @param read_type + The type of read to perform on the pin. + @return True if the message was successfully encoded and published. +*/ +/***************************************************************************/ +bool AnalogIOController::EncodePublishPinEvent( + uint8_t pin, float value, wippersnapper_sensor_SensorType read_type) { + char c_pin_name[12]; + sprintf(c_pin_name, "A%d", pin); + + if (read_type == wippersnapper_sensor_SensorType_SENSOR_TYPE_RAW) { + if (!_analogio_model->EncodeAnalogIOEventRaw(c_pin_name, value)) { + WS_DEBUG_PRINTLN("ERROR: Unable to encode AnalogIO raw adc message!"); + return false; + } + } else if (read_type == wippersnapper_sensor_SensorType_SENSOR_TYPE_VOLTAGE) { + if (!_analogio_model->EncodeAnalogIOEventVoltage(c_pin_name, value)) { + WS_DEBUG_PRINTLN("ERROR: Unable to encode AnalogIO voltage message!"); + return false; + } + } else { + WS_DEBUG_PRINTLN("ERROR: Invalid read type for AnalogIOEvent message!"); + return false; + } + + // Publish the AnalogIO message to the broker + if (!WsV2._sdCardV2->mode_offline) { + WS_DEBUG_PRINTLN("Publishing AnalogIOEvent message to broker..."); + if (!WsV2.PublishSignal( + wippersnapper_signal_DeviceToBroker_analogio_event_tag, + _analogio_model->GetAnalogIOEvent())) { + WS_DEBUG_PRINTLN( + "ERROR: Unable to publish analogio voltage event message, " + "moving onto the next pin!"); + return false; + } + WS_DEBUG_PRINTLN("Published AnalogIOEvent message to broker!") + } else { + // Print event data + WS_DEBUG_PRINTLN("AnalogIOEvent message:"); + WS_DEBUG_PRINT("Pin Name: "); + WS_DEBUG_PRINTLN(c_pin_name); + WS_DEBUG_PRINT("Value: "); + WS_DEBUG_PRINTLN(value); + WS_DEBUG_PRINT("Read Type: "); + WS_DEBUG_PRINTLN(read_type); + WS_DEBUG_PRINTLN("[analogio] Offline analogIOEvent message not published!"); + // TODO: Log out this data by calling a logging function in sdcard class + } + + return true; +} + +/***************************************************************************/ +/*! + @brief Encodes and publishes an AnalogIOEvent message to the broker. + @param pin + The requested pin. + @param value + The pin's value. + @return True if the message was successfully encoded and published, + False othewise. +*/ +/***************************************************************************/ +bool AnalogIOController::EncodePublishPinValue(uint8_t pin, uint16_t value) { + + if (WsV2._sdCardV2->mode_offline) { + return WsV2._sdCardV2->LogGPIOSensorEventToSD( + pin, value, wippersnapper_sensor_SensorType_SENSOR_TYPE_RAW); + } else { + return EncodePublishPinEvent( + pin, (float)value, wippersnapper_sensor_SensorType_SENSOR_TYPE_RAW); + } +} + +/***************************************************************************/ +/*! + @brief Encodes and publishes an AnalogIOEvent message to the broker. + @param pin + The requested pin. + @param value + The pin's value, as a voltage. + @return True if the message was successfully encoded and published, + False othewise. +*/ +/***************************************************************************/ +bool AnalogIOController::EncodePublishPinVoltage(uint8_t pin, float value) { + if (WsV2._sdCardV2->mode_offline) { + return WsV2._sdCardV2->LogGPIOSensorEventToSD( + pin, value, wippersnapper_sensor_SensorType_SENSOR_TYPE_VOLTAGE); + } else { + return EncodePublishPinEvent( + pin, value, wippersnapper_sensor_SensorType_SENSOR_TYPE_VOLTAGE); + } +} + +/***************************************************************************/ +/*! + @brief Update/polling loop for the AnalogIO controller. +*/ +/***************************************************************************/ +void AnalogIOController::update() { + // Bail-out if the vector is empty + if (_analogio_pins.empty()) + return; + + // Process analog input pins + for (int i = 0; i < _analogio_pins.size(); i++) { + // Create a pin object for this iteration + analogioPin &pin = _analogio_pins[i]; + // Go to the next pin if the period hasn't expired yet + ulong cur_time = millis(); + if (!IsPinTimerExpired(&pin, cur_time)) + continue; + + // Pins timer has expired, lets read the pin + if (pin.read_mode == wippersnapper_sensor_SensorType_SENSOR_TYPE_RAW) { + // Read the pin's raw value + uint16_t value = _analogio_hardware->GetPinValue(pin.name); + // Encode and publish it to the broker + EncodePublishPinValue(pin.name, value); + pin.prv_period = cur_time; // Reset the pin's period + } else if (pin.read_mode == + wippersnapper_sensor_SensorType_SENSOR_TYPE_VOLTAGE) { + // Convert the raw value into voltage + float pin_value = _analogio_hardware->GetPinVoltage(pin.name); + // Encode and publish the voltage value to the broker + EncodePublishPinVoltage(pin.name, pin_value); + pin.prv_period = cur_time; // Reset the pin's period + } else { + WS_DEBUG_PRINTLN("[analogio] ERROR: Invalid read mode for analog pin!"); + } + } +} \ No newline at end of file diff --git a/src/components/analogIO/controller.h b/src/components/analogIO/controller.h new file mode 100644 index 000000000..d046578c8 --- /dev/null +++ b/src/components/analogIO/controller.h @@ -0,0 +1,69 @@ +/*! + * @file controller.h + * + * Controller for the AnalogIO API + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2024 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#ifndef WS_ANALOGIO_CONTROLLER_H +#define WS_ANALOGIO_CONTROLLER_H +#include "Wippersnapper_V2.h" +#include "hardware.h" +#include "model.h" + +class Wippersnapper_V2; ///< Forward declaration +class AnalogIOModel; ///< Forward declaration +class AnalogIOHardware; ///< Forward declaration + +/** + * @struct analogioPin + * @brief Represents a device's analogio pin. + */ +struct analogioPin { + uint8_t name; ///< The pin's name. + float period; ///< The pin's period, in milliseconds. + float prv_period; ///< The pin's previous period, in milliseconds. + wippersnapper_sensor_SensorType read_mode; ///< Type of analog read to perform +}; + +/**************************************************************************/ +/*! + @brief Routes messages using the analogio.proto API to the + appropriate hardware and model classes, controls and tracks + the state of the hardware's digital I/O pins. +*/ +/**************************************************************************/ +class AnalogIOController { +public: + AnalogIOController(); + ~AnalogIOController(); + // Routing + bool Handle_AnalogIOAdd(pb_istream_t *stream); + bool Handle_AnalogIORemove(pb_istream_t *stream); + // Polling loop + void update(); + // Encoder + bool EncodePublishPinEvent(uint8_t pin, float value, + wippersnapper_sensor_SensorType read_type); + bool EncodePublishPinValue(uint8_t pin, uint16_t value); + bool EncodePublishPinVoltage(uint8_t pin, float value); + // Helpers + void SetTotalAnalogPins(uint8_t total_pins); + void SetRefVoltage(float voltage); + bool IsPinTimerExpired(analogioPin *pin, ulong cur_time); + +private: + AnalogIOModel *_analogio_model; ///< AnalogIO model + AnalogIOHardware *_analogio_hardware; ///< AnalogIO hardware + std::vector _analogio_pins; ///< Vector of analogio pins + uint8_t _total_analogio_pins; ///< Total number of analogio pins +}; +extern Wippersnapper_V2 WsV2; ///< Wippersnapper V2 instance +#endif // WS_ANALOGIO_CONTROLLER_H \ No newline at end of file diff --git a/src/components/analogIO/hardware.cpp b/src/components/analogIO/hardware.cpp new file mode 100644 index 000000000..b946b4250 --- /dev/null +++ b/src/components/analogIO/hardware.cpp @@ -0,0 +1,145 @@ +/*! + * @file hardware.cpp + * + * Hardware interface for the analogio.proto API + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2024 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#include "hardware.h" + +/***********************************************************************/ +/*! + @brief AnalogIO hardware constructor +*/ +/***********************************************************************/ +AnalogIOHardware::AnalogIOHardware() { + SetNativeADCResolution(); // Configure the device's native ADC resolution + SetResolution(16); // Wippersnapper's default resolution is 16-bit +} + +/***********************************************************************/ +/*! + @brief AnalogIO hardware destructor +*/ +/***********************************************************************/ +AnalogIOHardware::~AnalogIOHardware() {} + +/***********************************************************************/ +/*! + @brief Initializes an analog input pin. + @param pin + The requested pin. +*/ +/***********************************************************************/ +void AnalogIOHardware::InitPin(uint8_t pin) { pinMode(pin, INPUT); } + +/***********************************************************************/ +/*! + @brief Deinitializes an analog input pin and frees it for + other uses. + @param pin + The requested pin. +*/ +/***********************************************************************/ +void AnalogIOHardware::DeinitPin(uint8_t pin) { + pinMode(pin, INPUT); // set to a hi-z floating pin +} + +/*************************************************************************/ +/*! + @brief Sets the hardware's reference voltage for reading analog pins + @param voltage + The reference voltage. +*/ +/*************************************************************************/ +void AnalogIOHardware::SetReferenceVoltage(float voltage) { + _ref_voltage = voltage; +} + +/*************************************************************************/ +/*! + @brief Configures the hardware's native ADC resolution. +*/ +/*************************************************************************/ +void AnalogIOHardware::SetNativeADCResolution() { +#if defined(ARDUINO_ARCH_SAMD) + _native_adc_resolution = 12; +#elif defined(ARDUINO_ARCH_ESP32) +#if defined(CONFIG_IDF_TARGET_ESP32S3) + // In arduino-esp32, the ESP32-S3 ADC uses a 13-bit resolution + _native_adc_resolution = 13; +#else + // The ESP32, ESP32-S2, ESP32-C3 ADCs uses 12-bit resolution + _native_adc_resolution = 12; +#endif +#elif defined(ARDUINO_ARCH_RP2040) + _native_adc_resolution = 10; +#else + _native_adc_resolution = 10; +#endif + // Set the resolution (in bits) of the hardware's ADC + analogReadResolution(_native_adc_resolution); +} + +/*************************************************************************/ +/*! + @brief Sets the hardware ADC's resolution. + @param resolution + The requested resolution, in bits. +*/ +/*************************************************************************/ +void AnalogIOHardware::SetResolution(uint8_t resolution) { + _desired_adc_resolution = resolution; + // Calculate (or re-calculate) the scale factor whenever we set the desired + // read resolution + CalculateScaleFactor(); +} + +/*************************************************************************/ +/*! + @brief Calculates a factor used by the hardware for scaling + the ADC's resolution. +*/ +/*************************************************************************/ +void AnalogIOHardware::CalculateScaleFactor() { + _max_scale_resolution_desired = + pow(2, 16); // TODO: Change to _desired_adc_resolution + _max_scale_resolution_native = + pow(2, 13); // TODO: Change to _native_adc_resolution +} + +/*************************************************************************/ +/*! + @brief Gets the "raw" value of an analog pin from the ADC. + @param pin + The requested pin. + @return The pin's value. +*/ +/*************************************************************************/ +uint16_t AnalogIOHardware::GetPinValue(uint8_t pin) { + return (analogRead(pin) * _max_scale_resolution_desired) / + _max_scale_resolution_native; +} + +/*************************************************************************/ +/*! + @brief Gets the voltage of an analog pin. + @param pin + The requested pin. + @return The pin's voltage. +*/ +/*************************************************************************/ +float AnalogIOHardware::GetPinVoltage(uint8_t pin) { +#ifdef ARDUINO_ARCH_ESP32 + return analogReadMilliVolts(pin) / 1000.0; +#else + return (getPinValue(pin) * _ref_voltage) / 65536; +#endif // ARDUINO_ARCH_ESP32 +} \ No newline at end of file diff --git a/src/components/analogIO/hardware.h b/src/components/analogIO/hardware.h new file mode 100644 index 000000000..447495034 --- /dev/null +++ b/src/components/analogIO/hardware.h @@ -0,0 +1,48 @@ +/*! + * @file model.h + * + * Hardware implementation for the analogio.proto message. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2024 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#ifndef WS_ANALOGIO_HARDWARE_H +#define WS_ANALOGIO_HARDWARE_H +#include "Wippersnapper_V2.h" + +/**************************************************************************/ +/*! + @brief Interface for interacting with hardware's analog i/o pin API. +*/ +/**************************************************************************/ +class AnalogIOHardware { +public: + AnalogIOHardware(); + ~AnalogIOHardware(); + void SetNativeADCResolution(); + void SetResolution(uint8_t resolution); + void SetReferenceVoltage(float voltage); + void CalculateScaleFactor(); + // Arduino/Wiring API + void InitPin(uint8_t pin); + void DeinitPin(uint8_t pin); + uint16_t GetPinValue(uint8_t pin); + float GetPinVoltage(uint8_t pin); + +private: + uint8_t _native_adc_resolution; ///< Hardware's native ADC resolution. + uint8_t _desired_adc_resolution; ///< Desired (final) ADC resolution. + int _max_scale_resolution_desired; ///< Maximum scale resolution desired. + int _max_scale_resolution_native; ///< Maximum scale resolution native. + + bool _is_adc_resolution_scaled; ///< True if the ADC's resolution is scaled, + ///< False otherwise. + float _ref_voltage; ///< Reference voltage for reading analog pins. +}; +#endif // WS_ANALOGIO_HARDWARE_H \ No newline at end of file diff --git a/src/components/analogIO/model.cpp b/src/components/analogIO/model.cpp new file mode 100644 index 000000000..b5c9f9854 --- /dev/null +++ b/src/components/analogIO/model.cpp @@ -0,0 +1,168 @@ +/*! + * @file model.cpp + * + * Interfaces for the analogio.proto API + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2024 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#include "model.h" + +/***********************************************************************/ +/*! + @brief AnalogIOModel constructor +*/ +/***********************************************************************/ +AnalogIOModel::AnalogIOModel() { + _msg_AnalogioAdd = wippersnapper_analogio_AnalogIOAdd_init_default; +} + +/***********************************************************************/ +/*! + @brief AnalogIOModel destructor +*/ +/***********************************************************************/ +AnalogIOModel::~AnalogIOModel() {} + +/***********************************************************************/ +/*! + @brief Decodes an AnalogIOAdd message from a stream into an + AnalogIOAdd message struct. + @param stream + The pb_istream_t stream to decode. + @return True if successful, False otherwise. +*/ +/***********************************************************************/ +bool AnalogIOModel::DecodeAnalogIOAdd(pb_istream_t *stream) { + // Zero-out the AnalogIOAdd message struct. to ensure we don't have any old + // data + _msg_AnalogioAdd = wippersnapper_analogio_AnalogIOAdd_init_default; + // Decode the stream into a AnalogIOAdd message + return pb_decode(stream, wippersnapper_analogio_AnalogIOAdd_fields, + &_msg_AnalogioAdd); +} + +/***********************************************************************/ +/*! + @brief Gets an AnalogIOAdd message struct. + @return Pointer to an AnalogIOAdd message struct. +*/ +/***********************************************************************/ +wippersnapper_analogio_AnalogIOAdd *AnalogIOModel::GetAnalogIOAddMsg() { + return &_msg_AnalogioAdd; +} + +/***********************************************************************/ +/*! + @brief Decodes an AnalogIORemove message from a stream into an + AnalogIORemove message struct. + @param stream + The pb_istream_t stream to decode. + @return True if successful, False otherwise. +*/ +/***********************************************************************/ +bool AnalogIOModel::DecodeAnalogIORemove(pb_istream_t *stream) { + // Zero-out the AnalogIORemove message struct. to ensure we don't have any old + // data + _msg_AnalogioRemove = wippersnapper_analogio_AnalogIORemove_init_default; + // Decode the stream into a AnalogIORemove message + return pb_decode(stream, wippersnapper_analogio_AnalogIORemove_fields, + &_msg_AnalogioRemove); +} + +/*****************************************************************************/ +/*! + @brief Gets an AnalogIORemove message struct. + @return Pointer to an AnalogIORemove message struct. +*/ +/*****************************************************************************/ +wippersnapper_analogio_AnalogIORemove *AnalogIOModel::GetAnalogIORemoveMsg() { + return &_msg_AnalogioRemove; +} + +/*****************************************************************************/ +/*! + @brief Gets an AnalogIOEvent message struct. + @return Pointer to an AnalogIOEvent message struct. +*/ +/*****************************************************************************/ +wippersnapper_analogio_AnalogIOEvent *AnalogIOModel::GetAnalogIOEvent() { + return &_msg_AnalogioEvent; +} + +/*****************************************************************************/ +/*! + @brief Encodes an AnalogIOEvent message. + @param pin_name + The requested pin's name. + @param pin_value + The value of the pin. + @param read_type + The type of sensor event to encode. + @return True if successful, False otherwise. +*/ +/*****************************************************************************/ +bool AnalogIOModel::EncodeAnalogIOEvent( + char *pin_name, float pin_value, + wippersnapper_sensor_SensorType read_type) { + // Initialize the AnalogIOEvent message to default values + _msg_AnalogioEvent = wippersnapper_analogio_AnalogIOEvent_init_zero; + // Fill the AnalogIOEvent message's fields + strncpy(_msg_AnalogioEvent.pin_name, pin_name, + sizeof(_msg_AnalogioEvent.pin_name)); + _msg_AnalogioEvent.has_sensor_event = true; + _msg_AnalogioEvent.sensor_event.type = read_type; + _msg_AnalogioEvent.sensor_event.which_value = + wippersnapper_sensor_SensorEvent_float_value_tag; + _msg_AnalogioEvent.sensor_event.value.float_value = pin_value; + + // Obtain size of an encoded AnalogIOEvent message + size_t sz_aio_event_msg; + if (!pb_get_encoded_size(&sz_aio_event_msg, + wippersnapper_analogio_AnalogIOEvent_fields, + &_msg_AnalogioEvent)) + return false; + + // Encode the AnalogIOEvent message + uint8_t buf[sz_aio_event_msg]; + pb_ostream_t msg_stream = pb_ostream_from_buffer(buf, sizeof(buf)); + return pb_encode(&msg_stream, wippersnapper_analogio_AnalogIOEvent_fields, + &_msg_AnalogioEvent); +} + +/********************************************************************************/ +/*! + @brief Encodes an AnalogIOEvent message with a raw pin value. + @param pin_name + The requested pin's name. + @param pin_value + The value of the pin. + @return True if successful, False otherwise. +*/ +/********************************************************************************/ +bool AnalogIOModel::EncodeAnalogIOEventRaw(char *pin_name, float pin_value) { + return EncodeAnalogIOEvent(pin_name, pin_value, + wippersnapper_sensor_SensorType_SENSOR_TYPE_RAW); +} + +/********************************************************************************/ +/*! + @brief Encodes an AnalogIOEvent message with a voltage pin value. + @param pin_name + The requested pin's name. + @param pin_value + The value of the pin. + @return True if successful, False otherwise. +*/ +/********************************************************************************/ +bool AnalogIOModel::EncodeAnalogIOEventVoltage(char *pin_name, + float pin_value) { + return EncodeAnalogIOEvent( + pin_name, pin_value, wippersnapper_sensor_SensorType_SENSOR_TYPE_VOLTAGE); +} \ No newline at end of file diff --git a/src/components/analogIO/model.h b/src/components/analogIO/model.h new file mode 100644 index 000000000..f20fd4c46 --- /dev/null +++ b/src/components/analogIO/model.h @@ -0,0 +1,49 @@ +/*! + * @file model.h + * + * Model interface for the analogio.proto message. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2024 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#ifndef WS_ANALOGIO_MODEL_H +#define WS_ANALOGIO_MODEL_H +#include "Wippersnapper_V2.h" + +/**************************************************************************/ +/*! + @brief Provides an interface for creating, encoding, and parsing + messages from analogio.proto. +*/ +/**************************************************************************/ +class AnalogIOModel { +public: + AnalogIOModel(); + ~AnalogIOModel(); + // AnalogIOAdd + bool DecodeAnalogIOAdd(pb_istream_t *stream); + wippersnapper_analogio_AnalogIOAdd *GetAnalogIOAddMsg(); + // AnalogIORemove + bool DecodeAnalogIORemove(pb_istream_t *stream); + wippersnapper_analogio_AnalogIORemove *GetAnalogIORemoveMsg(); + // AnalogIOEvent + bool EncodeAnalogIOEvent(char *pin_name, float pin_value, + wippersnapper_sensor_SensorType read_type); + bool EncodeAnalogIOEventVoltage(char *pin_name, float pin_value); + bool EncodeAnalogIOEventRaw(char *pin_name, float pin_value); + wippersnapper_analogio_AnalogIOEvent *GetAnalogIOEvent(); + +private: + wippersnapper_analogio_AnalogIOAdd _msg_AnalogioAdd; ///< AnalogIOAdd message + wippersnapper_analogio_AnalogIORemove + _msg_AnalogioRemove; ///< AnalogIORemove message + wippersnapper_analogio_AnalogIOEvent + _msg_AnalogioEvent; ///< AnalogIOEvent message +}; +#endif // WS_DIGITALIO_MODEL_H \ No newline at end of file diff --git a/src/components/checkin/model.cpp b/src/components/checkin/model.cpp new file mode 100644 index 000000000..fbd6f983d --- /dev/null +++ b/src/components/checkin/model.cpp @@ -0,0 +1,190 @@ +/*! + * @file model.cpp + * + * Model for the Wippersnapper checkin proto API. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2024 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#include "model.h" + +/***********************************************************************/ +/*! + @brief CheckinModel constructor +*/ +/***********************************************************************/ +CheckinModel::CheckinModel() { + _CheckinRequest = wippersnapper_checkin_CheckinRequest_init_default; + _CheckinResponse = wippersnapper_checkin_CheckinResponse_init_default; +} + +/***********************************************************************/ +/*! + @brief CheckinModel destructor +*/ +/***********************************************************************/ +CheckinModel::~CheckinModel() { + _CheckinRequest = wippersnapper_checkin_CheckinRequest_init_default; + _CheckinResponse = wippersnapper_checkin_CheckinResponse_init_default; +} + +/***********************************************************************/ +/*! + @brief Fills and creates a CheckinRequest message + @param hardware_uid + Hardware's unique identifier. + @param firmware_version + WipperSnapper firmware version. +*/ +/***********************************************************************/ +void CheckinModel::CreateCheckinRequest(const char *hardware_uid, + const char *firmware_version) { + strcpy(_CheckinRequest.hardware_uid, hardware_uid); + strcpy(_CheckinRequest.firmware_version, firmware_version); +} + +/***********************************************************************/ +/*! + @brief Encodes a CheckinRequest message + @returns True if the message was successfully encoded, + False otherwise. +*/ +/***********************************************************************/ +bool CheckinModel::EncodeCheckinRequest() { + // Obtain size of the CheckinRequest message + size_t CheckinRequestSz; + if (!pb_get_encoded_size(&CheckinRequestSz, + wippersnapper_checkin_CheckinRequest_fields, + &_CheckinRequest)) + return false; + // Create a buffer for holding the CheckinRequest message + uint8_t buf[CheckinRequestSz]; + + // Create a stream that will write to buf + pb_ostream_t msg_stream = pb_ostream_from_buffer(buf, sizeof(buf)); + // Encode the message + return pb_encode(&msg_stream, wippersnapper_checkin_CheckinRequest_fields, + &_CheckinRequest); +} + +/***********************************************************************/ +/*! + @brief Decodes a CheckinResponse message + @param stream + Incoming data stream from buffer. + @returns True if the message was successfully decoded, + False otherwise. +*/ +/***********************************************************************/ +bool CheckinModel::DecodeCheckinResponse(pb_istream_t *stream) { + return pb_decode(stream, wippersnapper_checkin_CheckinResponse_fields, + &_CheckinResponse); +} + +/***********************************************************************/ +/*! + @brief Parses the fields of a CheckinResponse message. +*/ +/***********************************************************************/ +void CheckinModel::ParseCheckinResponse() { + setCheckinResponse(_CheckinResponse.response); + setTotalGPIOPins(_CheckinResponse.total_gpio_pins); + setTotalAnalogPins(_CheckinResponse.total_analog_pins); + setReferenceVoltage(_CheckinResponse.reference_voltage); +} + +/***********************************************************************/ +/*! + @brief Sets the CheckinResponse message's Response field + @param response + CheckinResponse message. +*/ +/***********************************************************************/ +void CheckinModel::setCheckinResponse( + wippersnapper_checkin_CheckinResponse_Response response) { + _response = _CheckinResponse.response; +} + +/***********************************************************************/ +/*! + @brief Gets the CheckinResponse message's Response field + @returns CheckinResponse message. +*/ +/***********************************************************************/ +wippersnapper_checkin_CheckinResponse_Response +CheckinModel::getCheckinResponse() { + return _response; +}; + +/***********************************************************************/ +/*! + @brief Gets the CheckinRequest message + @returns CheckinRequest message. +*/ +/***********************************************************************/ +wippersnapper_checkin_CheckinRequest *CheckinModel::getCheckinRequest() { + return &_CheckinRequest; +} + +/***********************************************************************/ +/*! + @brief Sets the CheckinResponse message's total GPIO pins field + @param total_gpio_pins + Total number of GPIO pins. +*/ +/***********************************************************************/ +void CheckinModel::setTotalGPIOPins(int32_t total_gpio_pins) { + _total_gpio_pins = total_gpio_pins; +} + +/***********************************************************************/ +/*! + @brief Gets the CheckinResponse message's total GPIO pins field + @returns Total number of GPIO pins. +*/ +/***********************************************************************/ +int32_t CheckinModel::getTotalGPIOPins() { return _total_gpio_pins; } + +/***********************************************************************/ +/*! + @brief Sets the CheckinResponse message's total analog pins field + @param total_analog_pins + Total number of analog pins. +*/ +/***********************************************************************/ +void CheckinModel::setTotalAnalogPins(int32_t total_analog_pins) { + _total_analog_pins = total_analog_pins; +} + +/***********************************************************************/ +/*! + @brief Gets the CheckinResponse message's total analog pins field + @returns Total number of analog pins. +*/ +/***********************************************************************/ +int32_t CheckinModel::getTotalAnalogPins() { return _total_analog_pins; } + +/***********************************************************************/ +/*! + @brief Sets the CheckinResponse message's reference voltage field + @param reference_voltage + Reference voltage. +*/ +/***********************************************************************/ +void CheckinModel::setReferenceVoltage(float reference_voltage) { + _reference_voltage = reference_voltage; +} + +/***********************************************************************/ +/*! + @brief Gets the CheckinResponse message's reference voltage field + @returns Reference voltage. +*/ +/***********************************************************************/ +float CheckinModel::getReferenceVoltage() { return _reference_voltage; } \ No newline at end of file diff --git a/src/components/checkin/model.h b/src/components/checkin/model.h new file mode 100644 index 000000000..f102f1abd --- /dev/null +++ b/src/components/checkin/model.h @@ -0,0 +1,55 @@ +/*! + * @file model.h + * + * Model for the Wippersnapper checkin proto API. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2024 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#ifndef WS_CHECKIN_MODEL_H +#define WS_CHECKIN_MODEL_H +#include "Wippersnapper_V2.h" + +/**************************************************************************/ +/*! + @brief Provides an interface for creating, encoding, and parsing + messages from checkin.proto. +*/ +/**************************************************************************/ +class CheckinModel { +public: + CheckinModel(); + ~CheckinModel(); + // Request Message + void CreateCheckinRequest(const char *hardware_uid, + const char *firmware_version); + bool EncodeCheckinRequest(); + wippersnapper_checkin_CheckinRequest* getCheckinRequest(); + // Response Message + bool DecodeCheckinResponse(pb_istream_t *stream); + void ParseCheckinResponse(); + void + setCheckinResponse(wippersnapper_checkin_CheckinResponse_Response response); + wippersnapper_checkin_CheckinResponse_Response getCheckinResponse(); + void setTotalGPIOPins(int32_t total_gpio_pins); + int32_t getTotalGPIOPins(); + void setTotalAnalogPins(int32_t total_analog_pins); + int32_t getTotalAnalogPins(); + void setReferenceVoltage(float reference_voltage); + float getReferenceVoltage(); + +private: + wippersnapper_checkin_CheckinRequest _CheckinRequest; + wippersnapper_checkin_CheckinResponse _CheckinResponse; + wippersnapper_checkin_CheckinResponse_Response _response; + int32_t _total_gpio_pins; + int32_t _total_analog_pins; + float _reference_voltage; +}; +#endif // WS_CHECKIN_H \ No newline at end of file diff --git a/src/components/digitalIO/controller.cpp b/src/components/digitalIO/controller.cpp new file mode 100644 index 000000000..ad7f6d3ad --- /dev/null +++ b/src/components/digitalIO/controller.cpp @@ -0,0 +1,386 @@ +/*! + * @file controller.cpp + * + * Controller for the digitalio.proto API + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2024 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#include "controller.h" + +/***********************************************************************/ +/*! + @brief DigitalIOController constructor +*/ +/***********************************************************************/ +DigitalIOController::DigitalIOController() { + _dio_model = new DigitalIOModel(); + _dio_hardware = new DigitalIOHardware(); + // Set the default maximum number of digital pins to 0 + // NOTE: This will be set during runtime by the CheckinResponse message + SetMaxDigitalPins(0); +} + +/***********************************************************************/ +/*! + @brief DigitalIOController destructor +*/ +/***********************************************************************/ +DigitalIOController::~DigitalIOController() { + delete _dio_model; + delete _dio_hardware; +} + +/***********************************************************************/ +/*! + @brief Set the maximum number of digital pins + @param max_digital_pins + The maximum number of digital pins +*/ +/***********************************************************************/ +void DigitalIOController::SetMaxDigitalPins(uint8_t max_digital_pins) { + _max_digitalio_pins = max_digital_pins; +} + +/***********************************************************************/ +/*! + @brief Add a new digital pin to the controller + @param stream + The nanopb input stream. + @return True if the digital pin was successfully added. +*/ +/***********************************************************************/ +bool DigitalIOController::Handle_DigitalIO_Add(pb_istream_t *stream) { + // Early-out if we have reached the maximum number of digital pins + if (_digitalio_pins.size() >= _max_digitalio_pins) { + WS_DEBUG_PRINTLN("[digitalio] ERROR: Can not add new pin, all pins have " + "already been allocated!"); + return false; + } + + // Attempt to decode the DigitalIOAdd message and parse it into the model + if (!_dio_model->DecodeDigitalIOAdd(stream)) { + WS_DEBUG_PRINTLN( + "[digitalio] ERROR: Unable to decode DigitalIOAdd message!"); + return false; + } + + // Strip the D/A prefix off the pin name and convert to a uint8_t pin number + int pin_name = atoi(_dio_model->GetDigitalIOAddMsg()->pin_name + 1); + + // Check if the provided pin is also the status LED pin + if (_dio_hardware->IsStatusLEDPin(pin_name)) + releaseStatusLED(); + + // Deinit the pin if it's already in use + if (GetPinIdx(pin_name) != -1) + _dio_hardware->deinit(pin_name); + + // Attempt to configure the pin + if (!_dio_hardware->ConfigurePin( + pin_name, _dio_model->GetDigitalIOAddMsg()->gpio_direction)) { + WS_DEBUG_PRINTLN( + "[digitalio] ERROR: Pin provided an invalid protobuf direction!"); + return false; + } + + ulong period = _dio_model->GetDigitalIOAddMsg()->period; + // Create the digital pin and add it to the vector + DigitalIOPin new_pin = { + .pin_name = pin_name, + .pin_direction = _dio_model->GetDigitalIOAddMsg()->gpio_direction, + .sample_mode = _dio_model->GetDigitalIOAddMsg()->sample_mode, + .pin_value = _dio_model->GetDigitalIOAddMsg()->value, + .pin_period = period * 1000, + .prv_pin_time = (millis() - 1) - period}; + _digitalio_pins.push_back(new_pin); + + // Print out the pin's details + WS_DEBUG_PRINTLN("[digitalio] Added new pin:"); + WS_DEBUG_PRINT("Pin Name: "); + WS_DEBUG_PRINTLN(new_pin.pin_name); + WS_DEBUG_PRINT("Period: "); + WS_DEBUG_PRINTLN(new_pin.pin_period); + WS_DEBUG_PRINT("Sample Mode: "); + WS_DEBUG_PRINTLN(new_pin.sample_mode); + WS_DEBUG_PRINT("Direction: "); + WS_DEBUG_PRINTLN(new_pin.pin_direction); + + return true; +} + +/***********************************************************************/ +/*! + @brief Removes a digital pin from the controller, if it exists + @param stream + The nanopb input stream. + @return True if the digital pin was successfully removed. +*/ +/***********************************************************************/ +bool DigitalIOController::Handle_DigitalIO_Remove(pb_istream_t *stream) { + // Attempt to decode the DigitalIORemove message + if (!_dio_model->DecodeDigitalIORemove(stream)) { + WS_DEBUG_PRINTLN( + "[digitalio] ERROR: Unable to decode DigitalIORemove message!"); + return false; + } + + // Get the pin's name + int pin_name = atoi(_dio_model->GetDigitalIOAddMsg()->pin_name + 1); + + // Bail out if the pin does not exist within controller + if (GetPinIdx(pin_name) == -1) { + WS_DEBUG_PRINTLN( + "[digitalio] ERROR: Unable to find output pin on the controller!"); + return false; + } + + // Deinitialize the pin + _dio_hardware->deinit(pin_name); + return true; +} + +/***********************************************************************/ +/*! + @brief Get the index of a digital output pin + @param pin_name + The pin's name. + @return The index of the digital output pin. +*/ +/***********************************************************************/ +int DigitalIOController::GetPinIdx(uint8_t pin_name) { + for (int i = 0; i < _digitalio_pins.size(); i++) { + if (_digitalio_pins[i].pin_name == pin_name) { + return i; + } + } + return -1; // Pin not found +} + +/***********************************************************************/ +/*! + @brief Write a digital pin + @param stream + The nanopb input stream. + @return True if the digital pin was successfully written. +*/ +/***********************************************************************/ +bool DigitalIOController::Handle_DigitalIO_Write(pb_istream_t *stream) { + // Attempt to decode the DigitalIOWrite message + if (!_dio_model->DecodeDigitalIOWrite(stream)) { + WS_DEBUG_PRINTLN("[digitalio] ERROR: Unable to decode Write message!"); + return false; + } + + // Get the digital pin + int pin_idx = + GetPinIdx(atoi(_dio_model->GetDigitalIOWriteMsg()->pin_name + 1)); + // Check if the pin was found and is a valid digital output pin + if (pin_idx == -1) { + WS_DEBUG_PRINTLN( + "[digitalio] ERROR: Unable to find the requested output pin!"); + return false; + } + + // Ensure we got the correct value type + if (!_dio_model->GetDigitalIOWriteMsg()->value.which_value == + wippersnapper_sensor_SensorEvent_bool_value_tag) { + WS_DEBUG_PRINTLN("[digitalio] ERROR: controller got invalid value type!"); + return false; + } + + WS_DEBUG_PRINT("[digitalio] Writing value: "); + WS_DEBUG_PRINTLN(_dio_model->GetDigitalIOWriteMsg()->value.value.bool_value); + WS_DEBUG_PRINT("on Pin: "); + WS_DEBUG_PRINTLN(_digitalio_pins[pin_idx].pin_name); + + // Is the pin already set to this value? If so, we don't need to write it + // again + if (_digitalio_pins[pin_idx].pin_value == + _dio_model->GetDigitalIOWriteMsg()->value.value.bool_value) + return true; + + // Call hardware to write the value type + _dio_hardware->SetValue( + _digitalio_pins[pin_idx].pin_name, + _dio_model->GetDigitalIOWriteMsg()->value.value.bool_value); + + // Update the pin's value + _digitalio_pins[pin_idx].pin_value = + _dio_model->GetDigitalIOWriteMsg()->value.value.bool_value; + + return true; +} + +/***********************************************************************/ +/*! + @brief Check if a pin's timer has expired + @param pin + The pin to check. + @param cur_time + The current time. + @return True if the pin's timer has expired. +*/ +/***********************************************************************/ +bool DigitalIOController::IsPinTimerExpired(DigitalIOPin *pin, ulong cur_time) { + return cur_time - pin->prv_pin_time > pin->pin_period; +} + +/***********************************************************************/ +/*! + @brief Print a pin's ID and value + @param pin + The specified pin. +*/ +/***********************************************************************/ +void DigitalIOController::PrintPinValue(DigitalIOPin *pin) { + if (WsV2._sdCardV2->mode_offline) + return; + WS_DEBUG_PRINT("[digitalio] DIO Pin D"); + WS_DEBUG_PRINT(pin->pin_name); + WS_DEBUG_PRINT(" | value: "); + WS_DEBUG_PRINTLN(pin->prv_pin_value); +} + +/***********************************************************************/ +/*! + @brief Check if a pin's timer has expired + @param pin + The pin to check. + @return True if the pin's timer has expired. +*/ +/***********************************************************************/ +bool DigitalIOController::CheckTimerPin(DigitalIOPin *pin) { + ulong cur_time = millis(); + // Bail out if the pin's timer has not expired + if (!IsPinTimerExpired(pin, cur_time)) + return false; + + // Fill in the pin's current time and value + pin->prv_pin_time = cur_time; + pin->pin_value = _dio_hardware->GetValue(pin->pin_name); + + PrintPinValue(pin); + return true; +} + +/***********************************************************************/ +/*! + @brief Check if a pin's value has changed + @param pin + The pin to check. + @return True if the pin's value has changed. +*/ +/***********************************************************************/ +bool DigitalIOController::CheckEventPin(DigitalIOPin *pin) { + // Get the pin's current value + pin->pin_value = _dio_hardware->GetValue(pin->pin_name); + // Bail out if the pin value hasn't changed + if (pin->pin_value == pin->prv_pin_value) + return false; + // Update the pin's previous value to the current value + pin->prv_pin_value = pin->pin_value; + + PrintPinValue(pin); + return true; +} + +/***********************************************************************/ +/*! + @brief Encode and publish a pin event + @param pin_name + The pin's name. + @param pin_value + The pin's value. + @return True if the pin event was successfully encoded and published. +*/ +/***********************************************************************/ +bool DigitalIOController::EncodePublishPinEvent(uint8_t pin_name, + bool pin_value) { + // Prefix pin_name with "D" to match the expected pin name format + char c_pin_name[12]; + sprintf(c_pin_name, "D%d", pin_name); + + // If we are in ONLINE mode, publish the event to the broker + if (!WsV2._sdCardV2->mode_offline) { + WS_DEBUG_PRINT( + "[digitalio] Publishing DigitalIOEvent message to broker for pin: "); + WS_DEBUG_PRINTLN(c_pin_name); + // Encode the DigitalIOEvent message + if (!_dio_model->EncodeDigitalIOEvent(c_pin_name, pin_value)) { + WS_DEBUG_PRINTLN("ERROR: Unable to encode DigitalIOEvent message!"); + return false; + } + + // Publish the DigitalIOEvent message to the broker + if (!WsV2.PublishSignal( + wippersnapper_signal_DeviceToBroker_digitalio_event_tag, + _dio_model->GetDigitalIOEventMsg())) { + WS_DEBUG_PRINTLN("[digitalio] ERROR: Unable to publish event message, " + "moving onto the next pin!"); + return false; + } + WS_DEBUG_PRINTLN("[digitalio] Published DigitalIOEvent to broker!") + } else { + // let's log the event to the SD card + if (!WsV2._sdCardV2->LogGPIOSensorEventToSD( + pin_name, pin_value, + wippersnapper_sensor_SensorType_SENSOR_TYPE_BOOLEAN)) + return false; + } + + return true; +} + +/***********************************************************************/ +/*! + @brief Iterates through the digital pins and updates their values + (if necessary) and publishes the event to the broker. +*/ +/***********************************************************************/ +void DigitalIOController::Update() { + // Bail out if we have no digital pins to poll + if (_digitalio_pins.empty()) + return; + + for (int i = 0; i < _digitalio_pins.size(); i++) { + // Create a pin object for this iteration + DigitalIOPin &pin = _digitalio_pins[i]; + // Skip if the pin is an output + if (pin.pin_direction == + wippersnapper_digitalio_DigitalIODirection_DIGITAL_IO_DIRECTION_OUTPUT) + continue; + + if (pin.sample_mode == + wippersnapper_digitalio_DigitalIOSampleMode_DIGITAL_IO_SAMPLE_MODE_EVENT) { + // Check if the pin value has changed + if (!CheckEventPin(&pin)) + continue; // No change in pin value detected, move onto the next pin + + // Encode and publish the event + if (!EncodePublishPinEvent(pin.pin_name, pin.pin_value)) + continue; // Unable to encode and publish event, move onto the next pin + } else if ( + pin.sample_mode == + wippersnapper_digitalio_DigitalIOSampleMode_DIGITAL_IO_SAMPLE_MODE_TIMER) { + // Check if the timer has expired + if (!CheckTimerPin(&pin)) + continue; // Timer has not expired yet, move onto the next pin + + // Encode and publish the event + if (!EncodePublishPinEvent(pin.pin_name, pin.pin_value)) + continue; // Failed to encode and publish event, move onto the next pin + } else { + // Invalid sample mode + WS_DEBUG_PRINT("[digitalio] ERROR: DigitalIO Pin "); + WS_DEBUG_PRINT(pin.pin_name); + WS_DEBUG_PRINTLN(" contains an invalid sample mode!"); + } + } +} \ No newline at end of file diff --git a/src/components/digitalIO/controller.h b/src/components/digitalIO/controller.h new file mode 100644 index 000000000..e1de9666e --- /dev/null +++ b/src/components/digitalIO/controller.h @@ -0,0 +1,76 @@ +/*! + * @file controller.h + * + * Controller for the digitalio API + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2024 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#ifndef WS_DIGITALIO_CONTROLLER_H +#define WS_DIGITALIO_CONTROLLER_H +#include "Wippersnapper_V2.h" +#include "hardware.h" +#include "model.h" + +class Wippersnapper_V2; + +/** + * @struct DigitalIOPin + * @brief This struct represents a digital I/O pin. + */ +struct DigitalIOPin { + uint8_t pin_name; ///< The pin's name. + wippersnapper_digitalio_DigitalIODirection + pin_direction; ///< The pin's direction. + wippersnapper_digitalio_DigitalIOSampleMode + sample_mode; ///< The pin's sample mode. + bool pin_value; ///< The pin's value. + bool prv_pin_value; ///< The pin's previous value. + ulong pin_period; ///< The pin's period. + ulong prv_pin_time; ///< The pin's previous time. +}; + +class DigitalIOModel; // Forward declaration +class DigitalIOHardware; // Forward declaration + +/**************************************************************************/ +/*! + @brief Routes messages using the digitalio.proto API to the + appropriate hardware and model classes, controls and tracks + the state of the hardware's digital I/O pins. +*/ +/**************************************************************************/ +class DigitalIOController { +public: + DigitalIOController(); + ~DigitalIOController(); + // Called by the cbDecodeBrokerToDevice router function + bool Handle_DigitalIO_Add(pb_istream_t *stream); + bool Handle_DigitalIO_Write(pb_istream_t *stream); + bool Handle_DigitalIO_Remove(pb_istream_t *stream); + + void Update(); + + bool EncodePublishPinEvent(uint8_t pin_name, bool pin_value); + bool CheckEventPin(DigitalIOPin *pin); + bool CheckTimerPin(DigitalIOPin *pin); + bool IsPinTimerExpired(DigitalIOPin *pin, ulong cur_time); + + void PrintPinValue(DigitalIOPin *pin); + void SetMaxDigitalPins(uint8_t max_digital_pins); + int GetPinIdx(uint8_t pin_name); + +private: + std::vector _digitalio_pins; + uint8_t _max_digitalio_pins; + DigitalIOModel *_dio_model; + DigitalIOHardware *_dio_hardware; +}; +extern Wippersnapper_V2 WsV2; ///< Wippersnapper V2 instance +#endif // WS_DIGITALIO_CONTROLLER_H \ No newline at end of file diff --git a/src/components/digitalIO/hardware.cpp b/src/components/digitalIO/hardware.cpp new file mode 100644 index 000000000..f6503e1a8 --- /dev/null +++ b/src/components/digitalIO/hardware.cpp @@ -0,0 +1,127 @@ +/*! + * @file hardware.cpp + * + * Hardware driver for the digitalio.proto API + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2024 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#include "hardware.h" + +/***********************************************************************/ +/*! + @brief DigitalIOHardware constructor +*/ +/***********************************************************************/ +DigitalIOHardware::DigitalIOHardware() {} + +/***********************************************************************/ +/*! + @brief DigitalIOHardware destructor +*/ +/***********************************************************************/ +DigitalIOHardware::~DigitalIOHardware() {} + +/***********************************************************************/ +/*! + @brief Configures a digital pin. + @param name + The pin's name. + @param direction + The pin's direction. + @return True if the pin was successfully configured. False otherwise. +*/ +/***********************************************************************/ +bool DigitalIOHardware::ConfigurePin( + uint8_t name, wippersnapper_digitalio_DigitalIODirection direction) { + // Configure an output pin + if (direction == + wippersnapper_digitalio_DigitalIODirection_DIGITAL_IO_DIRECTION_OUTPUT) { + // Set pin mode to OUTPUT + pinMode(name, OUTPUT); +// Initialize pin value to LOW +#if defined(ARDUINO_ESP8266_ADAFRUIT_HUZZAH) + // The Adafruit Feather ESP8266's built-in LED is reverse wired so setting + // the pin LOW will turn the LED on. + digitalWrite(name, !0); +#else + pinMode(name, OUTPUT); + digitalWrite(name, LOW); // initialize LOW +#endif + } else if ( + direction == + wippersnapper_digitalio_DigitalIODirection_DIGITAL_IO_DIRECTION_INPUT) { + pinMode(name, INPUT); + } else if ( + direction == + wippersnapper_digitalio_DigitalIODirection_DIGITAL_IO_DIRECTION_INPUT_PULL_UP) { + pinMode(name, INPUT_PULLUP); + } else { + return false; // Invalid pin configuration + } + return true; +} + +/***********************************************************************/ +/*! + @brief Deinitializes a digital pin. + @param pin_name + The digital pin to deinitialize. +*/ +/***********************************************************************/ +void DigitalIOHardware::deinit(uint8_t pin_name) { + // Turn off pin output and reset mode to hi-z floating state + digitalWrite(pin_name, LOW); + pinMode(pin_name, INPUT); + // Prior to using this pin as a DIO, + // was this a status LED pin? + if (IsStatusLEDPin(pin_name)) { + initStatusLED(); // it was! re-init status led + } +} + +/***********************************************************************/ +/*! + @brief Sets a digital pin's value. + @param pin_name + The pin's name. + @param pin_value + The pin's value. +*/ +/***********************************************************************/ +void DigitalIOHardware::SetValue(uint8_t pin_name, bool pin_value) { + digitalWrite(pin_name, pin_value ? HIGH : LOW); +} + +/***********************************************************************/ +/*! + @brief Gets a digital pin's value. + @param pin_name + The pin's name. + @return The pin's value. +*/ +/***********************************************************************/ +bool DigitalIOHardware::GetValue(uint8_t pin_name) { + return digitalRead(pin_name); +} + +/***********************************************************************/ +/*! + @brief Checks if a pin is the status LED pin. + @param pin_name + The pin's name. + @return True if the pin is the status LED pin. +*/ +/***********************************************************************/ +bool DigitalIOHardware::IsStatusLEDPin(uint8_t pin_name) { +#ifdef STATUS_LED_PIN + return pin_name == STATUS_LED_PIN; +#endif + return false; +} \ No newline at end of file diff --git a/src/components/digitalIO/hardware.h b/src/components/digitalIO/hardware.h new file mode 100644 index 000000000..df789440c --- /dev/null +++ b/src/components/digitalIO/hardware.h @@ -0,0 +1,36 @@ +/*! + * @file model.h + * + * Model for the digitalio.proto message. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2024 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#ifndef WS_DIGITALIO_HARDWARE_H +#define WS_DIGITALIO_HARDWARE_H +#include "Wippersnapper_V2.h" + +/**************************************************************************/ +/*! + @brief Interface for interacting with hardware's digital I/O pin API. +*/ +/**************************************************************************/ +class DigitalIOHardware { +public: + DigitalIOHardware(); + ~DigitalIOHardware(); + bool ConfigurePin(uint8_t name, + wippersnapper_digitalio_DigitalIODirection direction); + void SetValue(uint8_t pin_name, bool pin_value); + bool GetValue(uint8_t pin_name); + void deinit(uint8_t pin_name); + bool IsStatusLEDPin(uint8_t pin_name); +private: +}; +#endif // WS_DIGITALIO_HARDWARE_H \ No newline at end of file diff --git a/src/components/digitalIO/model.cpp b/src/components/digitalIO/model.cpp new file mode 100644 index 000000000..6643b1c49 --- /dev/null +++ b/src/components/digitalIO/model.cpp @@ -0,0 +1,159 @@ +/*! + * @file model.cpp + * + * Model for the digitalio.proto message. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2024 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#include "model.h" + +/***********************************************************************/ +/*! + @brief DigitalIOModel constructor +*/ +/***********************************************************************/ +DigitalIOModel::DigitalIOModel() { + _msg_dio_add = wippersnapper_digitalio_DigitalIOAdd_init_default; + _msg_dio_remove = wippersnapper_digitalio_DigitalIORemove_init_default; + _msg_dio_event = wippersnapper_digitalio_DigitalIOEvent_init_default; + _msg_dio_write = wippersnapper_digitalio_DigitalIOWrite_init_default; +} + +/***********************************************************************/ +/*! + @brief DigitalIOModel destructor +*/ +/***********************************************************************/ +DigitalIOModel::~DigitalIOModel() {} + +/***********************************************************************/ +/*! + @brief Parses a DigitalIOAdd message. + @return DigitalIOAdd message object. +*/ +/***********************************************************************/ +wippersnapper_digitalio_DigitalIOAdd *DigitalIOModel::GetDigitalIOAddMsg() { + return &_msg_dio_add; +} + +/***********************************************************************/ +/*! + @brief Parses a DigitalIORemove message. + @param stream + The nanopb input stream. + @return DigitalIORemove message object. +*/ +/***********************************************************************/ +bool DigitalIOModel::DecodeDigitalIORemove(pb_istream_t *stream) { + // Zero-out the DigitalIORemove message struct. to ensure we don't have any + // old data + _msg_dio_remove = wippersnapper_digitalio_DigitalIORemove_init_default; + + // Decode the stream into a DigitalIORemove message + return pb_decode(stream, wippersnapper_digitalio_DigitalIORemove_fields, + &_msg_dio_remove); +} + +/***********************************************************************/ +/*! + @brief Gets a DigitalIOWrite message. + @return DigitalIOWrite message object. +*/ +/***********************************************************************/ +wippersnapper_digitalio_DigitalIOWrite *DigitalIOModel::GetDigitalIOWriteMsg() { + return &_msg_dio_write; +} + +/***********************************************************************/ +/*! + @brief Gets a DigitalIOEvent message. + @return DigitalIOEvent message object. +*/ +/***********************************************************************/ +wippersnapper_digitalio_DigitalIOEvent *DigitalIOModel::GetDigitalIOEventMsg() { + return &_msg_dio_event; +} + +/***********************************************************************/ +/*! + @brief Decodes a DigitalIOAdd message into the _msg_dio_add object + from a nanopb stream. + @param stream + The nanopb input stream. + @return True if the DigitalIOAdd message was successfully decoded. +*/ +/***********************************************************************/ +bool DigitalIOModel::DecodeDigitalIOAdd(pb_istream_t *stream) { + // Zero-out the DigitalIOAdd message struct. to ensure we don't have any old + // data + _msg_dio_add = wippersnapper_digitalio_DigitalIOAdd_init_default; + + // Decode the stream into a DigitalIOAdd message + return pb_decode(stream, wippersnapper_digitalio_DigitalIOAdd_fields, + &_msg_dio_add); +} + +/***********************************************************************/ +/*! + @brief Decodes a DigitalIOWrite message into the _msg_dio_write + object from a nanopb stream. + @param stream + The nanopb input stream. + @return True if the DigitalIOWrite message was successfully decoded. +*/ +/***********************************************************************/ +bool DigitalIOModel::DecodeDigitalIOWrite(pb_istream_t *stream) { + // Zero-out the DigitalIOWrite message struct. to ensure we don't have any old + // data + _msg_dio_write = wippersnapper_digitalio_DigitalIOWrite_init_default; + // Decode the stream into a DigitalIOWrite message + return pb_decode(stream, wippersnapper_digitalio_DigitalIOWrite_fields, + &_msg_dio_write); +} + +/***********************************************************************/ +/*! + @brief Encodes a DigitalIOEvent message into the + _msg_dio_event object. + @param pin_name + The pin's name. + @param value + The pin's value. + @return True if the DigitalIOEvent message was successfully encoded. + False if encoding resulted in a failure. +*/ +/***********************************************************************/ +bool DigitalIOModel::EncodeDigitalIOEvent(char *pin_name, bool value) { + // Initialize the DigitalIOEvent + _msg_dio_event = wippersnapper_digitalio_DigitalIOEvent_init_default; + // Fill the DigitalIOEvent + strncpy(_msg_dio_event.pin_name, pin_name, sizeof(_msg_dio_event.pin_name)); + _msg_dio_event.has_value = true; + // Fill the DigitalIOEvent's SensorEvent sub-message + _msg_dio_event.value.type = + wippersnapper_sensor_SensorType_SENSOR_TYPE_BOOLEAN; + _msg_dio_event.value.which_value = + wippersnapper_sensor_SensorEvent_bool_value_tag; + _msg_dio_event.value.value.bool_value = value; + + // Encode the DigitalIOEvent message + size_t sz_dio_event_msg; + if (!pb_get_encoded_size(&sz_dio_event_msg, + wippersnapper_digitalio_DigitalIOEvent_fields, + &_msg_dio_event)) + return false; + + // Create an output stream + uint8_t buf[sz_dio_event_msg]; + pb_ostream_t msg_stream = pb_ostream_from_buffer(buf, sizeof(buf)); + // Encode the message + return pb_encode(&msg_stream, wippersnapper_digitalio_DigitalIOEvent_fields, + &_msg_dio_event); +} \ No newline at end of file diff --git a/src/components/digitalIO/model.h b/src/components/digitalIO/model.h new file mode 100644 index 000000000..d0840efd0 --- /dev/null +++ b/src/components/digitalIO/model.h @@ -0,0 +1,51 @@ +/*! + * @file model.h + * + * Model for the digitalio.proto message. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2024 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#ifndef WS_DIGITALIO_MODEL_H +#define WS_DIGITALIO_MODEL_H +#include "Wippersnapper_V2.h" + +/**************************************************************************/ +/*! + @brief Provides an interface for creating, encoding, and parsing + messages from digitalio.proto. +*/ +/**************************************************************************/ +class DigitalIOModel { +public: + DigitalIOModel(); + ~DigitalIOModel(); + // DigitalIOAdd + bool DecodeDigitalIOAdd(pb_istream_t *stream); + wippersnapper_digitalio_DigitalIOAdd *GetDigitalIOAddMsg(); + // DigitalIORemove + bool DecodeDigitalIORemove(pb_istream_t *stream); + // DigitalIOWrite + bool DecodeDigitalIOWrite(pb_istream_t *stream); + wippersnapper_digitalio_DigitalIOWrite *GetDigitalIOWriteMsg(); + // DigitalIOEvent + bool EncodeDigitalIOEvent(char *pin_name, bool value); + wippersnapper_digitalio_DigitalIOEvent *GetDigitalIOEventMsg(); + +private: + wippersnapper_digitalio_DigitalIOAdd + _msg_dio_add; ///< DigitalIOAdd message object + wippersnapper_digitalio_DigitalIORemove + _msg_dio_remove; ///< DigitalIORemove message object + wippersnapper_digitalio_DigitalIOEvent + _msg_dio_event; ///< DigitalIOEvent message object + wippersnapper_digitalio_DigitalIOWrite + _msg_dio_write; ///< DigitalIOWrite message object +}; +#endif // WS_DIGITALIO_MODEL_H \ No newline at end of file diff --git a/src/components/ds18x20/controller.cpp b/src/components/ds18x20/controller.cpp new file mode 100644 index 000000000..ab5823037 --- /dev/null +++ b/src/components/ds18x20/controller.cpp @@ -0,0 +1,266 @@ +/*! + * @file controller.cpp + * + * Controller for the ds18x20.proto API + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2024 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#include "controller.h" + +/***********************************************************************/ +/*! + @brief DS18X20Controller constructor +*/ +/***********************************************************************/ +DS18X20Controller::DS18X20Controller() { _DS18X20_model = new DS18X20Model(); } + +/***********************************************************************/ +/*! + @brief DS18X20Controller destructor +*/ +/***********************************************************************/ +DS18X20Controller::~DS18X20Controller() { delete _DS18X20_model; } + +/***********************************************************************/ +/*! + @brief Handles a Ds18x20Add message from the broker. Attempts to + initialize a OneWire bus on the requested pin, attempts to + initialize a DSTherm driver on the OneWire bus, adds the + OneWire bus to the controller, and publishes a Ds18x20Added + message to the broker indicating the result of this function. + @param stream + The nanopb input stream. + @return True if the sensor was successfully initialized, + added to the controller, and a response was succesfully + published to the broker. False otherwise. +*/ +/***********************************************************************/ +bool DS18X20Controller::Handle_Ds18x20Add(pb_istream_t *stream) { + // Attempt to decode the incoming message into a Ds18x20Add message + if (!_DS18X20_model->DecodeDS18x20Add(stream)) { + WS_DEBUG_PRINTLN("ERROR | DS18x20: Unable to decode Ds18x20Add message"); + return false; + } + + // If we receive no sensor types to configure, bail out + if (_DS18X20_model->GetDS18x20AddMsg()->sensor_types_count == 0) { + WS_DEBUG_PRINTLN("ERROR | DS18x20: No ds18x sensor types provided!"); + return false; + } + + // Extract the OneWire pin from the message + uint8_t pin_name = atoi(_DS18X20_model->GetDS18x20AddMsg()->onewire_pin + 1); + + // Initialize the DS18X20Hardware object + auto new_dsx_driver = std::make_unique(pin_name); + // Attempt to get the sensor's ID on the OneWire bus to show it's been init'd + bool is_initialized = new_dsx_driver->GetSensor(); + + if (is_initialized) { + WS_DEBUG_PRINTLN("Sensor found on OneWire bus and initialized"); + + // Set the sensor's pin name (non-logical name) + new_dsx_driver->setOneWirePinName( + _DS18X20_model->GetDS18x20AddMsg()->onewire_pin); + + // Set the sensor's resolution + new_dsx_driver->SetResolution( + _DS18X20_model->GetDS18x20AddMsg()->sensor_resolution); + + // Set the sensor's period + new_dsx_driver->SetPeriod(_DS18X20_model->GetDS18x20AddMsg()->period); + + // Configure the types of sensor reads to perform + for (int i = 0; i < _DS18X20_model->GetDS18x20AddMsg()->sensor_types_count; + i++) { + if (_DS18X20_model->GetDS18x20AddMsg()->sensor_types[i] == + wippersnapper_sensor_SensorType_SENSOR_TYPE_OBJECT_TEMPERATURE) { + new_dsx_driver->is_read_temp_c = true; + } else if ( + _DS18X20_model->GetDS18x20AddMsg()->sensor_types[i] == + wippersnapper_sensor_SensorType_SENSOR_TYPE_OBJECT_TEMPERATURE_FAHRENHEIT) { + new_dsx_driver->is_read_temp_f = true; + } else { + WS_DEBUG_PRINTLN( + "ERROR | DS18x20: Unknown SensorType, failed to add sensor!"); + is_initialized = false; + } + } + + // If the sensor was successfully initialized, add it to the controller + if (is_initialized == true) + _DS18X20_pins.push_back(std::move(new_dsx_driver)); + } else { + WS_DEBUG_PRINTLN("ERROR | DS18x20: Unable to get sensor ID!"); + is_initialized = false; + } + + // If we're not in offline mode, publish a Ds18x20Added message back to the broker + if (! WsV2._sdCardV2->mode_offline) { + // Encode and publish a Ds18x20Added message back to the broker + if (!_DS18X20_model->EncodeDS18x20Added( + _DS18X20_model->GetDS18x20AddMsg()->onewire_pin, is_initialized)) { + WS_DEBUG_PRINTLN("ERROR | DS18x20: Unable to encode Ds18x20Added message!"); + return false; + } + + if (!WsV2.PublishSignal(wippersnapper_signal_DeviceToBroker_ds18x20_added_tag, + _DS18X20_model->GetDS18x20AddedMsg())) { + WS_DEBUG_PRINTLN( + "ERROR | DS18x20: Unable to publish Ds18x20Added message!"); + return false; + } + } + + return true; +} + +/***********************************************************************/ +/*! + @brief Handles a Ds18x20Remove message from the broker. Attempts to + remove a DS18X20Hardware object from the controller and + release the OneWire pin for other uses. + @param stream + The nanopb input stream. + @return True if the sensor was successfully removed from the + controller, False otherwise. +*/ +/***********************************************************************/ +bool DS18X20Controller::Handle_Ds18x20Remove(pb_istream_t *stream) { + WS_DEBUG_PRINT("Removing DS18X20 sensor..."); + // Attempt to decode the stream + if (!_DS18X20_model->DecodeDS18x20Remove(stream)) { + WS_DEBUG_PRINTLN("ERROR | DS18x20: Unable to decode Ds18x20Remove message"); + return false; + } + // Create a temp. instance of the Ds18x20Remove message + wippersnapper_ds18x20_Ds18x20Remove *msg_remove = + _DS18X20_model->GetDS18x20RemoveMsg(); + uint8_t pin_name = atoi(msg_remove->onewire_pin + 1); + + // Find the driver/bus in the vector and remove it + for (size_t i = 0; i < _DS18X20_pins.size(); ++i) { + if (_DS18X20_pins[i]->GetOneWirePin() == pin_name) { + _DS18X20_pins.erase(_DS18X20_pins.begin() + i); + return true; + } + } + WS_DEBUG_PRINTLN("Removed!"); + return true; +} + +/***********************************************************************/ +/*! + @brief Update/polling loop for the DS18X20 controller. +*/ +/***********************************************************************/ +void DS18X20Controller::update() { +#ifdef DEBUG_PROFILE + unsigned long total_start_time = millis(); +#endif + + // Bail out if there are no OneWire pins to poll + if (_DS18X20_pins.empty()) + return; + + // Iterate through the vector + for (uint8_t i = 0; i < _DS18X20_pins.size(); i++) { +#ifdef DEBUG_PROFILE + unsigned long sensor_start_time = millis(); +#endif + + // Create a reference to the DS18X20Hardware object + DS18X20Hardware &temp_dsx_driver = *(_DS18X20_pins[i]); + + // Check if the driver's timer has not expired yet + if (!temp_dsx_driver.IsTimerExpired()) { + continue; + } + +// Attempt to read the temperature in Celsius +#ifdef DEBUG_PROFILE + unsigned long temp_c_start_time = millis(); +#endif + + if (!temp_dsx_driver.ReadTemperatureC()) { + WS_DEBUG_PRINTLN( + "ERROR | DS18x20: Unable to read temperature in Celsius"); + continue; + } + +#ifdef DEBUG_PROFILE + unsigned long temp_c_end_time = millis(); + WS_DEBUG_PRINT("Read temperature Celsius time: "); + WS_DEBUG_PRINTLN(temp_c_end_time - temp_c_start_time); +#endif + + // We got a temperature value from the hardware, let's create a new + // sensor_event + _DS18X20_model->InitDS18x20EventMsg(temp_dsx_driver.getOneWirePinName()); + + // Are we reading the temperature in Celsius, Fahrenheit, or both? + if (temp_dsx_driver.is_read_temp_c) { + float temp_c = temp_dsx_driver.GetTemperatureC(); + _DS18X20_model->addSensorEvent( + wippersnapper_sensor_SensorType_SENSOR_TYPE_OBJECT_TEMPERATURE, + temp_c); + } + if (temp_dsx_driver.is_read_temp_f) { + float temp_f = temp_dsx_driver.GetTemperatureF(); + _DS18X20_model->addSensorEvent( + wippersnapper_sensor_SensorType_SENSOR_TYPE_OBJECT_TEMPERATURE_FAHRENHEIT, + temp_f); + } + + // Get the Ds18x20Event message + wippersnapper_ds18x20_Ds18x20Event *event_msg = + _DS18X20_model->GetDS18x20EventMsg(); + pb_size_t event_count = event_msg->sensor_events_count; + + if (! WsV2._sdCardV2->mode_offline) + { + // Encode the Ds18x20Event message + if (!_DS18X20_model->EncodeDs18x20Event()) { + WS_DEBUG_PRINTLN( + "ERROR | DS18x20: Failed to encode Ds18x20Event message"); + continue; + } + // Publish the Ds18x20Event message to the broker + WS_DEBUG_PRINT("DS18x20: Publishing event to broker..."); + if (!WsV2.PublishSignal( + wippersnapper_signal_DeviceToBroker_ds18x20_event_tag, + _DS18X20_model->GetDS18x20EventMsg())) { + WS_DEBUG_PRINTLN( + "ERROR | DS18x20: Failed to publish Ds18x20Event message"); + continue; + } + WS_DEBUG_PRINTLN("Published!"); + } else { + if (!WsV2._sdCardV2->LogDS18xSensorEventToSD(_DS18X20_model->GetDS18x20EventMsg())) { + WS_DEBUG_PRINTLN("ERROR | DS18x20: Failed to log DS18x20 event to SD card"); + continue; + } + } + +#ifdef DEBUG_PROFILE + unsigned long sensor_end_time = millis(); + WS_DEBUG_PRINT("Total sensor processing time: "); + WS_DEBUG_PRINTLN(sensor_end_time - sensor_start_time); +#endif + } + +#ifdef DEBUG_PROFILE + unsigned long total_end_time = millis(); + if (total_end_time - total_start_time != 0) { + WS_DEBUG_PRINT("Total update() execution time: "); + WS_DEBUG_PRINTLN(total_end_time - total_start_time); + } +#endif +} diff --git a/src/components/ds18x20/controller.h b/src/components/ds18x20/controller.h new file mode 100644 index 000000000..a77182f47 --- /dev/null +++ b/src/components/ds18x20/controller.h @@ -0,0 +1,46 @@ +/*! + * @file controller.h + * + * Controller for the DS18X20 API + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2024 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#ifndef WS_DS18X20_CONTROLLER_H +#define WS_DS18X20_CONTROLLER_H +#include "Wippersnapper_V2.h" +#include "hardware.h" +#include "model.h" +#include + +class Wippersnapper_V2; ///< Forward declaration +class DS18X20Model; ///< Forward declaration +class DS18X20Hardware; ///< Forward declaration + +/**************************************************************************/ +/*! + @brief Routes messages between the ds18x20.proto API and the hardware. +*/ +/**************************************************************************/ +class DS18X20Controller { +public: + DS18X20Controller(); + ~DS18X20Controller(); + // Routing + bool Handle_Ds18x20Add(pb_istream_t *stream); + bool Handle_Ds18x20Remove(pb_istream_t *stream); + // Polling + void update(); + +private: + DS18X20Model *_DS18X20_model; ///< ds18x20 model + std::vector> _DS18X20_pins; +}; +extern Wippersnapper_V2 WsV2; ///< Wippersnapper V2 instance +#endif // WS_DS18X20_CONTROLLER_H \ No newline at end of file diff --git a/src/components/ds18x20/hardware.cpp b/src/components/ds18x20/hardware.cpp new file mode 100644 index 000000000..4936d983f --- /dev/null +++ b/src/components/ds18x20/hardware.cpp @@ -0,0 +1,195 @@ +/*! + * @file hardware.cpp + * + * Hardware interface for the ds18x20.proto API + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2024 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#include "hardware.h" + +/***********************************************************************/ +/*! + @brief DS18X20Hardware constructor + @param onewire_pin + The OneWire bus pin to use. +*/ +/***********************************************************************/ +DS18X20Hardware::DS18X20Hardware(uint8_t onewire_pin) : _drv_therm(_ow) { + is_read_temp_c = false; + is_read_temp_f = false; + // Initialize the OneWire bus object + _onewire_pin = onewire_pin; + new (&_ow) OneWireNg_CurrentPlatform(onewire_pin, false); +} + +/***********************************************************************/ +/*! + @brief DS18X20Hardware destructor +*/ +/***********************************************************************/ +DS18X20Hardware::~DS18X20Hardware() { + pinMode(_onewire_pin, + INPUT); // Set the pin to hi-z and release it for other uses +} + +/***********************************************************************/ +/*! + @brief Get the sensor's ID + @returns True if the sensor was successfully identified, False otherwise. +*/ +/***********************************************************************/ +bool DS18X20Hardware::GetSensor() { + OneWireNg::ErrorCode ec = _ow->readSingleId(_sensorId); + return ec == OneWireNg::EC_SUCCESS; +} + +/***********************************************************************/ +/*! + @brief Gets the pin used as a OneWire bus. + @returns The OneWire bus pin. +*/ +/***********************************************************************/ +uint8_t DS18X20Hardware::GetOneWirePin() { return _onewire_pin; } + +/***********************************************************************/ +/*! + @brief Sets the name of the OneWire bus pin. + @param prettyOWPinName + The name of the OneWire bus pin (non-logical pin name, + includes the "D" or "A" prefix). +*/ +/***********************************************************************/ +void DS18X20Hardware::setOneWirePinName(const char *prettyOWPinName) { + strncpy(_onewire_pin_name, prettyOWPinName, sizeof(_onewire_pin_name)); + _onewire_pin_name[sizeof(_onewire_pin_name) - 1] = '\0'; +} + +/***********************************************************************/ +/*! + @brief Gets the name of the OneWire bus pin. + @returns The name of the OneWire bus pin (non-logical pin name, + includes the "D" or "A" prefix). +*/ +/***********************************************************************/ +const char *DS18X20Hardware::getOneWirePinName() { return _onewire_pin_name; } + +/*************************************************************************/ +/*! + @brief Sets the DS18X20 sensor's resolution. + @param resolution + The desired resolution of the DS18X20 sensor, in bits (from + 9 to 12). +*/ +/*************************************************************************/ +void DS18X20Hardware::SetResolution(int resolution) { + // Set the resolution of the DS18X20 sensor driver + switch (resolution) { + case 9: + _resolution = DSTherm::Resolution::RES_9_BIT; + break; + case 10: + _resolution = DSTherm::Resolution::RES_10_BIT; + break; + case 11: + _resolution = DSTherm::Resolution::RES_11_BIT; + break; + case 12: + _resolution = DSTherm::Resolution::RES_12_BIT; + break; + default: + _resolution = + DSTherm::Resolution::RES_12_BIT; // Default to 12-bit resolution + break; + } + + // Set common resolution for all sensors. + // Th, Tl (high/low alarm triggers) are set to 0. + _drv_therm.writeScratchpadAll(0, 0, _resolution); + + // The configuration above is stored in volatile sensors scratchpad + // memory and will be lost after power unplug. Therefore store the + // configuration permanently in sensors EEPROM. + _drv_therm.copyScratchpadAll(false); +} + +/*************************************************************************/ +/*! + @brief Sets the timer to read from the sensor. + @param period + The desired period to read the sensor, in seconds. +*/ +/*************************************************************************/ +void DS18X20Hardware::SetPeriod(float period) { + _period = period * 1000; // Convert to milliseconds + _prv_period = 0; // Also reset the previous period whenever we set a + // new period +} + +/*************************************************************************/ +/*! + @brief Obtains the current time in milliseconds and compares it to + the last time the sensor was polled. + @returns True if the timer has expired, False otherwise. +*/ +/*************************************************************************/ +bool DS18X20Hardware::IsTimerExpired() { + return millis() - _prv_period > _period; +} + +/*************************************************************************/ +/*! + @brief Gets the temperature value last read by the sensor, in Celsius. + @returns The temperature in Celsius. +*/ +/*************************************************************************/ +float DS18X20Hardware::GetTemperatureC() { return _temp_c; } + +/*************************************************************************/ +/*! + @brief Gets the temperature value last read by the sensor, in Fahrenheit. + @returns The temperature in Fahrenheit. +*/ +/*************************************************************************/ +float DS18X20Hardware::GetTemperatureF() { + _temp_f = _temp_c * 9.0 / 5.0 + 32.0; + return _temp_f; +} + +/*************************************************************************/ +/*! + @brief Attempts to obtain the temperature from the sensor, in + degrees Celsius. + @returns True if the temperature was successfully read, False otherwise. +*/ +/*************************************************************************/ +bool DS18X20Hardware::ReadTemperatureC() { + // Start temperature conversion for the first identified sensor on the OneWire + // bus + OneWireNg::ErrorCode ec = + _drv_therm.convertTemp(_sensorId, DSTherm::MAX_CONV_TIME, false); + + if (ec != OneWireNg::EC_SUCCESS) + return false; + + // Scratchpad placeholder is static to allow reuse of the associated + // sensor id while reissuing readScratchpadSingle() calls. + // Note, due to its storage class the placeholder is zero initialized. + static Placeholder scrpd; + ec = _drv_therm.readScratchpad(_sensorId, scrpd); + if (ec != OneWireNg::EC_SUCCESS) + return false; + + // Read the temperature from the sensor + long temp = scrpd->getTemp2(); + _temp_c = temp / 16.0; // Convert from 16-bit int to float + + _prv_period = millis(); // Update the last time the sensor was polled + return true; +} \ No newline at end of file diff --git a/src/components/ds18x20/hardware.h b/src/components/ds18x20/hardware.h new file mode 100644 index 000000000..0fcb8288c --- /dev/null +++ b/src/components/ds18x20/hardware.h @@ -0,0 +1,61 @@ +/*! + * @file model.h + * + * Hardware implementation for the ds18x20.proto message. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2024 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#ifndef WS_DS18X20_HARDWARE_H +#define WS_DS18X20_HARDWARE_H +#include "Wippersnapper_V2.h" + +#include "OneWireNg_CurrentPlatform.h" +#include "drivers/DSTherm.h" +#include "utils/Placeholder.h" + +/**************************************************************************/ +/*! + @brief Interface for interacting with the's DallasTemp + and OneWire APIs. +*/ +/**************************************************************************/ +class DS18X20Hardware { +public: + DS18X20Hardware(uint8_t onewire_pin); + ~DS18X20Hardware(); + uint8_t GetOneWirePin(); + void SetResolution(int resolution); + void SetPeriod(float period); + void setOneWirePinName(const char *prettyOWPinName); + const char *getOneWirePinName(); + bool IsTimerExpired(); + bool GetSensor(); + bool ReadTemperatureC(); + float GetTemperatureC(); + float GetTemperatureF(); + bool is_read_temp_c; ///< Flag telling the controller to read the temperature + ///< in degrees Celsius + bool is_read_temp_f; ///< Flag telling the controller to read the temperature + ///< in degrees Fahrenheit +private: + Placeholder _ow; ///< OneWire bus object + OneWireNg::Id _sensorId; ///< Sensor ID + DSTherm _drv_therm; ///< DS18X20 driver object + DSTherm::Resolution _resolution; ///< Resolution of the DS18X20 sensor + float _temp_c; ///< Temperature in Celsius + float _temp_f; ///< Temperature in Fahrenheit + // From the PB model + uint8_t + _onewire_pin; ///< Pin utilized by the OneWire bus, used for addressing + char _onewire_pin_name[5]; ///< Name of the OneWire bus pin + float _period; ///< The desired period to read the sensor, in seconds + float _prv_period; ///< Last time the sensor was polled, in seconds +}; +#endif // WS_DS18X20_HARDWARE_H \ No newline at end of file diff --git a/src/components/ds18x20/model.cpp b/src/components/ds18x20/model.cpp new file mode 100644 index 000000000..54246f17f --- /dev/null +++ b/src/components/ds18x20/model.cpp @@ -0,0 +1,186 @@ +/*! + * @file model.cpp + * + * Model for the ds18x20.proto message. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2024 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#include "model.h" + +/***********************************************************************/ +/*! + @brief DS18X20Model constructor +*/ +/***********************************************************************/ +DS18X20Model::DS18X20Model() { + // Initialize the DS18X20 messages + _msg_DS18x20Add = wippersnapper_ds18x20_Ds18x20Add_init_zero; + _msg_DS18x20Added = wippersnapper_ds18x20_Ds18x20Added_init_zero; + _msg_DS18x20Remove = wippersnapper_ds18x20_Ds18x20Remove_init_zero; +} + +/***********************************************************************/ +/*! + @brief DS18X20Model destructor +*/ +/***********************************************************************/ +DS18X20Model::~DS18X20Model() {} + +/***********************************************************************/ +/*! + @brief Attempts to decode a Ds18x20Add message from the broker. + @param stream + The nanopb input stream. + @return True if the message was successfully decoded, False otherwise. +*/ +/***********************************************************************/ +bool DS18X20Model::DecodeDS18x20Add(pb_istream_t *stream) { + _msg_DS18x20Add = wippersnapper_ds18x20_Ds18x20Add_init_zero; + // Attempt to decode the stream into a Ds18x20Add message + return pb_decode(stream, wippersnapper_ds18x20_Ds18x20Add_fields, + &_msg_DS18x20Add); +} + +/***********************************************************************/ +/*! + @brief Gets a pointer to the Ds18x20Add message. + @return Pointer to the Ds18x20Add message. +*/ +/***********************************************************************/ +wippersnapper_ds18x20_Ds18x20Add *DS18X20Model::GetDS18x20AddMsg() { + return &_msg_DS18x20Add; +} + +/***********************************************************************/ +/*! + @brief Returns a pointer to the Ds18x20Added message. + @return Pointer to the Ds18x20Added message. +*/ +/***********************************************************************/ +wippersnapper_ds18x20_Ds18x20Added *DS18X20Model::GetDS18x20AddedMsg() { + return &_msg_DS18x20Added; +} + +/***********************************************************************/ +/*! + @brief Encodes a Ds18x20Added message. + @param onewire_pin + The OneWire bus pin to add. + @param is_init + True if the sensor was successfully initialized, + False otherwise. + @return True if the message was successfully encoded, + False otherwise. +*/ +/***********************************************************************/ +bool DS18X20Model::EncodeDS18x20Added(char *onewire_pin, bool is_init) { + // Fill the Ds18x20Added message + _msg_DS18x20Added = wippersnapper_ds18x20_Ds18x20Added_init_zero; + _msg_DS18x20Added.is_initialized = is_init; + strcpy(_msg_DS18x20Added.onewire_pin, onewire_pin); + + // Encode the Ds18x20Added message + size_t sz_msg; + if (!pb_get_encoded_size(&sz_msg, wippersnapper_ds18x20_Ds18x20Added_fields, + &_msg_DS18x20Added)) + return false; + + uint8_t buf[sz_msg]; + pb_ostream_t msg_stream = pb_ostream_from_buffer(buf, sizeof(buf)); + return pb_encode(&msg_stream, wippersnapper_ds18x20_Ds18x20Added_fields, + &_msg_DS18x20Added); +} + +/*************************************************************************/ +/*! + @brief Attempts to decode a Ds18x20Remove message from the broker. + @param stream + The nanopb input stream. + @return True if the message was successfully decoded, False otherwise. +*/ +/*************************************************************************/ +bool DS18X20Model::DecodeDS18x20Remove(pb_istream_t *stream) { + _msg_DS18x20Remove = wippersnapper_ds18x20_Ds18x20Remove_init_zero; + return pb_decode(stream, wippersnapper_ds18x20_Ds18x20Remove_fields, + &_msg_DS18x20Remove); +} + +/*************************************************************************/ +/*! + @brief Gets a pointer to the Ds18x20Remove message. + @return Pointer to the Ds18x20Remove message. +*/ +/*************************************************************************/ +wippersnapper_ds18x20_Ds18x20Remove *DS18X20Model::GetDS18x20RemoveMsg() { + return &_msg_DS18x20Remove; +} + +/*************************************************************************/ +/*! + @brief Gets a pointer to the Ds18x20Event message. + @return Pointer to the Ds18x20Event message. +*/ +/*************************************************************************/ +wippersnapper_ds18x20_Ds18x20Event *DS18X20Model::GetDS18x20EventMsg() { + return &_msg_DS18x20Event; +} + +/*************************************************************************/ +/*! + @brief Encodes a Ds18x20Event message. + @return True if the message was successfully encoded, False otherwise. +*/ +/*************************************************************************/ +bool DS18X20Model::EncodeDs18x20Event() { + // take the filled _msg_DS18x20Event we built in the controller and encode it + size_t sz_msg; + if (!pb_get_encoded_size(&sz_msg, wippersnapper_ds18x20_Ds18x20Event_fields, + &_msg_DS18x20Event)) + return false; + + uint8_t buf[sz_msg]; + pb_ostream_t msg_stream = pb_ostream_from_buffer(buf, sizeof(buf)); + return pb_encode(&msg_stream, wippersnapper_ds18x20_Ds18x20Event_fields, + &_msg_DS18x20Event); +} + +/*************************************************************************/ +/*! + @brief Initializes the Ds18x20Event message. +*/ +/*************************************************************************/ +void DS18X20Model::InitDS18x20EventMsg(const char *ow_pin_name) { + _msg_DS18x20Event = wippersnapper_ds18x20_Ds18x20Event_init_zero; + _msg_DS18x20Event.sensor_events_count = 0; + strcpy(_msg_DS18x20Event.onewire_pin, ow_pin_name); +} + +/*************************************************************************/ +/*! + @brief Adds a "sensor event" to the Ds18x20Event message. + @param sensor_type + The event's SensorType. + @param sensor_value + The event's value. +*/ +/*************************************************************************/ +void DS18X20Model::addSensorEvent(wippersnapper_sensor_SensorType sensor_type, + float sensor_value) { + _msg_DS18x20Event.sensor_events[_msg_DS18x20Event.sensor_events_count].type = + sensor_type; + _msg_DS18x20Event.sensor_events[_msg_DS18x20Event.sensor_events_count] + .which_value = wippersnapper_sensor_SensorEvent_float_value_tag; + + // Convert the float to 2 decimal places + sensor_value = roundf(sensor_value * 100) / 100; + _msg_DS18x20Event.sensor_events[_msg_DS18x20Event.sensor_events_count] + .value.float_value = sensor_value; + _msg_DS18x20Event.sensor_events_count++; +} \ No newline at end of file diff --git a/src/components/ds18x20/model.h b/src/components/ds18x20/model.h new file mode 100644 index 000000000..0cae22782 --- /dev/null +++ b/src/components/ds18x20/model.h @@ -0,0 +1,54 @@ +/*! + * @file model.h + * + * Model interface for the DS18X20.proto message. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2024 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#ifndef WS_DS18X20_MODEL_H +#define WS_DS18X20_MODEL_H +#include "Wippersnapper_V2.h" + +/**************************************************************************/ +/*! + @brief Provides an interface for creating, encoding, and parsing + messages from DS18X20.proto. +*/ +/**************************************************************************/ +class DS18X20Model { +public: + DS18X20Model(); + ~DS18X20Model(); + // Ds18x20Add Message API + bool DecodeDS18x20Add(pb_istream_t *stream); + wippersnapper_ds18x20_Ds18x20Add *GetDS18x20AddMsg(); + // DS18x20Added Message API + bool EncodeDS18x20Added(char *onewire_pin, bool is_init); + wippersnapper_ds18x20_Ds18x20Added *GetDS18x20AddedMsg(); + // Ds18x20Remove Message API + bool DecodeDS18x20Remove(pb_istream_t *stream); + wippersnapper_ds18x20_Ds18x20Remove *GetDS18x20RemoveMsg(); + // Ds18x20Event Message API + bool EncodeDs18x20Event(); + wippersnapper_ds18x20_Ds18x20Event *GetDS18x20EventMsg(); + void InitDS18x20EventMsg(const char *ow_pin_name); + void addSensorEvent(wippersnapper_sensor_SensorType sensor_type, + float sensor_value); +private: + wippersnapper_ds18x20_Ds18x20Add + _msg_DS18x20Add; ///< wippersnapper_ds18x20_Ds18x20Add message + wippersnapper_ds18x20_Ds18x20Added + _msg_DS18x20Added; ///< wippersnapper_ds18x20_Ds18x20Added message + wippersnapper_ds18x20_Ds18x20Remove + _msg_DS18x20Remove; ///< wippersnapper_ds18x20_Ds18x20Remove message + wippersnapper_ds18x20_Ds18x20Event + _msg_DS18x20Event; ///< wippersnapper_ds18x20_Ds18x20Event message +}; +#endif // WS_DIGITALIO_MODEL_H \ No newline at end of file diff --git a/src/components/ds18x20/ws_ds18x20.cpp b/src/components/ds18x20/ws_ds18x20.cpp deleted file mode 100644 index 0cfa6e0c5..000000000 --- a/src/components/ds18x20/ws_ds18x20.cpp +++ /dev/null @@ -1,313 +0,0 @@ -/*! - * @file ws_ds18x20.cpp - * - * This component implements 1-wire communication - * for the DS18X20-line of Maxim Temperature ICs. - * - * Adafruit invests time and resources providing this open source code, - * please support Adafruit and open-source hardware by purchasing - * products from Adafruit! - * - * Copyright (c) Brent Rubell 2022-2023 for Adafruit Industries. - * - * BSD license, all text here must be included in any redistribution. - * - */ - -#include "ws_ds18x20.h" - -/*************************************************************/ -/*! - @brief Creates a new WipperSnapper Ds18x20 component. -*/ -/*************************************************************/ -ws_ds18x20::ws_ds18x20() {} - -/*************************************************************/ -/*! - @brief Destructor for a WipperSnapper DS18X20 component. -*/ -/*************************************************************/ -ws_ds18x20::~ws_ds18x20() { - // delete DallasTemp sensors and release onewire buses - for (size_t idx = 0; idx < _ds18xDrivers.size(); idx++) { - delete _ds18xDrivers[idx]->dallasTempObj; - delete _ds18xDrivers[idx]->oneWire; - } - // remove all elements - _ds18xDrivers.clear(); -} - -/********************************************************************/ -/*! - @brief Initializes a DS18x20 sensor using a - configuration sent by the broker and adds it to a - vector of ds18x20 sensor drivers. - @param msgDs18x20InitReq - Message containing configuration data for a - ds18x20 sensor. - @returns True if initialized successfully, False otherwise. -*/ -/********************************************************************/ -bool ws_ds18x20::addDS18x20( - wippersnapper_ds18x20_v1_Ds18x20InitRequest *msgDs18x20InitReq) { - bool is_success = false; - - // init. new ds18x20 object - ds18x20Obj *newObj = new ds18x20Obj(); - char *oneWirePin = msgDs18x20InitReq->onewire_pin + 1; - newObj->oneWire = new OneWire(atoi(oneWirePin)); - newObj->dallasTempObj = new DallasTemperature(newObj->oneWire); - newObj->dallasTempObj->begin(); - // attempt to obtain sensor address - if (newObj->dallasTempObj->getAddress(newObj->dallasTempAddr, 0)) { - // attempt to set sensor resolution - newObj->dallasTempObj->setResolution(msgDs18x20InitReq->sensor_resolution); - // copy the device's sensor properties - newObj->sensorPropertiesCount = - msgDs18x20InitReq->i2c_device_properties_count; - // TODO: Make sure this works, it's a new idea and untested :) - for (int i = 0; i < newObj->sensorPropertiesCount; i++) { - newObj->sensorProperties[i].sensor_type = - msgDs18x20InitReq->i2c_device_properties[i].sensor_type; - newObj->sensorProperties[i].sensor_period = - (long)msgDs18x20InitReq->i2c_device_properties[i].sensor_period * - 1000; - } - // set pin - strcpy(newObj->onewire_pin, msgDs18x20InitReq->onewire_pin); - // add the new ds18x20 driver to vec. - _ds18xDrivers.push_back(newObj); - is_success = true; - } else { - WS_DEBUG_PRINTLN("Failed to find DSx sensor on specified pin."); - } - - // fill and publish the initialization response back to the broker - size_t msgSz; // message's encoded size - wippersnapper_signal_v1_Ds18x20Response msgInitResp = - wippersnapper_signal_v1_Ds18x20Response_init_zero; - msgInitResp.which_payload = - wippersnapper_signal_v1_Ds18x20Response_resp_ds18x20_init_tag; - msgInitResp.payload.resp_ds18x20_init.is_initialized = is_success; - strcpy(msgInitResp.payload.resp_ds18x20_init.onewire_pin, - msgDs18x20InitReq->onewire_pin); - - WS_DEBUG_PRINT("Created OneWireBus on GPIO "); - WS_DEBUG_PRINT(msgDs18x20InitReq->onewire_pin); - WS_DEBUG_PRINTLN(" with DS18x20 attached!"); - -#ifdef USE_DISPLAY - char buffer[100]; - snprintf(buffer, 100, "[DS18x] Attached DS18x20 sensor to pin %s\n", - msgDs18x20InitReq->onewire_pin); - WS._ui_helper->add_text_to_terminal(buffer); -#endif - - // Encode and publish response back to broker - memset(WS._buffer_outgoing, 0, sizeof(WS._buffer_outgoing)); - pb_ostream_t ostream = - pb_ostream_from_buffer(WS._buffer_outgoing, sizeof(WS._buffer_outgoing)); - if (!ws_pb_encode(&ostream, wippersnapper_signal_v1_Ds18x20Response_fields, - &msgInitResp)) { - WS_DEBUG_PRINTLN("ERROR: Unable to encode msg_init response message!"); - return false; - } - pb_get_encoded_size(&msgSz, wippersnapper_signal_v1_Ds18x20Response_fields, - &msgInitResp); - WS_DEBUG_PRINT("-> DS18x Init Response..."); - WS._mqtt->publish(WS._topic_signal_ds18_device, WS._buffer_outgoing, msgSz, - 1); - WS_DEBUG_PRINTLN("Published!"); - - return is_success; -} - -/********************************************************************/ -/*! - @brief De-initializes a DS18x20 sensor and releases its - pin and resources. - @param msgDS18x20DeinitReq - Message containing configuration data for a - ds18x20 sensor. -*/ -/********************************************************************/ -void ws_ds18x20::deleteDS18x20( - wippersnapper_ds18x20_v1_Ds18x20DeInitRequest *msgDS18x20DeinitReq) { - // Loop thru vector of drivers to find the unique address - for (size_t idx = 0; idx < _ds18xDrivers.size(); idx++) { - if (strcmp(_ds18xDrivers[idx]->onewire_pin, - msgDS18x20DeinitReq->onewire_pin) == 0) { - WS_DEBUG_PRINT("Deleting OneWire instance on pin "); - WS_DEBUG_PRINTLN(msgDS18x20DeinitReq->onewire_pin); - delete _ds18xDrivers[idx] - ->dallasTempObj; // delete dallas temp instance on pin - delete _ds18xDrivers[idx] - ->oneWire; // delete OneWire instance on pin and release pin for reuse - _ds18xDrivers.erase(_ds18xDrivers.begin() + - idx); // erase vector and re-allocate - } - } - -#ifdef USE_DISPLAY - char buffer[100]; - snprintf(buffer, 100, "[DS18x] Deleted DS18x20 sensor on pin %s\n", - msgDS18x20DeinitReq->onewire_pin); - WS._ui_helper->add_text_to_terminal(buffer); -#endif -} - -/*************************************************************/ -/*! - @brief Iterates through each ds18x20 sensor and - reports data (if period expired) to Adafruit IO. -*/ -/*************************************************************/ -void ws_ds18x20::update() { - // return immediately if no drivers have been initialized - if (_ds18xDrivers.size() == 0) - return; - - long curTime; // used for holding the millis() value - std::vector::iterator iter, end; - for (iter = _ds18xDrivers.begin(), end = _ds18xDrivers.end(); iter != end; - ++iter) { - - // Create an empty DS18x20 event signal message and configure - wippersnapper_signal_v1_Ds18x20Response msgDS18x20Response = - wippersnapper_signal_v1_Ds18x20Response_init_zero; - msgDS18x20Response.which_payload = - wippersnapper_signal_v1_Ds18x20Response_resp_ds18x20_event_tag; - - // take the current time for the driver (*iter) - curTime = millis(); - // Poll each sensor type, if period has elapsed - for (int i = 0; i < (*iter)->sensorPropertiesCount; i++) { - // has sensor_period elapsed? - if (curTime - (*iter)->sensorPeriodPrv > - (long)(*iter)->sensorProperties[i].sensor_period) { - // issue global temperature request to all DS sensors - WS_DEBUG_PRINTLN("Requesting temperature.."); - (*iter)->dallasTempObj->requestTemperatures(); - // poll the DS sensor driver - float tempC = (*iter)->dallasTempObj->getTempC((*iter)->dallasTempAddr); - if (tempC == DEVICE_DISCONNECTED_C) { - WS_DEBUG_PRINTLN("ERROR: Could not read temperature data, is the " - "sensor disconnected?"); -#ifdef USE_DISPLAY - WS._ui_helper->add_text_to_terminal( - "[DS18x ERROR] Unable to read temperature, is the sensor " - "disconnected?\n"); -#endif - break; - } - - // check and pack based on sensorType - char buffer[100]; - if ((*iter)->sensorProperties[i].sensor_type == - wippersnapper_i2c_v1_SensorType_SENSOR_TYPE_AMBIENT_TEMPERATURE) { - - WS_DEBUG_PRINT("(OneWireBus GPIO: "); - WS_DEBUG_PRINT((*iter)->onewire_pin); - WS_DEBUG_PRINT(") DS18x20 Value: "); - WS_DEBUG_PRINT(tempC); - WS_DEBUG_PRINTLN("*C") - snprintf(buffer, 100, "[DS18x] Read %0.2f*C on GPIO %s\n", tempC, - (*iter)->onewire_pin); - - msgDS18x20Response.payload.resp_ds18x20_event.sensor_event[i].type = - wippersnapper_i2c_v1_SensorType_SENSOR_TYPE_AMBIENT_TEMPERATURE; - msgDS18x20Response.payload.resp_ds18x20_event.sensor_event[i].value = - tempC; - - msgDS18x20Response.payload.resp_ds18x20_event.sensor_event_count++; - } - - if ((*iter)->sensorProperties[i].sensor_type == - wippersnapper_i2c_v1_SensorType_SENSOR_TYPE_AMBIENT_TEMPERATURE_FAHRENHEIT) { - msgDS18x20Response.payload.resp_ds18x20_event.sensor_event[i].type = - wippersnapper_i2c_v1_SensorType_SENSOR_TYPE_AMBIENT_TEMPERATURE_FAHRENHEIT; - msgDS18x20Response.payload.resp_ds18x20_event.sensor_event[i].value = - (*iter)->dallasTempObj->toFahrenheit(tempC); - WS_DEBUG_PRINT("(OneWireBus GPIO: "); - WS_DEBUG_PRINT((*iter)->onewire_pin); - WS_DEBUG_PRINT(") DS18x20 Value: "); - WS_DEBUG_PRINT( - msgDS18x20Response.payload.resp_ds18x20_event.sensor_event[i] - .value); - WS_DEBUG_PRINTLN("*F") - snprintf(buffer, 100, "[DS18x] Read %0.2f*F on GPIO %s\n", - msgDS18x20Response.payload.resp_ds18x20_event.sensor_event[i] - .value, - (*iter)->onewire_pin); - - msgDS18x20Response.payload.resp_ds18x20_event.sensor_event_count++; - } - - // did we obtain the expected amount of sensor events for the - // `resp_ds18x20_event` message? - if (msgDS18x20Response.payload.resp_ds18x20_event.sensor_event_count == - (*iter)->sensorPropertiesCount) { - - // prep sensor event data for sending to IO - // use onewire_pin as the "address" - strcpy(msgDS18x20Response.payload.resp_ds18x20_event.onewire_pin, - (*iter)->onewire_pin); - // prep and encode buffer - memset(WS._buffer_outgoing, 0, sizeof(WS._buffer_outgoing)); - pb_ostream_t ostream = pb_ostream_from_buffer( - WS._buffer_outgoing, sizeof(WS._buffer_outgoing)); - if (!ws_pb_encode(&ostream, - wippersnapper_signal_v1_Ds18x20Response_fields, - &msgDS18x20Response)) { - WS_DEBUG_PRINTLN( - "ERROR: Unable to encode DS18x20 event responsemessage!"); - snprintf(buffer, 100, - "[DS18x ERROR] Unable to encode event message!"); - return; - } - - WS_DEBUG_PRINTLN( - "DEBUG: msgDS18x20Response sensor_event message contents:"); - for (int i = 0; - i < - msgDS18x20Response.payload.resp_ds18x20_event.sensor_event_count; - i++) { - WS_DEBUG_PRINT("sensor_event[#]: "); - WS_DEBUG_PRINTLN(i); - WS_DEBUG_PRINT("\tOneWire Bus: "); - WS_DEBUG_PRINTLN( - msgDS18x20Response.payload.resp_ds18x20_event.onewire_pin); - WS_DEBUG_PRINT("\tsensor_event type: "); - WS_DEBUG_PRINTLN( - msgDS18x20Response.payload.resp_ds18x20_event.sensor_event[i] - .type); - WS_DEBUG_PRINT("\tsensor_event value: "); - WS_DEBUG_PRINTLN( - msgDS18x20Response.payload.resp_ds18x20_event.sensor_event[i] - .value); - } - - // Publish I2CResponse msg - size_t msgSz; - pb_get_encoded_size(&msgSz, - wippersnapper_signal_v1_Ds18x20Response_fields, - &msgDS18x20Response); - WS_DEBUG_PRINT("PUBLISHING -> msgDS18x20Response Event Message..."); - if (!WS._mqtt->publish(WS._topic_signal_ds18_device, - WS._buffer_outgoing, msgSz, 1)) { - WS_DEBUG_PRINTLN("ERROR: Unable to publish DS18x20 event message - " - "MQTT Publish failed!"); - return; - }; - WS_DEBUG_PRINTLN("PUBLISHED!"); -#ifdef USE_DISPLAY - WS._ui_helper->add_text_to_terminal(buffer); -#endif - - (*iter)->sensorPeriodPrv = curTime; // set prv period - } - } - } - } -} \ No newline at end of file diff --git a/src/components/ds18x20/ws_ds18x20.h b/src/components/ds18x20/ws_ds18x20.h deleted file mode 100644 index 5398d4c4f..000000000 --- a/src/components/ds18x20/ws_ds18x20.h +++ /dev/null @@ -1,65 +0,0 @@ -/*! - * @file ws_ds18x20.h - * - * This component implements 1-wire communication - * for the DS18X20-line of Maxim Temperature ICs. - * - * Adafruit invests time and resources providing this open source code, - * please support Adafruit and open-source hardware by purchasing - * products from Adafruit! - * - * Copyright (c) Brent Rubell 2022 for Adafruit Industries. - * - * BSD license, all text here must be included in any redistribution. - * - */ -#ifndef WIPPERSNAPPER_DS18X20_H -#define WIPPERSNAPPER_DS18X20_H - -#include "Wippersnapper.h" - -#include -#include - -/** DS18x20 Object */ -struct ds18x20Obj { - OneWire * - oneWire; ///< Pointer to an OneWire bus used by a DallasTemperature object - char onewire_pin[5]; ///< Pin utilized by the OneWire bus, used for addressing - DallasTemperature - *dallasTempObj; ///< Pointer to a DallasTemperature sensor object - DeviceAddress dallasTempAddr; ///< Temperature sensor's address - int sensorPropertiesCount; ///< Tracks # of sensorProperties - wippersnapper_i2c_v1_I2CDeviceSensorProperties sensorProperties[2] = - wippersnapper_i2c_v1_I2CDeviceSensorProperties_init_zero; ///< DS sensor - ///< type(s) - long sensorPeriodPrv; ///< Last time the sensor was polled, in millis -}; - -// forward decl. -class Wippersnapper; - -/**************************************************************************/ -/*! - @brief Class that provides an interface with DS18X20-compatible - sensors. -*/ -/**************************************************************************/ -class ws_ds18x20 { -public: - ws_ds18x20(); - ~ws_ds18x20(); - - bool - addDS18x20(wippersnapper_ds18x20_v1_Ds18x20InitRequest *msgDs18x20InitReq); - void deleteDS18x20( - wippersnapper_ds18x20_v1_Ds18x20DeInitRequest *msgDS18x20DeinitReq); - void update(); - -private: - std::vector - _ds18xDrivers; ///< Vec. of ptrs. to ds18x driver objects -}; -extern Wippersnapper WS; - -#endif // WIPPERSNAPPER_DS18X20_H \ No newline at end of file diff --git a/src/components/sensor/model.cpp b/src/components/sensor/model.cpp new file mode 100644 index 000000000..85f1cb3d3 --- /dev/null +++ b/src/components/sensor/model.cpp @@ -0,0 +1,34 @@ +/*! + * @file model.cpp + * + * Model for the sensor.proto message. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2024 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#include "model.h" + +/***********************************************************************/ +/*! + @brief SensorModel constructor +*/ +/***********************************************************************/ +SensorModel::SensorModel() { + _msg_sensor_event = wippersnapper_sensor_SensorEvent_init_zero; +} + +/***********************************************************************/ +/*! + @brief SensorModel destructor +*/ +/***********************************************************************/ +SensorModel::~SensorModel() { + // Zero-out the SensorEvent message + _msg_sensor_event = wippersnapper_sensor_SensorEvent_init_zero; +} \ No newline at end of file diff --git a/src/components/sensor/model.h b/src/components/sensor/model.h new file mode 100644 index 000000000..6be8584f8 --- /dev/null +++ b/src/components/sensor/model.h @@ -0,0 +1,33 @@ +/*! + * @file model.h + * + * Model for the sensor.proto message. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2024 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#ifndef WS_SENSOR_MODEL_H +#define WS_SENSOR_MODEL_H +#include "Wippersnapper_V2.h" +#include "protos/sensor.pb.h" + +/**************************************************************************/ +/*! + @brief Provides an interface for creating, encoding, and parsing + messages from sensor.proto. +*/ +/**************************************************************************/ +class SensorModel { +public: + SensorModel(); + ~SensorModel(); +private: + wippersnapper_sensor_SensorEvent _msg_sensor_event; ///< SensorEvent message +}; +#endif // WS_SENSOR_MODEL_H \ No newline at end of file diff --git a/src/helpers/ws_helper_esp.h b/src/helpers/ws_helper_esp.h new file mode 100644 index 000000000..8f18c3f53 --- /dev/null +++ b/src/helpers/ws_helper_esp.h @@ -0,0 +1,58 @@ +/*! + * @file ws_helper_esp.h + * + * This file contains helper functions for the ESPx platforms. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2024 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#ifndef WS_HELPER_ESP_H +#define WS_HELPER_ESP_H +#include "esp_task_wdt.h" +#include + +/*******************************************************************************************************************************/ +/*! + @brief Converts reset reason type to a C string.. This uses the mechanism + in IDF that persists crash reasons across a reset. see: + https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/misc_system_api.html#software-reset + @param r + The reset reason, esp_reset_reason_t + @return A C string representing the reset reason. +*/ +/*******************************************************************************************************************************/ +inline const char *resetReasonName(esp_reset_reason_t r) { + switch (r) { + case ESP_RST_UNKNOWN: + return "Unknown"; + case ESP_RST_POWERON: + return "PowerOn"; // Power on or RST pin toggled + case ESP_RST_EXT: + return "ExtPin"; // External pin - not applicable for ESP32 + case ESP_RST_SW: + return "Reboot"; // esp_restart() + case ESP_RST_PANIC: + return "Crash"; // Exception/panic + case ESP_RST_INT_WDT: + return "WDT_Int"; // Interrupt watchdog (software or hardware) + case ESP_RST_TASK_WDT: + return "WDT_Task"; // Task watchdog + case ESP_RST_WDT: + return "WDT_Other"; // Other watchdog + case ESP_RST_DEEPSLEEP: + return "Sleep"; // Reset after exiting deep sleep mode + case ESP_RST_BROWNOUT: + return "BrownOut"; // Brownout reset (software or hardware) + case ESP_RST_SDIO: + return "SDIO"; // Reset over SDIO + default: + return ""; + } +} +#endif // WS_HELPER_ESP_H diff --git a/src/helpers/ws_helper_macros.h b/src/helpers/ws_helper_macros.h new file mode 100644 index 000000000..e82393a28 --- /dev/null +++ b/src/helpers/ws_helper_macros.h @@ -0,0 +1,70 @@ +#ifndef WS_HELPER_MACROS_H +#define WS_HELPER_MACROS_H + +#define WS_DEBUG ///< Define to enable debugging to serial terminal +#define WS_PRINTER Serial ///< Where debug messages will be printed + +// Define actual debug output functions when necessary. +#ifdef WS_DEBUG +#define WS_DEBUG_PRINT(...) \ + { WS_PRINTER.print(__VA_ARGS__); } ///< Prints debug output. +#define WS_DEBUG_PRINTLN(...) \ + { WS_PRINTER.println(__VA_ARGS__); } ///< Prints line from debug output. +#define WS_DEBUG_PRINTHEX(...) \ + { WS_PRINTER.print(__VA_ARGS__, HEX); } ///< Prints debug output. +#else +#define WS_DEBUG_PRINT(...) \ + {} ///< Prints debug output +#define WS_DEBUG_PRINTLN(...) \ + {} ///< Prints line from debug output. +#endif + +#define WS_DELAY_WITH_WDT(timeout) \ + { \ + unsigned long start = millis(); \ + while (millis() - start < timeout) { \ + delay(10); \ + yield(); \ + feedWDT(); \ + if (millis() < start) { \ + start = millis(); /* if rollover */ \ + } \ + } \ + } ///< Delay function + +/**************************************************************************/ +/*! + @brief Retry a function until a condition is met or a timeout is reached. + @param func + The function to retry. + @param result_type + The type of the result of the function. + @param result_var + The variable to store the last result of the function. + @param condition + The condition to check the result against. + @param timeout + The maximum time to retry the function. + @param interval + The time to wait between retries. + @param ... + The arguments to pass to the function. +*/ +/**************************************************************************/ +#define RETRY_FUNCTION_UNTIL_TIMEOUT(func, result_type, result_var, condition, \ + timeout, interval, ...) \ + { \ + unsigned long startTime = millis(); \ + while (millis() - startTime < timeout) { \ + result_type result_var = func(__VA_ARGS__); \ + if (condition(result_var)) { \ + break; \ + } \ + if (startTime > millis()) { \ + startTime = millis(); /* if rollover */ \ + } \ + WS_DELAY_WITH_WDT(interval); \ + } \ + } ///< Retry a function until a condition is met or a timeout is reached. + +#endif // WS_HELPER_MACROS_H \ No newline at end of file diff --git a/src/helpers/ws_helper_status.h b/src/helpers/ws_helper_status.h new file mode 100644 index 000000000..84a8c5513 --- /dev/null +++ b/src/helpers/ws_helper_status.h @@ -0,0 +1,60 @@ +#ifndef WS_HELPER_STATUS_H +#define WS_HELPER_STATUS_H + +/** Defines the Adafruit IO connection status */ +typedef enum { + WS_IDLE = 0, // Waiting for connection establishement + WS_NET_DISCONNECTED = 1, // Network disconnected + WS_DISCONNECTED = 2, // Disconnected from Adafruit IO + WS_FINGERPRINT_UNKOWN = 3, // Unknown WS_SSL_FINGERPRINT + + WS_NET_CONNECT_FAILED = 10, // Failed to connect to network + WS_CONNECT_FAILED = 11, // Failed to connect to Adafruit IO + WS_FINGERPRINT_INVALID = 12, // Unknown WS_SSL_FINGERPRINT + WS_AUTH_FAILED = 13, // Invalid Adafruit IO login credentials provided. + WS_SSID_INVALID = + 14, // SSID is "" or otherwise invalid, connection not attempted + + WS_NET_CONNECTED = 20, // Connected to Adafruit IO + WS_CONNECTED = 21, // Connected to network + WS_CONNECTED_INSECURE = 22, // Insecurely (non-SSL) connected to network + WS_FINGERPRINT_UNSUPPORTED = 23, // Unsupported WS_SSL_FINGERPRINT + WS_FINGERPRINT_VALID = 24, // Valid WS_SSL_FINGERPRINT + WS_BOARD_DESC_INVALID = 25, // Unable to send board description + WS_BOARD_RESYNC_FAILED = 26 // Board sync failure +} ws_status_t; + +/** Defines the Adafruit IO MQTT broker's connection return codes */ +typedef enum { + WS_MQTT_CONNECTED = 0, // Connected + WS_MQTT_INVALID_PROTOCOL = 1, // Invalid mqtt protocol + WS_MQTT_INVALID_CID = 2, // Client id rejected + WS_MQTT_SERVICE_UNAVALIABLE = 3, // Malformed user/pass + WS_MQTT_INVALID_USER_PASS = 4, // Unauthorized access to resource + WS_MQTT_UNAUTHORIZED = 5, // MQTT service unavailable + WS_MQTT_THROTTLED = 6, // Account throttled + WS_MQTT_BANNED = 7 // Account banned +} ws_mqtt_status_t; + +/** Defines the Wippersnapper client's hardware registration status */ +typedef enum { + WS_BOARD_DEF_IDLE, + WS_BOARD_DEF_SEND_FAILED, + WS_BOARD_DEF_SENT, + WS_BOARD_DEF_OK, + WS_BOARD_DEF_INVALID, + WS_BOARD_DEF_UNSPECIFIED +} ws_board_status_t; + +/** Defines the Wippersnapper client's network status */ +typedef enum { + FSM_NET_IDLE, + FSM_NET_CONNECTED, + FSM_MQTT_CONNECTED, + FSM_NET_CHECK_MQTT, + FSM_NET_CHECK_NETWORK, + FSM_NET_ESTABLISH_NETWORK, + FSM_NET_ESTABLISH_MQTT, +} fsm_net_t; + +#endif // WS_HELPER_STATUS_H \ No newline at end of file diff --git a/src/helpers/ws_helper_topics.h b/src/helpers/ws_helper_topics.h new file mode 100644 index 000000000..f45c3b045 --- /dev/null +++ b/src/helpers/ws_helper_topics.h @@ -0,0 +1,18 @@ +#ifndef WS_HELPER_TOPICS_H +#define WS_HELPER_TOPICS_H + +// Reserved Adafruit IO MQTT topics +#define TOPIC_IO_THROTTLE "/throttle" ///< Adafruit IO Throttle MQTT Topic +#define TOPIC_IO_ERRORS "/errors" ///< Adafruit IO Error MQTT Topic + +// Reserved Wippersnapper topics +#define TOPIC_WS "/wprsnpr/" ///< WipperSnapper topic +#define TOPIC_INFO "/info/" ///< Registration sub-topic +#define TOPIC_SIGNALS "/signals/" ///< Signals sub-topic +#define TOPIC_I2C "/i2c" ///< I2C sub-topic +#define MQTT_TOPIC_PIXELS_DEVICE \ + "/signals/device/pixel" ///< Pixels device->broker topic +#define MQTT_TOPIC_PIXELS_BROKER \ + "/signals/broker/pixel" ///< Pixels broker->device topic + +#endif // WS_HELPER_TOPICS_H \ No newline at end of file diff --git a/src/network_interfaces/Wippersnapper_ESP32.h b/src/network_interfaces/Wippersnapper_ESP32.h index 99c4d47be..054790696 100644 --- a/src/network_interfaces/Wippersnapper_ESP32.h +++ b/src/network_interfaces/Wippersnapper_ESP32.h @@ -47,6 +47,27 @@ class Wippersnapper_ESP32 : public Wippersnapper { _pass = 0; } + /**************************************************************************/ + /*! + @brief Overload for ESP32 devices without filesystem-backed provisioning. + */ + /**************************************************************************/ + Wippersnapper_ESP32(const char *aioUsername, const char *aioKey, + const char *netSSID, const char *netPass, + const char *brokerURL, uint16_t brokerPort) + : Wippersnapper() { + _ssid = netSSID; + _pass = netPass; + + // Move credentials to the config struct + strncpy(WS._config.network.ssid, _ssid, sizeof(WS._config.network.ssid)); + strncpy(WS._config.network.pass, _pass, sizeof(WS._config.network.pass)); + strncpy(WS._config.aio_key, aioKey, sizeof(WS._config.aio_key)); + strncpy(WS._config.aio_user, aioUsername, sizeof(WS._config.aio_user)); + strncpy(WS._config.aio_url, brokerURL, sizeof(WS._config.aio_url)); + WS._config.io_port = brokerPort; + } + /**************************************************************************/ /*! @brief Destructor for the Adafruit IO AirLift class. diff --git a/src/network_interfaces/Wippersnapper_ESP32_V2.h b/src/network_interfaces/Wippersnapper_ESP32_V2.h new file mode 100644 index 000000000..17e53868e --- /dev/null +++ b/src/network_interfaces/Wippersnapper_ESP32_V2.h @@ -0,0 +1,368 @@ +/*! + * @file Wippersnapper_ESP32_V2.h + * + * This is a driver for using the ESP32's network interface + * with Adafruit IO Wippersnapper. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2020-2024 for Adafruit Industries. + * + * MIT license, all text here must be included in any redistribution. + * + */ + +#ifndef Wippersnapper_ESP32_V2_H +#define Wippersnapper_ESP32_V2_H + +#ifdef ARDUINO_ARCH_ESP32 +#include "Wippersnapper_V2.h" + +#include "Adafruit_MQTT.h" +#include "Adafruit_MQTT_Client.h" +#include "Arduino.h" +#include "WiFi.h" +#include "WiFiMulti.h" +#include +#include +extern Wippersnapper_V2 WsV2; + +/****************************************************************************/ +/*! + @brief Class for using the ESP32 network interface. +*/ +/****************************************************************************/ +class Wippersnapper_ESP32V2 : public Wippersnapper_V2 { + +public: + /**************************************************************************/ + /*! + @brief Initializes the Adafruit IO class for ESP32 devices. + */ + /**************************************************************************/ + Wippersnapper_ESP32V2() : Wippersnapper_V2() { + _ssid = 0; + _pass = 0; + } + + /**************************************************************************/ + /*! + @brief Overload for ESP32 devices without filesystem-backed provisioning. + */ + /**************************************************************************/ + Wippersnapper_ESP32V2(const char *aioUsername, const char *aioKey, + const char *netSSID, const char *netPass, + const char *brokerURL, uint16_t brokerPort) + : Wippersnapper_V2() { + _ssid = netSSID; + _pass = netPass; + + // Move credentials to the config struct + strncpy(WsV2._configV2.network.ssid, _ssid, sizeof(WsV2._configV2.network.ssid)); + strncpy(WsV2._configV2.network.pass, _pass, sizeof(WsV2._configV2.network.pass)); + strncpy(WsV2._configV2.aio_key, aioKey, sizeof(WsV2._configV2.aio_key)); + strncpy(WsV2._configV2.aio_user, aioUsername, sizeof(WsV2._configV2.aio_user)); + strncpy(WsV2._configV2.aio_url, brokerURL, sizeof(WsV2._configV2.aio_url)); + WsV2._configV2.io_port = brokerPort; + } + + /**************************************************************************/ + /*! + @brief Destructor for the Adafruit IO AirLift class. + */ + /**************************************************************************/ + ~Wippersnapper_ESP32V2() { + if (_mqtt_client_secure) + delete _mqtt_client_secure; + if (_mqtt_client_insecure) + delete _mqtt_client_insecure; + } + + /********************************************************/ + /*! + @brief Sets the WiFi client's ssid and password. + @param ssid + WiFi network's SSID. + @param ssidPassword + WiFi network's password. + */ + /********************************************************/ + void set_ssid_passV2(const char *ssid, const char *ssidPassword) { + _ssid = ssid; + + // set the AP password + // check if ssidPassword was "" in secrets.json + if ((ssidPassword != NULL) && (strlen(ssidPassword) == 0)) { + _pass = NULL; // Set as NULL for open networks + } else { + _pass = ssidPassword; + } + } + + /**********************************************************/ + /*! + @brief Sets the WiFi client's ssid and password. + */ + /**********************************************************/ + void set_ssid_passV2() { + _ssid = WsV2._configV2.network.ssid; + _pass = WsV2._configV2.network.pass; + } + + /***********************************************************/ + /*! + @brief Performs a scan of local WiFi networks. + @returns True if `_network_ssid` is found, False otherwise. + */ + /***********************************************************/ + bool check_valid_ssidV2() { + // Set WiFi to station mode and disconnect from an AP if it was previously + // connected + WiFi.mode(WIFI_STA); + WiFi.disconnect(); + delay(100); + + // Perform a network scan + int n = WiFi.scanNetworks(); + if (n == 0) { + WS_DEBUG_PRINTLN("ERROR: No WiFi networks found!"); + return false; + } + + // Was the network within secrets.json found? + for (int i = 0; i < n; ++i) { + if (strcmp(_ssid, WiFi.SSID(i).c_str()) == 0) { + WS_DEBUG_PRINT("SSID ("); + WS_DEBUG_PRINT(_ssid); + WS_DEBUG_PRINT(") found! RSSI: "); + WS_DEBUG_PRINTLN(WiFi.RSSI(i)); + return true; + } + if (WsV2._isWiFiMultiV2) { + // multi network mode + for (int j = 0; j < WS_MAX_ALT_WIFI_NETWORKS; j++) { + if (strcmp(WsV2._multiNetworksV2[j].ssid, WiFi.SSID(i).c_str()) == 0) { + WS_DEBUG_PRINT("SSID ("); + WS_DEBUG_PRINT(WsV2._multiNetworksV2[j].ssid); + WS_DEBUG_PRINT(") found! RSSI: "); + WS_DEBUG_PRINTLN(WiFi.RSSI(i)); + return true; + } + } + } + } + + // User-set network not found, print scan results to serial console + WS_DEBUG_PRINTLN("ERROR: Your requested WiFi network was not found!"); + WS_DEBUG_PRINTLN("WipperSnapper found these WiFi networks: "); + for (int i = 0; i < n; ++i) { + WS_DEBUG_PRINT(WiFi.SSID(i)); + WS_DEBUG_PRINT(" "); + WS_DEBUG_PRINT(WiFi.RSSI(i)); + WS_DEBUG_PRINTLN("dB"); + } + + return false; + } + + /********************************************************/ + /*! + @brief Sets the ESP32's unique client identifier + @note On ESP32, the UID is the MAC address. + */ + /********************************************************/ + void getMacAddrV2() { + uint8_t mac[6] = {0}; + Network.macAddress(mac); + memcpy(WsV2._macAddrV2, mac, sizeof(mac)); + } + + /********************************************************/ + /*! + @brief Gets the current network RSSI value + @return int32_t RSSI value + */ + /********************************************************/ + int32_t getRSSIV2() { return WiFi.RSSI(); } + + /********************************************************/ + /*! + @brief Initializes the MQTT client + @param clientID + MQTT client identifier + */ + /********************************************************/ + void setupMQTTClientV2(const char *clientID) { + if (strcmp(WsV2._configV2.aio_url, "io.adafruit.com") == 0 || + strcmp(WsV2._configV2.aio_url, "io.adafruit.us") == 0) { + _mqtt_client_secure = new NetworkClientSecure(); + _mqtt_client_secure->setCACert( + strcmp(WsV2._configV2.aio_url, "io.adafruit.com") == 0 + ? _aio_root_ca_prod + : _aio_root_ca_staging); + WsV2._mqttV2 = new Adafruit_MQTT_Client( + _mqtt_client_secure, WsV2._configV2.aio_url, WsV2._configV2.io_port, clientID, + WsV2._configV2.aio_user, WsV2._configV2.aio_key); + } else { + // Insecure connections require a NetworkClient object rather than a + // NetworkClientSecure object + _mqtt_client_insecure = new NetworkClient(); + WsV2._mqttV2 = new Adafruit_MQTT_Client( + _mqtt_client_insecure, WsV2._configV2.aio_url, WsV2._configV2.io_port, + clientID, WsV2._configV2.aio_user, WsV2._configV2.aio_key); + } + } + + /********************************************************/ + /*! + @brief Returns the network status of an ESP32 module. + @return ws_status_t + */ + /********************************************************/ + ws_status_t networkStatusV2() { + switch (WiFi.status()) { + case WL_CONNECTED: + return WS_NET_CONNECTED; + case WL_CONNECT_FAILED: + return WS_NET_CONNECT_FAILED; + case WL_IDLE_STATUS: + return WS_IDLE; + default: + return WS_NET_DISCONNECTED; + } + } + + /*******************************************************************/ + /*! + @brief Returns the type of network connection used by Wippersnapper + @return ESP32 + */ + /*******************************************************************/ + const char *connectionType() { return "ESP32"; } + +protected: + const char *_ssid; ///< WiFi SSID + const char *_pass; ///< WiFi password + NetworkClientSecure + *_mqtt_client_secure; ///< Pointer to a secure network client object + NetworkClient + *_mqtt_client_insecure; ///< Pointer to an insecure network client object + WiFiMulti _wifiMulti; ///< WiFiMulti object for multi-network mode + + const char *_aio_root_ca_staging = + "-----BEGIN CERTIFICATE-----\n" + "MIIEVzCCAj+gAwIBAgIRALBXPpFzlydw27SHyzpFKzgwDQYJKoZIhvcNAQELBQAw\n" + "TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n" + "cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjQwMzEzMDAwMDAw\n" + "WhcNMjcwMzEyMjM1OTU5WjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg\n" + "RW5jcnlwdDELMAkGA1UEAxMCRTYwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATZ8Z5G\n" + "h/ghcWCoJuuj+rnq2h25EqfUJtlRFLFhfHWWvyILOR/VvtEKRqotPEoJhC6+QJVV\n" + "6RlAN2Z17TJOdwRJ+HB7wxjnzvdxEP6sdNgA1O1tHHMWMxCcOrLqbGL0vbijgfgw\n" + "gfUwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcD\n" + "ATASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBSTJ0aYA6lRaI6Y1sRCSNsj\n" + "v1iU0jAfBgNVHSMEGDAWgBR5tFnme7bl5AFzgAiIyBpY9umbbjAyBggrBgEFBQcB\n" + "AQQmMCQwIgYIKwYBBQUHMAKGFmh0dHA6Ly94MS5pLmxlbmNyLm9yZy8wEwYDVR0g\n" + "BAwwCjAIBgZngQwBAgEwJwYDVR0fBCAwHjAcoBqgGIYWaHR0cDovL3gxLmMubGVu\n" + "Y3Iub3JnLzANBgkqhkiG9w0BAQsFAAOCAgEAfYt7SiA1sgWGCIpunk46r4AExIRc\n" + "MxkKgUhNlrrv1B21hOaXN/5miE+LOTbrcmU/M9yvC6MVY730GNFoL8IhJ8j8vrOL\n" + "pMY22OP6baS1k9YMrtDTlwJHoGby04ThTUeBDksS9RiuHvicZqBedQdIF65pZuhp\n" + "eDcGBcLiYasQr/EO5gxxtLyTmgsHSOVSBcFOn9lgv7LECPq9i7mfH3mpxgrRKSxH\n" + "pOoZ0KXMcB+hHuvlklHntvcI0mMMQ0mhYj6qtMFStkF1RpCG3IPdIwpVCQqu8GV7\n" + "s8ubknRzs+3C/Bm19RFOoiPpDkwvyNfvmQ14XkyqqKK5oZ8zhD32kFRQkxa8uZSu\n" + "h4aTImFxknu39waBxIRXE4jKxlAmQc4QjFZoq1KmQqQg0J/1JF8RlFvJas1VcjLv\n" + "YlvUB2t6npO6oQjB3l+PNf0DpQH7iUx3Wz5AjQCi6L25FjyE06q6BZ/QlmtYdl/8\n" + "ZYao4SRqPEs/6cAiF+Qf5zg2UkaWtDphl1LKMuTNLotvsX99HP69V2faNyegodQ0\n" + "LyTApr/vT01YPE46vNsDLgK+4cL6TrzC/a4WcmF5SRJ938zrv/duJHLXQIku5v0+\n" + "EwOy59Hdm0PT/Er/84dDV0CSjdR/2XuZM3kpysSKLgD1cKiDA+IRguODCxfO9cyY\n" + "Ig46v9mFmBvyH04=\n" + "-----END CERTIFICATE-----\n"; ///< Root certificate for io.adafruit.us + + const char *_aio_root_ca_prod = + "-----BEGIN CERTIFICATE-----\n" + "MIIEjTCCA3WgAwIBAgIQDQd4KhM/xvmlcpbhMf/ReTANBgkqhkiG9w0BAQsFADBh\n" + "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n" + "d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\n" + "MjAeFw0xNzExMDIxMjIzMzdaFw0yNzExMDIxMjIzMzdaMGAxCzAJBgNVBAYTAlVT\n" + "MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\n" + "b20xHzAdBgNVBAMTFkdlb1RydXN0IFRMUyBSU0EgQ0EgRzEwggEiMA0GCSqGSIb3\n" + "DQEBAQUAA4IBDwAwggEKAoIBAQC+F+jsvikKy/65LWEx/TMkCDIuWegh1Ngwvm4Q\n" + "yISgP7oU5d79eoySG3vOhC3w/3jEMuipoH1fBtp7m0tTpsYbAhch4XA7rfuD6whU\n" + "gajeErLVxoiWMPkC/DnUvbgi74BJmdBiuGHQSd7LwsuXpTEGG9fYXcbTVN5SATYq\n" + "DfbexbYxTMwVJWoVb6lrBEgM3gBBqiiAiy800xu1Nq07JdCIQkBsNpFtZbIZhsDS\n" + "fzlGWP4wEmBQ3O67c+ZXkFr2DcrXBEtHam80Gp2SNhou2U5U7UesDL/xgLK6/0d7\n" + "6TnEVMSUVJkZ8VeZr+IUIlvoLrtjLbqugb0T3OYXW+CQU0kBAgMBAAGjggFAMIIB\n" + "PDAdBgNVHQ4EFgQUlE/UXYvkpOKmgP792PkA76O+AlcwHwYDVR0jBBgwFoAUTiJU\n" + "IBiV5uNu5g/6+rkS7QYXjzkwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsG\n" + "AQUFBwMBBggrBgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMDQGCCsGAQUFBwEB\n" + "BCgwJjAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEIGA1Ud\n" + "HwQ7MDkwN6A1oDOGMWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEds\n" + "b2JhbFJvb3RHMi5jcmwwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEW\n" + "HGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwDQYJKoZIhvcNAQELBQADggEB\n" + "AIIcBDqC6cWpyGUSXAjjAcYwsK4iiGF7KweG97i1RJz1kwZhRoo6orU1JtBYnjzB\n" + "c4+/sXmnHJk3mlPyL1xuIAt9sMeC7+vreRIF5wFBC0MCN5sbHwhNN1JzKbifNeP5\n" + "ozpZdQFmkCo+neBiKR6HqIA+LMTMCMMuv2khGGuPHmtDze4GmEGZtYLyF8EQpa5Y\n" + "jPuV6k2Cr/N3XxFpT3hRpt/3usU/Zb9wfKPtWpoznZ4/44c1p9rzFcZYrWkj3A+7\n" + "TNBJE0GmP2fhXhP1D/XVfIW/h0yCJGEiV9Glm/uGOa3DXHlmbAcxSyCRraG+ZBkA\n" + "7h4SeM6Y8l/7MBRpPCz6l8Y=\n" + "-----END CERTIFICATE-----\n"; ///< Root certificate for io.adafruit.com + + /**************************************************************************/ + /*! + @brief Establishes a connection with the wireless network. + */ + /**************************************************************************/ + void _connectV2() { + + if (WiFi.status() == WL_CONNECTED) + return; + + if (strlen(_ssid) == 0) { + _statusV2 = WS_SSID_INVALID; + } else { + WiFi.setAutoReconnect(false); + _disconnect(); + delay(100); + if (WsV2._isWiFiMultiV2) { + // multi network mode + _wifiMulti.APlistClean(); + _wifiMulti.setAllowOpenAP(false); + // add default network + _wifiMulti.addAP(_ssid, _pass); + // add array of alternative networks + for (int i = 0; i < WS_MAX_ALT_WIFI_NETWORKS; i++) { + if (strlen(WsV2._multiNetworksV2[i].ssid) > 0) { + _wifiMulti.addAP(WsV2._multiNetworksV2[i].ssid, + WsV2._multiNetworksV2[i].pass); + } + } + if (_wifiMulti.run(20000) == WL_CONNECTED) { + _statusV2 = WS_NET_CONNECTED; + } else { + _statusV2 = WS_NET_DISCONNECTED; + } + } else { + // single network mode + WiFi.begin(_ssid, _pass); + _statusV2 = WS_NET_DISCONNECTED; + WsV2.feedWDTV2(); + delay(5000); + } + WsV2.feedWDTV2(); + } + } + + /**************************************************************************/ + /*! + @brief Disconnects from the wireless network. + */ + /**************************************************************************/ + void _disconnect() { + WiFi.disconnect(); + delay(500); + } +}; + +#endif // ARDUINO_ARCH_ESP32_H +#endif // Wippersnapper_ESP32_V2_H \ No newline at end of file diff --git a/src/protos/analogio.pb.c b/src/protos/analogio.pb.c new file mode 100644 index 000000000..1e29a849b --- /dev/null +++ b/src/protos/analogio.pb.c @@ -0,0 +1,18 @@ +/* Automatically generated nanopb constant definitions */ +/* Generated by nanopb-0.4.8 */ + +#include "analogio.pb.h" +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +PB_BIND(wippersnapper_analogio_AnalogIOAdd, wippersnapper_analogio_AnalogIOAdd, AUTO) + + +PB_BIND(wippersnapper_analogio_AnalogIORemove, wippersnapper_analogio_AnalogIORemove, AUTO) + + +PB_BIND(wippersnapper_analogio_AnalogIOEvent, wippersnapper_analogio_AnalogIOEvent, AUTO) + + + diff --git a/src/protos/analogio.pb.h b/src/protos/analogio.pb.h new file mode 100644 index 000000000..6fe6b246b --- /dev/null +++ b/src/protos/analogio.pb.h @@ -0,0 +1,98 @@ +/* Automatically generated nanopb header */ +/* Generated by nanopb-0.4.8 */ + +#ifndef PB_WIPPERSNAPPER_ANALOGIO_ANALOGIO_PB_H_INCLUDED +#define PB_WIPPERSNAPPER_ANALOGIO_ANALOGIO_PB_H_INCLUDED +#include +#include "sensor.pb.h" + +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +/* Struct definitions */ +/* * + AnalogIOAdd adds an analog pin to the device. */ +typedef struct _wippersnapper_analogio_AnalogIOAdd { + char pin_name[64]; /* * Name of the pin. */ + float period; /* * Time between reads, in seconds. */ + wippersnapper_sensor_SensorType read_mode; /* * Desired read mode for the pin. */ +} wippersnapper_analogio_AnalogIOAdd; + +/* * + AnalogIORemove removes an analog pin from the device. */ +typedef struct _wippersnapper_analogio_AnalogIORemove { + char pin_name[64]; /* * Name of the pin. */ +} wippersnapper_analogio_AnalogIORemove; + +/* * + AnalogIOEvent is contains a value, sent when an analog pin is read. */ +typedef struct _wippersnapper_analogio_AnalogIOEvent { + char pin_name[64]; /* * Name of the pin. */ + bool has_sensor_event; + wippersnapper_sensor_SensorEvent sensor_event; /* * Reading(s) from an analog pin. */ +} wippersnapper_analogio_AnalogIOEvent; + + +#ifdef __cplusplus +extern "C" { +#endif + +/* Initializer values for message structs */ +#define wippersnapper_analogio_AnalogIOAdd_init_default {"", 0, _wippersnapper_sensor_SensorType_MIN} +#define wippersnapper_analogio_AnalogIORemove_init_default {""} +#define wippersnapper_analogio_AnalogIOEvent_init_default {"", false, wippersnapper_sensor_SensorEvent_init_default} +#define wippersnapper_analogio_AnalogIOAdd_init_zero {"", 0, _wippersnapper_sensor_SensorType_MIN} +#define wippersnapper_analogio_AnalogIORemove_init_zero {""} +#define wippersnapper_analogio_AnalogIOEvent_init_zero {"", false, wippersnapper_sensor_SensorEvent_init_zero} + +/* Field tags (for use in manual encoding/decoding) */ +#define wippersnapper_analogio_AnalogIOAdd_pin_name_tag 1 +#define wippersnapper_analogio_AnalogIOAdd_period_tag 2 +#define wippersnapper_analogio_AnalogIOAdd_read_mode_tag 3 +#define wippersnapper_analogio_AnalogIORemove_pin_name_tag 1 +#define wippersnapper_analogio_AnalogIOEvent_pin_name_tag 1 +#define wippersnapper_analogio_AnalogIOEvent_sensor_event_tag 2 + +/* Struct field encoding specification for nanopb */ +#define wippersnapper_analogio_AnalogIOAdd_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, pin_name, 1) \ +X(a, STATIC, SINGULAR, FLOAT, period, 2) \ +X(a, STATIC, SINGULAR, UENUM, read_mode, 3) +#define wippersnapper_analogio_AnalogIOAdd_CALLBACK NULL +#define wippersnapper_analogio_AnalogIOAdd_DEFAULT NULL + +#define wippersnapper_analogio_AnalogIORemove_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, pin_name, 1) +#define wippersnapper_analogio_AnalogIORemove_CALLBACK NULL +#define wippersnapper_analogio_AnalogIORemove_DEFAULT NULL + +#define wippersnapper_analogio_AnalogIOEvent_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, pin_name, 1) \ +X(a, STATIC, OPTIONAL, MESSAGE, sensor_event, 2) +#define wippersnapper_analogio_AnalogIOEvent_CALLBACK NULL +#define wippersnapper_analogio_AnalogIOEvent_DEFAULT NULL +#define wippersnapper_analogio_AnalogIOEvent_sensor_event_MSGTYPE wippersnapper_sensor_SensorEvent + +extern const pb_msgdesc_t wippersnapper_analogio_AnalogIOAdd_msg; +extern const pb_msgdesc_t wippersnapper_analogio_AnalogIORemove_msg; +extern const pb_msgdesc_t wippersnapper_analogio_AnalogIOEvent_msg; + +/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ +#define wippersnapper_analogio_AnalogIOAdd_fields &wippersnapper_analogio_AnalogIOAdd_msg +#define wippersnapper_analogio_AnalogIORemove_fields &wippersnapper_analogio_AnalogIORemove_msg +#define wippersnapper_analogio_AnalogIOEvent_fields &wippersnapper_analogio_AnalogIOEvent_msg + +/* Maximum encoded size of messages (where known) */ +#define WIPPERSNAPPER_ANALOGIO_ANALOGIO_PB_H_MAX_SIZE wippersnapper_analogio_AnalogIOAdd_size +#define wippersnapper_analogio_AnalogIOAdd_size 72 +#define wippersnapper_analogio_AnalogIORemove_size 65 +#if defined(wippersnapper_sensor_SensorEvent_size) +#define wippersnapper_analogio_AnalogIOEvent_size (71 + wippersnapper_sensor_SensorEvent_size) +#endif + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/src/protos/checkin.pb.c b/src/protos/checkin.pb.c new file mode 100644 index 000000000..05f7f72d5 --- /dev/null +++ b/src/protos/checkin.pb.c @@ -0,0 +1,16 @@ +/* Automatically generated nanopb constant definitions */ +/* Generated by nanopb-0.4.8 */ + +#include "checkin.pb.h" +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +PB_BIND(wippersnapper_checkin_CheckinRequest, wippersnapper_checkin_CheckinRequest, AUTO) + + +PB_BIND(wippersnapper_checkin_CheckinResponse, wippersnapper_checkin_CheckinResponse, AUTO) + + + + diff --git a/src/protos/checkin.pb.h b/src/protos/checkin.pb.h new file mode 100644 index 000000000..63de0007b --- /dev/null +++ b/src/protos/checkin.pb.h @@ -0,0 +1,97 @@ +/* Automatically generated nanopb header */ +/* Generated by nanopb-0.4.8 */ + +#ifndef PB_WIPPERSNAPPER_CHECKIN_CHECKIN_PB_H_INCLUDED +#define PB_WIPPERSNAPPER_CHECKIN_CHECKIN_PB_H_INCLUDED +#include + +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +/* Enum definitions */ +/* * + Response. Specifies if the hardware definiton is within the database. */ +typedef enum _wippersnapper_checkin_CheckinResponse_Response { + wippersnapper_checkin_CheckinResponse_Response_RESPONSE_UNSPECIFIED = 0, /* * Invalid response from server */ + wippersnapper_checkin_CheckinResponse_Response_RESPONSE_OK = 1, /* * Board found within definition index */ + wippersnapper_checkin_CheckinResponse_Response_RESPONSE_BOARD_NOT_FOUND = 2 /* * Board not found in definition index */ +} wippersnapper_checkin_CheckinResponse_Response; + +/* Struct definitions */ +/* * + CheckinRequest notifies the MQTT broker that a new/existing device is requesting to connect. */ +typedef struct _wippersnapper_checkin_CheckinRequest { + char hardware_uid[64]; /* * Identifies the client's physical hardware (board name + last 3 of NIC's MAC address). */ + char firmware_version[25]; /* * Identifies the client's firmware version. */ +} wippersnapper_checkin_CheckinRequest; + +/* * + CheckinResponse sends a broker response to the client's CheckinRequest. */ +typedef struct _wippersnapper_checkin_CheckinResponse { + wippersnapper_checkin_CheckinResponse_Response response; /* * Specifies if the hardware definition exists on the server. */ + int32_t total_gpio_pins; /* * Specifies the number of GPIO pins on the device. */ + int32_t total_analog_pins; /* * Specifies the number of analog pins on the device. */ + float reference_voltage; /* * Specifies the hardware's default reference voltage. */ +} wippersnapper_checkin_CheckinResponse; + + +#ifdef __cplusplus +extern "C" { +#endif + +/* Helper constants for enums */ +#define _wippersnapper_checkin_CheckinResponse_Response_MIN wippersnapper_checkin_CheckinResponse_Response_RESPONSE_UNSPECIFIED +#define _wippersnapper_checkin_CheckinResponse_Response_MAX wippersnapper_checkin_CheckinResponse_Response_RESPONSE_BOARD_NOT_FOUND +#define _wippersnapper_checkin_CheckinResponse_Response_ARRAYSIZE ((wippersnapper_checkin_CheckinResponse_Response)(wippersnapper_checkin_CheckinResponse_Response_RESPONSE_BOARD_NOT_FOUND+1)) + + +#define wippersnapper_checkin_CheckinResponse_response_ENUMTYPE wippersnapper_checkin_CheckinResponse_Response + + +/* Initializer values for message structs */ +#define wippersnapper_checkin_CheckinRequest_init_default {"", ""} +#define wippersnapper_checkin_CheckinResponse_init_default {_wippersnapper_checkin_CheckinResponse_Response_MIN, 0, 0, 0} +#define wippersnapper_checkin_CheckinRequest_init_zero {"", ""} +#define wippersnapper_checkin_CheckinResponse_init_zero {_wippersnapper_checkin_CheckinResponse_Response_MIN, 0, 0, 0} + +/* Field tags (for use in manual encoding/decoding) */ +#define wippersnapper_checkin_CheckinRequest_hardware_uid_tag 1 +#define wippersnapper_checkin_CheckinRequest_firmware_version_tag 2 +#define wippersnapper_checkin_CheckinResponse_response_tag 1 +#define wippersnapper_checkin_CheckinResponse_total_gpio_pins_tag 2 +#define wippersnapper_checkin_CheckinResponse_total_analog_pins_tag 3 +#define wippersnapper_checkin_CheckinResponse_reference_voltage_tag 4 + +/* Struct field encoding specification for nanopb */ +#define wippersnapper_checkin_CheckinRequest_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, hardware_uid, 1) \ +X(a, STATIC, SINGULAR, STRING, firmware_version, 2) +#define wippersnapper_checkin_CheckinRequest_CALLBACK NULL +#define wippersnapper_checkin_CheckinRequest_DEFAULT NULL + +#define wippersnapper_checkin_CheckinResponse_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UENUM, response, 1) \ +X(a, STATIC, SINGULAR, INT32, total_gpio_pins, 2) \ +X(a, STATIC, SINGULAR, INT32, total_analog_pins, 3) \ +X(a, STATIC, SINGULAR, FLOAT, reference_voltage, 4) +#define wippersnapper_checkin_CheckinResponse_CALLBACK NULL +#define wippersnapper_checkin_CheckinResponse_DEFAULT NULL + +extern const pb_msgdesc_t wippersnapper_checkin_CheckinRequest_msg; +extern const pb_msgdesc_t wippersnapper_checkin_CheckinResponse_msg; + +/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ +#define wippersnapper_checkin_CheckinRequest_fields &wippersnapper_checkin_CheckinRequest_msg +#define wippersnapper_checkin_CheckinResponse_fields &wippersnapper_checkin_CheckinResponse_msg + +/* Maximum encoded size of messages (where known) */ +#define WIPPERSNAPPER_CHECKIN_CHECKIN_PB_H_MAX_SIZE wippersnapper_checkin_CheckinRequest_size +#define wippersnapper_checkin_CheckinRequest_size 91 +#define wippersnapper_checkin_CheckinResponse_size 29 + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/src/protos/digitalio.pb.c b/src/protos/digitalio.pb.c new file mode 100644 index 000000000..5467990fd --- /dev/null +++ b/src/protos/digitalio.pb.c @@ -0,0 +1,23 @@ +/* Automatically generated nanopb constant definitions */ +/* Generated by nanopb-0.4.8 */ + +#include "digitalio.pb.h" +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +PB_BIND(wippersnapper_digitalio_DigitalIOAdd, wippersnapper_digitalio_DigitalIOAdd, AUTO) + + +PB_BIND(wippersnapper_digitalio_DigitalIORemove, wippersnapper_digitalio_DigitalIORemove, AUTO) + + +PB_BIND(wippersnapper_digitalio_DigitalIOEvent, wippersnapper_digitalio_DigitalIOEvent, AUTO) + + +PB_BIND(wippersnapper_digitalio_DigitalIOWrite, wippersnapper_digitalio_DigitalIOWrite, AUTO) + + + + + diff --git a/src/protos/digitalio.pb.h b/src/protos/digitalio.pb.h new file mode 100644 index 000000000..19b98ef49 --- /dev/null +++ b/src/protos/digitalio.pb.h @@ -0,0 +1,160 @@ +/* Automatically generated nanopb header */ +/* Generated by nanopb-0.4.8 */ + +#ifndef PB_WIPPERSNAPPER_DIGITALIO_DIGITALIO_PB_H_INCLUDED +#define PB_WIPPERSNAPPER_DIGITALIO_DIGITALIO_PB_H_INCLUDED +#include +#include "sensor.pb.h" + +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +/* Enum definitions */ +/* * + DigitalIOSampleMode specifies the pin's sample mode. */ +typedef enum _wippersnapper_digitalio_DigitalIOSampleMode { + wippersnapper_digitalio_DigitalIOSampleMode_DIGITAL_IO_SAMPLE_MODE_UNSPECIFIED = 0, /* * Invalid Sample Mode from Broker. */ + wippersnapper_digitalio_DigitalIOSampleMode_DIGITAL_IO_SAMPLE_MODE_TIMER = 1, /* * Periodically sample the pin's value. */ + wippersnapper_digitalio_DigitalIOSampleMode_DIGITAL_IO_SAMPLE_MODE_EVENT = 2 /* * Sample the pin's value when an event occurs. */ +} wippersnapper_digitalio_DigitalIOSampleMode; + +/* * + DigitalIODirection specifies the pin's direction, INPUT/INPUT_PULL_UP/OUTPUT. */ +typedef enum _wippersnapper_digitalio_DigitalIODirection { + wippersnapper_digitalio_DigitalIODirection_DIGITAL_IO_DIRECTION_UNSPECIFIED = 0, /* * Invalid Direction from Broker. */ + wippersnapper_digitalio_DigitalIODirection_DIGITAL_IO_DIRECTION_INPUT = 1, /* * Set the pin to behave as an input. */ + wippersnapper_digitalio_DigitalIODirection_DIGITAL_IO_DIRECTION_INPUT_PULL_UP = 2, /* * Set the pin to behave as an input. */ + wippersnapper_digitalio_DigitalIODirection_DIGITAL_IO_DIRECTION_OUTPUT = 3 /* * Set the pin to behave as an output. */ +} wippersnapper_digitalio_DigitalIODirection; + +/* Struct definitions */ +/* * + DigitalIOAdd adds a digital pin to the device. */ +typedef struct _wippersnapper_digitalio_DigitalIOAdd { + char pin_name[64]; /* * The pin's name. */ + wippersnapper_digitalio_DigitalIODirection gpio_direction; /* * The pin's direction. */ + wippersnapper_digitalio_DigitalIOSampleMode sample_mode; /* * Specifies the pin's sample mode. */ + float period; /* * Time between measurements in seconds, if MODE_TIMER. */ + bool value; /* * Re-sync only - send the pin's value. */ +} wippersnapper_digitalio_DigitalIOAdd; + +/* * + DigitalIORemove removes a digital pin from the device. */ +typedef struct _wippersnapper_digitalio_DigitalIORemove { + char pin_name[64]; /* * The pin's name. */ +} wippersnapper_digitalio_DigitalIORemove; + +/* * + DigitalIOEvent is sent from the device to the broker when a digital pin's value changes. */ +typedef struct _wippersnapper_digitalio_DigitalIOEvent { + char pin_name[64]; /* * The pin's name. */ + bool has_value; + wippersnapper_sensor_SensorEvent value; /* * The pin's value. */ +} wippersnapper_digitalio_DigitalIOEvent; + +/* * + DigitalIOWrite writes a boolean value to a digital pin. */ +typedef struct _wippersnapper_digitalio_DigitalIOWrite { + char pin_name[64]; /* * The pin's name. */ + bool has_value; + wippersnapper_sensor_SensorEvent value; /* * The pin's value. */ +} wippersnapper_digitalio_DigitalIOWrite; + + +#ifdef __cplusplus +extern "C" { +#endif + +/* Helper constants for enums */ +#define _wippersnapper_digitalio_DigitalIOSampleMode_MIN wippersnapper_digitalio_DigitalIOSampleMode_DIGITAL_IO_SAMPLE_MODE_UNSPECIFIED +#define _wippersnapper_digitalio_DigitalIOSampleMode_MAX wippersnapper_digitalio_DigitalIOSampleMode_DIGITAL_IO_SAMPLE_MODE_EVENT +#define _wippersnapper_digitalio_DigitalIOSampleMode_ARRAYSIZE ((wippersnapper_digitalio_DigitalIOSampleMode)(wippersnapper_digitalio_DigitalIOSampleMode_DIGITAL_IO_SAMPLE_MODE_EVENT+1)) + +#define _wippersnapper_digitalio_DigitalIODirection_MIN wippersnapper_digitalio_DigitalIODirection_DIGITAL_IO_DIRECTION_UNSPECIFIED +#define _wippersnapper_digitalio_DigitalIODirection_MAX wippersnapper_digitalio_DigitalIODirection_DIGITAL_IO_DIRECTION_OUTPUT +#define _wippersnapper_digitalio_DigitalIODirection_ARRAYSIZE ((wippersnapper_digitalio_DigitalIODirection)(wippersnapper_digitalio_DigitalIODirection_DIGITAL_IO_DIRECTION_OUTPUT+1)) + +#define wippersnapper_digitalio_DigitalIOAdd_gpio_direction_ENUMTYPE wippersnapper_digitalio_DigitalIODirection +#define wippersnapper_digitalio_DigitalIOAdd_sample_mode_ENUMTYPE wippersnapper_digitalio_DigitalIOSampleMode + + + + + +/* Initializer values for message structs */ +#define wippersnapper_digitalio_DigitalIOAdd_init_default {"", _wippersnapper_digitalio_DigitalIODirection_MIN, _wippersnapper_digitalio_DigitalIOSampleMode_MIN, 0, 0} +#define wippersnapper_digitalio_DigitalIORemove_init_default {""} +#define wippersnapper_digitalio_DigitalIOEvent_init_default {"", false, wippersnapper_sensor_SensorEvent_init_default} +#define wippersnapper_digitalio_DigitalIOWrite_init_default {"", false, wippersnapper_sensor_SensorEvent_init_default} +#define wippersnapper_digitalio_DigitalIOAdd_init_zero {"", _wippersnapper_digitalio_DigitalIODirection_MIN, _wippersnapper_digitalio_DigitalIOSampleMode_MIN, 0, 0} +#define wippersnapper_digitalio_DigitalIORemove_init_zero {""} +#define wippersnapper_digitalio_DigitalIOEvent_init_zero {"", false, wippersnapper_sensor_SensorEvent_init_zero} +#define wippersnapper_digitalio_DigitalIOWrite_init_zero {"", false, wippersnapper_sensor_SensorEvent_init_zero} + +/* Field tags (for use in manual encoding/decoding) */ +#define wippersnapper_digitalio_DigitalIOAdd_pin_name_tag 1 +#define wippersnapper_digitalio_DigitalIOAdd_gpio_direction_tag 2 +#define wippersnapper_digitalio_DigitalIOAdd_sample_mode_tag 3 +#define wippersnapper_digitalio_DigitalIOAdd_period_tag 4 +#define wippersnapper_digitalio_DigitalIOAdd_value_tag 5 +#define wippersnapper_digitalio_DigitalIORemove_pin_name_tag 1 +#define wippersnapper_digitalio_DigitalIOEvent_pin_name_tag 1 +#define wippersnapper_digitalio_DigitalIOEvent_value_tag 2 +#define wippersnapper_digitalio_DigitalIOWrite_pin_name_tag 1 +#define wippersnapper_digitalio_DigitalIOWrite_value_tag 2 + +/* Struct field encoding specification for nanopb */ +#define wippersnapper_digitalio_DigitalIOAdd_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, pin_name, 1) \ +X(a, STATIC, SINGULAR, UENUM, gpio_direction, 2) \ +X(a, STATIC, SINGULAR, UENUM, sample_mode, 3) \ +X(a, STATIC, SINGULAR, FLOAT, period, 4) \ +X(a, STATIC, SINGULAR, BOOL, value, 5) +#define wippersnapper_digitalio_DigitalIOAdd_CALLBACK NULL +#define wippersnapper_digitalio_DigitalIOAdd_DEFAULT NULL + +#define wippersnapper_digitalio_DigitalIORemove_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, pin_name, 1) +#define wippersnapper_digitalio_DigitalIORemove_CALLBACK NULL +#define wippersnapper_digitalio_DigitalIORemove_DEFAULT NULL + +#define wippersnapper_digitalio_DigitalIOEvent_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, pin_name, 1) \ +X(a, STATIC, OPTIONAL, MESSAGE, value, 2) +#define wippersnapper_digitalio_DigitalIOEvent_CALLBACK NULL +#define wippersnapper_digitalio_DigitalIOEvent_DEFAULT NULL +#define wippersnapper_digitalio_DigitalIOEvent_value_MSGTYPE wippersnapper_sensor_SensorEvent + +#define wippersnapper_digitalio_DigitalIOWrite_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, pin_name, 1) \ +X(a, STATIC, OPTIONAL, MESSAGE, value, 2) +#define wippersnapper_digitalio_DigitalIOWrite_CALLBACK NULL +#define wippersnapper_digitalio_DigitalIOWrite_DEFAULT NULL +#define wippersnapper_digitalio_DigitalIOWrite_value_MSGTYPE wippersnapper_sensor_SensorEvent + +extern const pb_msgdesc_t wippersnapper_digitalio_DigitalIOAdd_msg; +extern const pb_msgdesc_t wippersnapper_digitalio_DigitalIORemove_msg; +extern const pb_msgdesc_t wippersnapper_digitalio_DigitalIOEvent_msg; +extern const pb_msgdesc_t wippersnapper_digitalio_DigitalIOWrite_msg; + +/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ +#define wippersnapper_digitalio_DigitalIOAdd_fields &wippersnapper_digitalio_DigitalIOAdd_msg +#define wippersnapper_digitalio_DigitalIORemove_fields &wippersnapper_digitalio_DigitalIORemove_msg +#define wippersnapper_digitalio_DigitalIOEvent_fields &wippersnapper_digitalio_DigitalIOEvent_msg +#define wippersnapper_digitalio_DigitalIOWrite_fields &wippersnapper_digitalio_DigitalIOWrite_msg + +/* Maximum encoded size of messages (where known) */ +#define WIPPERSNAPPER_DIGITALIO_DIGITALIO_PB_H_MAX_SIZE wippersnapper_digitalio_DigitalIOAdd_size +#define wippersnapper_digitalio_DigitalIOAdd_size 76 +#define wippersnapper_digitalio_DigitalIORemove_size 65 +#if defined(wippersnapper_sensor_SensorEvent_size) +#define wippersnapper_digitalio_DigitalIOEvent_size (71 + wippersnapper_sensor_SensorEvent_size) +#define wippersnapper_digitalio_DigitalIOWrite_size (71 + wippersnapper_sensor_SensorEvent_size) +#endif + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/src/protos/ds18x20.pb.c b/src/protos/ds18x20.pb.c new file mode 100644 index 000000000..e34ce503a --- /dev/null +++ b/src/protos/ds18x20.pb.c @@ -0,0 +1,21 @@ +/* Automatically generated nanopb constant definitions */ +/* Generated by nanopb-0.4.8 */ + +#include "ds18x20.pb.h" +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +PB_BIND(wippersnapper_ds18x20_Ds18x20Add, wippersnapper_ds18x20_Ds18x20Add, AUTO) + + +PB_BIND(wippersnapper_ds18x20_Ds18x20Added, wippersnapper_ds18x20_Ds18x20Added, AUTO) + + +PB_BIND(wippersnapper_ds18x20_Ds18x20Remove, wippersnapper_ds18x20_Ds18x20Remove, AUTO) + + +PB_BIND(wippersnapper_ds18x20_Ds18x20Event, wippersnapper_ds18x20_Ds18x20Event, AUTO) + + + diff --git a/src/protos/ds18x20.pb.h b/src/protos/ds18x20.pb.h new file mode 100644 index 000000000..52040ab6a --- /dev/null +++ b/src/protos/ds18x20.pb.h @@ -0,0 +1,126 @@ +/* Automatically generated nanopb header */ +/* Generated by nanopb-0.4.8 */ + +#ifndef PB_WIPPERSNAPPER_DS18X20_DS18X20_PB_H_INCLUDED +#define PB_WIPPERSNAPPER_DS18X20_DS18X20_PB_H_INCLUDED +#include +#include "sensor.pb.h" + +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +/* Struct definitions */ +/* * + Ds18x20Add represents a to initialize + a DS18X20 Maxim temperature sensor, from the broker. + NOTE: This API currently only supports ONE device per OneWire bus. */ +typedef struct _wippersnapper_ds18x20_Ds18x20Add { + char onewire_pin[5]; /* * The desired pin to use as a OneWire bus. */ + int32_t sensor_resolution; /* * The desired sensor resolution (9, 10, 11, or 12 bits). */ + float period; /* * The desired period to read the sensor, in seconds. */ + pb_size_t sensor_types_count; + wippersnapper_sensor_SensorType sensor_types[2]; /* * SI types used by the DS18x20 sensor. */ +} wippersnapper_ds18x20_Ds18x20Add; + +/* * + Ds18x20AddDs18x20AddedResponse represents a device's response + to a Ds18x20Add message. */ +typedef struct _wippersnapper_ds18x20_Ds18x20Added { + bool is_initialized; /* * True if the 1-wire bus has been initialized successfully, False otherwise. */ + char onewire_pin[5]; /* * The pin being used as a OneWire bus. */ +} wippersnapper_ds18x20_Ds18x20Added; + +/* * + Ds18x20Remove represents a to de-initialize a DS18X20 + Maxim temperature sensor, from the broker. */ +typedef struct _wippersnapper_ds18x20_Ds18x20Remove { + char onewire_pin[5]; /* * The desired onewire bus to de-initialize a DS18x sensor on and release. */ +} wippersnapper_ds18x20_Ds18x20Remove; + +/* * + Ds18x20Event event represents data from **one** DS18X20 sensor. */ +typedef struct _wippersnapper_ds18x20_Ds18x20Event { + char onewire_pin[5]; /* * The desired pin to use as a OneWire bus. */ + pb_size_t sensor_events_count; + wippersnapper_sensor_SensorEvent sensor_events[2]; /* * The DS18X20's SensorEvent. */ +} wippersnapper_ds18x20_Ds18x20Event; + + +#ifdef __cplusplus +extern "C" { +#endif + +/* Initializer values for message structs */ +#define wippersnapper_ds18x20_Ds18x20Add_init_default {"", 0, 0, 0, {_wippersnapper_sensor_SensorType_MIN, _wippersnapper_sensor_SensorType_MIN}} +#define wippersnapper_ds18x20_Ds18x20Added_init_default {0, ""} +#define wippersnapper_ds18x20_Ds18x20Remove_init_default {""} +#define wippersnapper_ds18x20_Ds18x20Event_init_default {"", 0, {wippersnapper_sensor_SensorEvent_init_default, wippersnapper_sensor_SensorEvent_init_default}} +#define wippersnapper_ds18x20_Ds18x20Add_init_zero {"", 0, 0, 0, {_wippersnapper_sensor_SensorType_MIN, _wippersnapper_sensor_SensorType_MIN}} +#define wippersnapper_ds18x20_Ds18x20Added_init_zero {0, ""} +#define wippersnapper_ds18x20_Ds18x20Remove_init_zero {""} +#define wippersnapper_ds18x20_Ds18x20Event_init_zero {"", 0, {wippersnapper_sensor_SensorEvent_init_zero, wippersnapper_sensor_SensorEvent_init_zero}} + +/* Field tags (for use in manual encoding/decoding) */ +#define wippersnapper_ds18x20_Ds18x20Add_onewire_pin_tag 1 +#define wippersnapper_ds18x20_Ds18x20Add_sensor_resolution_tag 2 +#define wippersnapper_ds18x20_Ds18x20Add_period_tag 3 +#define wippersnapper_ds18x20_Ds18x20Add_sensor_types_tag 4 +#define wippersnapper_ds18x20_Ds18x20Added_is_initialized_tag 1 +#define wippersnapper_ds18x20_Ds18x20Added_onewire_pin_tag 2 +#define wippersnapper_ds18x20_Ds18x20Remove_onewire_pin_tag 1 +#define wippersnapper_ds18x20_Ds18x20Event_onewire_pin_tag 1 +#define wippersnapper_ds18x20_Ds18x20Event_sensor_events_tag 2 + +/* Struct field encoding specification for nanopb */ +#define wippersnapper_ds18x20_Ds18x20Add_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, onewire_pin, 1) \ +X(a, STATIC, SINGULAR, INT32, sensor_resolution, 2) \ +X(a, STATIC, SINGULAR, FLOAT, period, 3) \ +X(a, STATIC, REPEATED, UENUM, sensor_types, 4) +#define wippersnapper_ds18x20_Ds18x20Add_CALLBACK NULL +#define wippersnapper_ds18x20_Ds18x20Add_DEFAULT NULL + +#define wippersnapper_ds18x20_Ds18x20Added_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, BOOL, is_initialized, 1) \ +X(a, STATIC, SINGULAR, STRING, onewire_pin, 2) +#define wippersnapper_ds18x20_Ds18x20Added_CALLBACK NULL +#define wippersnapper_ds18x20_Ds18x20Added_DEFAULT NULL + +#define wippersnapper_ds18x20_Ds18x20Remove_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, onewire_pin, 1) +#define wippersnapper_ds18x20_Ds18x20Remove_CALLBACK NULL +#define wippersnapper_ds18x20_Ds18x20Remove_DEFAULT NULL + +#define wippersnapper_ds18x20_Ds18x20Event_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, onewire_pin, 1) \ +X(a, STATIC, REPEATED, MESSAGE, sensor_events, 2) +#define wippersnapper_ds18x20_Ds18x20Event_CALLBACK NULL +#define wippersnapper_ds18x20_Ds18x20Event_DEFAULT NULL +#define wippersnapper_ds18x20_Ds18x20Event_sensor_events_MSGTYPE wippersnapper_sensor_SensorEvent + +extern const pb_msgdesc_t wippersnapper_ds18x20_Ds18x20Add_msg; +extern const pb_msgdesc_t wippersnapper_ds18x20_Ds18x20Added_msg; +extern const pb_msgdesc_t wippersnapper_ds18x20_Ds18x20Remove_msg; +extern const pb_msgdesc_t wippersnapper_ds18x20_Ds18x20Event_msg; + +/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ +#define wippersnapper_ds18x20_Ds18x20Add_fields &wippersnapper_ds18x20_Ds18x20Add_msg +#define wippersnapper_ds18x20_Ds18x20Added_fields &wippersnapper_ds18x20_Ds18x20Added_msg +#define wippersnapper_ds18x20_Ds18x20Remove_fields &wippersnapper_ds18x20_Ds18x20Remove_msg +#define wippersnapper_ds18x20_Ds18x20Event_fields &wippersnapper_ds18x20_Ds18x20Event_msg + +/* Maximum encoded size of messages (where known) */ +#define WIPPERSNAPPER_DS18X20_DS18X20_PB_H_MAX_SIZE wippersnapper_ds18x20_Ds18x20Add_size +#define wippersnapper_ds18x20_Ds18x20Add_size 26 +#define wippersnapper_ds18x20_Ds18x20Added_size 8 +#define wippersnapper_ds18x20_Ds18x20Remove_size 6 +#if defined(wippersnapper_sensor_SensorEvent_size) +#define wippersnapper_ds18x20_Ds18x20Event_size (18 + 2*wippersnapper_sensor_SensorEvent_size) +#endif + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/src/protos/error.pb.c b/src/protos/error.pb.c new file mode 100644 index 000000000..431fe48ac --- /dev/null +++ b/src/protos/error.pb.c @@ -0,0 +1,12 @@ +/* Automatically generated nanopb constant definitions */ +/* Generated by nanopb-0.4.8 */ + +#include "error.pb.h" +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +PB_BIND(wippersnapper_error_Error, wippersnapper_error_Error, AUTO) + + + diff --git a/src/protos/error.pb.h b/src/protos/error.pb.h new file mode 100644 index 000000000..3ed3fbefc --- /dev/null +++ b/src/protos/error.pb.h @@ -0,0 +1,54 @@ +/* Automatically generated nanopb header */ +/* Generated by nanopb-0.4.8 */ + +#ifndef PB_WIPPERSNAPPER_ERROR_ERROR_PB_H_INCLUDED +#define PB_WIPPERSNAPPER_ERROR_ERROR_PB_H_INCLUDED +#include + +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +/* Struct definitions */ +typedef struct _wippersnapper_error_Error { + pb_size_t which_payload; + union { + int32_t ban_time; /* Account ban time, in seconds */ + int32_t throttle_time; /* Account time, in seconds */ + } payload; +} wippersnapper_error_Error; + + +#ifdef __cplusplus +extern "C" { +#endif + +/* Initializer values for message structs */ +#define wippersnapper_error_Error_init_default {0, {0}} +#define wippersnapper_error_Error_init_zero {0, {0}} + +/* Field tags (for use in manual encoding/decoding) */ +#define wippersnapper_error_Error_ban_time_tag 1 +#define wippersnapper_error_Error_throttle_time_tag 2 + +/* Struct field encoding specification for nanopb */ +#define wippersnapper_error_Error_FIELDLIST(X, a) \ +X(a, STATIC, ONEOF, INT32, (payload,ban_time,payload.ban_time), 1) \ +X(a, STATIC, ONEOF, INT32, (payload,throttle_time,payload.throttle_time), 2) +#define wippersnapper_error_Error_CALLBACK NULL +#define wippersnapper_error_Error_DEFAULT NULL + +extern const pb_msgdesc_t wippersnapper_error_Error_msg; + +/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ +#define wippersnapper_error_Error_fields &wippersnapper_error_Error_msg + +/* Maximum encoded size of messages (where known) */ +#define WIPPERSNAPPER_ERROR_ERROR_PB_H_MAX_SIZE wippersnapper_error_Error_size +#define wippersnapper_error_Error_size 11 + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/src/protos/i2c.pb.c b/src/protos/i2c.pb.c new file mode 100644 index 000000000..a2946787a --- /dev/null +++ b/src/protos/i2c.pb.c @@ -0,0 +1,35 @@ +/* Automatically generated nanopb constant definitions */ +/* Generated by nanopb-0.4.8 */ + +#include "i2c.pb.h" +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +PB_BIND(wippersnapper_i2c_I2cBusScan, wippersnapper_i2c_I2cBusScan, AUTO) + + +PB_BIND(wippersnapper_i2c_I2cBusScanned, wippersnapper_i2c_I2cBusScanned, 2) + + +PB_BIND(wippersnapper_i2c_I2cDeviceDescriptor, wippersnapper_i2c_I2cDeviceDescriptor, AUTO) + + +PB_BIND(wippersnapper_i2c_I2cDeviceAddOrReplace, wippersnapper_i2c_I2cDeviceAddOrReplace, AUTO) + + +PB_BIND(wippersnapper_i2c_I2cDeviceAddedOrReplaced, wippersnapper_i2c_I2cDeviceAddedOrReplaced, AUTO) + + +PB_BIND(wippersnapper_i2c_I2cDeviceRemove, wippersnapper_i2c_I2cDeviceRemove, AUTO) + + +PB_BIND(wippersnapper_i2c_I2cDeviceRemoved, wippersnapper_i2c_I2cDeviceRemoved, AUTO) + + +PB_BIND(wippersnapper_i2c_I2cDeviceEvent, wippersnapper_i2c_I2cDeviceEvent, 2) + + + + + diff --git a/src/protos/i2c.pb.h b/src/protos/i2c.pb.h new file mode 100644 index 000000000..8c9d819c3 --- /dev/null +++ b/src/protos/i2c.pb.h @@ -0,0 +1,270 @@ +/* Automatically generated nanopb header */ +/* Generated by nanopb-0.4.8 */ + +#ifndef PB_WIPPERSNAPPER_I2C_I2C_PB_H_INCLUDED +#define PB_WIPPERSNAPPER_I2C_I2C_PB_H_INCLUDED +#include +#include "sensor.pb.h" + +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +/* Enum definitions */ +/* * + I2cBusStatus represents the status of a board's I2C bus */ +typedef enum _wippersnapper_i2c_I2cBusStatus { + wippersnapper_i2c_I2cBusStatus_I2C_BUS_STATUS_UNSPECIFIED = 0, /* * Unspecified error occurred. * */ + wippersnapper_i2c_I2cBusStatus_I2C_BUS_STATUS_SUCCESS = 1, /* * I2C bus successfully initialized. * */ + wippersnapper_i2c_I2cBusStatus_I2C_BUS_STATUS_ERROR_HANG = 2, /* * I2C Bus hang, user should reset their board if this persists. * */ + wippersnapper_i2c_I2cBusStatus_I2C_BUS_STATUS_ERROR_PULLUPS = 3, /* * I2C bus failed to initialize - SDA or SCL needs a pull up. * */ + wippersnapper_i2c_I2cBusStatus_I2C_BUS_STATUS_ERROR_WIRING = 4, /* * I2C bus failed to communicate - Please check your wiring. * */ + wippersnapper_i2c_I2cBusStatus_I2C_BUS_STATUS_ERROR_INVALID_CHANNEL = 5 /* * I2C MUX failed - Output channel must be within range 0-7. * */ +} wippersnapper_i2c_I2cBusStatus; + +/* * + I2cDeviceStatus represents the state of an I2C device/peripheral */ +typedef enum _wippersnapper_i2c_I2cDeviceStatus { + wippersnapper_i2c_I2cDeviceStatus_I2C_DEVICE_STATUS_UNSPECIFIED = 0, /* * Unspecified error occurred. * */ + wippersnapper_i2c_I2cDeviceStatus_I2C_DEVICE_STATUS_SUCCESS = 1, /* * I2C device successfully initialized. * */ + wippersnapper_i2c_I2cDeviceStatus_I2C_DEVICE_STATUS_FAIL_INIT = 2, /* * I2C device failed to initialize. * */ + wippersnapper_i2c_I2cDeviceStatus_I2C_DEVICE_STATUS_FAIL_DEINIT = 3, /* * I2C device failed to deinitialize. * */ + wippersnapper_i2c_I2cDeviceStatus_I2C_DEVICE_STATUS_FAIL_UNSUPPORTED_SENSOR = 4, /* * WipperSnapper version is outdated and does not include this device. * */ + wippersnapper_i2c_I2cDeviceStatus_I2C_DEVICE_STATUS_NOT_FOUND = 5 /* * I2C device not found on the bus. * */ +} wippersnapper_i2c_I2cDeviceStatus; + +/* Struct definitions */ +/* * + I2cBusScan represents a command for a device to perform an i2c scan. It is an empty message. */ +typedef struct _wippersnapper_i2c_I2cBusScan { + char dummy_field; +} wippersnapper_i2c_I2cBusScan; + +/* * + I2cDeviceDescriptor represents the I2c device's address and related metadata. */ +typedef struct _wippersnapper_i2c_I2cDeviceDescriptor { + uint32_t i2c_bus; /* * The desired i2c bus to address.* */ + uint32_t i2c_device_mux_address; /* * The desired address of the I2C device or (optionally) the I2C MUX address.* */ + uint32_t i2c_mux_channel; /* * (Optional) The desired I2C mux's output channel, from 0-7.* */ +} wippersnapper_i2c_I2cDeviceDescriptor; + +/* * + I2cBusScanned represents a list of I2c addresses + found on the bus after I2cScan has executed. */ +typedef struct _wippersnapper_i2c_I2cBusScanned { + pb_size_t i2c_bus_found_devices_count; + wippersnapper_i2c_I2cDeviceDescriptor i2c_bus_found_devices[120]; /* * The 7-bit addresses of the I2c devices found on the bus, empty if not found. */ + wippersnapper_i2c_I2cBusStatus i2c_bus_status; /* * The I2c bus' status. * */ +} wippersnapper_i2c_I2cBusScanned; + +/* * + I2cDeviceAddOrReplace is a message for initializing (or replacing/updating) an i2c device. */ +typedef struct _wippersnapper_i2c_I2cDeviceAddOrReplace { + bool has_i2c_device_description; + wippersnapper_i2c_I2cDeviceDescriptor i2c_device_description; /* * The I2c device's address and metadata. */ + char i2c_device_name[15]; /* * The I2c device's name, MUST MATCH the name on the JSON definition file on +https://github.com/adafruit/Wippersnapper_Components. */ + float i2c_device_period; /* * The desired period to update the I2c device's sensor(s), in seconds. */ + pb_size_t i2c_device_sensor_types_count; + wippersnapper_sensor_SensorType i2c_device_sensor_types[15]; /* * SI Types for each sensor on the I2c device. */ +} wippersnapper_i2c_I2cDeviceAddOrReplace; + +/* * + I2cDeviceAddedOrReplaced contains the response from a device after processing a I2cDeviceAddOrReplace message. */ +typedef struct _wippersnapper_i2c_I2cDeviceAddedOrReplaced { + bool has_i2c_device_description; + wippersnapper_i2c_I2cDeviceDescriptor i2c_device_description; /* * The I2c device's address and metadata. */ + wippersnapper_i2c_I2cBusStatus i2c_bus_status; /* * The I2c bus' status. * */ + wippersnapper_i2c_I2cDeviceStatus i2c_device_status; /* * The I2c device's status. * */ +} wippersnapper_i2c_I2cDeviceAddedOrReplaced; + +/* * + I2cDeviceRemove represents a request to de-init an i2c device. */ +typedef struct _wippersnapper_i2c_I2cDeviceRemove { + bool has_i2c_device_description; + wippersnapper_i2c_I2cDeviceDescriptor i2c_device_description; /* * The I2c device's address and metadata. */ +} wippersnapper_i2c_I2cDeviceRemove; + +/* * + I2cDeviceRemoved represents a response to a I2cDeviceRemove message. */ +typedef struct _wippersnapper_i2c_I2cDeviceRemoved { + bool has_i2c_device_description; + wippersnapper_i2c_I2cDeviceDescriptor i2c_device_description; /* * The I2c device's address and metadata. */ + wippersnapper_i2c_I2cBusStatus i2c_bus_status; /* * The I2c bus' status. * */ + wippersnapper_i2c_I2cDeviceStatus i2c_device_status; /* * The I2c device's status. * */ +} wippersnapper_i2c_I2cDeviceRemoved; + +/* * + Each I2cDeviceEvent represents data from **one** I2c sensor. + NOTE: An I2cDeviceEvent can have multiple sensor events if + the I2c device contains > 1 sensor. */ +typedef struct _wippersnapper_i2c_I2cDeviceEvent { + bool has_i2c_device_description; + wippersnapper_i2c_I2cDeviceDescriptor i2c_device_description; /* * The I2c device's address and metadata. */ + pb_size_t i2c_device_events_count; + wippersnapper_sensor_SensorEvent i2c_device_events[15]; /* * A, optionally repeated, SensorEvent from a sensor. */ +} wippersnapper_i2c_I2cDeviceEvent; + + +#ifdef __cplusplus +extern "C" { +#endif + +/* Helper constants for enums */ +#define _wippersnapper_i2c_I2cBusStatus_MIN wippersnapper_i2c_I2cBusStatus_I2C_BUS_STATUS_UNSPECIFIED +#define _wippersnapper_i2c_I2cBusStatus_MAX wippersnapper_i2c_I2cBusStatus_I2C_BUS_STATUS_ERROR_INVALID_CHANNEL +#define _wippersnapper_i2c_I2cBusStatus_ARRAYSIZE ((wippersnapper_i2c_I2cBusStatus)(wippersnapper_i2c_I2cBusStatus_I2C_BUS_STATUS_ERROR_INVALID_CHANNEL+1)) + +#define _wippersnapper_i2c_I2cDeviceStatus_MIN wippersnapper_i2c_I2cDeviceStatus_I2C_DEVICE_STATUS_UNSPECIFIED +#define _wippersnapper_i2c_I2cDeviceStatus_MAX wippersnapper_i2c_I2cDeviceStatus_I2C_DEVICE_STATUS_NOT_FOUND +#define _wippersnapper_i2c_I2cDeviceStatus_ARRAYSIZE ((wippersnapper_i2c_I2cDeviceStatus)(wippersnapper_i2c_I2cDeviceStatus_I2C_DEVICE_STATUS_NOT_FOUND+1)) + + +#define wippersnapper_i2c_I2cBusScanned_i2c_bus_status_ENUMTYPE wippersnapper_i2c_I2cBusStatus + + +#define wippersnapper_i2c_I2cDeviceAddOrReplace_i2c_device_sensor_types_ENUMTYPE wippersnapper_sensor_SensorType + +#define wippersnapper_i2c_I2cDeviceAddedOrReplaced_i2c_bus_status_ENUMTYPE wippersnapper_i2c_I2cBusStatus +#define wippersnapper_i2c_I2cDeviceAddedOrReplaced_i2c_device_status_ENUMTYPE wippersnapper_i2c_I2cDeviceStatus + + +#define wippersnapper_i2c_I2cDeviceRemoved_i2c_bus_status_ENUMTYPE wippersnapper_i2c_I2cBusStatus +#define wippersnapper_i2c_I2cDeviceRemoved_i2c_device_status_ENUMTYPE wippersnapper_i2c_I2cDeviceStatus + + + +/* Initializer values for message structs */ +#define wippersnapper_i2c_I2cBusScan_init_default {0} +#define wippersnapper_i2c_I2cBusScanned_init_default {0, {wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default, wippersnapper_i2c_I2cDeviceDescriptor_init_default}, _wippersnapper_i2c_I2cBusStatus_MIN} +#define wippersnapper_i2c_I2cDeviceDescriptor_init_default {0, 0, 0} +#define wippersnapper_i2c_I2cDeviceAddOrReplace_init_default {false, wippersnapper_i2c_I2cDeviceDescriptor_init_default, "", 0, 0, {_wippersnapper_sensor_SensorType_MIN, _wippersnapper_sensor_SensorType_MIN, _wippersnapper_sensor_SensorType_MIN, _wippersnapper_sensor_SensorType_MIN, _wippersnapper_sensor_SensorType_MIN, _wippersnapper_sensor_SensorType_MIN, _wippersnapper_sensor_SensorType_MIN, _wippersnapper_sensor_SensorType_MIN, _wippersnapper_sensor_SensorType_MIN, _wippersnapper_sensor_SensorType_MIN, _wippersnapper_sensor_SensorType_MIN, _wippersnapper_sensor_SensorType_MIN, _wippersnapper_sensor_SensorType_MIN, _wippersnapper_sensor_SensorType_MIN, _wippersnapper_sensor_SensorType_MIN}} +#define wippersnapper_i2c_I2cDeviceAddedOrReplaced_init_default {false, wippersnapper_i2c_I2cDeviceDescriptor_init_default, _wippersnapper_i2c_I2cBusStatus_MIN, _wippersnapper_i2c_I2cDeviceStatus_MIN} +#define wippersnapper_i2c_I2cDeviceRemove_init_default {false, wippersnapper_i2c_I2cDeviceDescriptor_init_default} +#define wippersnapper_i2c_I2cDeviceRemoved_init_default {false, wippersnapper_i2c_I2cDeviceDescriptor_init_default, _wippersnapper_i2c_I2cBusStatus_MIN, _wippersnapper_i2c_I2cDeviceStatus_MIN} +#define wippersnapper_i2c_I2cDeviceEvent_init_default {false, wippersnapper_i2c_I2cDeviceDescriptor_init_default, 0, {wippersnapper_sensor_SensorEvent_init_default, wippersnapper_sensor_SensorEvent_init_default, wippersnapper_sensor_SensorEvent_init_default, wippersnapper_sensor_SensorEvent_init_default, wippersnapper_sensor_SensorEvent_init_default, wippersnapper_sensor_SensorEvent_init_default, wippersnapper_sensor_SensorEvent_init_default, wippersnapper_sensor_SensorEvent_init_default, wippersnapper_sensor_SensorEvent_init_default, wippersnapper_sensor_SensorEvent_init_default, wippersnapper_sensor_SensorEvent_init_default, wippersnapper_sensor_SensorEvent_init_default, wippersnapper_sensor_SensorEvent_init_default, wippersnapper_sensor_SensorEvent_init_default, wippersnapper_sensor_SensorEvent_init_default}} +#define wippersnapper_i2c_I2cBusScan_init_zero {0} +#define wippersnapper_i2c_I2cBusScanned_init_zero {0, {wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, wippersnapper_i2c_I2cDeviceDescriptor_init_zero}, _wippersnapper_i2c_I2cBusStatus_MIN} +#define wippersnapper_i2c_I2cDeviceDescriptor_init_zero {0, 0, 0} +#define wippersnapper_i2c_I2cDeviceAddOrReplace_init_zero {false, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, "", 0, 0, {_wippersnapper_sensor_SensorType_MIN, _wippersnapper_sensor_SensorType_MIN, _wippersnapper_sensor_SensorType_MIN, _wippersnapper_sensor_SensorType_MIN, _wippersnapper_sensor_SensorType_MIN, _wippersnapper_sensor_SensorType_MIN, _wippersnapper_sensor_SensorType_MIN, _wippersnapper_sensor_SensorType_MIN, _wippersnapper_sensor_SensorType_MIN, _wippersnapper_sensor_SensorType_MIN, _wippersnapper_sensor_SensorType_MIN, _wippersnapper_sensor_SensorType_MIN, _wippersnapper_sensor_SensorType_MIN, _wippersnapper_sensor_SensorType_MIN, _wippersnapper_sensor_SensorType_MIN}} +#define wippersnapper_i2c_I2cDeviceAddedOrReplaced_init_zero {false, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, _wippersnapper_i2c_I2cBusStatus_MIN, _wippersnapper_i2c_I2cDeviceStatus_MIN} +#define wippersnapper_i2c_I2cDeviceRemove_init_zero {false, wippersnapper_i2c_I2cDeviceDescriptor_init_zero} +#define wippersnapper_i2c_I2cDeviceRemoved_init_zero {false, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, _wippersnapper_i2c_I2cBusStatus_MIN, _wippersnapper_i2c_I2cDeviceStatus_MIN} +#define wippersnapper_i2c_I2cDeviceEvent_init_zero {false, wippersnapper_i2c_I2cDeviceDescriptor_init_zero, 0, {wippersnapper_sensor_SensorEvent_init_zero, wippersnapper_sensor_SensorEvent_init_zero, wippersnapper_sensor_SensorEvent_init_zero, wippersnapper_sensor_SensorEvent_init_zero, wippersnapper_sensor_SensorEvent_init_zero, wippersnapper_sensor_SensorEvent_init_zero, wippersnapper_sensor_SensorEvent_init_zero, wippersnapper_sensor_SensorEvent_init_zero, wippersnapper_sensor_SensorEvent_init_zero, wippersnapper_sensor_SensorEvent_init_zero, wippersnapper_sensor_SensorEvent_init_zero, wippersnapper_sensor_SensorEvent_init_zero, wippersnapper_sensor_SensorEvent_init_zero, wippersnapper_sensor_SensorEvent_init_zero, wippersnapper_sensor_SensorEvent_init_zero}} + +/* Field tags (for use in manual encoding/decoding) */ +#define wippersnapper_i2c_I2cDeviceDescriptor_i2c_bus_tag 1 +#define wippersnapper_i2c_I2cDeviceDescriptor_i2c_device_mux_address_tag 2 +#define wippersnapper_i2c_I2cDeviceDescriptor_i2c_mux_channel_tag 3 +#define wippersnapper_i2c_I2cBusScanned_i2c_bus_found_devices_tag 1 +#define wippersnapper_i2c_I2cBusScanned_i2c_bus_status_tag 2 +#define wippersnapper_i2c_I2cDeviceAddOrReplace_i2c_device_description_tag 1 +#define wippersnapper_i2c_I2cDeviceAddOrReplace_i2c_device_name_tag 2 +#define wippersnapper_i2c_I2cDeviceAddOrReplace_i2c_device_period_tag 3 +#define wippersnapper_i2c_I2cDeviceAddOrReplace_i2c_device_sensor_types_tag 4 +#define wippersnapper_i2c_I2cDeviceAddedOrReplaced_i2c_device_description_tag 1 +#define wippersnapper_i2c_I2cDeviceAddedOrReplaced_i2c_bus_status_tag 2 +#define wippersnapper_i2c_I2cDeviceAddedOrReplaced_i2c_device_status_tag 3 +#define wippersnapper_i2c_I2cDeviceRemove_i2c_device_description_tag 1 +#define wippersnapper_i2c_I2cDeviceRemoved_i2c_device_description_tag 1 +#define wippersnapper_i2c_I2cDeviceRemoved_i2c_bus_status_tag 2 +#define wippersnapper_i2c_I2cDeviceRemoved_i2c_device_status_tag 3 +#define wippersnapper_i2c_I2cDeviceEvent_i2c_device_description_tag 1 +#define wippersnapper_i2c_I2cDeviceEvent_i2c_device_events_tag 2 + +/* Struct field encoding specification for nanopb */ +#define wippersnapper_i2c_I2cBusScan_FIELDLIST(X, a) \ + +#define wippersnapper_i2c_I2cBusScan_CALLBACK NULL +#define wippersnapper_i2c_I2cBusScan_DEFAULT NULL + +#define wippersnapper_i2c_I2cBusScanned_FIELDLIST(X, a) \ +X(a, STATIC, REPEATED, MESSAGE, i2c_bus_found_devices, 1) \ +X(a, STATIC, SINGULAR, UENUM, i2c_bus_status, 2) +#define wippersnapper_i2c_I2cBusScanned_CALLBACK NULL +#define wippersnapper_i2c_I2cBusScanned_DEFAULT NULL +#define wippersnapper_i2c_I2cBusScanned_i2c_bus_found_devices_MSGTYPE wippersnapper_i2c_I2cDeviceDescriptor + +#define wippersnapper_i2c_I2cDeviceDescriptor_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, i2c_bus, 1) \ +X(a, STATIC, SINGULAR, UINT32, i2c_device_mux_address, 2) \ +X(a, STATIC, SINGULAR, UINT32, i2c_mux_channel, 3) +#define wippersnapper_i2c_I2cDeviceDescriptor_CALLBACK NULL +#define wippersnapper_i2c_I2cDeviceDescriptor_DEFAULT NULL + +#define wippersnapper_i2c_I2cDeviceAddOrReplace_FIELDLIST(X, a) \ +X(a, STATIC, OPTIONAL, MESSAGE, i2c_device_description, 1) \ +X(a, STATIC, SINGULAR, STRING, i2c_device_name, 2) \ +X(a, STATIC, SINGULAR, FLOAT, i2c_device_period, 3) \ +X(a, STATIC, REPEATED, UENUM, i2c_device_sensor_types, 4) +#define wippersnapper_i2c_I2cDeviceAddOrReplace_CALLBACK NULL +#define wippersnapper_i2c_I2cDeviceAddOrReplace_DEFAULT NULL +#define wippersnapper_i2c_I2cDeviceAddOrReplace_i2c_device_description_MSGTYPE wippersnapper_i2c_I2cDeviceDescriptor + +#define wippersnapper_i2c_I2cDeviceAddedOrReplaced_FIELDLIST(X, a) \ +X(a, STATIC, OPTIONAL, MESSAGE, i2c_device_description, 1) \ +X(a, STATIC, SINGULAR, UENUM, i2c_bus_status, 2) \ +X(a, STATIC, SINGULAR, UENUM, i2c_device_status, 3) +#define wippersnapper_i2c_I2cDeviceAddedOrReplaced_CALLBACK NULL +#define wippersnapper_i2c_I2cDeviceAddedOrReplaced_DEFAULT NULL +#define wippersnapper_i2c_I2cDeviceAddedOrReplaced_i2c_device_description_MSGTYPE wippersnapper_i2c_I2cDeviceDescriptor + +#define wippersnapper_i2c_I2cDeviceRemove_FIELDLIST(X, a) \ +X(a, STATIC, OPTIONAL, MESSAGE, i2c_device_description, 1) +#define wippersnapper_i2c_I2cDeviceRemove_CALLBACK NULL +#define wippersnapper_i2c_I2cDeviceRemove_DEFAULT NULL +#define wippersnapper_i2c_I2cDeviceRemove_i2c_device_description_MSGTYPE wippersnapper_i2c_I2cDeviceDescriptor + +#define wippersnapper_i2c_I2cDeviceRemoved_FIELDLIST(X, a) \ +X(a, STATIC, OPTIONAL, MESSAGE, i2c_device_description, 1) \ +X(a, STATIC, SINGULAR, UENUM, i2c_bus_status, 2) \ +X(a, STATIC, SINGULAR, UENUM, i2c_device_status, 3) +#define wippersnapper_i2c_I2cDeviceRemoved_CALLBACK NULL +#define wippersnapper_i2c_I2cDeviceRemoved_DEFAULT NULL +#define wippersnapper_i2c_I2cDeviceRemoved_i2c_device_description_MSGTYPE wippersnapper_i2c_I2cDeviceDescriptor + +#define wippersnapper_i2c_I2cDeviceEvent_FIELDLIST(X, a) \ +X(a, STATIC, OPTIONAL, MESSAGE, i2c_device_description, 1) \ +X(a, STATIC, REPEATED, MESSAGE, i2c_device_events, 2) +#define wippersnapper_i2c_I2cDeviceEvent_CALLBACK NULL +#define wippersnapper_i2c_I2cDeviceEvent_DEFAULT NULL +#define wippersnapper_i2c_I2cDeviceEvent_i2c_device_description_MSGTYPE wippersnapper_i2c_I2cDeviceDescriptor +#define wippersnapper_i2c_I2cDeviceEvent_i2c_device_events_MSGTYPE wippersnapper_sensor_SensorEvent + +extern const pb_msgdesc_t wippersnapper_i2c_I2cBusScan_msg; +extern const pb_msgdesc_t wippersnapper_i2c_I2cBusScanned_msg; +extern const pb_msgdesc_t wippersnapper_i2c_I2cDeviceDescriptor_msg; +extern const pb_msgdesc_t wippersnapper_i2c_I2cDeviceAddOrReplace_msg; +extern const pb_msgdesc_t wippersnapper_i2c_I2cDeviceAddedOrReplaced_msg; +extern const pb_msgdesc_t wippersnapper_i2c_I2cDeviceRemove_msg; +extern const pb_msgdesc_t wippersnapper_i2c_I2cDeviceRemoved_msg; +extern const pb_msgdesc_t wippersnapper_i2c_I2cDeviceEvent_msg; + +/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ +#define wippersnapper_i2c_I2cBusScan_fields &wippersnapper_i2c_I2cBusScan_msg +#define wippersnapper_i2c_I2cBusScanned_fields &wippersnapper_i2c_I2cBusScanned_msg +#define wippersnapper_i2c_I2cDeviceDescriptor_fields &wippersnapper_i2c_I2cDeviceDescriptor_msg +#define wippersnapper_i2c_I2cDeviceAddOrReplace_fields &wippersnapper_i2c_I2cDeviceAddOrReplace_msg +#define wippersnapper_i2c_I2cDeviceAddedOrReplaced_fields &wippersnapper_i2c_I2cDeviceAddedOrReplaced_msg +#define wippersnapper_i2c_I2cDeviceRemove_fields &wippersnapper_i2c_I2cDeviceRemove_msg +#define wippersnapper_i2c_I2cDeviceRemoved_fields &wippersnapper_i2c_I2cDeviceRemoved_msg +#define wippersnapper_i2c_I2cDeviceEvent_fields &wippersnapper_i2c_I2cDeviceEvent_msg + +/* Maximum encoded size of messages (where known) */ +#define WIPPERSNAPPER_I2C_I2C_PB_H_MAX_SIZE wippersnapper_i2c_I2cBusScanned_size +#define wippersnapper_i2c_I2cBusScan_size 0 +#define wippersnapper_i2c_I2cBusScanned_size 2402 +#define wippersnapper_i2c_I2cDeviceAddOrReplace_size 71 +#define wippersnapper_i2c_I2cDeviceAddedOrReplaced_size 24 +#define wippersnapper_i2c_I2cDeviceDescriptor_size 18 +#define wippersnapper_i2c_I2cDeviceRemove_size 20 +#define wippersnapper_i2c_I2cDeviceRemoved_size 24 +#if defined(wippersnapper_sensor_SensorEvent_size) +#define wippersnapper_i2c_I2cDeviceEvent_size (110 + 15*wippersnapper_sensor_SensorEvent_size) +#endif + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/src/protos/pixels.pb.c b/src/protos/pixels.pb.c new file mode 100644 index 000000000..21a6eed14 --- /dev/null +++ b/src/protos/pixels.pb.c @@ -0,0 +1,23 @@ +/* Automatically generated nanopb constant definitions */ +/* Generated by nanopb-0.4.8 */ + +#include "pixels.pb.h" +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +PB_BIND(wippersnapper_pixels_PixelsAdd, wippersnapper_pixels_PixelsAdd, AUTO) + + +PB_BIND(wippersnapper_pixels_PixelsAdded, wippersnapper_pixels_PixelsAdded, AUTO) + + +PB_BIND(wippersnapper_pixels_PixelsRemove, wippersnapper_pixels_PixelsRemove, AUTO) + + +PB_BIND(wippersnapper_pixels_PixelsWrite, wippersnapper_pixels_PixelsWrite, AUTO) + + + + + diff --git a/src/protos/pixels.pb.h b/src/protos/pixels.pb.h new file mode 100644 index 000000000..b2b1c23b5 --- /dev/null +++ b/src/protos/pixels.pb.h @@ -0,0 +1,167 @@ +/* Automatically generated nanopb header */ +/* Generated by nanopb-0.4.8 */ + +#ifndef PB_WIPPERSNAPPER_PIXELS_PIXELS_PB_H_INCLUDED +#define PB_WIPPERSNAPPER_PIXELS_PIXELS_PB_H_INCLUDED +#include + +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +/* Enum definitions */ +/* * + PixelsType defines the type/model of pixel strand. */ +typedef enum _wippersnapper_pixels_PixelsType { + wippersnapper_pixels_PixelsType_PIXELS_TYPE_UNSPECIFIED = 0, /* * Unspecified pixel type, error. */ + wippersnapper_pixels_PixelsType_PIXELS_TYPE_NEOPIXEL = 1, /* * NeoPixel pixel strand. */ + wippersnapper_pixels_PixelsType_PIXELS_TYPE_DOTSTAR = 2 /* * DotStar pixel strand. */ +} wippersnapper_pixels_PixelsType; + +/* * + PixelsOrder defines the color ordering. */ +typedef enum _wippersnapper_pixels_PixelsOrder { + wippersnapper_pixels_PixelsOrder_PIXELS_ORDER_UNSPECIFIED = 0, /* * Unspecified color ordering, error. */ + wippersnapper_pixels_PixelsOrder_PIXELS_ORDER_GRB = 1, /* * DEFAULT for NeoPixels - Green, Red, Blue */ + wippersnapper_pixels_PixelsOrder_PIXELS_ORDER_GRBW = 2, /* * Green, Red, Blue, White */ + wippersnapper_pixels_PixelsOrder_PIXELS_ORDER_RGB = 3, /* * Red, Green, Blue */ + wippersnapper_pixels_PixelsOrder_PIXELS_ORDER_RGBW = 4, /* * Red, Green, Blue, White */ + wippersnapper_pixels_PixelsOrder_PIXELS_ORDER_BRG = 5, /* * DEFAULT for DotStars - Blue, Red, Green */ + wippersnapper_pixels_PixelsOrder_PIXELS_ORDER_RBG = 6, /* * Red, Blue Green */ + wippersnapper_pixels_PixelsOrder_PIXELS_ORDER_GBR = 7, /* * Green, Blue, Red */ + wippersnapper_pixels_PixelsOrder_PIXELS_ORDER_BGR = 8 /* * Blue, Green, Red */ +} wippersnapper_pixels_PixelsOrder; + +/* Struct definitions */ +/* * + PixelsAdd represents a call from IO to a device. + Adds a strand of addressable pixels. + Initial brightness is always 128. */ +typedef struct _wippersnapper_pixels_PixelsAdd { + wippersnapper_pixels_PixelsType pixels_type; /* * Defines the model/type of pixel strand */ + uint32_t pixels_num; /* * Number of pixels attached to strand. */ + wippersnapper_pixels_PixelsOrder pixels_ordering; /* * Defines the pixel strand's color ordering. */ + uint32_t pixels_brightness; /* * Strand brightness, 0 to 255 */ + char pixels_pin_data[6]; /* * Data pin a NeoPixel or DotStar strand is connected to. */ + char pixels_pin_dotstar_clock[6]; /* * Clock pin a DotStar strand is connected to. */ +} wippersnapper_pixels_PixelsAdd; + +/* * + PixelsAdded represents response from a WipperSnapper + device to IO after a PixelsAdd call */ +typedef struct _wippersnapper_pixels_PixelsAdded { + bool is_success; /* * True if the strand initialized successfully, False otherwise. */ + char pixels_pin_data[6]; /* * Data pin the responding strand is connected to. */ +} wippersnapper_pixels_PixelsAdded; + +/* * + PixelAdd represents a call from IO to a device + Removes a strand of addressable pixels and release the resources and pin. */ +typedef struct _wippersnapper_pixels_PixelsRemove { + char pixels_pin_data[6]; /* * Data pin the pixel strand is connected to. */ +} wippersnapper_pixels_PixelsRemove; + +/* * + PixelsWrite represents a call from IO to a device. + Writes to a strand of pixels. */ +typedef struct _wippersnapper_pixels_PixelsWrite { + char pixels_pin_data[6]; /* * Data pin a strand is connected to. */ + uint32_t pixels_color; /* 32-bit color value. Most significant byte is white (for RGBW pixels) or ignored (for RGB pixels), +next is red, then green, and least significant byte is blue. */ +} wippersnapper_pixels_PixelsWrite; + + +#ifdef __cplusplus +extern "C" { +#endif + +/* Helper constants for enums */ +#define _wippersnapper_pixels_PixelsType_MIN wippersnapper_pixels_PixelsType_PIXELS_TYPE_UNSPECIFIED +#define _wippersnapper_pixels_PixelsType_MAX wippersnapper_pixels_PixelsType_PIXELS_TYPE_DOTSTAR +#define _wippersnapper_pixels_PixelsType_ARRAYSIZE ((wippersnapper_pixels_PixelsType)(wippersnapper_pixels_PixelsType_PIXELS_TYPE_DOTSTAR+1)) + +#define _wippersnapper_pixels_PixelsOrder_MIN wippersnapper_pixels_PixelsOrder_PIXELS_ORDER_UNSPECIFIED +#define _wippersnapper_pixels_PixelsOrder_MAX wippersnapper_pixels_PixelsOrder_PIXELS_ORDER_BGR +#define _wippersnapper_pixels_PixelsOrder_ARRAYSIZE ((wippersnapper_pixels_PixelsOrder)(wippersnapper_pixels_PixelsOrder_PIXELS_ORDER_BGR+1)) + +#define wippersnapper_pixels_PixelsAdd_pixels_type_ENUMTYPE wippersnapper_pixels_PixelsType +#define wippersnapper_pixels_PixelsAdd_pixels_ordering_ENUMTYPE wippersnapper_pixels_PixelsOrder + + + + + +/* Initializer values for message structs */ +#define wippersnapper_pixels_PixelsAdd_init_default {_wippersnapper_pixels_PixelsType_MIN, 0, _wippersnapper_pixels_PixelsOrder_MIN, 0, "", ""} +#define wippersnapper_pixels_PixelsAdded_init_default {0, ""} +#define wippersnapper_pixels_PixelsRemove_init_default {""} +#define wippersnapper_pixels_PixelsWrite_init_default {"", 0} +#define wippersnapper_pixels_PixelsAdd_init_zero {_wippersnapper_pixels_PixelsType_MIN, 0, _wippersnapper_pixels_PixelsOrder_MIN, 0, "", ""} +#define wippersnapper_pixels_PixelsAdded_init_zero {0, ""} +#define wippersnapper_pixels_PixelsRemove_init_zero {""} +#define wippersnapper_pixels_PixelsWrite_init_zero {"", 0} + +/* Field tags (for use in manual encoding/decoding) */ +#define wippersnapper_pixels_PixelsAdd_pixels_type_tag 1 +#define wippersnapper_pixels_PixelsAdd_pixels_num_tag 2 +#define wippersnapper_pixels_PixelsAdd_pixels_ordering_tag 3 +#define wippersnapper_pixels_PixelsAdd_pixels_brightness_tag 4 +#define wippersnapper_pixels_PixelsAdd_pixels_pin_data_tag 5 +#define wippersnapper_pixels_PixelsAdd_pixels_pin_dotstar_clock_tag 6 +#define wippersnapper_pixels_PixelsAdded_is_success_tag 1 +#define wippersnapper_pixels_PixelsAdded_pixels_pin_data_tag 2 +#define wippersnapper_pixels_PixelsRemove_pixels_pin_data_tag 1 +#define wippersnapper_pixels_PixelsWrite_pixels_pin_data_tag 1 +#define wippersnapper_pixels_PixelsWrite_pixels_color_tag 2 + +/* Struct field encoding specification for nanopb */ +#define wippersnapper_pixels_PixelsAdd_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UENUM, pixels_type, 1) \ +X(a, STATIC, SINGULAR, UINT32, pixels_num, 2) \ +X(a, STATIC, SINGULAR, UENUM, pixels_ordering, 3) \ +X(a, STATIC, SINGULAR, UINT32, pixels_brightness, 4) \ +X(a, STATIC, SINGULAR, STRING, pixels_pin_data, 5) \ +X(a, STATIC, SINGULAR, STRING, pixels_pin_dotstar_clock, 6) +#define wippersnapper_pixels_PixelsAdd_CALLBACK NULL +#define wippersnapper_pixels_PixelsAdd_DEFAULT NULL + +#define wippersnapper_pixels_PixelsAdded_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, BOOL, is_success, 1) \ +X(a, STATIC, SINGULAR, STRING, pixels_pin_data, 2) +#define wippersnapper_pixels_PixelsAdded_CALLBACK NULL +#define wippersnapper_pixels_PixelsAdded_DEFAULT NULL + +#define wippersnapper_pixels_PixelsRemove_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, pixels_pin_data, 1) +#define wippersnapper_pixels_PixelsRemove_CALLBACK NULL +#define wippersnapper_pixels_PixelsRemove_DEFAULT NULL + +#define wippersnapper_pixels_PixelsWrite_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, pixels_pin_data, 1) \ +X(a, STATIC, SINGULAR, UINT32, pixels_color, 2) +#define wippersnapper_pixels_PixelsWrite_CALLBACK NULL +#define wippersnapper_pixels_PixelsWrite_DEFAULT NULL + +extern const pb_msgdesc_t wippersnapper_pixels_PixelsAdd_msg; +extern const pb_msgdesc_t wippersnapper_pixels_PixelsAdded_msg; +extern const pb_msgdesc_t wippersnapper_pixels_PixelsRemove_msg; +extern const pb_msgdesc_t wippersnapper_pixels_PixelsWrite_msg; + +/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ +#define wippersnapper_pixels_PixelsAdd_fields &wippersnapper_pixels_PixelsAdd_msg +#define wippersnapper_pixels_PixelsAdded_fields &wippersnapper_pixels_PixelsAdded_msg +#define wippersnapper_pixels_PixelsRemove_fields &wippersnapper_pixels_PixelsRemove_msg +#define wippersnapper_pixels_PixelsWrite_fields &wippersnapper_pixels_PixelsWrite_msg + +/* Maximum encoded size of messages (where known) */ +#define WIPPERSNAPPER_PIXELS_PIXELS_PB_H_MAX_SIZE wippersnapper_pixels_PixelsAdd_size +#define wippersnapper_pixels_PixelsAdd_size 30 +#define wippersnapper_pixels_PixelsAdded_size 9 +#define wippersnapper_pixels_PixelsRemove_size 7 +#define wippersnapper_pixels_PixelsWrite_size 13 + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/src/protos/pwm.pb.c b/src/protos/pwm.pb.c new file mode 100644 index 000000000..9d52e1045 --- /dev/null +++ b/src/protos/pwm.pb.c @@ -0,0 +1,27 @@ +/* Automatically generated nanopb constant definitions */ +/* Generated by nanopb-0.4.8 */ + +#include "pwm.pb.h" +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +PB_BIND(wippersnapper_pwm_PWMAdd, wippersnapper_pwm_PWMAdd, AUTO) + + +PB_BIND(wippersnapper_pwm_PWMAdded, wippersnapper_pwm_PWMAdded, AUTO) + + +PB_BIND(wippersnapper_pwm_PWMRemove, wippersnapper_pwm_PWMRemove, AUTO) + + +PB_BIND(wippersnapper_pwm_PWMWriteDutyCycle, wippersnapper_pwm_PWMWriteDutyCycle, AUTO) + + +PB_BIND(wippersnapper_pwm_PWMWriteDutyCycleMulti, wippersnapper_pwm_PWMWriteDutyCycleMulti, AUTO) + + +PB_BIND(wippersnapper_pwm_PWMWriteFrequency, wippersnapper_pwm_PWMWriteFrequency, AUTO) + + + diff --git a/src/protos/pwm.pb.h b/src/protos/pwm.pb.h new file mode 100644 index 000000000..0f3083a2d --- /dev/null +++ b/src/protos/pwm.pb.h @@ -0,0 +1,161 @@ +/* Automatically generated nanopb header */ +/* Generated by nanopb-0.4.8 */ + +#ifndef PB_WIPPERSNAPPER_PWM_PWM_PB_H_INCLUDED +#define PB_WIPPERSNAPPER_PWM_PWM_PB_H_INCLUDED +#include + +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +/* Struct definitions */ +/* * + PWMAdd represents a to a device to attach/allocate a PWM pin. + On ESP32 Arduino, this will "attach" a pin to a LEDC channel/timer group. + On non-ESP32 Arduino, this does nothing. */ +typedef struct _wippersnapper_pwm_PWMAdd { + char pin[6]; /* * The pin to be attached. */ + int32_t frequency; /* * PWM frequency of an analog pin, in Hz. * */ + int32_t resolution; /* * The resolution of an analog pin, in bits. * */ +} wippersnapper_pwm_PWMAdd; + +/* * + PWMAdded represents a response from a device's execution of an + Add message. */ +typedef struct _wippersnapper_pwm_PWMAdded { + char pin[6]; /* * The ed pin. */ + bool did_attach; /* * True if Add successful, False otherwise. */ +} wippersnapper_pwm_PWMAdded; + +/* * + PWMRemove represents a to stop PWM'ing and release the pin for re-use. + On ESP32, this will "detach" a pin from a LEDC channel/timer group. + On non-ESP32 Arduino, this calls digitalWrite(LOW) on the pin */ +typedef struct _wippersnapper_pwm_PWMRemove { + char pin[6]; /* * The PWM pin to de-initialized. */ +} wippersnapper_pwm_PWMRemove; + +/* * + PWMWriteDutyCycle represents a to write a duty cycle to a pin with a frequency (fixed). + This is used for controlling LEDs. */ +typedef struct _wippersnapper_pwm_PWMWriteDutyCycle { + char pin[6]; /* * The pin to write to. */ + int32_t duty_cycle; /* * The desired duty cycle to write (range is from 0 to (2 ** duty_resolution)). +This value will be changed by the slider on Adafruit IO. * */ +} wippersnapper_pwm_PWMWriteDutyCycle; + +/* * + PWMWriteDutyCycleMulti represents a wrapper to write duty cycles to multiple pins. + This is used for controlling RGB/RGBW LEDs. */ +typedef struct _wippersnapper_pwm_PWMWriteDutyCycleMulti { + pb_size_t write_duty_cycle_reqs_count; + wippersnapper_pwm_PWMWriteDutyCycle write_duty_cycle_reqs[4]; /* * Multiple duty cycles to write, one per pin of a RGB LED. * */ +} wippersnapper_pwm_PWMWriteDutyCycleMulti; + +/* * + PWMWriteFrequency represents a to write a Frequency, in Hz, to a pin with a duty cycle of 50%. + This is used for playing tones using a piezo buzzer or speaker. */ +typedef struct _wippersnapper_pwm_PWMWriteFrequency { + char pin[6]; /* * The pin to write to. */ + int32_t frequency; /* * The desired PWM frequency, in Hz. This value will be changed by the slider on Adafruit IO. * */ +} wippersnapper_pwm_PWMWriteFrequency; + + +#ifdef __cplusplus +extern "C" { +#endif + +/* Initializer values for message structs */ +#define wippersnapper_pwm_PWMAdd_init_default {"", 0, 0} +#define wippersnapper_pwm_PWMAdded_init_default {"", 0} +#define wippersnapper_pwm_PWMRemove_init_default {""} +#define wippersnapper_pwm_PWMWriteDutyCycle_init_default {"", 0} +#define wippersnapper_pwm_PWMWriteDutyCycleMulti_init_default {0, {wippersnapper_pwm_PWMWriteDutyCycle_init_default, wippersnapper_pwm_PWMWriteDutyCycle_init_default, wippersnapper_pwm_PWMWriteDutyCycle_init_default, wippersnapper_pwm_PWMWriteDutyCycle_init_default}} +#define wippersnapper_pwm_PWMWriteFrequency_init_default {"", 0} +#define wippersnapper_pwm_PWMAdd_init_zero {"", 0, 0} +#define wippersnapper_pwm_PWMAdded_init_zero {"", 0} +#define wippersnapper_pwm_PWMRemove_init_zero {""} +#define wippersnapper_pwm_PWMWriteDutyCycle_init_zero {"", 0} +#define wippersnapper_pwm_PWMWriteDutyCycleMulti_init_zero {0, {wippersnapper_pwm_PWMWriteDutyCycle_init_zero, wippersnapper_pwm_PWMWriteDutyCycle_init_zero, wippersnapper_pwm_PWMWriteDutyCycle_init_zero, wippersnapper_pwm_PWMWriteDutyCycle_init_zero}} +#define wippersnapper_pwm_PWMWriteFrequency_init_zero {"", 0} + +/* Field tags (for use in manual encoding/decoding) */ +#define wippersnapper_pwm_PWMAdd_pin_tag 1 +#define wippersnapper_pwm_PWMAdd_frequency_tag 2 +#define wippersnapper_pwm_PWMAdd_resolution_tag 3 +#define wippersnapper_pwm_PWMAdded_pin_tag 1 +#define wippersnapper_pwm_PWMAdded_did_attach_tag 2 +#define wippersnapper_pwm_PWMRemove_pin_tag 1 +#define wippersnapper_pwm_PWMWriteDutyCycle_pin_tag 1 +#define wippersnapper_pwm_PWMWriteDutyCycle_duty_cycle_tag 2 +#define wippersnapper_pwm_PWMWriteDutyCycleMulti_write_duty_cycle_reqs_tag 1 +#define wippersnapper_pwm_PWMWriteFrequency_pin_tag 1 +#define wippersnapper_pwm_PWMWriteFrequency_frequency_tag 2 + +/* Struct field encoding specification for nanopb */ +#define wippersnapper_pwm_PWMAdd_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, pin, 1) \ +X(a, STATIC, SINGULAR, INT32, frequency, 2) \ +X(a, STATIC, SINGULAR, INT32, resolution, 3) +#define wippersnapper_pwm_PWMAdd_CALLBACK NULL +#define wippersnapper_pwm_PWMAdd_DEFAULT NULL + +#define wippersnapper_pwm_PWMAdded_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, pin, 1) \ +X(a, STATIC, SINGULAR, BOOL, did_attach, 2) +#define wippersnapper_pwm_PWMAdded_CALLBACK NULL +#define wippersnapper_pwm_PWMAdded_DEFAULT NULL + +#define wippersnapper_pwm_PWMRemove_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, pin, 1) +#define wippersnapper_pwm_PWMRemove_CALLBACK NULL +#define wippersnapper_pwm_PWMRemove_DEFAULT NULL + +#define wippersnapper_pwm_PWMWriteDutyCycle_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, pin, 1) \ +X(a, STATIC, SINGULAR, INT32, duty_cycle, 2) +#define wippersnapper_pwm_PWMWriteDutyCycle_CALLBACK NULL +#define wippersnapper_pwm_PWMWriteDutyCycle_DEFAULT NULL + +#define wippersnapper_pwm_PWMWriteDutyCycleMulti_FIELDLIST(X, a) \ +X(a, STATIC, REPEATED, MESSAGE, write_duty_cycle_reqs, 1) +#define wippersnapper_pwm_PWMWriteDutyCycleMulti_CALLBACK NULL +#define wippersnapper_pwm_PWMWriteDutyCycleMulti_DEFAULT NULL +#define wippersnapper_pwm_PWMWriteDutyCycleMulti_write_duty_cycle_reqs_MSGTYPE wippersnapper_pwm_PWMWriteDutyCycle + +#define wippersnapper_pwm_PWMWriteFrequency_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, pin, 1) \ +X(a, STATIC, SINGULAR, INT32, frequency, 2) +#define wippersnapper_pwm_PWMWriteFrequency_CALLBACK NULL +#define wippersnapper_pwm_PWMWriteFrequency_DEFAULT NULL + +extern const pb_msgdesc_t wippersnapper_pwm_PWMAdd_msg; +extern const pb_msgdesc_t wippersnapper_pwm_PWMAdded_msg; +extern const pb_msgdesc_t wippersnapper_pwm_PWMRemove_msg; +extern const pb_msgdesc_t wippersnapper_pwm_PWMWriteDutyCycle_msg; +extern const pb_msgdesc_t wippersnapper_pwm_PWMWriteDutyCycleMulti_msg; +extern const pb_msgdesc_t wippersnapper_pwm_PWMWriteFrequency_msg; + +/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ +#define wippersnapper_pwm_PWMAdd_fields &wippersnapper_pwm_PWMAdd_msg +#define wippersnapper_pwm_PWMAdded_fields &wippersnapper_pwm_PWMAdded_msg +#define wippersnapper_pwm_PWMRemove_fields &wippersnapper_pwm_PWMRemove_msg +#define wippersnapper_pwm_PWMWriteDutyCycle_fields &wippersnapper_pwm_PWMWriteDutyCycle_msg +#define wippersnapper_pwm_PWMWriteDutyCycleMulti_fields &wippersnapper_pwm_PWMWriteDutyCycleMulti_msg +#define wippersnapper_pwm_PWMWriteFrequency_fields &wippersnapper_pwm_PWMWriteFrequency_msg + +/* Maximum encoded size of messages (where known) */ +#define WIPPERSNAPPER_PWM_PWM_PB_H_MAX_SIZE wippersnapper_pwm_PWMWriteDutyCycleMulti_size +#define wippersnapper_pwm_PWMAdd_size 29 +#define wippersnapper_pwm_PWMAdded_size 9 +#define wippersnapper_pwm_PWMRemove_size 7 +#define wippersnapper_pwm_PWMWriteDutyCycleMulti_size 80 +#define wippersnapper_pwm_PWMWriteDutyCycle_size 18 +#define wippersnapper_pwm_PWMWriteFrequency_size 18 + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/src/protos/sensor.pb.c b/src/protos/sensor.pb.c new file mode 100644 index 000000000..6aabf0885 --- /dev/null +++ b/src/protos/sensor.pb.c @@ -0,0 +1,22 @@ +/* Automatically generated nanopb constant definitions */ +/* Generated by nanopb-0.4.8 */ + +#include "sensor.pb.h" +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +PB_BIND(wippersnapper_sensor_SensorEvent, wippersnapper_sensor_SensorEvent, AUTO) + + +PB_BIND(wippersnapper_sensor_SensorEvent_SensorEventColor, wippersnapper_sensor_SensorEvent_SensorEventColor, AUTO) + + +PB_BIND(wippersnapper_sensor_SensorEvent_SensorEvent3DVector, wippersnapper_sensor_SensorEvent_SensorEvent3DVector, AUTO) + + +PB_BIND(wippersnapper_sensor_SensorEvent_SensorEventOrientation, wippersnapper_sensor_SensorEvent_SensorEventOrientation, AUTO) + + + + diff --git a/src/protos/sensor.pb.h b/src/protos/sensor.pb.h new file mode 100644 index 000000000..ca74080a0 --- /dev/null +++ b/src/protos/sensor.pb.h @@ -0,0 +1,201 @@ +/* Automatically generated nanopb header */ +/* Generated by nanopb-0.4.8 */ + +#ifndef PB_WIPPERSNAPPER_SENSOR_SENSOR_PB_H_INCLUDED +#define PB_WIPPERSNAPPER_SENSOR_SENSOR_PB_H_INCLUDED +#include + +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +/* Enum definitions */ +/* * + SensorType allows us determine what types of units the sensor uses, etc. */ +typedef enum _wippersnapper_sensor_SensorType { + wippersnapper_sensor_SensorType_SENSOR_TYPE_UNSPECIFIED = 0, /* * Sensor value type which is not defined by this list, "Raw Value: {value}". */ + wippersnapper_sensor_SensorType_SENSOR_TYPE_ACCELEROMETER = 1, /* * Acceleration, in meter per second per second, "{value}m/s/s". */ + wippersnapper_sensor_SensorType_SENSOR_TYPE_MAGNETIC_FIELD = 2, /* * Magnetic field strength, in micro-Tesla, "{value}µT". */ + wippersnapper_sensor_SensorType_SENSOR_TYPE_ORIENTATION = 3, /* * Orientation angle, in degrees, "{value}°". */ + wippersnapper_sensor_SensorType_SENSOR_TYPE_GYROSCOPE = 4, /* * Angular rate, in radians per second, "{value}rad/s". */ + wippersnapper_sensor_SensorType_SENSOR_TYPE_LIGHT = 5, /* * Light-level, non-unit-specific (For a unit-specific measurement, see: Lux), +"Raw Value: {value}". */ + wippersnapper_sensor_SensorType_SENSOR_TYPE_PRESSURE = 6, /* * Pressure, in hectopascal, , "{value}hPa". */ + wippersnapper_sensor_SensorType_SENSOR_TYPE_PROXIMITY = 8, /* * Distance from an object to a sensor, non-unit-specific, "Raw Value: {value}". */ + wippersnapper_sensor_SensorType_SENSOR_TYPE_GRAVITY = 9, /* * Metres per second squared, "{value}m/s^2". */ + wippersnapper_sensor_SensorType_SENSOR_TYPE_LINEAR_ACCELERATION = 10, /* * Acceleration not including gravity, in meter per second squared, "{value}m/s^2". */ + wippersnapper_sensor_SensorType_SENSOR_TYPE_ROTATION_VECTOR = 11, /* * An angle in radians, "{value} rad". */ + wippersnapper_sensor_SensorType_SENSOR_TYPE_RELATIVE_HUMIDITY = 12, /* * in percent (%), "{value}%". */ + wippersnapper_sensor_SensorType_SENSOR_TYPE_AMBIENT_TEMPERATURE = 13, /* * Temperature of the air around a sensor, in degrees Celsius, "{value}°C". */ + wippersnapper_sensor_SensorType_SENSOR_TYPE_OBJECT_TEMPERATURE = 14, /* * Temperature of the object a sensor is touching/pointed at, in degrees Celsius, "{value}°C". */ + wippersnapper_sensor_SensorType_SENSOR_TYPE_VOLTAGE = 15, /* * Volts, "{value}V". */ + wippersnapper_sensor_SensorType_SENSOR_TYPE_CURRENT = 16, /* * Milliamps, "{value}mA". */ + wippersnapper_sensor_SensorType_SENSOR_TYPE_COLOR = 17, /* * Values are in 0..1.0 RGB channel luminosity and 32-bit RGBA format. "Color: {value}". */ + wippersnapper_sensor_SensorType_SENSOR_TYPE_RAW = 18, /* * Sensor reads a value which is not defined by this list, "Raw Value: {value}". */ + wippersnapper_sensor_SensorType_SENSOR_TYPE_PM10_STD = 19, /* * Standard Particulate Matter 1.0, in ppm, "{value}ppm". */ + wippersnapper_sensor_SensorType_SENSOR_TYPE_PM25_STD = 20, /* * Standard Particulate Matter 2.5, in ppm, "{value}ppm". */ + wippersnapper_sensor_SensorType_SENSOR_TYPE_PM100_STD = 21, /* * Standard Particulate Matter 100, in ppm, "{value}ppm". */ + wippersnapper_sensor_SensorType_SENSOR_TYPE_PM10_ENV = 22, /* * Environmental Particulate Matter 1.0, in ppm, "{value}ppm". */ + wippersnapper_sensor_SensorType_SENSOR_TYPE_PM25_ENV = 23, /* * Environmental Particulate Matter 2.5, in ppm, "{value}ppm". */ + wippersnapper_sensor_SensorType_SENSOR_TYPE_PM100_ENV = 24, /* * Environmental Particulate Matter 100, in ppm, "{value}ppm". */ + wippersnapper_sensor_SensorType_SENSOR_TYPE_CO2 = 25, /* * Measured CO2, in ppm, "{value}ppm". */ + wippersnapper_sensor_SensorType_SENSOR_TYPE_GAS_RESISTANCE = 26, /* * Proportional to the amount of VOC particles in the air, in Ohms, "{value}Ω". */ + wippersnapper_sensor_SensorType_SENSOR_TYPE_ALTITUDE = 27, /* * Values are in meters (m), "${$v} m". */ + wippersnapper_sensor_SensorType_SENSOR_TYPE_LUX = 28, /* * Light level, in lux, "Lux: {value}". */ + wippersnapper_sensor_SensorType_SENSOR_TYPE_ECO2 = 29, /* * equivalent/estimated CO2 in ppm (estimated from some other measurement), "{value}ppm". */ + wippersnapper_sensor_SensorType_SENSOR_TYPE_UNITLESS_PERCENT = 30, /* * Percentage, unit-less, "{value}%". */ + wippersnapper_sensor_SensorType_SENSOR_TYPE_AMBIENT_TEMPERATURE_FAHRENHEIT = 31, /* * Temperature of the air around a sensor, in degrees Fahrenheit, "{value}°F". */ + wippersnapper_sensor_SensorType_SENSOR_TYPE_OBJECT_TEMPERATURE_FAHRENHEIT = 32, /* * Temperature of the object a sensor is touching/pointed at, in Fahrenheit, "{value}°F". */ + wippersnapper_sensor_SensorType_SENSOR_TYPE_VOC_INDEX = 33, /* * Values are an index from 1-500 with 100 being normal, "${$v} VOC". */ + wippersnapper_sensor_SensorType_SENSOR_TYPE_NOX_INDEX = 34, /* * Values are an index from 1-500 with 100 being normal, "${$v} NOx". */ + wippersnapper_sensor_SensorType_SENSOR_TYPE_TVOC = 35, /* * Values are in parts per billion (ppb), "${$v} ppb". */ + wippersnapper_sensor_SensorType_SENSOR_TYPE_BYTES = 36, /* * Values are in bytes, "${$v} bytes". */ + wippersnapper_sensor_SensorType_SENSOR_TYPE_BOOLEAN = 37 /* * Values are boolean, "Boolean Value: ${$v}". */ +} wippersnapper_sensor_SensorType; + +/* Struct definitions */ +/* * + SensorEventColor is used to return a sensor's color values in RGB colorspace. */ +typedef struct _wippersnapper_sensor_SensorEvent_SensorEventColor { + float r; /* * The sensor's red channel value as a float. */ + float g; /* * The sensor's green channel value as a float. */ + float b; /* * The sensor's blue channel value as a float. */ + float a; /* * The sensor's (optional) alpha channel value as a float. */ +} wippersnapper_sensor_SensorEvent_SensorEventColor; + +/* * + SensorEvent3DVector is used to return a sensor's 3D vector values. */ +typedef struct _wippersnapper_sensor_SensorEvent_SensorEvent3DVector { + float x; /* * The sensor's x-axis value as a float. */ + float y; /* * The sensor's y-axis value as a float. */ + float z; /* * The sensor's z-axis value as a float. */ +} wippersnapper_sensor_SensorEvent_SensorEvent3DVector; + +/* * + SensorEventOrientation is used to return an orientation sensor's values. */ +typedef struct _wippersnapper_sensor_SensorEvent_SensorEventOrientation { + float roll; /* * The sensor's roll value as a float. */ + float pitch; /* * The sensor's pitch value as a float. */ + float heading; /* * The sensor's heading value as a float. */ +} wippersnapper_sensor_SensorEvent_SensorEventOrientation; + +/* * + SensorEvent is used to return the sensor's value and type. */ +typedef struct _wippersnapper_sensor_SensorEvent { + wippersnapper_sensor_SensorType type; /* * The sensor's type and corresponding SI unit */ + pb_size_t which_value; + union { + float float_value; /* * The sensor's value as a float. */ + pb_callback_t bytes_value; /* * The sensor's value as a byte array. */ + wippersnapper_sensor_SensorEvent_SensorEvent3DVector vector_value; /* * The sensor's 3D vector values, as floats. */ + wippersnapper_sensor_SensorEvent_SensorEventOrientation orientation_value; /* * The sensor's orientation values, as floats. */ + wippersnapper_sensor_SensorEvent_SensorEventColor color_value; /* * The sensor's color values, as floats. */ + bool bool_value; /* * The sensor's value, as a boolean. */ + } value; +} wippersnapper_sensor_SensorEvent; + + +#ifdef __cplusplus +extern "C" { +#endif + +/* Helper constants for enums */ +#define _wippersnapper_sensor_SensorType_MIN wippersnapper_sensor_SensorType_SENSOR_TYPE_UNSPECIFIED +#define _wippersnapper_sensor_SensorType_MAX wippersnapper_sensor_SensorType_SENSOR_TYPE_BOOLEAN +#define _wippersnapper_sensor_SensorType_ARRAYSIZE ((wippersnapper_sensor_SensorType)(wippersnapper_sensor_SensorType_SENSOR_TYPE_BOOLEAN+1)) + +#define wippersnapper_sensor_SensorEvent_type_ENUMTYPE wippersnapper_sensor_SensorType + + + + + +/* Initializer values for message structs */ +#define wippersnapper_sensor_SensorEvent_init_default {_wippersnapper_sensor_SensorType_MIN, 0, {0}} +#define wippersnapper_sensor_SensorEvent_SensorEventColor_init_default {0, 0, 0, 0} +#define wippersnapper_sensor_SensorEvent_SensorEvent3DVector_init_default {0, 0, 0} +#define wippersnapper_sensor_SensorEvent_SensorEventOrientation_init_default {0, 0, 0} +#define wippersnapper_sensor_SensorEvent_init_zero {_wippersnapper_sensor_SensorType_MIN, 0, {0}} +#define wippersnapper_sensor_SensorEvent_SensorEventColor_init_zero {0, 0, 0, 0} +#define wippersnapper_sensor_SensorEvent_SensorEvent3DVector_init_zero {0, 0, 0} +#define wippersnapper_sensor_SensorEvent_SensorEventOrientation_init_zero {0, 0, 0} + +/* Field tags (for use in manual encoding/decoding) */ +#define wippersnapper_sensor_SensorEvent_SensorEventColor_r_tag 1 +#define wippersnapper_sensor_SensorEvent_SensorEventColor_g_tag 2 +#define wippersnapper_sensor_SensorEvent_SensorEventColor_b_tag 3 +#define wippersnapper_sensor_SensorEvent_SensorEventColor_a_tag 4 +#define wippersnapper_sensor_SensorEvent_SensorEvent3DVector_x_tag 1 +#define wippersnapper_sensor_SensorEvent_SensorEvent3DVector_y_tag 2 +#define wippersnapper_sensor_SensorEvent_SensorEvent3DVector_z_tag 3 +#define wippersnapper_sensor_SensorEvent_SensorEventOrientation_roll_tag 1 +#define wippersnapper_sensor_SensorEvent_SensorEventOrientation_pitch_tag 2 +#define wippersnapper_sensor_SensorEvent_SensorEventOrientation_heading_tag 3 +#define wippersnapper_sensor_SensorEvent_type_tag 1 +#define wippersnapper_sensor_SensorEvent_float_value_tag 2 +#define wippersnapper_sensor_SensorEvent_bytes_value_tag 3 +#define wippersnapper_sensor_SensorEvent_vector_value_tag 4 +#define wippersnapper_sensor_SensorEvent_orientation_value_tag 5 +#define wippersnapper_sensor_SensorEvent_color_value_tag 6 +#define wippersnapper_sensor_SensorEvent_bool_value_tag 7 + +/* Struct field encoding specification for nanopb */ +#define wippersnapper_sensor_SensorEvent_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UENUM, type, 1) \ +X(a, STATIC, ONEOF, FLOAT, (value,float_value,value.float_value), 2) \ +X(a, CALLBACK, ONEOF, BYTES, (value,bytes_value,value.bytes_value), 3) \ +X(a, STATIC, ONEOF, MESSAGE, (value,vector_value,value.vector_value), 4) \ +X(a, STATIC, ONEOF, MESSAGE, (value,orientation_value,value.orientation_value), 5) \ +X(a, STATIC, ONEOF, MESSAGE, (value,color_value,value.color_value), 6) \ +X(a, STATIC, ONEOF, BOOL, (value,bool_value,value.bool_value), 7) +#define wippersnapper_sensor_SensorEvent_CALLBACK pb_default_field_callback +#define wippersnapper_sensor_SensorEvent_DEFAULT NULL +#define wippersnapper_sensor_SensorEvent_value_vector_value_MSGTYPE wippersnapper_sensor_SensorEvent_SensorEvent3DVector +#define wippersnapper_sensor_SensorEvent_value_orientation_value_MSGTYPE wippersnapper_sensor_SensorEvent_SensorEventOrientation +#define wippersnapper_sensor_SensorEvent_value_color_value_MSGTYPE wippersnapper_sensor_SensorEvent_SensorEventColor + +#define wippersnapper_sensor_SensorEvent_SensorEventColor_FIELDLIST(X, a_) \ +X(a_, STATIC, SINGULAR, FLOAT, r, 1) \ +X(a_, STATIC, SINGULAR, FLOAT, g, 2) \ +X(a_, STATIC, SINGULAR, FLOAT, b, 3) \ +X(a_, STATIC, SINGULAR, FLOAT, a, 4) +#define wippersnapper_sensor_SensorEvent_SensorEventColor_CALLBACK NULL +#define wippersnapper_sensor_SensorEvent_SensorEventColor_DEFAULT NULL + +#define wippersnapper_sensor_SensorEvent_SensorEvent3DVector_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, FLOAT, x, 1) \ +X(a, STATIC, SINGULAR, FLOAT, y, 2) \ +X(a, STATIC, SINGULAR, FLOAT, z, 3) +#define wippersnapper_sensor_SensorEvent_SensorEvent3DVector_CALLBACK NULL +#define wippersnapper_sensor_SensorEvent_SensorEvent3DVector_DEFAULT NULL + +#define wippersnapper_sensor_SensorEvent_SensorEventOrientation_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, FLOAT, roll, 1) \ +X(a, STATIC, SINGULAR, FLOAT, pitch, 2) \ +X(a, STATIC, SINGULAR, FLOAT, heading, 3) +#define wippersnapper_sensor_SensorEvent_SensorEventOrientation_CALLBACK NULL +#define wippersnapper_sensor_SensorEvent_SensorEventOrientation_DEFAULT NULL + +extern const pb_msgdesc_t wippersnapper_sensor_SensorEvent_msg; +extern const pb_msgdesc_t wippersnapper_sensor_SensorEvent_SensorEventColor_msg; +extern const pb_msgdesc_t wippersnapper_sensor_SensorEvent_SensorEvent3DVector_msg; +extern const pb_msgdesc_t wippersnapper_sensor_SensorEvent_SensorEventOrientation_msg; + +/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ +#define wippersnapper_sensor_SensorEvent_fields &wippersnapper_sensor_SensorEvent_msg +#define wippersnapper_sensor_SensorEvent_SensorEventColor_fields &wippersnapper_sensor_SensorEvent_SensorEventColor_msg +#define wippersnapper_sensor_SensorEvent_SensorEvent3DVector_fields &wippersnapper_sensor_SensorEvent_SensorEvent3DVector_msg +#define wippersnapper_sensor_SensorEvent_SensorEventOrientation_fields &wippersnapper_sensor_SensorEvent_SensorEventOrientation_msg + +/* Maximum encoded size of messages (where known) */ +/* wippersnapper_sensor_SensorEvent_size depends on runtime parameters */ +#define WIPPERSNAPPER_SENSOR_SENSOR_PB_H_MAX_SIZE wippersnapper_sensor_SensorEvent_SensorEventColor_size +#define wippersnapper_sensor_SensorEvent_SensorEvent3DVector_size 15 +#define wippersnapper_sensor_SensorEvent_SensorEventColor_size 20 +#define wippersnapper_sensor_SensorEvent_SensorEventOrientation_size 15 + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/src/protos/servo.pb.c b/src/protos/servo.pb.c new file mode 100644 index 000000000..902cce097 --- /dev/null +++ b/src/protos/servo.pb.c @@ -0,0 +1,21 @@ +/* Automatically generated nanopb constant definitions */ +/* Generated by nanopb-0.4.8 */ + +#include "servo.pb.h" +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +PB_BIND(wippersnapper_servo_ServoAdd, wippersnapper_servo_ServoAdd, AUTO) + + +PB_BIND(wippersnapper_servo_ServoAdded, wippersnapper_servo_ServoAdded, AUTO) + + +PB_BIND(wippersnapper_servo_ServoRemove, wippersnapper_servo_ServoRemove, AUTO) + + +PB_BIND(wippersnapper_servo_ServoWrite, wippersnapper_servo_ServoWrite, AUTO) + + + diff --git a/src/protos/servo.pb.h b/src/protos/servo.pb.h new file mode 100644 index 000000000..5471c9f55 --- /dev/null +++ b/src/protos/servo.pb.h @@ -0,0 +1,120 @@ +/* Automatically generated nanopb header */ +/* Generated by nanopb-0.4.8 */ + +#ifndef PB_WIPPERSNAPPER_SERVO_SERVO_PB_H_INCLUDED +#define PB_WIPPERSNAPPER_SERVO_SERVO_PB_H_INCLUDED +#include + +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +/* Struct definitions */ +/* * + ServoAdd represents a request to attach a servo to a pin. */ +typedef struct _wippersnapper_servo_ServoAdd { + char servo_pin[6]; /* * The name of pin to attach a servo to. */ + int32_t servo_freq; /* * The overall PWM frequency, default sent by Adafruit IO is 50Hz. * */ + int32_t min_pulse_width; /* * The minimum pulse length in uS. Default sent by Adafruit IO is 500uS. * */ + int32_t max_pulse_width; /* * The maximum pulse length in uS. Default sent by Adafruit IO is 2500uS. * */ +} wippersnapper_servo_ServoAdd; + +/* * + ServoAdded represents the result of attaching a servo to a pin. */ +typedef struct _wippersnapper_servo_ServoAdded { + bool attach_success; /* * True if a servo was attached successfully, False otherwise. * */ + char servo_pin[6]; /* * The name of pin we're responding about. */ +} wippersnapper_servo_ServoAdded; + +/* * + ServoRemove represents a request to detach a servo from a pin and de-initialize the pin for other uses. */ +typedef struct _wippersnapper_servo_ServoRemove { + char servo_pin[6]; /* * The name of pin to use as a servo pin. */ +} wippersnapper_servo_ServoRemove; + +/* * + ServoWrite represents a message to write the servo's position. + + NOTE: Position is sent from Adafruit IO as a pulse width in uS between 0uS + and 2500uS. The client application must convert pulse width to duty cycle w/fixed + freq of 50Hz prior to writing to the servo pin. */ +typedef struct _wippersnapper_servo_ServoWrite { + char servo_pin[6]; /* * The name of pin we're addressing. */ + int32_t pulse_width; /* * The pulse width to write to the servo, in uS * */ +} wippersnapper_servo_ServoWrite; + + +#ifdef __cplusplus +extern "C" { +#endif + +/* Initializer values for message structs */ +#define wippersnapper_servo_ServoAdd_init_default {"", 0, 0, 0} +#define wippersnapper_servo_ServoAdded_init_default {0, ""} +#define wippersnapper_servo_ServoRemove_init_default {""} +#define wippersnapper_servo_ServoWrite_init_default {"", 0} +#define wippersnapper_servo_ServoAdd_init_zero {"", 0, 0, 0} +#define wippersnapper_servo_ServoAdded_init_zero {0, ""} +#define wippersnapper_servo_ServoRemove_init_zero {""} +#define wippersnapper_servo_ServoWrite_init_zero {"", 0} + +/* Field tags (for use in manual encoding/decoding) */ +#define wippersnapper_servo_ServoAdd_servo_pin_tag 1 +#define wippersnapper_servo_ServoAdd_servo_freq_tag 2 +#define wippersnapper_servo_ServoAdd_min_pulse_width_tag 3 +#define wippersnapper_servo_ServoAdd_max_pulse_width_tag 4 +#define wippersnapper_servo_ServoAdded_attach_success_tag 1 +#define wippersnapper_servo_ServoAdded_servo_pin_tag 2 +#define wippersnapper_servo_ServoRemove_servo_pin_tag 1 +#define wippersnapper_servo_ServoWrite_servo_pin_tag 1 +#define wippersnapper_servo_ServoWrite_pulse_width_tag 2 + +/* Struct field encoding specification for nanopb */ +#define wippersnapper_servo_ServoAdd_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, servo_pin, 1) \ +X(a, STATIC, SINGULAR, INT32, servo_freq, 2) \ +X(a, STATIC, SINGULAR, INT32, min_pulse_width, 3) \ +X(a, STATIC, SINGULAR, INT32, max_pulse_width, 4) +#define wippersnapper_servo_ServoAdd_CALLBACK NULL +#define wippersnapper_servo_ServoAdd_DEFAULT NULL + +#define wippersnapper_servo_ServoAdded_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, BOOL, attach_success, 1) \ +X(a, STATIC, SINGULAR, STRING, servo_pin, 2) +#define wippersnapper_servo_ServoAdded_CALLBACK NULL +#define wippersnapper_servo_ServoAdded_DEFAULT NULL + +#define wippersnapper_servo_ServoRemove_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, servo_pin, 1) +#define wippersnapper_servo_ServoRemove_CALLBACK NULL +#define wippersnapper_servo_ServoRemove_DEFAULT NULL + +#define wippersnapper_servo_ServoWrite_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, servo_pin, 1) \ +X(a, STATIC, SINGULAR, INT32, pulse_width, 2) +#define wippersnapper_servo_ServoWrite_CALLBACK NULL +#define wippersnapper_servo_ServoWrite_DEFAULT NULL + +extern const pb_msgdesc_t wippersnapper_servo_ServoAdd_msg; +extern const pb_msgdesc_t wippersnapper_servo_ServoAdded_msg; +extern const pb_msgdesc_t wippersnapper_servo_ServoRemove_msg; +extern const pb_msgdesc_t wippersnapper_servo_ServoWrite_msg; + +/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ +#define wippersnapper_servo_ServoAdd_fields &wippersnapper_servo_ServoAdd_msg +#define wippersnapper_servo_ServoAdded_fields &wippersnapper_servo_ServoAdded_msg +#define wippersnapper_servo_ServoRemove_fields &wippersnapper_servo_ServoRemove_msg +#define wippersnapper_servo_ServoWrite_fields &wippersnapper_servo_ServoWrite_msg + +/* Maximum encoded size of messages (where known) */ +#define WIPPERSNAPPER_SERVO_SERVO_PB_H_MAX_SIZE wippersnapper_servo_ServoAdd_size +#define wippersnapper_servo_ServoAdd_size 40 +#define wippersnapper_servo_ServoAdded_size 9 +#define wippersnapper_servo_ServoRemove_size 7 +#define wippersnapper_servo_ServoWrite_size 18 + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/src/protos/signal.pb.c b/src/protos/signal.pb.c new file mode 100644 index 000000000..0531e4784 --- /dev/null +++ b/src/protos/signal.pb.c @@ -0,0 +1,15 @@ +/* Automatically generated nanopb constant definitions */ +/* Generated by nanopb-0.4.8 */ + +#include "signal.pb.h" +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +PB_BIND(wippersnapper_signal_BrokerToDevice, wippersnapper_signal_BrokerToDevice, 2) + + +PB_BIND(wippersnapper_signal_DeviceToBroker, wippersnapper_signal_DeviceToBroker, 2) + + + diff --git a/src/protos/signal.pb.h b/src/protos/signal.pb.h new file mode 100644 index 000000000..be9fb3cee --- /dev/null +++ b/src/protos/signal.pb.h @@ -0,0 +1,272 @@ +/* Automatically generated nanopb header */ +/* Generated by nanopb-0.4.8 */ + +#ifndef PB_WIPPERSNAPPER_SIGNAL_SIGNAL_PB_H_INCLUDED +#define PB_WIPPERSNAPPER_SIGNAL_SIGNAL_PB_H_INCLUDED +#include +#include "analogio.pb.h" +#include "checkin.pb.h" +#include "digitalio.pb.h" +#include "ds18x20.pb.h" +#include "error.pb.h" +#include "i2c.pb.h" +#include "pixels.pb.h" +#include "pwm.pb.h" +#include "servo.pb.h" +#include "uart.pb.h" + +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +/* Struct definitions */ +/* BrokerToDevice + The BrokerToDevice message is sent from the broker to the device. + It contains a oneof payload, which is a union of all the possible + messages that can be sent from the broker to a device. */ +typedef struct _wippersnapper_signal_BrokerToDevice { + pb_callback_t cb_payload; + pb_size_t which_payload; + union { + /* digitalio.proto */ + wippersnapper_digitalio_DigitalIOAdd digitalio_add; + wippersnapper_digitalio_DigitalIORemove digitalio_remove; + wippersnapper_digitalio_DigitalIOEvent digitalio_event; + wippersnapper_digitalio_DigitalIOWrite digitalio_write; + /* analogio.proto */ + wippersnapper_analogio_AnalogIOAdd analogio_add; + wippersnapper_analogio_AnalogIORemove analogio_remove; + /* checkin.proto */ + wippersnapper_checkin_CheckinResponse checkin_response; + /* servo.proto */ + wippersnapper_servo_ServoAdd servo_add; + wippersnapper_servo_ServoRemove servo_remove; + wippersnapper_servo_ServoWrite servo_write; + /* pwm.proto */ + wippersnapper_pwm_PWMAdd pwm_add; + wippersnapper_pwm_PWMRemove pwm_remove; + wippersnapper_pwm_PWMWriteDutyCycle pwm_write_duty; + wippersnapper_pwm_PWMWriteDutyCycleMulti pwm_write_duty_multi; + wippersnapper_pwm_PWMWriteFrequency pwm_write_freq; + /* pixels.proto */ + wippersnapper_pixels_PixelsAdd pixels_add; + wippersnapper_pixels_PixelsRemove pixels_remove; + wippersnapper_pixels_PixelsWrite pixels_write; + /* ds18x20.proto */ + wippersnapper_ds18x20_Ds18x20Add ds18x20_add; + wippersnapper_ds18x20_Ds18x20Remove ds18x20_remove; + /* uart.proto */ + wippersnapper_uart_UARTAdd uart_add; + wippersnapper_uart_UARTRemove uart_remove; + /* i2c.proto */ + wippersnapper_i2c_I2cBusScan i2c_bus_scan; + wippersnapper_i2c_I2cDeviceAddOrReplace i2c_device_add_replace; + wippersnapper_i2c_I2cDeviceRemove i2c_device_remove; + /* error.proto */ + wippersnapper_error_Error error; + } payload; +} wippersnapper_signal_BrokerToDevice; + +/* DeviceToBroker + The DeviceToBroker message is sent from the device to the broker. + It contains a oneof payload, which is a union of all the possible + messages that can be sent from a device to the broker. */ +typedef struct _wippersnapper_signal_DeviceToBroker { + pb_callback_t cb_payload; + pb_size_t which_payload; + union { + /* digitalio.proto */ + wippersnapper_digitalio_DigitalIOEvent digitalio_event; + /* analogio.proto */ + wippersnapper_analogio_AnalogIOEvent analogio_event; + /* checkin.proto */ + wippersnapper_checkin_CheckinRequest checkin_request; + /* servo.proto */ + wippersnapper_servo_ServoAdded servo_added; + /* pwm.proto */ + wippersnapper_pwm_PWMAdded pwm_added; + /* pixels.proto */ + wippersnapper_pixels_PixelsAdded pixels_added; + /* ds18x20.proto */ + wippersnapper_ds18x20_Ds18x20Added ds18x20_added; + wippersnapper_ds18x20_Ds18x20Event ds18x20_event; + /* uart.proto */ + wippersnapper_uart_UARTAdded uart_added; + wippersnapper_uart_UARTEvent uart_event; + /* i2c.proto */ + wippersnapper_i2c_I2cBusScanned i2c_bus_scanned; + wippersnapper_i2c_I2cDeviceAddedOrReplaced i2c_device_added_replaced; + wippersnapper_i2c_I2cDeviceRemoved i2c_device_removed; + wippersnapper_i2c_I2cDeviceEvent i2c_device_event; + } payload; +} wippersnapper_signal_DeviceToBroker; + + +#ifdef __cplusplus +extern "C" { +#endif + +/* Initializer values for message structs */ +#define wippersnapper_signal_BrokerToDevice_init_default {{{NULL}, NULL}, 0, {wippersnapper_digitalio_DigitalIOAdd_init_default}} +#define wippersnapper_signal_DeviceToBroker_init_default {{{NULL}, NULL}, 0, {wippersnapper_digitalio_DigitalIOEvent_init_default}} +#define wippersnapper_signal_BrokerToDevice_init_zero {{{NULL}, NULL}, 0, {wippersnapper_digitalio_DigitalIOAdd_init_zero}} +#define wippersnapper_signal_DeviceToBroker_init_zero {{{NULL}, NULL}, 0, {wippersnapper_digitalio_DigitalIOEvent_init_zero}} + +/* Field tags (for use in manual encoding/decoding) */ +#define wippersnapper_signal_BrokerToDevice_digitalio_add_tag 10 +#define wippersnapper_signal_BrokerToDevice_digitalio_remove_tag 11 +#define wippersnapper_signal_BrokerToDevice_digitalio_event_tag 12 +#define wippersnapper_signal_BrokerToDevice_digitalio_write_tag 13 +#define wippersnapper_signal_BrokerToDevice_analogio_add_tag 20 +#define wippersnapper_signal_BrokerToDevice_analogio_remove_tag 21 +#define wippersnapper_signal_BrokerToDevice_checkin_response_tag 30 +#define wippersnapper_signal_BrokerToDevice_servo_add_tag 40 +#define wippersnapper_signal_BrokerToDevice_servo_remove_tag 41 +#define wippersnapper_signal_BrokerToDevice_servo_write_tag 42 +#define wippersnapper_signal_BrokerToDevice_pwm_add_tag 50 +#define wippersnapper_signal_BrokerToDevice_pwm_remove_tag 51 +#define wippersnapper_signal_BrokerToDevice_pwm_write_duty_tag 52 +#define wippersnapper_signal_BrokerToDevice_pwm_write_duty_multi_tag 53 +#define wippersnapper_signal_BrokerToDevice_pwm_write_freq_tag 54 +#define wippersnapper_signal_BrokerToDevice_pixels_add_tag 60 +#define wippersnapper_signal_BrokerToDevice_pixels_remove_tag 61 +#define wippersnapper_signal_BrokerToDevice_pixels_write_tag 62 +#define wippersnapper_signal_BrokerToDevice_ds18x20_add_tag 70 +#define wippersnapper_signal_BrokerToDevice_ds18x20_remove_tag 71 +#define wippersnapper_signal_BrokerToDevice_uart_add_tag 80 +#define wippersnapper_signal_BrokerToDevice_uart_remove_tag 81 +#define wippersnapper_signal_BrokerToDevice_i2c_bus_scan_tag 90 +#define wippersnapper_signal_BrokerToDevice_i2c_device_add_replace_tag 91 +#define wippersnapper_signal_BrokerToDevice_i2c_device_remove_tag 92 +#define wippersnapper_signal_BrokerToDevice_error_tag 100 +#define wippersnapper_signal_DeviceToBroker_digitalio_event_tag 10 +#define wippersnapper_signal_DeviceToBroker_analogio_event_tag 20 +#define wippersnapper_signal_DeviceToBroker_checkin_request_tag 30 +#define wippersnapper_signal_DeviceToBroker_servo_added_tag 40 +#define wippersnapper_signal_DeviceToBroker_pwm_added_tag 50 +#define wippersnapper_signal_DeviceToBroker_pixels_added_tag 60 +#define wippersnapper_signal_DeviceToBroker_ds18x20_added_tag 70 +#define wippersnapper_signal_DeviceToBroker_ds18x20_event_tag 80 +#define wippersnapper_signal_DeviceToBroker_uart_added_tag 90 +#define wippersnapper_signal_DeviceToBroker_uart_event_tag 100 +#define wippersnapper_signal_DeviceToBroker_i2c_bus_scanned_tag 110 +#define wippersnapper_signal_DeviceToBroker_i2c_device_added_replaced_tag 111 +#define wippersnapper_signal_DeviceToBroker_i2c_device_removed_tag 112 +#define wippersnapper_signal_DeviceToBroker_i2c_device_event_tag 113 + +/* Struct field encoding specification for nanopb */ +#define wippersnapper_signal_BrokerToDevice_FIELDLIST(X, a) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,digitalio_add,payload.digitalio_add), 10) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,digitalio_remove,payload.digitalio_remove), 11) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,digitalio_event,payload.digitalio_event), 12) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,digitalio_write,payload.digitalio_write), 13) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,analogio_add,payload.analogio_add), 20) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,analogio_remove,payload.analogio_remove), 21) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,checkin_response,payload.checkin_response), 30) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,servo_add,payload.servo_add), 40) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,servo_remove,payload.servo_remove), 41) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,servo_write,payload.servo_write), 42) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,pwm_add,payload.pwm_add), 50) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,pwm_remove,payload.pwm_remove), 51) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,pwm_write_duty,payload.pwm_write_duty), 52) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,pwm_write_duty_multi,payload.pwm_write_duty_multi), 53) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,pwm_write_freq,payload.pwm_write_freq), 54) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,pixels_add,payload.pixels_add), 60) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,pixels_remove,payload.pixels_remove), 61) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,pixels_write,payload.pixels_write), 62) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,ds18x20_add,payload.ds18x20_add), 70) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,ds18x20_remove,payload.ds18x20_remove), 71) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,uart_add,payload.uart_add), 80) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,uart_remove,payload.uart_remove), 81) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,i2c_bus_scan,payload.i2c_bus_scan), 90) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,i2c_device_add_replace,payload.i2c_device_add_replace), 91) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,i2c_device_remove,payload.i2c_device_remove), 92) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,error,payload.error), 100) +#define wippersnapper_signal_BrokerToDevice_CALLBACK NULL +#define wippersnapper_signal_BrokerToDevice_DEFAULT NULL +#define wippersnapper_signal_BrokerToDevice_payload_digitalio_add_MSGTYPE wippersnapper_digitalio_DigitalIOAdd +#define wippersnapper_signal_BrokerToDevice_payload_digitalio_remove_MSGTYPE wippersnapper_digitalio_DigitalIORemove +#define wippersnapper_signal_BrokerToDevice_payload_digitalio_event_MSGTYPE wippersnapper_digitalio_DigitalIOEvent +#define wippersnapper_signal_BrokerToDevice_payload_digitalio_write_MSGTYPE wippersnapper_digitalio_DigitalIOWrite +#define wippersnapper_signal_BrokerToDevice_payload_analogio_add_MSGTYPE wippersnapper_analogio_AnalogIOAdd +#define wippersnapper_signal_BrokerToDevice_payload_analogio_remove_MSGTYPE wippersnapper_analogio_AnalogIORemove +#define wippersnapper_signal_BrokerToDevice_payload_checkin_response_MSGTYPE wippersnapper_checkin_CheckinResponse +#define wippersnapper_signal_BrokerToDevice_payload_servo_add_MSGTYPE wippersnapper_servo_ServoAdd +#define wippersnapper_signal_BrokerToDevice_payload_servo_remove_MSGTYPE wippersnapper_servo_ServoRemove +#define wippersnapper_signal_BrokerToDevice_payload_servo_write_MSGTYPE wippersnapper_servo_ServoWrite +#define wippersnapper_signal_BrokerToDevice_payload_pwm_add_MSGTYPE wippersnapper_pwm_PWMAdd +#define wippersnapper_signal_BrokerToDevice_payload_pwm_remove_MSGTYPE wippersnapper_pwm_PWMRemove +#define wippersnapper_signal_BrokerToDevice_payload_pwm_write_duty_MSGTYPE wippersnapper_pwm_PWMWriteDutyCycle +#define wippersnapper_signal_BrokerToDevice_payload_pwm_write_duty_multi_MSGTYPE wippersnapper_pwm_PWMWriteDutyCycleMulti +#define wippersnapper_signal_BrokerToDevice_payload_pwm_write_freq_MSGTYPE wippersnapper_pwm_PWMWriteFrequency +#define wippersnapper_signal_BrokerToDevice_payload_pixels_add_MSGTYPE wippersnapper_pixels_PixelsAdd +#define wippersnapper_signal_BrokerToDevice_payload_pixels_remove_MSGTYPE wippersnapper_pixels_PixelsRemove +#define wippersnapper_signal_BrokerToDevice_payload_pixels_write_MSGTYPE wippersnapper_pixels_PixelsWrite +#define wippersnapper_signal_BrokerToDevice_payload_ds18x20_add_MSGTYPE wippersnapper_ds18x20_Ds18x20Add +#define wippersnapper_signal_BrokerToDevice_payload_ds18x20_remove_MSGTYPE wippersnapper_ds18x20_Ds18x20Remove +#define wippersnapper_signal_BrokerToDevice_payload_uart_add_MSGTYPE wippersnapper_uart_UARTAdd +#define wippersnapper_signal_BrokerToDevice_payload_uart_remove_MSGTYPE wippersnapper_uart_UARTRemove +#define wippersnapper_signal_BrokerToDevice_payload_i2c_bus_scan_MSGTYPE wippersnapper_i2c_I2cBusScan +#define wippersnapper_signal_BrokerToDevice_payload_i2c_device_add_replace_MSGTYPE wippersnapper_i2c_I2cDeviceAddOrReplace +#define wippersnapper_signal_BrokerToDevice_payload_i2c_device_remove_MSGTYPE wippersnapper_i2c_I2cDeviceRemove +#define wippersnapper_signal_BrokerToDevice_payload_error_MSGTYPE wippersnapper_error_Error + +#define wippersnapper_signal_DeviceToBroker_FIELDLIST(X, a) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,digitalio_event,payload.digitalio_event), 10) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,analogio_event,payload.analogio_event), 20) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,checkin_request,payload.checkin_request), 30) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,servo_added,payload.servo_added), 40) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,pwm_added,payload.pwm_added), 50) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,pixels_added,payload.pixels_added), 60) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,ds18x20_added,payload.ds18x20_added), 70) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,ds18x20_event,payload.ds18x20_event), 80) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,uart_added,payload.uart_added), 90) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,uart_event,payload.uart_event), 100) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,i2c_bus_scanned,payload.i2c_bus_scanned), 110) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,i2c_device_added_replaced,payload.i2c_device_added_replaced), 111) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,i2c_device_removed,payload.i2c_device_removed), 112) \ +X(a, STATIC, ONEOF, MSG_W_CB, (payload,i2c_device_event,payload.i2c_device_event), 113) +#define wippersnapper_signal_DeviceToBroker_CALLBACK NULL +#define wippersnapper_signal_DeviceToBroker_DEFAULT NULL +#define wippersnapper_signal_DeviceToBroker_payload_digitalio_event_MSGTYPE wippersnapper_digitalio_DigitalIOEvent +#define wippersnapper_signal_DeviceToBroker_payload_analogio_event_MSGTYPE wippersnapper_analogio_AnalogIOEvent +#define wippersnapper_signal_DeviceToBroker_payload_checkin_request_MSGTYPE wippersnapper_checkin_CheckinRequest +#define wippersnapper_signal_DeviceToBroker_payload_servo_added_MSGTYPE wippersnapper_servo_ServoAdded +#define wippersnapper_signal_DeviceToBroker_payload_pwm_added_MSGTYPE wippersnapper_pwm_PWMAdded +#define wippersnapper_signal_DeviceToBroker_payload_pixels_added_MSGTYPE wippersnapper_pixels_PixelsAdded +#define wippersnapper_signal_DeviceToBroker_payload_ds18x20_added_MSGTYPE wippersnapper_ds18x20_Ds18x20Added +#define wippersnapper_signal_DeviceToBroker_payload_ds18x20_event_MSGTYPE wippersnapper_ds18x20_Ds18x20Event +#define wippersnapper_signal_DeviceToBroker_payload_uart_added_MSGTYPE wippersnapper_uart_UARTAdded +#define wippersnapper_signal_DeviceToBroker_payload_uart_event_MSGTYPE wippersnapper_uart_UARTEvent +#define wippersnapper_signal_DeviceToBroker_payload_i2c_bus_scanned_MSGTYPE wippersnapper_i2c_I2cBusScanned +#define wippersnapper_signal_DeviceToBroker_payload_i2c_device_added_replaced_MSGTYPE wippersnapper_i2c_I2cDeviceAddedOrReplaced +#define wippersnapper_signal_DeviceToBroker_payload_i2c_device_removed_MSGTYPE wippersnapper_i2c_I2cDeviceRemoved +#define wippersnapper_signal_DeviceToBroker_payload_i2c_device_event_MSGTYPE wippersnapper_i2c_I2cDeviceEvent + +extern const pb_msgdesc_t wippersnapper_signal_BrokerToDevice_msg; +extern const pb_msgdesc_t wippersnapper_signal_DeviceToBroker_msg; + +/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ +#define wippersnapper_signal_BrokerToDevice_fields &wippersnapper_signal_BrokerToDevice_msg +#define wippersnapper_signal_DeviceToBroker_fields &wippersnapper_signal_DeviceToBroker_msg + +/* Maximum encoded size of messages (where known) */ +#if defined(wippersnapper_digitalio_DigitalIOEvent_size) && defined(wippersnapper_digitalio_DigitalIOWrite_size) && defined(wippersnapper_uart_UARTAdd_size) && defined(wippersnapper_uart_UARTRemove_size) +union wippersnapper_signal_BrokerToDevice_payload_size_union {char f12[(6 + wippersnapper_digitalio_DigitalIOEvent_size)]; char f13[(6 + wippersnapper_digitalio_DigitalIOWrite_size)]; char f80[(7 + wippersnapper_uart_UARTAdd_size)]; char f81[(7 + wippersnapper_uart_UARTRemove_size)]; char f0[83];}; +#endif +#if defined(wippersnapper_digitalio_DigitalIOEvent_size) && defined(wippersnapper_analogio_AnalogIOEvent_size) && defined(wippersnapper_ds18x20_Ds18x20Event_size) && defined(wippersnapper_uart_UARTAdded_size) && defined(wippersnapper_uart_UARTEvent_size) && defined(wippersnapper_i2c_I2cDeviceEvent_size) +union wippersnapper_signal_DeviceToBroker_payload_size_union {char f10[(6 + wippersnapper_digitalio_DigitalIOEvent_size)]; char f20[(7 + wippersnapper_analogio_AnalogIOEvent_size)]; char f80[(7 + wippersnapper_ds18x20_Ds18x20Event_size)]; char f90[(7 + wippersnapper_uart_UARTAdded_size)]; char f100[(7 + wippersnapper_uart_UARTEvent_size)]; char f113[(7 + wippersnapper_i2c_I2cDeviceEvent_size)]; char f0[2406];}; +#endif +#if defined(wippersnapper_digitalio_DigitalIOEvent_size) && defined(wippersnapper_digitalio_DigitalIOWrite_size) && defined(wippersnapper_uart_UARTAdd_size) && defined(wippersnapper_uart_UARTRemove_size) +#define WIPPERSNAPPER_SIGNAL_SIGNAL_PB_H_MAX_SIZE wippersnapper_signal_BrokerToDevice_size +#define wippersnapper_signal_BrokerToDevice_size (0 + sizeof(union wippersnapper_signal_BrokerToDevice_payload_size_union)) +#endif +#if defined(wippersnapper_digitalio_DigitalIOEvent_size) && defined(wippersnapper_analogio_AnalogIOEvent_size) && defined(wippersnapper_ds18x20_Ds18x20Event_size) && defined(wippersnapper_uart_UARTAdded_size) && defined(wippersnapper_uart_UARTEvent_size) && defined(wippersnapper_i2c_I2cDeviceEvent_size) +#define wippersnapper_signal_DeviceToBroker_size (0 + sizeof(union wippersnapper_signal_DeviceToBroker_payload_size_union)) +#endif + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/src/protos/uart.pb.c b/src/protos/uart.pb.c new file mode 100644 index 000000000..827e9c1d6 --- /dev/null +++ b/src/protos/uart.pb.c @@ -0,0 +1,24 @@ +/* Automatically generated nanopb constant definitions */ +/* Generated by nanopb-0.4.8 */ + +#include "uart.pb.h" +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +PB_BIND(wippersnapper_uart_UARTBusData, wippersnapper_uart_UARTBusData, AUTO) + + +PB_BIND(wippersnapper_uart_UARTAdd, wippersnapper_uart_UARTAdd, AUTO) + + +PB_BIND(wippersnapper_uart_UARTAdded, wippersnapper_uart_UARTAdded, AUTO) + + +PB_BIND(wippersnapper_uart_UARTRemove, wippersnapper_uart_UARTRemove, AUTO) + + +PB_BIND(wippersnapper_uart_UARTEvent, wippersnapper_uart_UARTEvent, AUTO) + + + diff --git a/src/protos/uart.pb.h b/src/protos/uart.pb.h new file mode 100644 index 000000000..6c4e07eaa --- /dev/null +++ b/src/protos/uart.pb.h @@ -0,0 +1,147 @@ +/* Automatically generated nanopb header */ +/* Generated by nanopb-0.4.8 */ + +#ifndef PB_WIPPERSNAPPER_UART_UART_PB_H_INCLUDED +#define PB_WIPPERSNAPPER_UART_UART_PB_H_INCLUDED +#include +#include "sensor.pb.h" + +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +/* Struct definitions */ +/* * + UARTBusData represents a message to configure a UART bus for communication with a device. + NOTE: This message is never sent directly, it is packed inside UARTAdd. */ +typedef struct _wippersnapper_uart_UARTBusData { + int32_t baudrate; /* * The baudrate to use for UART communication (may be a common baud rate such as: +1200bps, 2400bps, 4800bps, 19200bps, 38400bps, 57600bps, or 115200bps). */ + char pin_rx[6]; /* * The pin on which to receive UART stream data. */ + char pin_tx[6]; /* * The pin on which to transmit UART stream data. */ + bool is_invert; /* * Inverts the UART signal on RX and TX pins. Defaults to False. */ +} wippersnapper_uart_UARTBusData; + +/* * + UARTAdd represents a message sent from IO to a device + to configure the UART bus (if not already configured) and attach a device. */ +typedef struct _wippersnapper_uart_UARTAdd { + bool has_bus_info; + wippersnapper_uart_UARTBusData bus_info; /* * The UART bus configuration. */ + pb_callback_t device_id; /* * The unique identifier of the device to attach to the UART bus, from Adafruit_WipperSnapper_Components. */ + int32_t polling_interval; /* * The polling interval, in milliseconds, to use for the device. */ +} wippersnapper_uart_UARTAdd; + +/* * + UARTAdded represents a message sent from a device to IO to + confirm that a device has been attached to the UART bus. */ +typedef struct _wippersnapper_uart_UARTAdded { + pb_callback_t device_id; /* * The unique identifier of the device to attach to the UART bus, from Adafruit_WipperSnapper_Components. */ + bool is_success; /* * True if the UARTInit was successful, False otherwise. */ +} wippersnapper_uart_UARTAdded; + +/* UARTRemove represents a message sent from IO to a device + to detach a device from the UART bus. */ +typedef struct _wippersnapper_uart_UARTRemove { + pb_callback_t device_id; /* * The unique identifier of the device to detach from the UART bus. */ +} wippersnapper_uart_UARTRemove; + +/* * + UARTEvent represents incoming data from a UART sensor. */ +typedef struct _wippersnapper_uart_UARTEvent { + pb_callback_t device_id; /* * Unique identifier of the device to attach to the UART bus, from Adafruit_WipperSnapper_Components. */ + pb_callback_t sensor_events; /* * An optionally repeated event from a sensor. */ +} wippersnapper_uart_UARTEvent; + + +#ifdef __cplusplus +extern "C" { +#endif + +/* Initializer values for message structs */ +#define wippersnapper_uart_UARTBusData_init_default {0, "", "", 0} +#define wippersnapper_uart_UARTAdd_init_default {false, wippersnapper_uart_UARTBusData_init_default, {{NULL}, NULL}, 0} +#define wippersnapper_uart_UARTAdded_init_default {{{NULL}, NULL}, 0} +#define wippersnapper_uart_UARTRemove_init_default {{{NULL}, NULL}} +#define wippersnapper_uart_UARTEvent_init_default {{{NULL}, NULL}, {{NULL}, NULL}} +#define wippersnapper_uart_UARTBusData_init_zero {0, "", "", 0} +#define wippersnapper_uart_UARTAdd_init_zero {false, wippersnapper_uart_UARTBusData_init_zero, {{NULL}, NULL}, 0} +#define wippersnapper_uart_UARTAdded_init_zero {{{NULL}, NULL}, 0} +#define wippersnapper_uart_UARTRemove_init_zero {{{NULL}, NULL}} +#define wippersnapper_uart_UARTEvent_init_zero {{{NULL}, NULL}, {{NULL}, NULL}} + +/* Field tags (for use in manual encoding/decoding) */ +#define wippersnapper_uart_UARTBusData_baudrate_tag 1 +#define wippersnapper_uart_UARTBusData_pin_rx_tag 2 +#define wippersnapper_uart_UARTBusData_pin_tx_tag 3 +#define wippersnapper_uart_UARTBusData_is_invert_tag 4 +#define wippersnapper_uart_UARTAdd_bus_info_tag 1 +#define wippersnapper_uart_UARTAdd_device_id_tag 2 +#define wippersnapper_uart_UARTAdd_polling_interval_tag 3 +#define wippersnapper_uart_UARTAdded_device_id_tag 1 +#define wippersnapper_uart_UARTAdded_is_success_tag 2 +#define wippersnapper_uart_UARTRemove_device_id_tag 1 +#define wippersnapper_uart_UARTEvent_device_id_tag 1 +#define wippersnapper_uart_UARTEvent_sensor_events_tag 2 + +/* Struct field encoding specification for nanopb */ +#define wippersnapper_uart_UARTBusData_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, INT32, baudrate, 1) \ +X(a, STATIC, SINGULAR, STRING, pin_rx, 2) \ +X(a, STATIC, SINGULAR, STRING, pin_tx, 3) \ +X(a, STATIC, SINGULAR, BOOL, is_invert, 4) +#define wippersnapper_uart_UARTBusData_CALLBACK NULL +#define wippersnapper_uart_UARTBusData_DEFAULT NULL + +#define wippersnapper_uart_UARTAdd_FIELDLIST(X, a) \ +X(a, STATIC, OPTIONAL, MESSAGE, bus_info, 1) \ +X(a, CALLBACK, SINGULAR, STRING, device_id, 2) \ +X(a, STATIC, SINGULAR, INT32, polling_interval, 3) +#define wippersnapper_uart_UARTAdd_CALLBACK pb_default_field_callback +#define wippersnapper_uart_UARTAdd_DEFAULT NULL +#define wippersnapper_uart_UARTAdd_bus_info_MSGTYPE wippersnapper_uart_UARTBusData + +#define wippersnapper_uart_UARTAdded_FIELDLIST(X, a) \ +X(a, CALLBACK, SINGULAR, STRING, device_id, 1) \ +X(a, STATIC, SINGULAR, BOOL, is_success, 2) +#define wippersnapper_uart_UARTAdded_CALLBACK pb_default_field_callback +#define wippersnapper_uart_UARTAdded_DEFAULT NULL + +#define wippersnapper_uart_UARTRemove_FIELDLIST(X, a) \ +X(a, CALLBACK, SINGULAR, STRING, device_id, 1) +#define wippersnapper_uart_UARTRemove_CALLBACK pb_default_field_callback +#define wippersnapper_uart_UARTRemove_DEFAULT NULL + +#define wippersnapper_uart_UARTEvent_FIELDLIST(X, a) \ +X(a, CALLBACK, SINGULAR, STRING, device_id, 1) \ +X(a, CALLBACK, REPEATED, MESSAGE, sensor_events, 2) +#define wippersnapper_uart_UARTEvent_CALLBACK pb_default_field_callback +#define wippersnapper_uart_UARTEvent_DEFAULT NULL +#define wippersnapper_uart_UARTEvent_sensor_events_MSGTYPE wippersnapper_sensor_SensorEvent + +extern const pb_msgdesc_t wippersnapper_uart_UARTBusData_msg; +extern const pb_msgdesc_t wippersnapper_uart_UARTAdd_msg; +extern const pb_msgdesc_t wippersnapper_uart_UARTAdded_msg; +extern const pb_msgdesc_t wippersnapper_uart_UARTRemove_msg; +extern const pb_msgdesc_t wippersnapper_uart_UARTEvent_msg; + +/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ +#define wippersnapper_uart_UARTBusData_fields &wippersnapper_uart_UARTBusData_msg +#define wippersnapper_uart_UARTAdd_fields &wippersnapper_uart_UARTAdd_msg +#define wippersnapper_uart_UARTAdded_fields &wippersnapper_uart_UARTAdded_msg +#define wippersnapper_uart_UARTRemove_fields &wippersnapper_uart_UARTRemove_msg +#define wippersnapper_uart_UARTEvent_fields &wippersnapper_uart_UARTEvent_msg + +/* Maximum encoded size of messages (where known) */ +/* wippersnapper_uart_UARTAdd_size depends on runtime parameters */ +/* wippersnapper_uart_UARTAdded_size depends on runtime parameters */ +/* wippersnapper_uart_UARTRemove_size depends on runtime parameters */ +/* wippersnapper_uart_UARTEvent_size depends on runtime parameters */ +#define WIPPERSNAPPER_UART_UART_PB_H_MAX_SIZE wippersnapper_uart_UARTBusData_size +#define wippersnapper_uart_UARTBusData_size 27 + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/src/provisioning/littlefs/WipperSnapper_LittleFS.cpp b/src/provisioning/littlefs/WipperSnapper_LittleFS.cpp index de76fc79f..ec1c8de46 100644 --- a/src/provisioning/littlefs/WipperSnapper_LittleFS.cpp +++ b/src/provisioning/littlefs/WipperSnapper_LittleFS.cpp @@ -17,8 +17,9 @@ defined(ARDUINO_ADAFRUIT_ITSYBITSY_ESP32) || \ defined(ARDUINO_ADAFRUIT_FEATHER_ESP32_V2) || \ defined(ARDUINO_ADAFRUIT_QTPY_ESP32_PICO) || \ - defined(ARDUINO_ADAFRUIT_QTPY_ESP32C3) || \ - defined(ARDUINO_ADAFRUIT_FEATHER_ESP32C6) + defined(ARDUINO_ADAFRUIT_QTPY_ESP32C3) || \ + defined(ARDUINO_ESP32_DEV) || \ + defined(ESP32_DEV) #include "WipperSnapper_LittleFS.h" /**************************************************************************/ diff --git a/src/provisioning/littlefs/WipperSnapper_LittleFS_V2.cpp b/src/provisioning/littlefs/WipperSnapper_LittleFS_V2.cpp new file mode 100644 index 000000000..c53d5012b --- /dev/null +++ b/src/provisioning/littlefs/WipperSnapper_LittleFS_V2.cpp @@ -0,0 +1,158 @@ +/*! + * @file Wippersnapper_LittleFS.cpp + * + * Interfaces with LittleFS filesystem for ESP32, ESP8266 platforms. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2021-2024 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#if defined(ARDUINO_FEATHER_ESP32) || \ + defined(ARDUINO_ESP8266_ADAFRUIT_HUZZAH) || \ + defined(ARDUINO_ADAFRUIT_ITSYBITSY_ESP32) || \ + defined(ARDUINO_ADAFRUIT_FEATHER_ESP32_V2) || \ + defined(ARDUINO_ADAFRUIT_QTPY_ESP32_PICO) || \ + defined(ARDUINO_ADAFRUIT_QTPY_ESP32C3) || \ + defined(ARDUINO_ESP32_DEV) || \ + defined(ESP32_DEV) +#include "WipperSnapper_LittleFS_V2.h" + +/**************************************************************************/ +/*! + @brief Attempts to set up and initialize a pre-existing LittleFS + filesystem. +*/ +/**************************************************************************/ +WipperSnapper_LittleFS_V2::WipperSnapper_LittleFS_V2() { + // Attempt to initialize filesystem + if (!LittleFS.begin()) { + setStatusLEDColor(RED); + fsHalt("ERROR: Failure initializing LittleFS!"); + } +} + +/**************************************************************************/ +/*! + @brief Destructor for LittleFS +*/ +/**************************************************************************/ +WipperSnapper_LittleFS_V2::~WipperSnapper_LittleFS_V2() { LittleFS.end(); } + +/**************************************************************************/ +/*! + @brief Locates, opens and parses the WipperSnapper secrets file + on the LittleFS filesystem. +*/ +/**************************************************************************/ +void WipperSnapper_LittleFS_V2::parseSecrets() { + // Check if `secrets.json` file exists on FS + if (!LittleFS.exists("/secrets.json")) { + fsHalt("ERROR: No secrets.json found on filesystem - did you upload " + "credentials?"); + } + + // Attempt to open secrets.json file for reading + File secretsFile = LittleFS.open("/secrets.json", "r"); + if (!secretsFile) { + fsHalt("ERROR: Could not open secrets.json file for reading!"); + } + + // Attempt to deserialize the file's JSON document + JsonDocument doc; + DeserializationError error = deserializeJson(doc, secretsFile); + if (error) { + fsHalt(String("ERROR: deserializeJson() failed with code ") + + error.c_str()); + } + if (doc.containsKey("network_type_wifi")) { + // set default network config + convertFromJson(doc["network_type_wifi"], WS._config.network); + + if (!doc["network_type_wifi"].containsKey("alternative_networks")) { + // do nothing extra, we already have the only network + WS_DEBUG_PRINTLN("Found single wifi network in secrets.json"); + + } else if (doc["network_type_wifi"]["alternative_networks"] + .is()) { + + WS_DEBUG_PRINTLN("Found multiple wifi networks in secrets.json"); + // Parse network credentials from array in secrets + JsonArray altnetworks = doc["network_type_wifi"]["alternative_networks"]; + int8_t altNetworkCount = (int8_t)altnetworks.size(); + WS_DEBUG_PRINT("Network count: "); + WS_DEBUG_PRINTLN(altNetworkCount); + if (altNetworkCount == 0) { + fsHalt("ERROR: No alternative network entries found under " + "network_type_wifi.alternative_networks in secrets.json!"); + } + // check if over 3, warn user and take first three + for (int i = 0; i < altNetworkCount; i++) { + if (i >= 3) { + WS_DEBUG_PRINT("WARNING: More than 3 networks in secrets.json, " + "only the first 3 will be used. Not using "); + WS_DEBUG_PRINTLN(altnetworks[i]["network_ssid"].as()); + break; + } + convertFromJson(altnetworks[i], WS._multiNetworks[i]); + WS_DEBUG_PRINT("Added SSID: "); + WS_DEBUG_PRINTLN(WS._multiNetworks[i].ssid); + WS_DEBUG_PRINT("PASS: "); + WS_DEBUG_PRINTLN(WS._multiNetworks[i].pass); + } + WS._isWiFiMulti = true; + } else { + fsHalt("ERROR: Unrecognised value type for " + "network_type_wifi.alternative_networks in secrets.json!"); + } + } else { + fsHalt("ERROR: Could not find network_type_wifi in secrets.json!"); + } + + // Extract a config struct from the JSON document + WS._config = doc.as(); + + // Validate the config struct is not filled with default values + if (strcmp(WS._config.aio_user, "YOUR_IO_USERNAME_HERE") == 0 || + strcmp(WS._config.aio_key, "YOUR_IO_KEY_HERE") == 0) { + fsHalt( + "ERROR: Invalid IO credentials in secrets.json! TO FIX: Please change " + "io_username and io_key to match your Adafruit IO credentials!\n"); + } + + if (strcmp(WS._config.network.ssid, "YOUR_WIFI_SSID_HERE") == 0 || + strcmp(WS._config.network.pass, "YOUR_WIFI_PASS_HERE") == 0) { + fsHalt("ERROR: Invalid network credentials in secrets.json! TO FIX: Please " + "change network_ssid and network_password to match your Adafruit IO " + "credentials!\n"); + } + + // Close the file + secretsFile.close(); + + // Stop LittleFS, we no longer need it + LittleFS.end(); +} + +/**************************************************************************/ +/*! + @brief Halts execution and blinks the status LEDs yellow. + @param msg + Error message to print to serial console. +*/ +/**************************************************************************/ +void WipperSnapper_LittleFS_V2::fsHalt(String msg) { + statusLEDSolid(WS_LED_STATUS_FS_WRITE); + while (1) { + WS_DEBUG_PRINTLN("Fatal Error: Halted execution!"); + WS_DEBUG_PRINTLN(msg.c_str()); + delay(1000); + yield(); + } +} + +#endif \ No newline at end of file diff --git a/src/provisioning/littlefs/WipperSnapper_LittleFS_V2.h b/src/provisioning/littlefs/WipperSnapper_LittleFS_V2.h new file mode 100644 index 000000000..751b1b1bb --- /dev/null +++ b/src/provisioning/littlefs/WipperSnapper_LittleFS_V2.h @@ -0,0 +1,39 @@ +/*! + * @file Wippersnapper_LittleFS.h + * + * Interfaces with LittleFS filesystem for ESP32, ESP8266 platforms. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2021-2024 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#ifndef WIPPERSNAPPER_LITTLEFS_V2_H +#define WIPPERSNAPPER_LITTLEFS_V2_H + +#include "Wippersnapper_V2.h" + +#include +#include + +// forward decl. +class Wippersnapper_V2; + +/***************************************************************************/ +/*! + @brief Class that handles WipperSnapper's LittleFS filesystem. +*/ +/***************************************************************************/ +class WipperSnapper_LittleFS_V2 { +public: + WipperSnapper_LittleFS_V2(); + ~WipperSnapper_LittleFS_V2(); + void parseSecrets(); + void fsHalt(String msg); +}; +extern Wippersnapper_V2 WsV2; +#endif // WIPPERSNAPPER_LITTLEFS_H \ No newline at end of file diff --git a/src/provisioning/sdcard/ws_sdcard.cpp b/src/provisioning/sdcard/ws_sdcard.cpp new file mode 100644 index 000000000..5c9c64043 --- /dev/null +++ b/src/provisioning/sdcard/ws_sdcard.cpp @@ -0,0 +1,784 @@ +/*! + * @file ws_sdcard.cpp + * + * Interface for Wippersnapper's SD card filesystem. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2024 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#include "ws_sdcard.h" + +/**************************************************************************/ +/*! + @brief Constructs an instance of the Wippersnapper SD card class. +*/ +/**************************************************************************/ +ws_sdcard::ws_sdcard() { + mode_offline = false; + _wokwi_runner = false; +#ifndef SD_CS_PIN + return; +#endif + + // Attempt to initialize the SD card + if (_sd.begin(SD_CS_PIN)) { + mode_offline = true; + } +} + +/**************************************************************************/ +/*! + @brief Destructs an instance of the Wippersnapper SD card class. +*/ +/**************************************************************************/ +ws_sdcard::~ws_sdcard() { + // TODO: Close any open files + // Then, end the SD card (ends SPI transaction) + if (mode_offline) { + _sd.end(); + mode_offline = false; + } +} + +/**************************************************************************/ +/*! + @brief Enables logging via a physical RTC to a SD-card, if available. + Otherwise, enables logging using millis() timestamps. +*/ +/**************************************************************************/ +void ws_sdcard::EnableLogging() { + // Attempt to search for a DS3231 RTC + WS_DEBUG_PRINTLN("Searching for DS1307 RTC..."); + _rtc_ds1307 = new RTC_DS1307(); + if (_rtc_ds1307->begin()) { + WS_DEBUG_PRINTLN("Found DS1307 RTC!"); + if (!_rtc_ds1307->isrunning()) { + Serial.println("RTC is not running, let's set the time!"); + // When time needs to be set on a new device, or after a power loss, the + // following line sets the RTC to the date & time this sketch was compiled + _rtc_ds1307->adjust(DateTime(F(__DATE__), F(__TIME__))); + } + } else { + WS_DEBUG_PRINT("Unable to find DS1307 RTC, "); + delete _rtc_ds1307; + _rtc_ds1307 = nullptr; + // Attempt to search for a DS3231 RTC if DS1307 is not found + WS_DEBUG_PRINTLN("searching for DS3231 RTC..."); + _rtc_ds3231 = new RTC_DS3231(); + if (_rtc_ds3231->begin()) { + WS_DEBUG_PRINTLN("Found DS3231 RTC!"); + if (_rtc_ds3231->lostPower()) { + Serial.println("RTC lost power, let's set the time!"); + // When time needs to be set on a new device, or after a power loss, the + // following line sets the RTC to the date & time this sketch was + // compiled + _rtc_ds3231->adjust(DateTime(F(__DATE__), F(__TIME__))); + } + } else { + WS_DEBUG_PRINT("Unable to find DS3231 RTC, "); + delete _rtc_ds3231; + _rtc_ds3231 = nullptr; + // Attempt to search for a DS3231 RTC if DS1307 is not found + WS_DEBUG_PRINTLN("searching for PCF8523 RTC..."); + _rtc_pcf8523 = new RTC_PCF8523(); + if (_rtc_pcf8523->begin()) { + WS_DEBUG_PRINTLN("Found PCF8523 RTC!"); + if (_rtc_pcf8523->lostPower()) { + Serial.println("RTC lost power, let's set the time!"); + // When time needs to be set on a new device, or after a power loss, + // the following line sets the RTC to the date & time this sketch was + // compiled + _rtc_pcf8523->adjust(DateTime(F(__DATE__), F(__TIME__))); + } + } + } + } + + // Fallback to millis() if no RTC is found + if (_rtc_ds1307 == nullptr && _rtc_ds3231 == nullptr) { + WS_DEBUG_PRINTLN( + "[SD] No RTC found, defaulting to use millis() timestamps!") + } else { + WS_DEBUG_PRINTLN("[SD] RTC found, using RTC timestamps!"); + } +} + +/**************************************************************************/ +/*! + @brief Searches for and parses the JSON configuration file and sets up + the hardware accordingly. + @returns True if the JSON file was successfully parsed and the hardware + was successfully configured. False otherwise. +*/ +/**************************************************************************/ +bool ws_sdcard::parseConfigFile() { + File32 file_config; // TODO: MAke this global? +#ifndef OFFLINE_MODE_DEBUG + file_config = _sd.open("config.json", FILE_READ); +#endif + + JsonDocument doc; + // TODO: Change max input length to fit an expected/max json size + int max_input_len = 2048; + + // Attempt to de-serialize the JSON document + DeserializationError error; +#ifdef OFFLINE_MODE_DEBUG + if (!_use_test_data) { + // Read the config file from the serial input buffer + WS_DEBUG_PRINTLN("[SD] Reading JSON config file..."); + error = deserializeJson(doc, _serialInput.c_str(), max_input_len); + } else { + // Read the config file from the test JSON string + WS_DEBUG_PRINTLN("[SD] Reading test JSON data..."); + error = deserializeJson(doc, json_test_data, max_input_len); + } +#else + // Read the config file from the SD card + WS_DEBUG_PRINTLN("[SD] Reading config file..."); +// TODO - implement this +// error = deserializeJson(doc, file_config, max_input_len); +#endif + + // If the JSON document failed to deserialize - halt the running device and + // print the error because it is not possible to continue running in offline + // mode without a valid config file + if (error) { + WS_DEBUG_PRINTLN("[SD] deserializeJson() failed, error code: " + + String(error.c_str())); + return false; + } + + // TODO: Let's refactor this outwards to a function called `CheckInJSON()` + // NOTE: While we can't do a "proper" check-in procedure with + // the MQTT broker while in offline mode, we can still configure + // the hardware by parsing the JSON object's "exportedFromDevice" + // contents and setting up the hardware + WS_DEBUG_PRINT("[SD] Performing check-in process..."); + JsonObject exportedFromDevice = doc["exportedFromDevice"]; + if (exportedFromDevice.isNull()) { + WS_DEBUG_PRINTLN("[SD] FATAL Parsing error - No exportedFromDevice object " + "found in JSON string! Unable to configure hardware!"); + return false; + } + WsV2.digital_io_controller->SetMaxDigitalPins( + exportedFromDevice["totalGPIOPins"]); + WsV2.analogio_controller->SetRefVoltage( + exportedFromDevice["referenceVoltage"]); + WsV2.analogio_controller->SetTotalAnalogPins( + exportedFromDevice["totalAnalogPins"]); + WS_DEBUG_PRINTLN("OK!"); + + // Parse the "components" array into a JsonObject + JsonArray components_ar = doc["components"].as(); + int count = components_ar.size(); + WS_DEBUG_PRINTLN("[SD] Found " + String(count) + " components in JSON file!"); + + // Use the iterator feature of ArduinoJSON v7 to quickly iterate over + // components[] + for (JsonObject component : doc["components"].as()) { + // Create a new signal message + wippersnapper_signal_BrokerToDevice msg_signal_b2d; + // Parse the component API type + const char *component_api_type = component["componentAPI"]; + if (component_api_type == nullptr) { + WS_DEBUG_PRINTLN("[SD] FATAL Parsing error - No component API type found " + "in JSON string!"); + return false; + } + + // This is enabled for wokwi-cli testing only + const char *exportedBy = doc["exportedBy"]; + if (strcmp(exportedBy, "wokwi") == 0) { + _wokwi_runner = true; + } + + // Determine the component type and parse it into a PB message + if (strcmp(component_api_type, "digitalio") == 0) { + WS_DEBUG_PRINTLN( + "[SD] DigitalIO component found, decoding JSON to PB..."); + // Parse the JSON component's fields into a new DigitalIOAdd message + wippersnapper_digitalio_DigitalIOAdd msg_DigitalIOAdd = + wippersnapper_digitalio_DigitalIOAdd_init_default; + strcpy(msg_DigitalIOAdd.pin_name, component["pinName"]); + msg_DigitalIOAdd.period = component["period"]; + msg_DigitalIOAdd.value = component["value"]; + // Determine the sample mode + if (strcmp(component["sampleMode"], "TIMER") == 0) { + msg_DigitalIOAdd.sample_mode = + wippersnapper_digitalio_DigitalIOSampleMode_DIGITAL_IO_SAMPLE_MODE_TIMER; + } else if (strcmp(component["sampleMode"], "EVENT") == 0) { + msg_DigitalIOAdd.sample_mode = + wippersnapper_digitalio_DigitalIOSampleMode_DIGITAL_IO_SAMPLE_MODE_EVENT; + } else { + WS_DEBUG_PRINTLN("[SD] Parsing Error: Unknown sample mode found: " + + String(component["sampleMode"])); + } + // Determine the pin direction and pull + if (strcmp(component["direction"], "INPUT") == 0) { + if (component["pull"] != nullptr) { + msg_DigitalIOAdd.gpio_direction = + wippersnapper_digitalio_DigitalIODirection_DIGITAL_IO_DIRECTION_INPUT_PULL_UP; + } else { + msg_DigitalIOAdd.gpio_direction = + wippersnapper_digitalio_DigitalIODirection_DIGITAL_IO_DIRECTION_INPUT; + } + } else if (strcmp(component["direction"], "OUTPUT") == 0) { + WS_DEBUG_PRINTLN( + "[SD] Error - Can not set OUTPUT direction in offline mode!"); + return false; + } else { + WS_DEBUG_PRINTLN("[SD] Parsing Error: Unknown direction found: " + + String(component["direction"])); + return false; + } + + msg_signal_b2d = wippersnapper_signal_BrokerToDevice_init_zero; + msg_signal_b2d.which_payload = + wippersnapper_signal_BrokerToDevice_digitalio_add_tag; + msg_signal_b2d.payload.digitalio_add = msg_DigitalIOAdd; + } else if (strcmp(component_api_type, "analogio") == 0) { + WS_DEBUG_PRINTLN("[SD] AnalogIO component found, decoding JSON to PB..."); + wippersnapper_analogio_AnalogIOAdd msg_AnalogIOAdd = + wippersnapper_analogio_AnalogIOAdd_init_default; + strcpy(msg_AnalogIOAdd.pin_name, component["pinName"]); + msg_AnalogIOAdd.period = component["period"]; + if (strcmp(component["analogReadMode"], "PIN_VALUE") == 0) { + msg_AnalogIOAdd.read_mode = + wippersnapper_sensor_SensorType_SENSOR_TYPE_RAW; + } else if (strcmp(component["analogReadMode"], "VOLTAGE") == 0) { + msg_AnalogIOAdd.read_mode = + wippersnapper_sensor_SensorType_SENSOR_TYPE_VOLTAGE; + } else { + // Unknown analog read mode, bail out + WS_DEBUG_PRINTLN("[SD] Unknown analog read mode found: " + + String(component["analogReadMode"])); + return false; + } + + msg_signal_b2d = wippersnapper_signal_BrokerToDevice_init_zero; + msg_signal_b2d.which_payload = + wippersnapper_signal_BrokerToDevice_analogio_add_tag; + msg_signal_b2d.payload.analogio_add = msg_AnalogIOAdd; + } else if (strcmp(component_api_type, "ds18x20") == 0) { + WS_DEBUG_PRINTLN("[SD] ds18x20 component found, decoding JSON to PB..."); + // Create new DS18X20Add message + wippersnapper_ds18x20_Ds18x20Add msg_DS18X20Add = + wippersnapper_ds18x20_Ds18x20Add_init_default; + // Parse JSON into the DS18X20Add message + // TODO: This pattern should be refactored into a function like + // "ParseAndAssign(component["type"], msg_field)" + if (component["pinName"] != nullptr) { + strcpy(msg_DS18X20Add.onewire_pin, component["pinName"]); + } else { + WS_DEBUG_PRINTLN( + "[SD] FATAL Parsing error - No pin name found in JSON string!"); + return false; + } + + if (component["sensorResolution"] != nullptr) { + msg_DS18X20Add.sensor_resolution = component["sensorResolution"]; + } else { + WS_DEBUG_PRINTLN("[SD] FATAL Parsing error - No sensor resolution " + "found in JSON string!"); + return false; + } + + if (component["period"] != nullptr) { + msg_DS18X20Add.period = component["period"]; + } else { + WS_DEBUG_PRINTLN( + "[SD] FATAL Parsing error - No period found in JSON string!"); + return false; + } + + if (component["sensorTypeCount"] != nullptr) { + msg_DS18X20Add.sensor_types_count = component["sensorTypeCount"]; + } else { + WS_DEBUG_PRINTLN("[SD] FATAL Parsing error - No sensor type count " + "found in JSON string!"); + return false; + } + + WS_DEBUG_PRINT("[SD] msg_DS18X20Add.sensor_types_count: "); + WS_DEBUG_PRINTLN(msg_DS18X20Add.sensor_types_count); + + // Parse the sensor types into the DS18X20Add message + // TODO: This structor needs a refactoring pass! It's too confusing + if (msg_DS18X20Add.sensor_types_count == 1 || + msg_DS18X20Add.sensor_types_count == 2) { + if (strcmp(component["sensorType1"], "ambient-temp-fahrenheit") == 0) { + msg_DS18X20Add.sensor_types[0] = + wippersnapper_sensor_SensorType_SENSOR_TYPE_OBJECT_TEMPERATURE_FAHRENHEIT; + } else if (strcmp(component["sensorType1"], "ambient-temp") == 0) { + msg_DS18X20Add.sensor_types[0] = + wippersnapper_sensor_SensorType_SENSOR_TYPE_OBJECT_TEMPERATURE; + } else { + WS_DEBUG_PRINTLN( + "[SD] FATAL Parsing error - Unsupported ds18x sensor " + "type found in JSON!"); + return false; + } + } + if (msg_DS18X20Add.sensor_types_count == 2) { + WS_DEBUG_PRINTLN("[SD] Parsing sensor type 2..."); + if (component["sensorType2"] != nullptr) { + if (strcmp(component["sensorType2"], "ambient-temp-fahrenheit") == + 0) { + msg_DS18X20Add.sensor_types[1] = + wippersnapper_sensor_SensorType_SENSOR_TYPE_OBJECT_TEMPERATURE_FAHRENHEIT; + } else if (strcmp(component["sensorType2"], "ambient-temp") == 0) { + msg_DS18X20Add.sensor_types[1] = + wippersnapper_sensor_SensorType_SENSOR_TYPE_OBJECT_TEMPERATURE; + } else { + WS_DEBUG_PRINTLN( + "[SD] FATAL Parsing error - Unsupported ds18x sensor " + "type found in JSON!"); + return false; + } + } + } + + // Configure the signal message for the ds18x20 payload + msg_signal_b2d = wippersnapper_signal_BrokerToDevice_init_zero; + msg_signal_b2d.which_payload = + wippersnapper_signal_BrokerToDevice_ds18x20_add_tag; + msg_signal_b2d.payload.ds18x20_add = msg_DS18X20Add; + } else { + // Unknown component API type + WS_DEBUG_PRINTLN("[SD] Unknown component API type found: " + + String(component_api_type)); + return false; + } + + // Create a temporary buffer to hold the encoded signal message + std::vector tempBuf(128); + size_t tempBufSz; + + // Get the encoded size of the signal message first so we can resize the + // buffer prior to encoding + WS_DEBUG_PRINTLN("Encoding D2b signal message..."); + if (!pb_get_encoded_size(&tempBufSz, + wippersnapper_signal_BrokerToDevice_fields, + &msg_signal_b2d)) { + WS_DEBUG_PRINTLN("[SD] ERROR: Unable to get signal message size!"); + return false; + } + WS_DEBUG_PRINTLN("Signal message size: " + String(tempBufSz)); + // Encode and push the signal message to the shared config buffer + tempBuf.resize(tempBufSz); + pb_ostream_t ostream = + pb_ostream_from_buffer(tempBuf.data(), tempBuf.size()); + if (!ws_pb_encode(&ostream, wippersnapper_signal_BrokerToDevice_fields, + &msg_signal_b2d)) { + WS_DEBUG_PRINTLN("[SD] ERROR: Unable to encode D2B signal message!"); + return false; + } + WsV2._sharedConfigBuffers.push_back(std::move(tempBuf)); + WS_DEBUG_PRINTLN("Encoded the D2b signal message"); + } + return true; +} + +/**************************************************************************/ +/*! + @brief Validates a JSON string. + @param input + A JSON string to validate. + @returns True if the provided JSON string is valid, False otherwise. +*/ +/**************************************************************************/ +bool ws_sdcard::validateJson(const char *input) { + JsonDocument doc, filter; + + DeserializationError error = + deserializeJson(doc, input, DeserializationOption::Filter(filter)); + return error == DeserializationError::Ok; +} + +/**************************************************************************/ +/*! + @brief Waits for a valid JSON string to be received via the hardware's + serial input or from a hardcoded test JSON string. + @returns True if a valid JSON string was received, False otherwise. +*/ +/**************************************************************************/ +bool ws_sdcard::waitForSerialConfig() { + + // We provide three ways to use this function: + // 1. Use a SD card with a JSON config file + // 2. Provide a JSON string via the hardware's serial input + // 3. Use a test JSON string - for debugging purposes ONLY + + _use_test_data = false; + json_test_data = "{" + "\"exportVersion\": \"1.0.0\"," + "\"exportedBy\": \"tester\"," + "\"exportedAt\": \"2024-10-28T18:58:23.976Z\"," + "\"exportedFromDevice\": {" + "\"board\": \"metroesp32s3\"," + "\"firmwareVersion\": \"1.0.0-beta.93\"," + "\"referenceVoltage\": 2.6," + "\"totalGPIOPins\": 11," + "\"totalAnalogPins\": 6" + "}," + "\"components\": [" + "{" + "\"componentAPI\": \"analogio\"," + "\"name\": \"Analog Pin\"," + "\"pinName\": \"D14\"," + "\"type\": \"analog_pin\"," + "\"mode\": \"ANALOG\"," + "\"direction\": \"INPUT\"," + "\"sampleMode\": \"TIMER\"," + "\"analogReadMode\": \"PIN_VALUE\"," + "\"period\": 5," + "\"isPin\": true" + "}," + "{" + "\"componentAPI\": \"analogio\"," + "\"name\": \"Analog Pin\"," + "\"pinName\": \"D27\"," + "\"type\": \"analog_pin\"," + "\"mode\": \"ANALOG\"," + "\"direction\": \"INPUT\"," + "\"sampleMode\": \"TIMER\"," + "\"analogReadMode\": \"PIN_VALUE\"," + "\"period\": 5," + "\"isPin\": true" + "}," + "{" + "\"componentAPI\": \"digitalio\"," + "\"name\": \"Button (D4)\"," + "\"pinName\": \"D4\"," + "\"type\": \"push_button\"," + "\"mode\": \"DIGITAL\"," + "\"sampleMode\": \"EVENT\"," + "\"direction\": \"INPUT\"," + "\"period\": 5," + "\"pull\": \"UP\"," + "\"isPin\": true" + "}," + "{" + "\"componentAPI\": \"ds18x20\"," + "\"name\": \"DS18B20: Temperature Sensor (°F)\"," + "\"sensorTypeCount\": 2," + "\"sensorType1\": \"ambient-temp-fahrenheit\"," + "\"sensorType2\": \"ambient-temp\"," + "\"pinName\": \"D12\"," + "\"sensorResolution\": 12," + "\"period\": 5" + "}," + "{" + "\"componentAPI\": \"ds18x20\"," + "\"name\": \"DS18B20: Temperature Sensor (°F)\"," + "\"sensorTypeCount\": 2," + "\"sensorType1\": \"ambient-temp-fahrenheit\"," + "\"sensorType2\": \"ambient-temp\"," + "\"pinName\": \"D25\"," + "\"sensorResolution\": 12," + "\"period\": 5" + "}" + "]" + "}\\n\r\n"; + + _serialInput = ""; // Clear the serial input buffer +if (!_use_test_data) { + WS_DEBUG_PRINTLN("[SD] Waiting for incoming JSON string..."); + while (true) { + // Check if there is data available to read + if (Serial.available() > 0) { + // Read and append to _serialInput + char c = Serial.read(); + _serialInput += c; + + // DEBUG - Check JSON output as an Int and total output + // WS_DEBUG_PRINT("[SD] Character read: "); + // WS_DEBUG_PRINTLN((int)c); + // WS_DEBUG_PRINTLN(_serialInput); + + // Check for end of JSON string using \n sequence + if (_serialInput.endsWith("\\n")) { + WS_DEBUG_PRINTLN("[SD] End of JSON string detected!"); + break; + } + } + } +} + + + // Strip the '\n' off the end of _serialInput + _serialInput.trim(); + + // Print out the received JSON string + WS_DEBUG_PRINT("[SD][Debug] JSON string received!"); + if (_use_test_data) { + WS_DEBUG_PRINTLN("[from json test data]"); + WS_DEBUG_PRINTLN(json_test_data); + } else { + WS_DEBUG_PRINTLN(_serialInput); + } + + // Attempt to validate the string as JSON + if (!_use_test_data) { + if (!validateJson(_serialInput.c_str())) { + WS_DEBUG_PRINTLN("[SD] Invalid JSON string received!"); + return false; + } + } else { + if (!validateJson(json_test_data)) { + WS_DEBUG_PRINTLN("[SD] Invalid JSON string received!"); + return false; + } + } + + WS_DEBUG_PRINTLN("[SD] Valid JSON string received!"); + return true; +} + +/**************************************************************************/ +/*! + @brief Obtains a timestamp from the hardware (or software) RTC. + @returns The current timestamp, in unixtime format. +*/ +/**************************************************************************/ +uint32_t ws_sdcard::GetTimestamp() { + // Obtain RTC timestamp (TODO - refactor this out) + DateTime now; + if (_rtc_ds3231 != nullptr) + now = _rtc_ds3231->now(); + else if (_rtc_ds1307 != nullptr) + now = _rtc_ds1307->now(); + else if (_rtc_pcf8523 != nullptr) + now = _rtc_pcf8523->now(); + else { + // TODO! implement software millis() version of now() and unixtime() + } + + if (_wokwi_runner) + return 0; + + return now.unixtime(); +} + +/**************************************************************************/ +/*! + @brief Converts a SensorType enum to a string. + @param sensorType + The SensorType enum to convert. + @returns A string representation of the SensorType enum. +*/ +/**************************************************************************/ +const char *SensorTypeToString(wippersnapper_sensor_SensorType sensorType) { + switch (sensorType) { + case wippersnapper_sensor_SensorType_SENSOR_TYPE_UNSPECIFIED: + return "UNSPECIFIED"; + case wippersnapper_sensor_SensorType_SENSOR_TYPE_ACCELEROMETER: + return "ACCELEROMETER"; + case wippersnapper_sensor_SensorType_SENSOR_TYPE_MAGNETIC_FIELD: + return "MAGNETIC_FIELD"; + case wippersnapper_sensor_SensorType_SENSOR_TYPE_ORIENTATION: + return "ORIENTATION"; + case wippersnapper_sensor_SensorType_SENSOR_TYPE_GYROSCOPE: + return "GYROSCOPE"; + case wippersnapper_sensor_SensorType_SENSOR_TYPE_LIGHT: + return "LIGHT"; + case wippersnapper_sensor_SensorType_SENSOR_TYPE_PRESSURE: + return "PRESSURE"; + case wippersnapper_sensor_SensorType_SENSOR_TYPE_PROXIMITY: + return "PROXIMITY"; + case wippersnapper_sensor_SensorType_SENSOR_TYPE_GRAVITY: + return "GRAVITY"; + case wippersnapper_sensor_SensorType_SENSOR_TYPE_LINEAR_ACCELERATION: + return "LINEAR_ACCELERATION"; + case wippersnapper_sensor_SensorType_SENSOR_TYPE_ROTATION_VECTOR: + return "ROTATION_VECTOR"; + case wippersnapper_sensor_SensorType_SENSOR_TYPE_RELATIVE_HUMIDITY: + return "RELATIVE_HUMIDITY"; + case wippersnapper_sensor_SensorType_SENSOR_TYPE_AMBIENT_TEMPERATURE: + return "AMBIENT_TEMPERATURE"; + case wippersnapper_sensor_SensorType_SENSOR_TYPE_OBJECT_TEMPERATURE: + return "OBJECT_TEMPERATURE"; + case wippersnapper_sensor_SensorType_SENSOR_TYPE_VOLTAGE: + return "VOLTAGE"; + case wippersnapper_sensor_SensorType_SENSOR_TYPE_CURRENT: + return "CURRENT"; + case wippersnapper_sensor_SensorType_SENSOR_TYPE_COLOR: + return "COLOR"; + case wippersnapper_sensor_SensorType_SENSOR_TYPE_RAW: + return "RAW"; + case wippersnapper_sensor_SensorType_SENSOR_TYPE_PM10_STD: + return "PM10_STD"; + case wippersnapper_sensor_SensorType_SENSOR_TYPE_PM25_STD: + return "PM25_STD"; + case wippersnapper_sensor_SensorType_SENSOR_TYPE_PM100_STD: + return "PM100_STD"; + case wippersnapper_sensor_SensorType_SENSOR_TYPE_PM10_ENV: + return "PM10_ENV"; + case wippersnapper_sensor_SensorType_SENSOR_TYPE_PM25_ENV: + return "PM25_ENV"; + case wippersnapper_sensor_SensorType_SENSOR_TYPE_PM100_ENV: + return "PM100_ENV"; + case wippersnapper_sensor_SensorType_SENSOR_TYPE_CO2: + return "CO2"; + case wippersnapper_sensor_SensorType_SENSOR_TYPE_GAS_RESISTANCE: + return "GAS_RESISTANCE"; + case wippersnapper_sensor_SensorType_SENSOR_TYPE_ALTITUDE: + return "ALTITUDE"; + case wippersnapper_sensor_SensorType_SENSOR_TYPE_LUX: + return "LUX"; + case wippersnapper_sensor_SensorType_SENSOR_TYPE_ECO2: + return "ECO2"; + case wippersnapper_sensor_SensorType_SENSOR_TYPE_UNITLESS_PERCENT: + return "UNITLESS_PERCENT"; + case wippersnapper_sensor_SensorType_SENSOR_TYPE_AMBIENT_TEMPERATURE_FAHRENHEIT: + return "AMBIENT_TEMPERATURE_FAHRENHEIT"; + case wippersnapper_sensor_SensorType_SENSOR_TYPE_OBJECT_TEMPERATURE_FAHRENHEIT: + return "OBJECT_TEMPERATURE_FAHRENHEIT"; + case wippersnapper_sensor_SensorType_SENSOR_TYPE_VOC_INDEX: + return "VOC_INDEX"; + case wippersnapper_sensor_SensorType_SENSOR_TYPE_NOX_INDEX: + return "NOX_INDEX"; + case wippersnapper_sensor_SensorType_SENSOR_TYPE_TVOC: + return "TVOC"; + case wippersnapper_sensor_SensorType_SENSOR_TYPE_BYTES: + return "BYTES"; + case wippersnapper_sensor_SensorType_SENSOR_TYPE_BOOLEAN: + return "BOOLEAN"; + default: + return "UNKNOWN"; + } +} + +/**************************************************************************/ +/*! + @brief Logs a GPIO sensor event to the SD card. + @param pin + The GPIO pin number. + @param value + The sensor value. + @param read_type + The sensor type. + @returns True if the event was successfully logged, False otherwise. +*/ +/**************************************************************************/ +bool ws_sdcard::LogGPIOSensorEventToSD( + uint8_t pin, float value, wippersnapper_sensor_SensorType read_type) { + // Get the pin name in the correct format ("A0", "A1", etc.) + char c_pin_name[12]; + sprintf(c_pin_name, "A%d", pin); + + // Get the RTC's timestamp + uint32_t timestamp = GetTimestamp(); + + // Create the JSON document + JsonDocument doc; + + doc["timestamp"] = timestamp; + doc["pin"] = c_pin_name; + doc["value"] = value; + doc["si_unit"] = SensorTypeToString(read_type); + serializeJson(doc, Serial); + return true; +} + +/**************************************************************************/ +/*! + @brief Logs a GPIO sensor event to the SD card. + @param pin + The GPIO pin number. + @param value + The sensor value. + @param read_type + The sensor type. + @returns True if the event was successfully logged, False otherwise. +*/ +/**************************************************************************/ +bool ws_sdcard::LogGPIOSensorEventToSD( + uint8_t pin, uint16_t value, wippersnapper_sensor_SensorType read_type) { + // Get the pin name in the correct format ("A0", "A1", etc.) + char c_pin_name[12]; + sprintf(c_pin_name, "A%d", pin); + + // Get the RTC's timestamp + uint32_t timestamp = GetTimestamp(); + + // Append to the file in JSONL format + JsonDocument doc; + doc["timestamp"] = timestamp; + doc["pin"] = c_pin_name; + doc["value"] = value; + doc["si_unit"] = SensorTypeToString(read_type); + serializeJson(doc, Serial); + Serial.println(""); // JSON requires a newline at the end of each log line + return true; +} + +/**************************************************************************/ +/*! + @brief Logs a GPIO sensor event to the SD card. + @param pin + The GPIO pin number. + @param value + The sensor value. + @param read_type + The sensor type. + @returns True if the event was successfully logged, False otherwise. +*/ +/**************************************************************************/ +bool ws_sdcard::LogGPIOSensorEventToSD( + uint8_t pin, bool value, wippersnapper_sensor_SensorType read_type) { + // Get the pin name in the correct format ("A0", "A1", etc.) + char c_pin_name[12]; + sprintf(c_pin_name, "A%d", pin); + + // Get the RTC's timestamp + uint32_t timestamp = GetTimestamp(); + + // Create the JSON document + JsonDocument doc; + doc["timestamp"] = timestamp; + doc["pin"] = c_pin_name; + doc["value"] = value; + doc["si_unit"] = SensorTypeToString(read_type); + serializeJson(doc, Serial); + Serial.println(""); + return true; +} + +/**************************************************************************/ +/*! + @brief Logs a GPIO sensor event to the SD card. + @param pin + The GPIO pin number. + @param value + The sensor value. + @param read_type + The sensor type. + @returns True if the event was successfully logged, False otherwise. +*/ +/**************************************************************************/ +bool ws_sdcard::LogDS18xSensorEventToSD( + wippersnapper_ds18x20_Ds18x20Event *event_msg) { + // Get the RTC's timestamp + uint32_t timestamp = GetTimestamp(); + + // Create the JSON document + JsonDocument doc; + // Iterate over the event message's sensor events + for (int i = 0; i < event_msg->sensor_events_count; i++) { + doc["timestamp"] = timestamp; + doc["pin"] = event_msg->onewire_pin; + doc["value"] = event_msg->sensor_events[i].value.float_value; + doc["si_unit"] = SensorTypeToString(event_msg->sensor_events[i].type); + serializeJson(doc, Serial); + Serial.println(""); + } + return true; +} \ No newline at end of file diff --git a/src/provisioning/sdcard/ws_sdcard.h b/src/provisioning/sdcard/ws_sdcard.h new file mode 100644 index 000000000..b9e3bae52 --- /dev/null +++ b/src/provisioning/sdcard/ws_sdcard.h @@ -0,0 +1,68 @@ +/*! + * @file ws_sdcard.h + * + * Interface for Wippersnapper's SD card filesystem. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2024 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#ifndef WS_SDCARD_H +#define WS_SDCARD_H + +#include "RTClib.h" +#include "SdFat.h" +#include "Wippersnapper_V2.h" +#define SD_FAT_TYPE 3 +// forward decl. +class Wippersnapper_V2; + +/***************************************************************************/ +/*! + @brief Class that handles Wippersnapper's optional filesystem commands + and storage. +*/ +/***************************************************************************/ +class ws_sdcard { +public: + ws_sdcard(); + ~ws_sdcard(); + void EnableLogging(); + bool parseConfigFile(); + bool waitForSerialConfig(); + bool validateJson(const char *input); + bool mode_offline; + // Encoders for SD card logging + // GPIO (Analog and Digital Pin) Events + bool LogGPIOSensorEventToSD(uint8_t pin, float value, + wippersnapper_sensor_SensorType read_type); + bool LogGPIOSensorEventToSD(uint8_t pin, bool value, + wippersnapper_sensor_SensorType read_type); + bool LogGPIOSensorEventToSD(uint8_t pin, uint16_t value, + wippersnapper_sensor_SensorType read_type); + bool LogDS18xSensorEventToSD(wippersnapper_ds18x20_Ds18x20Event *event_msg); + // RTC + uint32_t GetTimestamp(); + // Logging + // TODO: + // 1) Create a logging file on the SD + // 2) Create a func to decode pb data to json string + // 3) Log the json string x`xto the file +private: + SdFat _sd; ///< SD object from Adafruit SDFat library + String _serialInput; ///< Serial input buffer + const char *json_test_data; ///< Json test data + bool _use_test_data; ///< True if sample data is being used to test, instead + ///< of serial input, False otherwise. + bool _wokwi_runner; ///< True if `exportedBy` key is "wokwi", otherwise False + RTC_DS3231 *_rtc_ds3231 = nullptr; ///< DS3231 RTC object + RTC_DS1307 *_rtc_ds1307 = nullptr; ///< DS1307 RTC object + RTC_PCF8523 *_rtc_pcf8523 = nullptr; ///< PCF8523 RTC object +}; +extern Wippersnapper_V2 WsV2; +#endif // WS_SDCARD_H \ No newline at end of file diff --git a/src/provisioning/tinyusb/Wippersnapper_FS.cpp b/src/provisioning/tinyusb/Wippersnapper_FS.cpp index d99c6098b..eacffc252 100644 --- a/src/provisioning/tinyusb/Wippersnapper_FS.cpp +++ b/src/provisioning/tinyusb/Wippersnapper_FS.cpp @@ -618,17 +618,17 @@ void qspi_msc_flush_cb(void) { //--------------------------------------------------------------------+ extern "C" { -DSTATUS disk_status(BYTE pdrv) { +DSTATUS fdisk_status(BYTE pdrv) { (void)pdrv; return 0; } -DSTATUS disk_initialize(BYTE pdrv) { +DSTATUS fdisk_initialize(BYTE pdrv) { (void)pdrv; return 0; } -DRESULT disk_read(BYTE pdrv, /* Physical drive nmuber to identify the drive */ +DRESULT fdisk_read(BYTE pdrv, /* Physical drive nmuber to identify the drive */ BYTE *buff, /* Data buffer to store read data */ DWORD sector, /* Start sector in LBA */ UINT count /* Number of sectors to read */ @@ -637,7 +637,7 @@ DRESULT disk_read(BYTE pdrv, /* Physical drive nmuber to identify the drive */ return flash.readBlocks(sector, buff, count) ? RES_OK : RES_ERROR; } -DRESULT disk_write(BYTE pdrv, /* Physical drive nmuber to identify the drive */ +DRESULT fdisk_write(BYTE pdrv, /* Physical drive nmuber to identify the drive */ const BYTE *buff, /* Data to be written */ DWORD sector, /* Start sector in LBA */ UINT count /* Number of sectors to write */ @@ -646,7 +646,7 @@ DRESULT disk_write(BYTE pdrv, /* Physical drive nmuber to identify the drive */ return flash.writeBlocks(sector, buff, count) ? RES_OK : RES_ERROR; } -DRESULT disk_ioctl(BYTE pdrv, /* Physical drive nmuber (0..) */ +DRESULT fdisk_ioctl(BYTE pdrv, /* Physical drive nmuber (0..) */ BYTE cmd, /* Control code */ void *buff /* Buffer to send/receive control data */ ) { diff --git a/src/provisioning/tinyusb/Wippersnapper_FS.h b/src/provisioning/tinyusb/Wippersnapper_FS.h index 22fe7df27..c4baca50a 100644 --- a/src/provisioning/tinyusb/Wippersnapper_FS.h +++ b/src/provisioning/tinyusb/Wippersnapper_FS.h @@ -65,6 +65,7 @@ class Wippersnapper_FS { private: bool _freshFS = false; /*!< True if filesystem was initialized by WipperSnapper, False otherwise. */ + bool _isFormatted = false; }; extern Wippersnapper WS; diff --git a/src/provisioning/tinyusb/Wippersnapper_FS_V2.cpp b/src/provisioning/tinyusb/Wippersnapper_FS_V2.cpp new file mode 100644 index 000000000..4b9eb95ed --- /dev/null +++ b/src/provisioning/tinyusb/Wippersnapper_FS_V2.cpp @@ -0,0 +1,704 @@ +/*! + * @file Wippersnapper_FS_V2.cpp + * + * Wippersnapper TinyUSB Filesystem Driver + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2024 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#if defined(ARDUINO_MAGTAG29_ESP32S2) || defined(ARDUINO_METRO_ESP32S2) || \ + defined(ARDUINO_FUNHOUSE_ESP32S2) || \ + defined(ADAFRUIT_PYPORTAL_M4_TITANO) || \ + defined(ADAFRUIT_METRO_M4_AIRLIFT_LITE) || defined(ADAFRUIT_PYPORTAL) || \ + defined(ARDUINO_ADAFRUIT_FEATHER_ESP32S2) || \ + defined(ARDUINO_ADAFRUIT_QTPY_ESP32S2) || \ + defined(ARDUINO_ADAFRUIT_FEATHER_ESP32S2_TFT) || \ + defined(ARDUINO_ADAFRUIT_FEATHER_ESP32S3_NOPSRAM) || \ + defined(ARDUINO_ADAFRUIT_QTPY_ESP32S3_NOPSRAM) || \ + defined(ARDUINO_ADAFRUIT_FEATHER_ESP32S3) || \ + defined(ARDUINO_ADAFRUIT_FEATHER_ESP32S3_TFT) || \ + defined(ARDUINO_RASPBERRY_PI_PICO_W) || \ + defined(ARDUINO_ADAFRUIT_FEATHER_ESP32S3_REVTFT) || \ + defined(ARDUINO_ADAFRUIT_FEATHER_ESP32S2_REVTFT) || \ + defined(ARDUINO_ADAFRUIT_QTPY_ESP32S3_N4R2) +#include "Wippersnapper_FS_V2.h" +// On-board external flash (QSPI or SPI) macros should already +// defined in your board variant if supported +// - EXTERNAL_FLASH_USE_QSPI +// - EXTERNAL_FLASH_USE_CS/EXTERNAL_FLASH_USE_SPI +#if defined(EXTERNAL_FLASH_USE_QSPI) +Adafruit_FlashTransport_QSPI flashTransport_v2; +#elif defined(EXTERNAL_FLASH_USE_SPI) +Adafruit_FlashTransport_SPI flashTransport_v2(EXTERNAL_FLASH_USE_CS, + EXTERNAL_FLASH_USE_SPI); +#elif defined(ARDUINO_ARCH_ESP32) +// ESP32-S2 uses same flash device that stores code. +// Therefore there is no need to specify the SPI and SS +Adafruit_FlashTransport_ESP32 flashTransport_v2; +#elif defined(ARDUINO_ARCH_RP2040) +// RP2040 use same flash device that store code. +// Therefore there is no need to specify the SPI and SS +// Use default (no-args) constructor to be compatible with CircuitPython +// partition scheme +Adafruit_FlashTransport_RP2040 flashTransport_v2; +#else +#error No QSPI/SPI flash are defined on your board variant.h! +#endif + +Adafruit_SPIFlash flash_v2(&flashTransport_v2); ///< SPIFlash object +FatVolume wipperFatFs_v2; ///< File system object from Adafruit SDFat library +Adafruit_USBD_MSC usb_msc_v2; /*!< USB mass storage object */ + +/**************************************************************************/ +/*! + @brief Formats the flash filesystem as FAT12. + @returns FR_OK if filesystem formatted correctly, + otherwise any FRESULT enum. +*/ +/**************************************************************************/ +FRESULT format_fs_fat12(void) { + FATFS elmchamFatfs_v2; + uint8_t workbuf_v2[4096]; + + // make filesystem + FRESULT r = f_mkfs("", FM_FAT | FM_SFD, 0, workbuf_v2, sizeof(workbuf_v2)); + + // mount to set disk label + r = f_mount(&elmchamFatfs_v2, "0:", 1); + if (r != FR_OK) + return r; + + // set fs label + r = f_setlabel("WIPPER"); + if (r != FR_OK) + return r; + + // unmount fs + f_unmount("0:"); + + // sync to make sure all data is written to flash + flash_v2.syncBlocks(); + return r; +} + +/**************************************************************************/ +/*! + @brief Initializes USB-MSC and the QSPI flash filesystem. +*/ +/**************************************************************************/ +Wippersnapper_FS_V2::Wippersnapper_FS_V2() { + // Detach USB device during init. + TinyUSBDevice.detach(); + // Wait for detach + delay(500); + + // Attempt to initialize the flash chip + if (!flash_v2.begin()) { + setStatusLEDColor(RED); + fsHalt("Failed to initialize the flash chip!"); + } + + // Attempt to initialize the filesystem + bool is_fs_formatted = wipperFatFs_v2.begin(&flash_v2); + + // If we are not formatted, attempt to format the filesystem as fat12 + if (!is_fs_formatted) { + FRESULT rc = format_fs_fat12(); + + if (rc != FR_OK) { + setStatusLEDColor(RED); + fsHalt("FATAL ERROR: Failed to format the filesystem!"); + } + + // Now that we have a formatted filesystem, we need to inititalize it + if (!wipperFatFs_v2.begin(&flash_v2)) { + setStatusLEDColor(RED); + fsHalt("FATAL ERROR: Failed to mount newly created filesystem!"); + } + } + + // Write contents to the formatted filesystem + if (!writeFSContents()) { + setStatusLEDColor(RED); + fsHalt("FATAL ERROR: Could not write filesystem contents!"); + } + + // Initialize USB-MSC + initUSBMSC(); + + // If we wrote a fresh secrets.json file, halt until user edits the file and + // RESETs the device Signal to user that action must be taken (edit + // secrets.json) + if (_is_secrets_file_empty) { + writeToBootOut( + "Please edit the secrets.json file. Then, reset your board.\n"); +#ifdef USE_DISPLAY + WsV2._ui_helper->show_scr_error( + "INVALID SETTINGS FILE", + "The settings.json file on the WIPPER drive contains default values. " + "Please edit it to reflect your Adafruit IO and network credentials. " + "When you're done, press RESET on the board."); +#endif + fsHalt("The settings.json file on the WIPPER drive contains default " + "values\n. Using a text editor, edit it to reflect your Adafruit IO " + "and WiFi credentials. Then, reset the board."); + } +} + +/************************************************************/ +/*! + @brief Filesystem destructor +*/ +/************************************************************/ +Wippersnapper_FS_V2::~Wippersnapper_FS_V2() { + // Unmount filesystem + wipperFatFs_v2.end(); +} + +/**************************************************************************/ +/*! + @brief Writes files to the filesystem to disable macOS from indexing. + @returns True if files written successfully, false otherwise. +*/ +/**************************************************************************/ +bool disableMacOSIndexing() { + wipperFatFs_v2.mkdir("/.fseventsd/"); + File32 writeFile = wipperFatFs_v2.open("/.fseventsd/no_log", FILE_WRITE); + if (!writeFile) + return false; + writeFile.close(); + + writeFile = wipperFatFs_v2.open("/.metadata_never_index", FILE_WRITE); + if (!writeFile) + return false; + writeFile.close(); + + writeFile = wipperFatFs_v2.open("/.Trashes", FILE_WRITE); + if (!writeFile) + return false; + writeFile.close(); + + return true; +} + +/**************************************************************************/ +/*! + @brief Initializes the flash filesystem. + @param force_format + If true, forces a new filesystem to be created. [DESTRUCTIVE] + @return True if filesystem initialized correctly, false otherwise. +*/ +/**************************************************************************/ +bool Wippersnapper_FS_V2::writeFSContents() { + // If CircuitPython was previously installed - erase CircuitPython's default + // filesystem + eraseCPFS(); + + // If WipperSnapper was previously installed - remove the old + // wippersnapper_boot_out.txt file + eraseBootFile(); + + // Disble indexing on macOS + disableMacOSIndexing(); + + // Create wippersnapper_boot_out.txt file + if (!createBootFile()) + return false; + + // Check if secrets.json file already exists + if (!getSecretsFile()) { + // Create new secrets.json file and halt + createSecretsFile(); + _is_secrets_file_empty = true; + } + return true; +} + +/**************************************************************************/ +/*! + @brief Initializes the USB MSC device. +*/ +/**************************************************************************/ +void Wippersnapper_FS_V2::initUSBMSC() { + // Set disk vendor id, product id and revision with string up to 8, 16, 4 + // characters respectively + usb_msc_v2.setID("Adafruit", "External Flash", "1.0"); + // Set callback + usb_msc_v2.setReadWriteCallback(qspi_msc_read_cb_v2, qspi_msc_write_cb_v2, + qspi_msc_flush_cb_v2); + + // Set disk size, block size should be 512 regardless of spi flash page size + usb_msc_v2.setCapacity(flash_v2.pageSize() * flash_v2.numPages() / 512, 512); + + // MSC is ready for read/write + usb_msc_v2.setUnitReady(true); + + // init MSC + usb_msc_v2.begin(); + + // If already enumerated, additional class driverr begin() e.g msc, hid, midi + // won't take effect until re-enumeration + if (TinyUSBDevice.mounted()) { + TinyUSBDevice.detach(); + delay(10); + TinyUSBDevice.attach(); + } +} + +/**************************************************************************/ +/*! + @brief Checks if secrets.json file exists on the flash filesystem. + @returns True if secrets.json file exists, False otherwise. +*/ +/**************************************************************************/ +bool Wippersnapper_FS_V2::getSecretsFile() { + // Does secrets.json file exist? + return wipperFatFs_v2.exists("/secrets.json"); +} + +/**************************************************************************/ +/*! + @brief Erases the default CircuitPython filesystem if it exists. +*/ +/**************************************************************************/ +void Wippersnapper_FS_V2::eraseCPFS() { + if (wipperFatFs_v2.exists("/boot_out.txt")) { + wipperFatFs_v2.remove("/boot_out.txt"); + wipperFatFs_v2.remove("/code.py"); + File32 libDir = wipperFatFs_v2.open("/lib"); + libDir.rmRfStar(); + } +} + +/**************************************************************************/ +/*! + @brief Erases the existing "wipper_boot_out.txt" file from the FS. +*/ +/**************************************************************************/ +void Wippersnapper_FS_V2::eraseBootFile() { + // overwrite previous boot_out file on each boot + if (wipperFatFs_v2.exists("/wipper_boot_out.txt")) + wipperFatFs_v2.remove("/wipper_boot_out.txt"); +} + +/**************************************************************************/ +/*! + @brief Creates or overwrites `wipper_boot_out.txt` file to FS. +*/ +/**************************************************************************/ +bool Wippersnapper_FS_V2::createBootFile() { + bool is_success = false; + char sMAC[18] = {0}; + + File32 bootFile = wipperFatFs_v2.open("/wipper_boot_out.txt", FILE_WRITE); + if (bootFile) { + bootFile.println("Adafruit.io WipperSnapper"); + + bootFile.print("Firmware Version: "); + bootFile.println(WS_VERSION); + + bootFile.print("Board ID: "); + bootFile.println(BOARD_ID); + + sprintf(sMAC, "%02X:%02X:%02X:%02X:%02X:%02X", WsV2._macAddrV2[0], + WsV2._macAddrV2[1], WsV2._macAddrV2[2], WsV2._macAddrV2[3], + WsV2._macAddrV2[4], WsV2._macAddrV2[5]); + bootFile.print("MAC Address: "); + bootFile.println(sMAC); + +// Print ESP-specific info to boot file +#ifdef ARDUINO_ARCH_ESP32 + // Get version of ESP-IDF + bootFile.print("ESP-IDF Version: "); + bootFile.println(ESP.getSdkVersion()); + // Get version of this core + bootFile.print("ESP32 Core Version: "); + bootFile.println(ESP.getCoreVersion()); +#endif + + bootFile.flush(); + bootFile.close(); + is_success = true; + } else { + bootFile.close(); + } + return is_success; +} + +/**************************************************************************/ +/*! + @brief Creates a default secrets.json file on the filesystem. +*/ +/**************************************************************************/ +void Wippersnapper_FS_V2::createSecretsFile() { + // Open file for writing + File32 secretsFile = wipperFatFs_v2.open("/secrets.json", FILE_WRITE); + + // Create a default secretsConfig structure + secretsConfig secretsConfig; + strcpy(secretsConfig.aio_user, "YOUR_IO_USERNAME_HERE"); + strcpy(secretsConfig.aio_key, "YOUR_IO_KEY_HERE"); + strcpy(secretsConfig.network.ssid, "YOUR_WIFI_SSID_HERE"); + strcpy(secretsConfig.network.pass, "YOUR_WIFI_PASS_HERE"); + secretsConfig.status_pixel_brightness = 0.2; + + // Create and fill JSON document from secretsConfig + JsonDocument doc; + doc.set(secretsConfig); + + // Serialize JSON to file + serializeJsonPretty(doc, secretsFile); + + // Flush and close file + secretsFile.flush(); + secretsFile.close(); + delay(2500); +} + +/**************************************************************************/ +/*! + @brief Parses a secrets.json file on the flash filesystem. +*/ +/**************************************************************************/ +void Wippersnapper_FS_V2::parseSecrets() { + // Attempt to open the secrets.json file for reading + File32 secretsFile = wipperFatFs_v2.open("/secrets.json"); + if (!secretsFile) { + fsHalt("ERROR: Could not open secrets.json file for reading!"); + } + + // Attempt to deserialize the file's JSON document + JsonDocument doc; + DeserializationError error = deserializeJson(doc, secretsFile); + if (error) { + fsHalt(String("ERROR: Unable to parse secrets.json file - " + "deserializeJson() failed with code") + + error.c_str()); + } + + if (doc.containsKey("network_type_wifi")) { + // set default network config + convertFromJson(doc["network_type_wifi"], WsV2._configV2.network); + + if (!doc["network_type_wifi"].containsKey("alternative_networks")) { + // do nothing extra, we already have the only network + WS_DEBUG_PRINTLN("Found single wifi network in secrets.json"); + + } else if (doc["network_type_wifi"]["alternative_networks"] + .is()) { + + WS_DEBUG_PRINTLN("Found multiple wifi networks in secrets.json"); + // Parse network credentials from array in secrets + JsonArray altnetworks = doc["network_type_wifi"]["alternative_networks"]; + int8_t altNetworkCount = (int8_t)altnetworks.size(); + WS_DEBUG_PRINT("Network count: "); + WS_DEBUG_PRINTLN(altNetworkCount); + if (altNetworkCount == 0) { + fsHalt("ERROR: No alternative network entries found under " + "network_type_wifi.alternative_networks in secrets.json!"); + } + // check if over 3, warn user and take first three + for (int i = 0; i < altNetworkCount; i++) { + if (i >= 3) { + WS_DEBUG_PRINT("WARNING: More than 3 networks in secrets.json, " + "only the first 3 will be used. Not using "); + WS_DEBUG_PRINTLN(altnetworks[i]["network_ssid"].as()); + break; + } + convertFromJson(altnetworks[i], WsV2._multiNetworksV2[i]); + WS_DEBUG_PRINT("Added SSID: "); + WS_DEBUG_PRINTLN(WsV2._multiNetworksV2[i].ssid); + WS_DEBUG_PRINT("PASS: "); + WS_DEBUG_PRINTLN(WsV2._multiNetworksV2[i].pass); + } + WsV2._isWiFiMultiV2 = true; + } else { + fsHalt("ERROR: Unrecognised value type for " + "network_type_wifi.alternative_networks in secrets.json!"); + } + } else { + fsHalt("ERROR: Could not find network_type_wifi in secrets.json!"); + } + + // Extract a config struct from the JSON document + WsV2._configV2 = doc.as(); + + // Validate the config struct is not filled with default values + if (strcmp(WsV2._configV2.aio_user, "YOUR_IO_USERNAME_HERE") == 0 || + strcmp(WsV2._configV2.aio_key, "YOUR_IO_KEY_HERE") == 0) { + writeToBootOut( + "ERROR: Invalid IO credentials in secrets.json! TO FIX: Please change " + "io_username and io_key to match your Adafruit IO credentials!\n"); +#ifdef USE_DISPLAY + WsV2._ui_helper->show_scr_error( + "INVALID IO CREDS", + "The \"io_username/io_key\" fields within secrets.json are invalid, " + "please " + "change it to match your Adafruit IO credentials. Then, press RESET."); +#endif + fsHalt( + "ERROR: Invalid IO credentials in secrets.json! TO FIX: Please change " + "io_username and io_key to match your Adafruit IO credentials!"); + } + + if (strcmp(WsV2._configV2.network.ssid, "YOUR_WIFI_SSID_HERE") == 0 || + strcmp(WsV2._configV2.network.pass, "YOUR_WIFI_PASS_HERE") == 0) { + writeToBootOut("ERROR: Invalid network credentials in secrets.json! TO " + "FIX: Please change network_ssid and network_password to " + "match your Adafruit IO credentials!\n"); +#ifdef USE_DISPLAY + WsV2._ui_helper->show_scr_error( + "INVALID NETWORK", + "The \"network_ssid and network_password\" fields within secrets.json " + "are invalid, please change it to match your WiFi credentials. Then, " + "press RESET."); +#endif + fsHalt("ERROR: Invalid network credentials in secrets.json! TO FIX: Please " + "change network_ssid and network_password to match your Adafruit IO " + "credentials!"); + } + + writeToBootOut("Secrets Contents\n"); + writeToBootOut("Network Info\n: "); + writeToBootOut(WsV2._configV2.network.ssid); + writeToBootOut(WsV2._configV2.network.pass); + writeToBootOut("IO Creds.\n: "); + writeToBootOut(WsV2._configV2.aio_user); + writeToBootOut(WsV2._configV2.aio_key); + + // Close secrets.json file + secretsFile.close(); +} + +/**************************************************************************/ +/*! + @brief Appends message string to wipper_boot_out.txt file. + @param str + PROGMEM string. +*/ +/**************************************************************************/ +void Wippersnapper_FS_V2::writeToBootOut(PGM_P str) { + // Append error output to FS + File32 bootFile = wipperFatFs_v2.open("/wipper_boot_out.txt", FILE_WRITE); + if (!bootFile) + fsHalt("ERROR: Unable to open wipper_boot_out.txt for logging!"); + bootFile.print(str); + bootFile.flush(); + bootFile.close(); +} + +/**************************************************************************/ +/*! + @brief Halts execution and blinks the status LEDs yellow. + @param msg + Error message to print to serial console. +*/ +/**************************************************************************/ +void Wippersnapper_FS_V2::fsHalt(String msg) { + TinyUSBDevice.attach(); + delay(500); + statusLEDSolid(WS_LED_STATUS_FS_WRITE); + while (1) { + WS_DEBUG_PRINT("Execution Halted: "); + WS_DEBUG_PRINTLN(msg.c_str()); + delay(5000); + yield(); + } +} + +#ifdef ARDUINO_FUNHOUSE_ESP32S2 +/**************************************************************************/ +/*! + @brief Creates a default display_config.json file on the filesystem. +*/ +/**************************************************************************/ +void Wippersnapper_FS_V2::createDisplayConfig() { + // Open file for writing + File32 displayFile = wipperFatFs_v2.open("/display_config.json", FILE_WRITE); + + // Create a default displayConfig structure + displayConfig displayConfig; + strcpy(displayConfig.driver, "ST7789"); + displayConfig.width = 240; + displayConfig.height = 240; + displayConfig.rotation = 0; + displayConfig.spiConfig.pinCs = 40; + displayConfig.spiConfig.pinDc = 39; + displayConfig.spiConfig.pinMosi = 0; + displayConfig.spiConfig.pinSck = 0; + displayConfig.spiConfig.pinRst = 41; + + // Create and fill JSON document from displayConfig + JsonDocument doc; + if (!doc.set(displayConfig)) { + fsHalt("ERROR: Unable to set displayConfig, no space in arduinoJSON " + "document!"); + } + // Write the file out to the filesystem + serializeJsonPretty(doc, displayFile); + displayFile.flush(); + displayFile.close(); + delay(2500); // give FS some time to write the file +} + +/**************************************************************************/ +/*! + @brief Parses a display_config.json file on the flash filesystem. + @param dispCfg + displayConfig struct to populate. +*/ +/**************************************************************************/ +void Wippersnapper_FS_V2::parseDisplayConfig(displayConfig &dispCfg) { + // Check if display_config.json file exists, if not, generate it + if (!wipperFatFs_v2.exists("/display_config.json")) { + WS_DEBUG_PRINTLN("Could not find display_config.json, generating..."); +#ifdef ARDUINO_FUNHOUSE_ESP32S2 + createDisplayConfig(); // generate a default display_config.json for + // FunHouse +#endif + } + + // Attempt to open file for JSON parsing + File32 file = wipperFatFs_v2.open("/display_config.json", FILE_READ); + if (!file) { + fsHalt("FATAL ERROR: Unable to open display_config.json for parsing"); + } + + // Attempt to deserialize the file's json document + JsonDocument doc; + DeserializationError error = deserializeJson(doc, file); + if (error) { + fsHalt(String("FATAL ERROR: Unable to parse display_config.json - " + "deserializeJson() failed with code") + + error.c_str()); + } + // Close the file, we're done with it + file.close(); + // Extract a displayConfig struct from the JSON document + dispCfg = doc.as(); +} +#endif // ARDUINO_FUNHOUSE_ESP32S2 + +/**************************************************************************/ +/*! + @brief Callback invoked when received READ10 command. Copies disk's + data up to buffer. + @param lba + Logical address of first block to read. + @param buffer + Desired buffer to read from. + @param bufsize + Desired size of buffer to read. + @returns Number of written bytes (must be multiple of block size) +*/ +/**************************************************************************/ +int32_t qspi_msc_read_cb_v2(uint32_t lba, void *buffer, uint32_t bufsize) { + // Note: SPIFLash Bock API: readBlocks/writeBlocks/syncBlocks + // already include 4K sector caching internally. We don't need to cache it, + // yahhhh!! + return flash_v2.readBlocks(lba, (uint8_t *)buffer, bufsize / 512) ? bufsize + : -1; +} + +/**************************************************************************/ +/*! + @brief Callback invoked when received WRITE command. Process data + in buffer to disk's storage. + @param lba + Logical address of first block to write. + @param buffer + Desired buffer to write to. + @param bufsize + Desired size of buffer to write. + @returns Number of written bytes (must be multiple of block size) +*/ +/**************************************************************************/ +int32_t qspi_msc_write_cb_v2(uint32_t lba, uint8_t *buffer, uint32_t bufsize) { + // Note: SPIFLash Bock API: readBlocks/writeBlocks/syncBlocks + // already include 4K sector caching internally. We don't need to cache it, + // yahhhh!! + return flash_v2.writeBlocks(lba, buffer, bufsize / 512) ? bufsize : -1; +} + +/***************************************************************************/ +/*! + @brief Callback invoked when WRITE10 command is completed (status + received and accepted by host). Used to flush any pending cache. +*/ +/***************************************************************************/ +void qspi_msc_flush_cb_v2(void) { + // sync w/flash + flash_v2.syncBlocks(); + // clear file system's cache to force refresh + wipperFatFs_v2.cacheClear(); +} + +//--------------------------------------------------------------------+ +// fatfs diskio +//--------------------------------------------------------------------+ +extern "C" { + +DSTATUS disk_status(BYTE pdrv) { + (void)pdrv; + return 0; +} + +DSTATUS disk_initialize(BYTE pdrv) { + (void)pdrv; + return 0; +} + +DRESULT disk_read(BYTE pdrv, /* Physical drive nmuber to identify the drive */ + BYTE *buff, /* Data buffer to store read data */ + DWORD sector, /* Start sector in LBA */ + UINT count /* Number of sectors to read */ +) { + (void)pdrv; + return flash_v2.readBlocks(sector, buff, count) ? RES_OK : RES_ERROR; +} + +DRESULT disk_write(BYTE pdrv, /* Physical drive nmuber to identify the drive */ + const BYTE *buff, /* Data to be written */ + DWORD sector, /* Start sector in LBA */ + UINT count /* Number of sectors to write */ +) { + (void)pdrv; + return flash_v2.writeBlocks(sector, buff, count) ? RES_OK : RES_ERROR; +} + +DRESULT disk_ioctl(BYTE pdrv, /* Physical drive nmuber (0..) */ + BYTE cmd, /* Control code */ + void *buff /* Buffer to send/receive control data */ +) { + (void)pdrv; + + switch (cmd) { + case CTRL_SYNC: + flash_v2.syncBlocks(); + return RES_OK; + + case GET_SECTOR_COUNT: + *((DWORD *)buff) = flash_v2.size() / 512; + return RES_OK; + + case GET_SECTOR_SIZE: + *((WORD *)buff) = 512; + return RES_OK; + + case GET_BLOCK_SIZE: + *((DWORD *)buff) = 8; // erase block size in units of sector size + return RES_OK; + + default: + return RES_PARERR; + } +} +} + +#endif // USE_TINYUSB \ No newline at end of file diff --git a/src/provisioning/tinyusb/Wippersnapper_FS_V2.h b/src/provisioning/tinyusb/Wippersnapper_FS_V2.h new file mode 100644 index 000000000..8e35f65c3 --- /dev/null +++ b/src/provisioning/tinyusb/Wippersnapper_FS_V2.h @@ -0,0 +1,71 @@ +/*! + * @file Wippersnapper_FS.h + * + * Wippersnapper filesystem + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Copyright (c) Brent Rubell 2021-2024 for Adafruit Industries. + * + * BSD license, all text here must be included in any redistribution. + * + */ +#ifndef WIPPERSNAPPER_FS_V2_H +#define WIPPERSNAPPER_FS_V2_H + +#include "Adafruit_SPIFlash.h" +#include "Adafruit_TinyUSB.h" +#include "SdFat.h" +// using f_mkfs() for formatting +#include "fatfs/diskio.h" +#include "fatfs/ff.h" // NOTE: This should be #included before fatfs/diskio.h!!! + +#include "Wippersnapper_V2.h" +#define SD_FAT_TYPE 3 + +// forward decl. +class Wippersnapper_V2; +struct displayConfig; + +// global TinyUSB callbacks +int32_t qspi_msc_write_cb_v2(uint32_t lba, uint8_t *buffer, uint32_t bufsize); +int32_t qspi_msc_read_cb_v2(uint32_t lba, void *buffer, uint32_t bufsize); +void qspi_msc_flush_cb_v2(void); + +/***************************************************************************/ +/*! + @brief Class that handles Wippersnapper's optional filesystem commands + and storage. +*/ +/***************************************************************************/ +class Wippersnapper_FS_V2 { +public: + Wippersnapper_FS_V2(); + ~Wippersnapper_FS_V2(); + + void initUSBMSC(); + + bool writeFSContents(); + void fsHalt(String msg); + void eraseCPFS(); + + bool createBootFile(); + void writeToBootOut(PGM_P str); + void eraseBootFile(); + + // Secrets.json API + void createSecretsFile(); + bool getSecretsFile(); + void parseSecrets(); + +#ifdef ARDUINO_FUNHOUSE_ESP32S2 + void parseDisplayConfig(displayConfig &displayFile); + void createDisplayConfig(); +#endif +private: + bool _is_secrets_file_empty = false; +}; +extern Wippersnapper_V2 WsV2; +#endif // Wippersnapper_FS_V2_V2_H \ No newline at end of file diff --git a/src/ws_manager.cpp b/src/ws_manager.cpp new file mode 100644 index 000000000..0351cfa7b --- /dev/null +++ b/src/ws_manager.cpp @@ -0,0 +1,101 @@ +#include "ws_manager.h" + +/**************************************************************************/ +/*! + @brief Constructor for Wippersnapper_Manager +*/ +/**************************************************************************/ +Wippersnapper_Manager::Wippersnapper_Manager() : ws_instance(nullptr) {} + +/**************************************************************************/ +/*! + @brief Destructor for Wippersnapper_Manager +*/ +/**************************************************************************/ +Wippersnapper_Manager::~Wippersnapper_Manager() { + if (ws_instance) { + delete ws_instance; + } + if (ws_instance_v2) { + delete ws_instance_v2; + } +} + +/**************************************************************************/ +/*! + @brief Performs the connect() function in Wippersnapper*.cpp +*/ +/**************************************************************************/ +void Wippersnapper_Manager::connect() { + if (_api_version == 2) { + WS_DEBUG_PRINTLN("api v2 instance::connect()"); + ws_instance_v2->connectV2(); + } else if (_api_version == 1) { + WS_DEBUG_PRINTLN("api v1 instance::connect()"); + ws_instance->connect(); + } else { + WS_DEBUG_PRINTLN("Error: Could not call connect(), unknown API version!"); + } +} + +/**************************************************************************/ +/*! + @brief Performs the provision() function in Wippersnapper*.cpp +*/ +/**************************************************************************/ +void Wippersnapper_Manager::provision() { + if (_api_version == 2) { + WS_DEBUG_PRINTLN("api v2 instance::provision()"); + ws_instance_v2->provisionV2(); + } else if (_api_version == 1) { + WS_DEBUG_PRINTLN("api v1 instance::provision()"); + ws_instance->provision(); + } else { + WS_DEBUG_PRINTLN("Error: Could not call provision(), unknown API version!"); + } +} + +/**************************************************************************/ +/*! + @brief Checks the API version by reading the state of a pin + @param pinNum + The pin number to read +*/ +/**************************************************************************/ +void Wippersnapper_Manager::checkAPIVersion(int pinNum) { + // Check if pin D12 is high + pinMode(pinNum, INPUT_PULLUP); + bool readButton = digitalRead(pinNum); + // NOTE: For debugging right now, we are forcing the API version + readButton = true; + if (readButton) { // API version 2 if D12 is high + ws_instance_v2 = new Wippersnapper_WiFiV2(); + _api_version = 2; + } else { // API version 1 if D12 is low + ws_instance = new Wippersnapper_WiFi(); + _api_version = 1; + } +} + +/**************************************************************************/ +/*! + @brief Performs the run() function in Wippersnapper*.cpp +*/ +/**************************************************************************/ +void Wippersnapper_Manager::run() { + if (_api_version == 2) { + ws_instance_v2->runV2(); + } else if (_api_version == 1) { + ws_instance->run(); + } else { + WS_DEBUG_PRINTLN("Error: Could not call run(), unknown API version!"); + } +} + +/**************************************************************************/ +/*! + @brief Returns the API version + @returns The API version +*/ +/**************************************************************************/ +int Wippersnapper_Manager::getAPIVersion() { return _api_version; } \ No newline at end of file diff --git a/src/ws_manager.h b/src/ws_manager.h new file mode 100644 index 000000000..a81679794 --- /dev/null +++ b/src/ws_manager.h @@ -0,0 +1,35 @@ +#ifndef WIPPERSNAPPER_MANAGER_H +#define WIPPERSNAPPER_MANAGER_H + +#include "network_interfaces/Wippersnapper_ESP32.h" +typedef Wippersnapper_ESP32 Wippersnapper_WiFi; +#include "network_interfaces/Wippersnapper_ESP32_V2.h" +typedef Wippersnapper_ESP32V2 Wippersnapper_WiFiV2; + +/****************************************************************************/ +/*! + @brief Helper class to manage the WipperSnapper API versions dynamically + at runtime. +*/ +/****************************************************************************/ +class Wippersnapper_Manager { +public: + Wippersnapper_Manager(); + ~Wippersnapper_Manager(); + + // API version checks + void checkAPIVersion(int pinNum); + int getAPIVersion(); + + // High-level functions (called from demo sketch ino) + void provision(); + void connect(); + void run(); + +protected: + Wippersnapper_WiFi *ws_instance; ///< Instance of Wippersnapper API v1 + Wippersnapper_WiFiV2 *ws_instance_v2; ///< Instance of Wippersnapper API v2 +private: + int _api_version; +}; +#endif // WIPPERSNAPPER_MANAGER_H diff --git a/tests/bin/offline/firmware.elf b/tests/bin/offline/firmware.elf new file mode 100755 index 000000000..dcffe0d21 Binary files /dev/null and b/tests/bin/offline/firmware.elf differ diff --git a/tests/diagram.json b/tests/diagram.json new file mode 100644 index 000000000..fa86f30fd --- /dev/null +++ b/tests/diagram.json @@ -0,0 +1,160 @@ +{ + "version": 1, + "author": "Uri Shaked", + "editor": "wokwi", + "parts": [ + { "type": "wokwi-breadboard", "id": "bb1", "top": -89.4, "left": -266, "attrs": {} }, + { + "type": "wokwi-esp32-devkit-v1", + "id": "esp", + "top": -91.9, + "left": -208.2, + "rotate": 90, + "attrs": {} + }, + { + "type": "wokwi-microsd-card", + "id": "sd1", + "top": -112.63, + "left": -50.93, + "rotate": 90, + "attrs": {} + }, + { + "type": "wokwi-ds1307", + "id": "rtc1", + "top": -119.9, + "left": 26, + "rotate": 270, + "attrs": {} + }, + { "type": "wokwi-potentiometer", "id": "pot1", "top": -87.7, "left": 143.8, "attrs": {} }, + { "type": "wokwi-potentiometer", "id": "pot2", "top": -1.3, "left": 143.8, "attrs": {} }, + { + "type": "wokwi-pushbutton", + "id": "btn1", + "top": 9.2, + "left": 74, + "rotate": 270, + "attrs": { "bounce": "0", "color": "green" } + }, + { "type": "board-ds18b20", "id": "temp1", "top": -87.53, "left": 272.88, "attrs": {} }, + { + "type": "wokwi-resistor", + "id": "r1", + "top": 4.8, + "left": 277.85, + "rotate": 90, + "attrs": { "value": "7400" } + }, + { "type": "board-ds18b20", "id": "temp2", "top": -87.53, "left": 234.48, "attrs": {} }, + { + "type": "wokwi-resistor", + "id": "r2", + "top": -6.2, + "left": 239.15, + "rotate": 270, + "attrs": { "value": "7400" } + } + ], + "connections": [ + [ "esp:TX0", "$serialMonitor:RX", "", [] ], + [ "esp:RX0", "$serialMonitor:TX", "", [] ], + [ "sd1:CS", "esp:D15", "gold", [ "v144", "h-144.26" ] ], + [ "sd1:DI", "esp:D23", "blue", [ "v0" ] ], + [ "sd1:SCK", "esp:D18", "violet", [ "v153.6", "h-115.19" ] ], + [ "sd1:DO", "esp:D19", "orange", [ "v124.8", "h-125.01" ] ], + [ "rtc1:SDA", "esp:D21", "blue", [ "v115.2", "h-183.1" ] ], + [ "rtc1:SCL", "esp:D22", "green", [ "v105.6", "h-163.7" ] ], + [ "pot2:SIG", "esp:D27", "green", [ "h-0.4", "v76.8", "h-345.9" ] ], + [ "esp:3V3", "bb1:bp.1", "red", [ "h-9.6", "v39" ] ], + [ "sd1:VCC", "bb1:bp.18", "red", [ "v0" ] ], + [ "rtc1:GND", "bb1:bn.25", "black", [ "v0" ] ], + [ "esp:GND.1", "bb1:bn.3", "black", [ "v0" ] ], + [ "bb1:33b.h", "bb1:33b.g", "green", [ "v0" ] ], + [ "rtc1:5V", "esp:VIN", "red", [ "v-19.2", "h-269.2" ] ], + [ "pot1:SIG", "esp:D14", "blue", [ "v9.6", "h-48.4", "v144", "h-316.8", "v-172.8" ] ], + [ "btn1:1.l", "esp:D4", "orange", [ "v57.6", "h-268.7" ] ], + [ "temp1:GND", "bb1:bn.48", "black", [ "v0" ] ], + [ "temp1:DQ", "r1:1", "green", [ "v0" ] ], + [ "temp1:VCC", "r1:2", "red", [ "v0" ] ], + [ "r1:1", "esp:D12", "green", [ "v-124.8", "h-490.3" ] ], + [ "temp2:GND", "bb1:bn.36", "black", [ "v0" ] ], + [ "temp2:DQ", "r2:2", "violet", [ "v0" ] ], + [ "temp2:VCC", "r2:1", "red", [ "v0" ] ], + [ "esp:D25", "r2:2", "violet", [ "v-96", "h412.8" ] ], + [ "sd1:GND", "bb1:bn.21", "black", [ "v0" ] ], + [ "btn1:2.r", "bb1:tn.31", "black", [ "h9.8", "v-67.3" ] ], + [ "pot1:GND", "bb1:tn.32", "black", [ "h-38.4", "v-47.9" ] ], + [ "pot1:VCC", "bb1:bp.39", "red", [ "h18.4", "v116.1" ] ], + [ "pot2:GND", "bb1:bn.35", "black", [ "v0" ] ], + [ "pot2:VCC", "bb1:bp.37", "red", [ "v0" ] ], + [ "r2:1", "bb1:bp.44", "red", [ "h0" ] ], + [ "r1:2", "bb1:bp.47", "red", [ "h0" ] ], + [ "esp:VIN", "bb1:4t.a", "", [ "$bb" ] ], + [ "esp:GND.2", "bb1:5t.a", "", [ "$bb" ] ], + [ "esp:D13", "bb1:6t.a", "", [ "$bb" ] ], + [ "esp:D12", "bb1:7t.a", "", [ "$bb" ] ], + [ "esp:D14", "bb1:8t.a", "", [ "$bb" ] ], + [ "esp:D27", "bb1:9t.a", "", [ "$bb" ] ], + [ "esp:D26", "bb1:10t.a", "", [ "$bb" ] ], + [ "esp:D25", "bb1:11t.a", "", [ "$bb" ] ], + [ "esp:D33", "bb1:12t.a", "", [ "$bb" ] ], + [ "esp:D32", "bb1:13t.a", "", [ "$bb" ] ], + [ "esp:D35", "bb1:14t.a", "", [ "$bb" ] ], + [ "esp:D34", "bb1:15t.a", "", [ "$bb" ] ], + [ "esp:VN", "bb1:16t.a", "", [ "$bb" ] ], + [ "esp:VP", "bb1:17t.a", "", [ "$bb" ] ], + [ "esp:EN", "bb1:18t.a", "", [ "$bb" ] ], + [ "esp:3V3", "bb1:4b.i", "", [ "$bb" ] ], + [ "esp:GND.1", "bb1:5b.i", "", [ "$bb" ] ], + [ "esp:D15", "bb1:6b.i", "", [ "$bb" ] ], + [ "esp:D2", "bb1:7b.i", "", [ "$bb" ] ], + [ "esp:D4", "bb1:8b.i", "", [ "$bb" ] ], + [ "esp:RX2", "bb1:9b.i", "", [ "$bb" ] ], + [ "esp:TX2", "bb1:10b.i", "", [ "$bb" ] ], + [ "esp:D5", "bb1:11b.i", "", [ "$bb" ] ], + [ "esp:D18", "bb1:12b.i", "", [ "$bb" ] ], + [ "esp:D19", "bb1:13b.i", "", [ "$bb" ] ], + [ "esp:D21", "bb1:14b.i", "", [ "$bb" ] ], + [ "esp:RX0", "bb1:15b.i", "", [ "$bb" ] ], + [ "esp:TX0", "bb1:16b.i", "", [ "$bb" ] ], + [ "esp:D22", "bb1:17b.i", "", [ "$bb" ] ], + [ "esp:D23", "bb1:18b.i", "", [ "$bb" ] ], + [ "sd1:CD", "bb1:28t.a", "", [ "$bb" ] ], + [ "sd1:DO", "bb1:27t.a", "", [ "$bb" ] ], + [ "sd1:GND", "bb1:26t.a", "", [ "$bb" ] ], + [ "sd1:SCK", "bb1:25t.a", "", [ "$bb" ] ], + [ "sd1:VCC", "bb1:24t.a", "", [ "$bb" ] ], + [ "sd1:DI", "bb1:23t.a", "", [ "$bb" ] ], + [ "sd1:CS", "bb1:22t.a", "", [ "$bb" ] ], + [ "rtc1:GND", "bb1:31t.a", "", [ "$bb" ] ], + [ "rtc1:5V", "bb1:32t.a", "", [ "$bb" ] ], + [ "rtc1:SDA", "bb1:33t.a", "", [ "$bb" ] ], + [ "rtc1:SCL", "bb1:34t.a", "", [ "$bb" ] ], + [ "rtc1:SQW", "bb1:35t.a", "", [ "$bb" ] ], + [ "pot1:GND", "bb1:44t.c", "", [ "$bb" ] ], + [ "pot1:SIG", "bb1:45t.c", "", [ "$bb" ] ], + [ "pot1:VCC", "bb1:46t.c", "", [ "$bb" ] ], + [ "pot2:GND", "bb1:44b.j", "", [ "$bb" ] ], + [ "pot2:SIG", "bb1:45b.j", "", [ "$bb" ] ], + [ "pot2:VCC", "bb1:46b.j", "", [ "$bb" ] ], + [ "btn1:1.l", "bb1:36b.j", "", [ "$bb" ] ], + [ "btn1:2.l", "bb1:38b.j", "", [ "$bb" ] ], + [ "btn1:1.r", "bb1:36t.e", "", [ "$bb" ] ], + [ "btn1:2.r", "bb1:38t.e", "", [ "$bb" ] ], + [ "r1:1", "bb1:58t.c", "", [ "$bb" ] ], + [ "r1:2", "bb1:58b.g", "", [ "$bb" ] ], + [ "r2:1", "bb1:54b.f", "", [ "$bb" ] ], + [ "r2:2", "bb1:54t.b", "", [ "$bb" ] ], + [ "temp1:GND", "bb1:55t.a", "", [ "$bb" ] ], + [ "temp1:DQ", "bb1:56t.a", "", [ "$bb" ] ], + [ "temp1:VCC", "bb1:57t.a", "", [ "$bb" ] ], + [ "temp2:GND", "bb1:51t.a", "", [ "$bb" ] ], + [ "temp2:DQ", "bb1:52t.a", "", [ "$bb" ] ], + [ "temp2:VCC", "bb1:53t.a", "", [ "$bb" ] ], + [ "esp:GND.2", "bb1:tn.3", "black", [ "v0" ] ] + ], + "serialMonitor": { "display": "terminal", "newline": "lf" }, + "dependencies": {} +} \ No newline at end of file diff --git a/tests/diagrams/offline.json b/tests/diagrams/offline.json new file mode 100644 index 000000000..fa86f30fd --- /dev/null +++ b/tests/diagrams/offline.json @@ -0,0 +1,160 @@ +{ + "version": 1, + "author": "Uri Shaked", + "editor": "wokwi", + "parts": [ + { "type": "wokwi-breadboard", "id": "bb1", "top": -89.4, "left": -266, "attrs": {} }, + { + "type": "wokwi-esp32-devkit-v1", + "id": "esp", + "top": -91.9, + "left": -208.2, + "rotate": 90, + "attrs": {} + }, + { + "type": "wokwi-microsd-card", + "id": "sd1", + "top": -112.63, + "left": -50.93, + "rotate": 90, + "attrs": {} + }, + { + "type": "wokwi-ds1307", + "id": "rtc1", + "top": -119.9, + "left": 26, + "rotate": 270, + "attrs": {} + }, + { "type": "wokwi-potentiometer", "id": "pot1", "top": -87.7, "left": 143.8, "attrs": {} }, + { "type": "wokwi-potentiometer", "id": "pot2", "top": -1.3, "left": 143.8, "attrs": {} }, + { + "type": "wokwi-pushbutton", + "id": "btn1", + "top": 9.2, + "left": 74, + "rotate": 270, + "attrs": { "bounce": "0", "color": "green" } + }, + { "type": "board-ds18b20", "id": "temp1", "top": -87.53, "left": 272.88, "attrs": {} }, + { + "type": "wokwi-resistor", + "id": "r1", + "top": 4.8, + "left": 277.85, + "rotate": 90, + "attrs": { "value": "7400" } + }, + { "type": "board-ds18b20", "id": "temp2", "top": -87.53, "left": 234.48, "attrs": {} }, + { + "type": "wokwi-resistor", + "id": "r2", + "top": -6.2, + "left": 239.15, + "rotate": 270, + "attrs": { "value": "7400" } + } + ], + "connections": [ + [ "esp:TX0", "$serialMonitor:RX", "", [] ], + [ "esp:RX0", "$serialMonitor:TX", "", [] ], + [ "sd1:CS", "esp:D15", "gold", [ "v144", "h-144.26" ] ], + [ "sd1:DI", "esp:D23", "blue", [ "v0" ] ], + [ "sd1:SCK", "esp:D18", "violet", [ "v153.6", "h-115.19" ] ], + [ "sd1:DO", "esp:D19", "orange", [ "v124.8", "h-125.01" ] ], + [ "rtc1:SDA", "esp:D21", "blue", [ "v115.2", "h-183.1" ] ], + [ "rtc1:SCL", "esp:D22", "green", [ "v105.6", "h-163.7" ] ], + [ "pot2:SIG", "esp:D27", "green", [ "h-0.4", "v76.8", "h-345.9" ] ], + [ "esp:3V3", "bb1:bp.1", "red", [ "h-9.6", "v39" ] ], + [ "sd1:VCC", "bb1:bp.18", "red", [ "v0" ] ], + [ "rtc1:GND", "bb1:bn.25", "black", [ "v0" ] ], + [ "esp:GND.1", "bb1:bn.3", "black", [ "v0" ] ], + [ "bb1:33b.h", "bb1:33b.g", "green", [ "v0" ] ], + [ "rtc1:5V", "esp:VIN", "red", [ "v-19.2", "h-269.2" ] ], + [ "pot1:SIG", "esp:D14", "blue", [ "v9.6", "h-48.4", "v144", "h-316.8", "v-172.8" ] ], + [ "btn1:1.l", "esp:D4", "orange", [ "v57.6", "h-268.7" ] ], + [ "temp1:GND", "bb1:bn.48", "black", [ "v0" ] ], + [ "temp1:DQ", "r1:1", "green", [ "v0" ] ], + [ "temp1:VCC", "r1:2", "red", [ "v0" ] ], + [ "r1:1", "esp:D12", "green", [ "v-124.8", "h-490.3" ] ], + [ "temp2:GND", "bb1:bn.36", "black", [ "v0" ] ], + [ "temp2:DQ", "r2:2", "violet", [ "v0" ] ], + [ "temp2:VCC", "r2:1", "red", [ "v0" ] ], + [ "esp:D25", "r2:2", "violet", [ "v-96", "h412.8" ] ], + [ "sd1:GND", "bb1:bn.21", "black", [ "v0" ] ], + [ "btn1:2.r", "bb1:tn.31", "black", [ "h9.8", "v-67.3" ] ], + [ "pot1:GND", "bb1:tn.32", "black", [ "h-38.4", "v-47.9" ] ], + [ "pot1:VCC", "bb1:bp.39", "red", [ "h18.4", "v116.1" ] ], + [ "pot2:GND", "bb1:bn.35", "black", [ "v0" ] ], + [ "pot2:VCC", "bb1:bp.37", "red", [ "v0" ] ], + [ "r2:1", "bb1:bp.44", "red", [ "h0" ] ], + [ "r1:2", "bb1:bp.47", "red", [ "h0" ] ], + [ "esp:VIN", "bb1:4t.a", "", [ "$bb" ] ], + [ "esp:GND.2", "bb1:5t.a", "", [ "$bb" ] ], + [ "esp:D13", "bb1:6t.a", "", [ "$bb" ] ], + [ "esp:D12", "bb1:7t.a", "", [ "$bb" ] ], + [ "esp:D14", "bb1:8t.a", "", [ "$bb" ] ], + [ "esp:D27", "bb1:9t.a", "", [ "$bb" ] ], + [ "esp:D26", "bb1:10t.a", "", [ "$bb" ] ], + [ "esp:D25", "bb1:11t.a", "", [ "$bb" ] ], + [ "esp:D33", "bb1:12t.a", "", [ "$bb" ] ], + [ "esp:D32", "bb1:13t.a", "", [ "$bb" ] ], + [ "esp:D35", "bb1:14t.a", "", [ "$bb" ] ], + [ "esp:D34", "bb1:15t.a", "", [ "$bb" ] ], + [ "esp:VN", "bb1:16t.a", "", [ "$bb" ] ], + [ "esp:VP", "bb1:17t.a", "", [ "$bb" ] ], + [ "esp:EN", "bb1:18t.a", "", [ "$bb" ] ], + [ "esp:3V3", "bb1:4b.i", "", [ "$bb" ] ], + [ "esp:GND.1", "bb1:5b.i", "", [ "$bb" ] ], + [ "esp:D15", "bb1:6b.i", "", [ "$bb" ] ], + [ "esp:D2", "bb1:7b.i", "", [ "$bb" ] ], + [ "esp:D4", "bb1:8b.i", "", [ "$bb" ] ], + [ "esp:RX2", "bb1:9b.i", "", [ "$bb" ] ], + [ "esp:TX2", "bb1:10b.i", "", [ "$bb" ] ], + [ "esp:D5", "bb1:11b.i", "", [ "$bb" ] ], + [ "esp:D18", "bb1:12b.i", "", [ "$bb" ] ], + [ "esp:D19", "bb1:13b.i", "", [ "$bb" ] ], + [ "esp:D21", "bb1:14b.i", "", [ "$bb" ] ], + [ "esp:RX0", "bb1:15b.i", "", [ "$bb" ] ], + [ "esp:TX0", "bb1:16b.i", "", [ "$bb" ] ], + [ "esp:D22", "bb1:17b.i", "", [ "$bb" ] ], + [ "esp:D23", "bb1:18b.i", "", [ "$bb" ] ], + [ "sd1:CD", "bb1:28t.a", "", [ "$bb" ] ], + [ "sd1:DO", "bb1:27t.a", "", [ "$bb" ] ], + [ "sd1:GND", "bb1:26t.a", "", [ "$bb" ] ], + [ "sd1:SCK", "bb1:25t.a", "", [ "$bb" ] ], + [ "sd1:VCC", "bb1:24t.a", "", [ "$bb" ] ], + [ "sd1:DI", "bb1:23t.a", "", [ "$bb" ] ], + [ "sd1:CS", "bb1:22t.a", "", [ "$bb" ] ], + [ "rtc1:GND", "bb1:31t.a", "", [ "$bb" ] ], + [ "rtc1:5V", "bb1:32t.a", "", [ "$bb" ] ], + [ "rtc1:SDA", "bb1:33t.a", "", [ "$bb" ] ], + [ "rtc1:SCL", "bb1:34t.a", "", [ "$bb" ] ], + [ "rtc1:SQW", "bb1:35t.a", "", [ "$bb" ] ], + [ "pot1:GND", "bb1:44t.c", "", [ "$bb" ] ], + [ "pot1:SIG", "bb1:45t.c", "", [ "$bb" ] ], + [ "pot1:VCC", "bb1:46t.c", "", [ "$bb" ] ], + [ "pot2:GND", "bb1:44b.j", "", [ "$bb" ] ], + [ "pot2:SIG", "bb1:45b.j", "", [ "$bb" ] ], + [ "pot2:VCC", "bb1:46b.j", "", [ "$bb" ] ], + [ "btn1:1.l", "bb1:36b.j", "", [ "$bb" ] ], + [ "btn1:2.l", "bb1:38b.j", "", [ "$bb" ] ], + [ "btn1:1.r", "bb1:36t.e", "", [ "$bb" ] ], + [ "btn1:2.r", "bb1:38t.e", "", [ "$bb" ] ], + [ "r1:1", "bb1:58t.c", "", [ "$bb" ] ], + [ "r1:2", "bb1:58b.g", "", [ "$bb" ] ], + [ "r2:1", "bb1:54b.f", "", [ "$bb" ] ], + [ "r2:2", "bb1:54t.b", "", [ "$bb" ] ], + [ "temp1:GND", "bb1:55t.a", "", [ "$bb" ] ], + [ "temp1:DQ", "bb1:56t.a", "", [ "$bb" ] ], + [ "temp1:VCC", "bb1:57t.a", "", [ "$bb" ] ], + [ "temp2:GND", "bb1:51t.a", "", [ "$bb" ] ], + [ "temp2:DQ", "bb1:52t.a", "", [ "$bb" ] ], + [ "temp2:VCC", "bb1:53t.a", "", [ "$bb" ] ], + [ "esp:GND.2", "bb1:tn.3", "black", [ "v0" ] ] + ], + "serialMonitor": { "display": "terminal", "newline": "lf" }, + "dependencies": {} +} \ No newline at end of file diff --git a/tests/report.xml b/tests/report.xml new file mode 100644 index 000000000..07d7c851b --- /dev/null +++ b/tests/report.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/requirements.txt b/tests/requirements.txt new file mode 100644 index 000000000..55b033e90 --- /dev/null +++ b/tests/requirements.txt @@ -0,0 +1 @@ +pytest \ No newline at end of file diff --git a/tests/scenarios/offline/test-invalid-json.scenario.yaml b/tests/scenarios/offline/test-invalid-json.scenario.yaml new file mode 100644 index 000000000..e4dbf45fe --- /dev/null +++ b/tests/scenarios/offline/test-invalid-json.scenario.yaml @@ -0,0 +1,9 @@ +name: Offline Mode - Invalid JSON +version: 1 +author: Brent Rubell for Adafruit Industries + +steps: + - wait-serial: '[SD] Waiting for incoming JSON string...' + - write-serial: '{"exportVersion":"1.0.0",' + - write-serial: '\n' + - wait-serial: 'ERROR [WDT RESET]: Unable to validate incoming JSON file' \ No newline at end of file diff --git a/tests/scenarios/offline/test-log-analogin.scenario.yaml b/tests/scenarios/offline/test-log-analogin.scenario.yaml new file mode 100644 index 000000000..b410b3d8f --- /dev/null +++ b/tests/scenarios/offline/test-log-analogin.scenario.yaml @@ -0,0 +1,21 @@ +name: Offline Mode - Analog Input +version: 1 +author: Brent Rubell for Adafruit Industries + +steps: + - wait-serial: '[SD] Waiting for incoming JSON string...' + - write-serial: '{"exportVersion":"1.0.0","exportedBy":"wokwi","exportedAt":"2024-10-28T18:58:23.976Z","exportedFromDevice":{"board":"metroesp32s3","firmwareVersion":"1.0.0-beta.93","referenceVoltage":2.6,"totalGPIOPins":11,"totalAnalogPins":6},"components":[{"componentAPI":"analogio","name":"Analog Pin","pinName":"D14","type":"analog_pin","mode":"ANALOG","direction":"INPUT","sampleMode":"TIMER","analogReadMode":"PIN_VALUE","period":5,"isPin":true}]}' + - write-serial: '\n' + - wait-serial: '[SD] Valid JSON string received!' + - wait-serial: '[analogio] Added new pin:' + - wait-serial: 'Pin Name: 14' + - wait-serial: 'Period: 5000.00' + - wait-serial: 'Read Mode: 18' + - delay: 5s + - wait-serial: '{"timestamp":0,"pin":"A14","value":0,"si_unit":"RAW"}' + - set-control: + part-id: pot1 + control: position + value: 0.5 + - delay: 3s + - wait-serial: '{"timestamp":0,"pin":"A14","value":16384,"si_unit":"RAW"}' \ No newline at end of file diff --git a/tests/scenarios/offline/test-log-digital-in.scenario.yaml b/tests/scenarios/offline/test-log-digital-in.scenario.yaml new file mode 100644 index 000000000..309a0f8eb --- /dev/null +++ b/tests/scenarios/offline/test-log-digital-in.scenario.yaml @@ -0,0 +1,20 @@ +name: Offline Mode - Digital Input +version: 1 +author: Brent Rubell for Adafruit Industries + +steps: + - wait-serial: '[SD] Waiting for incoming JSON string...' + - write-serial: '{"exportVersion":"1.0.0","exportedBy":"wokwi","exportedAt":"2024-10-28T18:58:23.976Z","exportedFromDevice":{"board":"metroesp32s3","firmwareVersion":"1.0.0-beta.93","referenceVoltage":2.6,"totalGPIOPins":11,"totalAnalogPins":6},"components":[{"componentAPI":"digitalio","name":"Button (D4)","pinName":"D4","type":"push_button","mode":"DIGITAL","sampleMode":"TIMER","direction":"INPUT","period":5,"pull":"UP","isPin":true}]}' + - write-serial: '\n' + - wait-serial: '[SD] Valid JSON string received!' + - wait-serial: '[digitalio] Added new pin:' + - wait-serial: 'Pin Name: 4' + - wait-serial: 'Period: 5000' + - wait-serial: 'Sample Mode: 1' + - wait-serial: 'Direction: 2' + - wait-serial: '{"timestamp":0,"pin":"A4","value":true,"si_unit":"BOOLEAN"}' + - set-control: + part-id: btn1 + control: pressed + value: 1 + - wait-serial: '{"timestamp":0,"pin":"A4","value":false,"si_unit":"BOOLEAN"}' \ No newline at end of file diff --git a/tests/scenarios/offline/test-log-ds18b20.scenario.yaml b/tests/scenarios/offline/test-log-ds18b20.scenario.yaml new file mode 100644 index 000000000..cd946422f --- /dev/null +++ b/tests/scenarios/offline/test-log-ds18b20.scenario.yaml @@ -0,0 +1,11 @@ +name: Offline Mode - DS18b20 +version: 1 +author: Brent Rubell for Adafruit Industries + +steps: + - wait-serial: '[SD] Waiting for incoming JSON string...' + - write-serial: '{"exportVersion":"1.0.0","exportedBy":"wokwi","exportedAt":"2024-10-28T18:58:23.976Z","exportedFromDevice":{"board":"metroesp32s3","firmwareVersion":"1.0.0-beta.93","referenceVoltage":2.6,"totalGPIOPins":11,"totalAnalogPins":6},"components":[{"componentAPI":"ds18x20","name":"DS18B20: Temperature Sensor (°F)","sensorTypeCount":2,"sensorType1":"ambient-temp-fahrenheit","sensorType2":"ambient-temp","pinName":"D6","sensorResolution":12,"period":5}]}' + - write-serial: '\n' + - wait-serial: '[SD] Valid JSON string received!' + - wait-serial: '{"timestamp":0,"pin":"D6","value":0,"si_unit":"OBJECT_TEMPERATURE"}' + - wait-serial: '{"timestamp":0,"pin":"D6","value":32,"si_unit":"OBJECT_TEMPERATURE_FAHRENHEIT"}' \ No newline at end of file diff --git a/tests/scenarios/test_netfsm_establish_wifi.scenario.yaml b/tests/scenarios/test_netfsm_establish_wifi.scenario.yaml new file mode 100644 index 000000000..1250ad9be --- /dev/null +++ b/tests/scenarios/test_netfsm_establish_wifi.scenario.yaml @@ -0,0 +1,10 @@ +name: NetFSM - Establish WiFi Connection +version: 1 +author: Brent Rubell for Adafruit Industries + +steps: + - wait-serial: 'Adafruit.io WipperSnapper' + - wait-serial: 'Establishing network connection...' + - wait-serial: 'Connecting to WiFi (attempt #0)' + - wait-serial: 'Connected to WiFi!' + - wait-serial: 'Connecting to AIO MQTT (attempt #0)' \ No newline at end of file diff --git a/tests/test_offline.py b/tests/test_offline.py new file mode 100644 index 000000000..1976bf1f8 --- /dev/null +++ b/tests/test_offline.py @@ -0,0 +1,39 @@ +# SPDX-FileCopyrightText: 2024 Brent Rubell for Adafruit Industries +# SPDX-License-Identifier: MIT +# SPDX-FileDescription: Unit tests for WipperSnapper's offline mode +import pytest +import subprocess + +def run_wokwi_cli(binary, timeout, scenario, diagram): + result = subprocess.run( + [ + "wokwi-cli", + "--elf", + binary, + "--timeout", + timeout, + "--scenario", + scenario, + "--diagram-file", + diagram, + ] + ) + return result + +def test_invalid_json(): + result = run_wokwi_cli(f"tests/bin/offline/firmware.elf", "120000", f"tests/scenarios/offline/test-invalid-json.scenario.yaml", f"tests/diagrams/offline.json") + assert result.returncode == 0 + + +def test_analog_input(): + result = run_wokwi_cli(f"tests/bin/offline/firmware.elf", "120000", f"tests/scenarios/offline/test-log-analogin.scenario.yaml", f"tests/diagrams/offline.json") + assert result.returncode == 0 + + +def test_digital_input(): + result = run_wokwi_cli(f"tests/bin/offline/firmware.elf", "120000", f"tests/scenarios/offline/test-log-digital-in.scenario.yaml", f"tests/diagrams/offline.json") + assert result.returncode == 0 + +def test_ds18b20(): + result = run_wokwi_cli(f"tests/bin/offline/firmware.elf", "120000", f"tests/scenarios/offline/test-log-ds18b20.scenario.yaml", f"tests/diagrams/offline.json") + assert result.returncode == 0 \ No newline at end of file diff --git a/tests/wokwi.toml b/tests/wokwi.toml new file mode 100644 index 000000000..646b42181 --- /dev/null +++ b/tests/wokwi.toml @@ -0,0 +1,6 @@ +[wokwi] +version = 1 +elf = "../.pio/build/esp32dev/firmware.elf" +firmware = "../.pio/build/esp32dev/firmware.bin" +gdbServerPort=3333 +rfc2217ServerPort = 4000 \ No newline at end of file diff --git a/tinyuf2-partitions-4MB-noota.csv b/tinyuf2-partitions-4MB-noota.csv new file mode 100644 index 000000000..ab7fa8669 --- /dev/null +++ b/tinyuf2-partitions-4MB-noota.csv @@ -0,0 +1,10 @@ +# ESP-IDF Partition Table +# Name, Type, SubType, Offset, Size, Flags +# bootloader.bin,, 0x1000, 32K +# partition table, 0x8000, 4K + +nvs, data, nvs, 0x9000, 20K, +otadata, data, ota, 0xe000, 8K, +ota_0, app, ota_0, 0x10000, 2816K, +uf2, app, factory,0x2d0000, 256K, +ffat, data, fat, 0x310000, 960K, diff --git a/tinyuf2-partitions-4MB.csv b/tinyuf2-partitions-4MB.csv new file mode 100644 index 000000000..f3112a23b --- /dev/null +++ b/tinyuf2-partitions-4MB.csv @@ -0,0 +1,11 @@ +# ESP-IDF Partition Table +# Name, Type, SubType, Offset, Size, Flags +# bootloader.bin,, 0x1000, 32K +# partition table, 0x8000, 4K + +nvs, data, nvs, 0x9000, 20K, +otadata, data, ota, 0xe000, 8K, +ota_0, app, ota_0, 0x10000, 1408K, +ota_1, app, ota_1, 0x170000, 1408K, +uf2, app, factory,0x2d0000, 256K, +ffat, data, fat, 0x310000, 960K, diff --git a/upload_no_build.py b/upload_no_build.py new file mode 100644 index 000000000..092d08f42 --- /dev/null +++ b/upload_no_build.py @@ -0,0 +1,8 @@ +Import("env") +env.AddCustomTarget( + "uploadnobuild", + None, + 'pio run -e %s -t nobuild -t upload' % + env["PIOENV"], + title="Uploads without building" +) \ No newline at end of file