From e95623d0222f2b923c7f91e07f2c67dde555b058 Mon Sep 17 00:00:00 2001 From: mike wakerly Date: Sun, 2 Aug 2026 07:08:35 +0000 Subject: [PATCH 01/39] core: rewrite for esp32 on esphome Pours are now assembled on-device. Kegbot Server's API already accepts a finished pour (POST /api/taps/ with ticks/duration/pour_time/now), so the board detects the pour, applies calibration, and posts a drink directly: no kegbot-pycore, no usb tether, and a server outage costs a retry rather than a pour. - legacy avr firmware, the python kbsp library, eagle files, and the old sphinx docs leave main; all preserved on the `arduino` branch - adds the framework-agnostic core -- pour_session, tick_series (bounded : series), kegbot_request, ring_queue -- carrying no esphome, arduino, or esp-idf headers; 471 assertions run on a plain host compiler in ~1s, and check-core-purity.py keeps the boundary honest - drink posts omit volume_ml by default: the server already stores ml_per_tick per meter behind a calibration ui, and two sources of truth for volume reliably produces confusing data. opt in via send_volume - pour timestamps fall back to a monotonic uptime pair when the clock has never synced; the server only uses (now - pour_time), so a board that has never seen ntp still reports accurate pour times - queue overflow evicts oldest and counts the loss, bounding a long outage and making the data loss visible rather than silent - test output is scoped by os and arch, since the repo is often shared between a host and a container over a bind mount; -Werror requires STRICT=1, which ci passes - tooling mirrors esphome's, since these components compile into their tree: clang-format v13 with their .clang-format verbatim, ruff, yamllint, via pre-commit - relicensed mit to match the rest of kegbot, clean because every gpl-licensed file left main. built images stay gplv3 (esphome's runtime); the `arduino` branch keeps gplv2-or-later - reporting is http only for now; mqtt, ble, and websocket are planned --- .clang-format | 137 + .gitignore | 22 +- .pre-commit-config.yaml | 32 + .yamllint | 21 + LICENSE.txt | 296 +- README.md | 113 +- arduino/kegboard/KegboardPacket.cpp | 152 - arduino/kegboard/KegboardPacket.h | 26 - arduino/kegboard/Makefile | 459 -- arduino/kegboard/OneWire.cpp | 403 -- arduino/kegboard/OneWire.h | 107 - arduino/kegboard/PCInterrupt.cpp | 153 - arduino/kegboard/PCInterrupt.h | 46 - arduino/kegboard/Wiegand.cpp | 52 - arduino/kegboard/Wiegand.h | 35 - arduino/kegboard/buzzer.cpp | 51 - arduino/kegboard/buzzer.h | 18 - arduino/kegboard/ds1820.cpp | 181 - arduino/kegboard/ds1820.h | 37 - arduino/kegboard/kegboard.h | 94 - arduino/kegboard/kegboard.ino | 1039 ---- arduino/kegboard/kegboard_config.h | 150 - arduino/kegboard/kegboard_eeprom.cpp | 54 - arduino/kegboard/kegboard_eeprom.h | 43 - arduino/kegboard/tones.h | 92 - arduino/kegboard/version.h | 6 - components/kegboard/CORE.md | 41 + components/kegboard/kegbot_request.cpp | 121 + components/kegboard/kegbot_request.h | 102 + components/kegboard/pour_session.cpp | 78 + components/kegboard/pour_session.h | 117 + components/kegboard/ring_queue.h | 78 + components/kegboard/tick_series.cpp | 90 + components/kegboard/tick_series.h | 73 + docs/Makefile | 153 - docs/source/changelog.rst | 78 - docs/source/conf.py | 289 - docs/source/firmware.rst | 84 - docs/source/index.rst | 22 - docs/source/operation.rst | 83 - docs/source/overview.rst | 62 - docs/source/serial-protocol.rst | 351 -- docs/source/support-library.rst | 129 - docs/source/wiring.rst | 50 - hw/ChangeLog | 40 - .../kegboard-coaster-alt.brd | 693 --- .../kegboard-coaster-alt.sch | 2191 -------- .../kegboard-coaster-board.pdf | Bin 11656 -> 0 bytes .../kegboard-coaster-schematic.pdf | Bin 10895 -> 0 bytes hw/kegboard-coaster/kegboard-coaster.brd | Bin 13192 -> 0 bytes hw/kegboard-coaster/kegboard-coaster.sch | Bin 38803 -> 0 bytes hw/kegboard-mega/kegboard-mega.brd | Bin 30668 -> 0 bytes hw/kegboard-mega/kegboard-mega.sch | Bin 120191 -> 0 bytes hw/kegboard-mini/kegboard-mini-board.pdf | Bin 76115 -> 0 bytes hw/kegboard-mini/kegboard-mini-schematic.pdf | Bin 55995 -> 0 bytes hw/kegboard-mini/kegboard-mini.brd | 3235 ----------- hw/kegboard-mini/kegboard-mini.sch | 4915 ----------------- moat.yaml | 28 + pyproject.toml | 13 + python/Makefile | 7 - python/bin/kegboard-info.py | 52 - python/bin/kegboard-monitor.py | 65 - python/bin/kegboard-tester.py | 72 - python/bin/set-kegboard-serialnumber | 73 - python/distribute_setup.py | 481 -- python/kegbot/__init__.py | 1 - python/kegbot/kegboard/__init__.py | 1 - python/kegbot/kegboard/crc16.py | 32 - python/kegbot/kegboard/crc16_test.py | 15 - python/kegbot/kegboard/exceptions.py | 25 - python/kegbot/kegboard/kegboard.py | 243 - python/kegbot/kegboard/kegboard_test.py | 59 - python/kegbot/kegboard/message.py | 276 - .../kegboard/testdata/one_flow_active.bin | Bin 684 -> 0 bytes python/setup.py | 48 - script/check-core-purity.py | 84 + tests/core/Makefile | 65 + tests/core/test_kegbot_request.cpp | 226 + tests/core/test_pour_session.cpp | 245 + tests/core/test_ring_queue.cpp | 159 + tests/core/test_support.h | 85 + tests/core/test_tick_series.cpp | 132 + 82 files changed, 2052 insertions(+), 17329 deletions(-) create mode 100644 .clang-format create mode 100644 .pre-commit-config.yaml create mode 100644 .yamllint delete mode 100644 arduino/kegboard/KegboardPacket.cpp delete mode 100644 arduino/kegboard/KegboardPacket.h delete mode 100644 arduino/kegboard/Makefile delete mode 100644 arduino/kegboard/OneWire.cpp delete mode 100644 arduino/kegboard/OneWire.h delete mode 100644 arduino/kegboard/PCInterrupt.cpp delete mode 100644 arduino/kegboard/PCInterrupt.h delete mode 100644 arduino/kegboard/Wiegand.cpp delete mode 100644 arduino/kegboard/Wiegand.h delete mode 100644 arduino/kegboard/buzzer.cpp delete mode 100644 arduino/kegboard/buzzer.h delete mode 100644 arduino/kegboard/ds1820.cpp delete mode 100644 arduino/kegboard/ds1820.h delete mode 100644 arduino/kegboard/kegboard.h delete mode 100644 arduino/kegboard/kegboard.ino delete mode 100644 arduino/kegboard/kegboard_config.h delete mode 100644 arduino/kegboard/kegboard_eeprom.cpp delete mode 100644 arduino/kegboard/kegboard_eeprom.h delete mode 100644 arduino/kegboard/tones.h delete mode 100644 arduino/kegboard/version.h create mode 100644 components/kegboard/CORE.md create mode 100644 components/kegboard/kegbot_request.cpp create mode 100644 components/kegboard/kegbot_request.h create mode 100644 components/kegboard/pour_session.cpp create mode 100644 components/kegboard/pour_session.h create mode 100644 components/kegboard/ring_queue.h create mode 100644 components/kegboard/tick_series.cpp create mode 100644 components/kegboard/tick_series.h delete mode 100644 docs/Makefile delete mode 100644 docs/source/changelog.rst delete mode 100644 docs/source/conf.py delete mode 100644 docs/source/firmware.rst delete mode 100644 docs/source/index.rst delete mode 100644 docs/source/operation.rst delete mode 100644 docs/source/overview.rst delete mode 100644 docs/source/serial-protocol.rst delete mode 100644 docs/source/support-library.rst delete mode 100644 docs/source/wiring.rst delete mode 100644 hw/ChangeLog delete mode 100644 hw/kegboard-coaster-alt/kegboard-coaster-alt.brd delete mode 100644 hw/kegboard-coaster-alt/kegboard-coaster-alt.sch delete mode 100644 hw/kegboard-coaster/kegboard-coaster-board.pdf delete mode 100644 hw/kegboard-coaster/kegboard-coaster-schematic.pdf delete mode 100644 hw/kegboard-coaster/kegboard-coaster.brd delete mode 100644 hw/kegboard-coaster/kegboard-coaster.sch delete mode 100644 hw/kegboard-mega/kegboard-mega.brd delete mode 100644 hw/kegboard-mega/kegboard-mega.sch delete mode 100644 hw/kegboard-mini/kegboard-mini-board.pdf delete mode 100644 hw/kegboard-mini/kegboard-mini-schematic.pdf delete mode 100644 hw/kegboard-mini/kegboard-mini.brd delete mode 100644 hw/kegboard-mini/kegboard-mini.sch create mode 100644 moat.yaml create mode 100644 pyproject.toml delete mode 100644 python/Makefile delete mode 100755 python/bin/kegboard-info.py delete mode 100755 python/bin/kegboard-monitor.py delete mode 100755 python/bin/kegboard-tester.py delete mode 100755 python/bin/set-kegboard-serialnumber delete mode 100644 python/distribute_setup.py delete mode 100644 python/kegbot/__init__.py delete mode 100644 python/kegbot/kegboard/__init__.py delete mode 100644 python/kegbot/kegboard/crc16.py delete mode 100644 python/kegbot/kegboard/crc16_test.py delete mode 100644 python/kegbot/kegboard/exceptions.py delete mode 100644 python/kegbot/kegboard/kegboard.py delete mode 100644 python/kegbot/kegboard/kegboard_test.py delete mode 100644 python/kegbot/kegboard/message.py delete mode 100644 python/kegbot/kegboard/testdata/one_flow_active.bin delete mode 100755 python/setup.py create mode 100755 script/check-core-purity.py create mode 100644 tests/core/Makefile create mode 100644 tests/core/test_kegbot_request.cpp create mode 100644 tests/core/test_pour_session.cpp create mode 100644 tests/core/test_ring_queue.cpp create mode 100644 tests/core/test_support.h create mode 100644 tests/core/test_tick_series.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..f2d86c5 --- /dev/null +++ b/.clang-format @@ -0,0 +1,137 @@ +Language: Cpp +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: DontAlign +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 120 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^' + Priority: 2 + - Regex: '^<.*\.h>' + Priority: 1 + - Regex: '^<.*' + Priority: 2 + - Regex: '.*' + Priority: 3 +IncludeIsMainRegex: '([-_](test|unittest))?$' +IndentCaseLabels: true +IndentPPDirectives: None +IndentWidth: 2 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 2000 +PointerAlignment: Right +RawStringFormats: + - Language: Cpp + Delimiters: + - cc + - CC + - cpp + - Cpp + - CPP + - 'c++' + - 'C++' + CanonicalDelimiter: '' + BasedOnStyle: google + - Language: TextProto + Delimiters: + - pb + - PB + - proto + - PROTO + EnclosingFunctions: + - EqualsProto + - EquivToProto + - PARSE_PARTIAL_TEXT_PROTO + - PARSE_TEST_PROTO + - PARSE_TEXT_PROTO + - ParseTextOrDie + - ParseTextProtoOrDie + CanonicalDelimiter: '' + BasedOnStyle: google +ReflowComments: true +SortIncludes: false +SortUsingDeclarations: false +SpaceAfterCStyleCast: true +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto +TabWidth: 2 +UseTab: Never diff --git a/.gitignore b/.gitignore index 97bdb1c..86bf578 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,15 @@ -### global stuff -*.DS_Store +# Python / tooling +.venv/ +__pycache__/ *.pyc -*.egg -*.o -*.hex -python/dist/* -python/distribute-* -python/*egg-info +# ESPHome build output +.esphome/ +secrets.yaml -arduino/kegboard/.dep -arduino/kegboard/.lib +# Host test build output +tests/core/build/ + +# Editors / OS +.DS_Store +compile_commands.json diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..2e99324 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,32 @@ +--- +# Formatting and lint hooks. Install with: pre-commit install +# +# The C++ and Python conventions here deliberately mirror ESPHome's own, since +# our components are compiled into their tree and most contributors will be +# coming from that codebase. clang-format is pinned to the same v13 they pin. +repos: + - repo: https://github.com/pre-commit/mirrors-clang-format + rev: v13.0.1 + hooks: + - id: clang-format + types_or: [c, c++] + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.16.1 + hooks: + - id: ruff + args: [--fix] + - id: ruff-format + + - repo: https://github.com/adrienverge/yamllint.git + rev: v1.37.1 + hooks: + - id: yamllint + exclude: ^\.clang-format$ + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: end-of-file-fixer + - id: trailing-whitespace + - id: check-merge-conflict diff --git a/.yamllint b/.yamllint new file mode 100644 index 0000000..8623698 --- /dev/null +++ b/.yamllint @@ -0,0 +1,21 @@ +--- +extends: default + +ignore: | + .clang-format + .venv/ + +rules: + document-start: + present: true + line-length: + max: 120 + allow-non-breakable-words: true + allow-non-breakable-inline-mappings: true + truthy: + # GitHub Actions uses a bare `on:` key. + check-keys: false + comments: + min-spaces-from-content: 1 + braces: + max-spaces-inside: 1 diff --git a/LICENSE.txt b/LICENSE.txt index 4eb3f1e..370eb90 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,278 +1,18 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. +Copyright 2003-2026 The Kegbot Project Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 830c9b1..8018a7f 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,110 @@ # Kegboard -This is Kegboard, the Arduino-based beer kegerator controller from Kegbot. You can use -Kegboard with Kegbot, or something else. +Kegboard is the kegerator controller firmware from the [Kegbot][kegbot] project. +It reads flow meters, temperature sensors, and auth tokens, drives valves and +relays, and reports pours to a [Kegbot Server][kegbot-server]. -The main repository is located at: https://github.com/Kegbot/kegboard +This is **Kegboard 2.x**, a ground-up rewrite for **ESP32** built on +[ESPHome][esphome]. -You can find more information about Kegboard at: http://kegbot.org/kegboard/ +> **Looking for the Arduino version?** The original AVR firmware lives on the +> [`arduino`](https://github.com/Kegbot/kegboard/tree/arduino) branch. It is +> feature-frozen and does not speak the same protocol as this one. -## More Info & Help +## What changed, and why -If you're reading this on Github, please note that we don't maintain much documentation there. +The old Kegboard was a dumb sensor pipe: it streamed raw tick counts over USB +serial, and a host daemon (`kegbot-pycore`) assembled those ticks into pours and +posted them to the server. That meant a cable, a always-on host, and a lost pour +whenever the host was down. -Please see our main website, http://kegbot.org/, for [documentation](http://kegbot.org/docs), -the [Kegbot Forum](http://kegbot.org/kegbb/), and the [Kegbot Blog](http://kegbot.org/blog/). +Kegboard 2.x is a networked appliance: -You can also find us on **#kegbot** on freenode IRC. +- **Pours are assembled on the device.** The board detects the start and end of + a pour, applies calibration, and posts a finished drink to Kegbot Server. +- **Outages are survivable.** Undelivered pours are buffered and retried. Kegbot + Server's API records a pour by *elapsed time*, so a pour delivered an hour + late still lands with the correct timestamp — even on a board whose clock + never synced. +- **No `kegbot-pycore`, no USB cable.** WiFi and HTTP. +- **It's ESPHome.** Adding a display, a pressure sensor, a second thermometer, + or a different RFID reader is YAML you write, not a firmware release we ship. -You should definitely follow [@kegbot](http://twitter.com/kegbot) on Twitter, all the -cool kids are. +## Status -## License and Copyright +Early. Under active construction — see the table below. -All code is offered under the **GPLv2** license, unless otherwise noted. Please see -``LICENSE.txt`` for the full license. +| Area | State | +|---|---| +| Core pour logic (state machine, tick series, queue, API requests) | Done, unit tested | +| `kegboard` hub component | In progress | +| `kegboard_meter` flow meter component | In progress | +| `kegboard_kegbot` HTTP reporter | In progress | +| Temperature, relays, buzzer, LEDs | Planned | +| Auth tokens (RFID, iButton) | Planned | +| Prebuilt binaries + web installer | Planned | -All code and documentation are **Copyright 2003-2012 Mike Wakerly**, unless otherwise noted. +Reporting is **HTTP only** for now. MQTT, BLE, and WebSocket transports are +planned. -## Contributing +## Repository layout -We love getting patches! Send us a pull request, or hop on to IRC if you'd like to chat -about something substantial. +``` +components/ ESPHome external components (this repo is the component source) + kegboard/ Hub component + the framework-agnostic core (see CORE.md) +packages/ Composable YAML users include +boards/ Pin maps per target board +examples/ Worked configurations +tests/core/ Host unit tests -- plain g++, no hardware, no toolchain +script/ CI helpers +``` +## Development + +Host unit tests for the core logic need nothing but a C++ compiler: + +```console +$ make -C tests/core +$ make -C tests/core STRICT=1 # warnings as errors, as CI runs it +``` + +Build output is scoped by OS and architecture (`build/Darwin-arm64/`, etc.), so +a checkout shared between a host and a container or VM won't hand one +platform's binaries to the other. + +Formatting and lint mirror ESPHome's own conventions, since these components +compile into ESPHome's tree: + +```console +$ pip install pre-commit && pre-commit install +$ pre-commit run --all-files +``` + +## Hardware notes + +**ESP32 GPIOs are not 5 V tolerant.** Most beer flow meters are open-collector +hall-effect sensors, which are safe on a 3.3 V pull-up because they only ever +pull the line to ground. Meters with a push-pull 5 V output will damage the +ESP32 and need a level shifter or divider. Check your meter before wiring it. + +## License and copyright + +Kegboard 2.x is offered under the **MIT** license, matching the rest of the +Kegbot project; see `LICENSE.txt`. + +Two notes on what that does and doesn't cover: + +- **Built firmware images are GPLv3.** ESPHome's C++ runtime is GPLv3, and a + compiled Kegboard image links against it. MIT is GPL-compatible, so this is + fine and is how ESPHome external components normally work — but if you + distribute binaries, you are distributing GPLv3 binaries. The source in this + repository remains MIT and is reusable as such. +- **The `arduino` branch is still GPLv2-or-later.** The legacy AVR firmware was + written under that license and had outside contributors; nothing here + relicenses it. + +Copyright 2003-2026 The Kegbot Project Contributors + +[kegbot]: https://kegbot.org/ +[kegbot-server]: https://github.com/Kegbot/kegbot-server +[esphome]: https://esphome.io/ diff --git a/arduino/kegboard/KegboardPacket.cpp b/arduino/kegboard/KegboardPacket.cpp deleted file mode 100644 index 3ac3adf..0000000 --- a/arduino/kegboard/KegboardPacket.cpp +++ /dev/null @@ -1,152 +0,0 @@ -/** - * kegboard.pde - Kegboard v3 Arduino project - * Copyright 2003-2011 Mike Wakerly - * - * This file is part of the Kegbot package of the Kegbot project. - * For more information on Kegbot, see http://kegbot.org/ - * - * Kegbot is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * Kegbot is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Kegbot. If not, see . - */ - -#include "Arduino.h" -#include "kegboard.h" -#include "KegboardPacket.h" - -#include -#include -#include -#include - -static uint16_t crc_ccitt_update_int(uint16_t crc, int value) { - crc = _crc_ccitt_update(crc, value & 0xff); - return _crc_ccitt_update(crc, (value >> 8) & 0xff); -} - -static void serial_print_int(int value) { - Serial.write(value & 0xff); - Serial.write((value >> 8) & 0xff); -} - -KegboardPacket::KegboardPacket() -{ - Reset(); -} - -bool KegboardPacket::IsReset() { - return (m_type == 0) && (m_len == 0); -} - -void KegboardPacket::Reset() -{ - m_len = 0; - m_type = 0; -} - -void KegboardPacket::AddTag(uint8_t tag, uint8_t buflen, const char *buf) -{ - m_payload[m_len++] = tag; - m_payload[m_len++] = buflen; - AppendBytes(buf, buflen); -} - -int KegboardPacket::FindTagLength(uint8_t tagnum) { - uint8_t* buf = FindTag(tagnum); - if (buf == NULL) { - return -1; - } - return buf[1] & 0xff; -} - -uint8_t* KegboardPacket::FindTag(uint8_t tagnum) { - uint8_t pos=0; - while (pos < m_len && pos < KBSP_PAYLOAD_MAXLEN) { - uint8_t tag = m_payload[pos]; - if (tag == tagnum) { - return m_payload+pos; - } - pos += 2 + m_payload[pos+1]; - } - return NULL; -} - -bool KegboardPacket::ReadTag(uint8_t tagnum, uint8_t *value) { - uint8_t *offptr = FindTag(tagnum); - if (offptr == NULL) { - return false; - } - *value = *(offptr+2); - return true; -} - -int KegboardPacket::CopyTagData(uint8_t tagnum, void* dest) { - uint8_t *offptr = FindTag(tagnum); - if (offptr == NULL) { - return -1; - } - uint8_t slen = *(offptr+1); - memcpy(dest, (offptr+2), slen); - return slen; -} - -void KegboardPacket::AppendBytes(const char *buf, int buflen) -{ - int i=0; - while (i < buflen && m_len < KBSP_PAYLOAD_MAXLEN) { - m_payload[m_len++] = (uint8_t) (*(buf+i)); - i++; - } -} - -uint16_t KegboardPacket::GenCrc() -{ - uint16_t crc = KBSP_PREFIX_CRC; - - crc = crc_ccitt_update_int(crc, m_type); - crc = crc_ccitt_update_int(crc, m_len); - - for (int i=0; i - -class KegboardPacket { - public: - KegboardPacket(); - void SetType(int type) {m_type = type;} - int GetType() {return m_type;} - void AddTag(uint8_t tag, uint8_t buflen, const char *buf); - - bool ReadTag(uint8_t tagnum, uint8_t *value); - int CopyTagData(uint8_t tagnum, void *dest); - - // Returns length of tag's payload, or -1 if not found. - int FindTagLength(uint8_t tagnum); - uint8_t* FindTag(uint8_t tagnum); - - void AppendBytes(const char *buf, int buflen); - void Reset(); - bool IsReset(); - void Print(); - uint16_t GenCrc(); - private: - int m_type; - uint8_t m_len; - uint8_t m_payload[KBSP_PAYLOAD_MAXLEN]; -}; diff --git a/arduino/kegboard/Makefile b/arduino/kegboard/Makefile deleted file mode 100644 index ea52dd9..0000000 --- a/arduino/kegboard/Makefile +++ /dev/null @@ -1,459 +0,0 @@ -#_______________________________________________________________________________ -# -# edam's Arduino makefile -#_______________________________________________________________________________ -# version 0.5 -# -# Copyright (C) 2011, 2012, 2013 Tim Marston . -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -# -#_______________________________________________________________________________ -# -# -# This is a general purpose makefile for use with Arduino hardware and -# software. It works with the arduino-1.0 and later software releases. It -# should work GNU/Linux and OS X. To download the latest version of this -# makefile visit the following website where you can also find documentation on -# it's use. (The following text can only really be considered a reference.) -# -# http://ed.am/dev/make/arduino-mk -# -# This makefile can be used as a drop-in replacement for the Arduino IDE's -# build system. To use it, just copy arduino.mk in to your project directory. -# Or, you could save it somewhere (I keep mine at ~/src/arduino.mk) and create -# a symlink to it in your project directory, named "Makefile". For example: -# -# $ ln -s ~/src/arduino.mk Makefile -# -# The Arduino software (version 1.0 or later) is required. On GNU/Linux you -# can probably install the software from your package manager. If you are -# using Debian (or a derivative), try `apt-get install arduino`. Otherwise, -# you can download the Arduino software manually from http://arduino.cc/. It -# is suggested that you install it at ~/opt/arduino (or /Applications on OS X) -# if you are unsure. -# -# If you downloaded the Arduino software manually and unpacked it somewhere -# other than ~/opt/arduino (or /Applications), you will need to set up the -# ARDUINODIR environment variable to be the path where you unpacked it. (If -# unset, ARDUINODIR defaults to some sensible places). You could set this in -# your ~/.profile by adding something like this: -# -# export ARDUINODIR=~/somewhere/arduino-1.0 -# -# For each project, you will also need to set BOARD to the type of Arduino -# you're building for. Type `make boards` for a list of acceptable values. -# For example: -# -# $ export BOARD=uno -# $ make -# -# You may also need to set SERIALDEV if it is not detected correctly. -# -# The presence of a .ino (or .pde) file causes the arduino.mk to automatically -# determine values for SOURCES, TARGET and LIBRARIES. Any .c, .cc and .cpp -# files in the project directory (or any "util" or "utility" subdirectories) -# are automatically included in the build and are scanned for Arduino libraries -# that have been #included. Note, there can only be one .ino (or .pde) file in -# a project directory and if you want to be compatible with the Arduino IDE, it -# should be called the same as the directory name. -# -# Alternatively, if you want to manually specify build variables, create a -# Makefile that defines SOURCES and LIBRARIES and then includes arduino.mk. -# (There is no need to define TARGET). You can also specify the BOARD here, if -# the project has a specific one. Here is an example Makefile: -# -# SOURCES := main.cc other.cc -# LIBRARIES := EEPROM -# BOARD := pro5v -# include ~/src/arduino.mk -# -# Here is a complete list of configuration parameters: -# -# ARDUINODIR The path where the Arduino software is installed on your system. -# -# ARDUINOCONST The Arduino software version, as an integer, used to define the -# ARDUINO version constant. This defaults to 100 if undefined. -# -# AVRDUDECONF The avrdude.conf to use. If undefined, this defaults to a guess -# based on where avrdude is. If set empty, no avrdude.conf is -# passed to avrdude (so the system default is used). -# -# AVRDUDEFLAGS Specify any additional flags for avrdude. The usual flags, -# required to build the project, will be appended to this. -# -# AVRTOOLSPATH A space-separated list of directories that is searched in order -# when looking for the avr build tools. This defaults to PATH, -# followed by subdirectories in ARDUINODIR. -# -# BOARD Specify a target board type. Run `make boards` to see available -# board types. -# -# CPPFLAGS Specify any additional flags for the compiler. The usual flags, -# required to build the project, will be appended to this. -# -# LINKFLAGS Specify any additional flags for the linker. The usual flags, -# required to build the project, will be appended to this. -# -# LIBRARIES A list of Arduino libraries to build and include. This is set -# automatically if a .ino (or .pde) is found. -# -# LIBRARYPATH A space-separated list of directories that is searched in order -# when looking for Arduino libraries. This defaults to "libs", -# "libraries" (in the project directory), then your sketchbook -# "libraries" directory, then the Arduino libraries directory. -# -# SERIALDEV The POSIX device name of the serial device that is the Arduino. -# If unspecified, an attempt is made to guess the name of a -# connected Arduino's serial device, which may work in some cases. -# -# SOURCES A list of all source files of whatever language. The language -# type is determined by the file extension. This is set -# automatically if a .ino (or .pde) is found. -# -# TARGET The name of the target file. This is set automatically if a -# .ino (or .pde) is found, but it is not necessary to set it -# otherwise. -# -# This makefile also defines the following goals for use on the command line -# when you run make: -# -# all This is the default if no goal is specified. It builds the -# target. -# -# target Builds the target. -# -# upload Uploads the target (building it, as necessary) to an attached -# Arduino. -# -# clean Deletes files created during the build. -# -# boards Display a list of available board names, so that you can set the -# BOARD environment variable appropriately. -# -# monitor Start `screen` on the serial device. This is meant to be an -# equivalent to the Arduino serial monitor. -# -# size Displays size information about the built target. -# -# bootloader Burns the bootloader for your board to it. -# -# Builds the specified file, either an object file or the target, -# from those that that would be built for the project. -#_______________________________________________________________________________ -# - -# default arduino software directory, check software exists -ifndef ARDUINODIR -ARDUINODIR := $(firstword $(wildcard ~/opt/arduino /usr/share/arduino \ - /Applications/Arduino.app/Contents/Resources/Java \ - $(HOME)/Applications/Arduino.app/Contents/Resources/Java)) -endif -ifeq "$(wildcard $(ARDUINODIR)/hardware/arduino/boards.txt)" "" -$(error ARDUINODIR is not set correctly; arduino software not found) -endif - -# default arduino version -ARDUINOCONST ?= 100 - -# default path for avr tools -AVRTOOLSPATH ?= $(subst :, , $(PATH)) $(ARDUINODIR)/hardware/tools \ - $(ARDUINODIR)/hardware/tools/avr/bin - -# default path to find libraries -LIBRARYPATH ?= libraries libs $(SKETCHBOOKDIR)/libraries $(ARDUINODIR)/libraries - -# default serial device to a poor guess (something that might be an arduino) -SERIALDEVGUESS := 0 -ifndef SERIALDEV -SERIALDEV := $(firstword $(wildcard \ - /dev/ttyACM? /dev/ttyUSB? /dev/tty.usbserial* /dev/tty.usbmodem*)) -SERIALDEVGUESS := 1 -endif - -# no board? -ifndef BOARD -ifneq "$(MAKECMDGOALS)" "boards" -ifneq "$(MAKECMDGOALS)" "clean" -$(error BOARD is unset. Type 'make boards' to see possible values) -endif -endif -endif - -# obtain board parameters from the arduino boards.txt file -BOARDSFILE := $(ARDUINODIR)/hardware/arduino/boards.txt -readboardsparam = $(shell sed -ne "s/$(BOARD).$(1)=\(.*\)/\1/p" $(BOARDSFILE)) -BOARD_BUILD_MCU := $(call readboardsparam,build.mcu) -BOARD_BUILD_FCPU := $(call readboardsparam,build.f_cpu) -BOARD_BUILD_VARIANT := $(call readboardsparam,build.variant) -BOARD_UPLOAD_SPEED := $(call readboardsparam,upload.speed) -BOARD_UPLOAD_PROTOCOL := $(call readboardsparam,upload.protocol) -BOARD_USB_VID := $(call readboardsparam,build.vid) -BOARD_USB_PID := $(call readboardsparam,build.pid) -BOARD_BOOTLOADER_UNLOCK := $(call readboardsparam,bootloader.unlock_bits) -BOARD_BOOTLOADER_LOCK := $(call readboardsparam,bootloader.lock_bits) -BOARD_BOOTLOADER_LFUSES := $(call readboardsparam,bootloader.low_fuses) -BOARD_BOOTLOADER_HFUSES := $(call readboardsparam,bootloader.high_fuses) -BOARD_BOOTLOADER_EFUSES := $(call readboardsparam,bootloader.extended_fuses) -BOARD_BOOTLOADER_PATH := $(call readboardsparam,bootloader.path) -BOARD_BOOTLOADER_FILE := $(call readboardsparam,bootloader.file) - -# obtain preferences from the IDE's preferences.txt -PREFERENCESFILE := $(firstword $(wildcard \ - $(HOME)/.arduino/preferences.txt $(HOME)/Library/Arduino/preferences.txt)) -ifneq "$(PREFERENCESFILE)" "" -readpreferencesparam = $(shell sed -ne "s/$(1)=\(.*\)/\1/p" $(PREFERENCESFILE)) -SKETCHBOOKDIR := $(call readpreferencesparam,sketchbook.path) -endif - -# invalid board? -ifeq "$(BOARD_BUILD_MCU)" "" -ifneq "$(MAKECMDGOALS)" "boards" -ifneq "$(MAKECMDGOALS)" "clean" -$(error BOARD is invalid. Type 'make boards' to see possible values) -endif -endif -endif - -# auto mode? -INOFILE := $(wildcard *.ino *.pde) -ifdef INOFILE -ifneq "$(words $(INOFILE))" "1" -$(error There is more than one .pde or .ino file in this directory!) -endif - -# automatically determine sources and targeet -TARGET := $(basename $(INOFILE)) -SOURCES := $(INOFILE) \ - $(wildcard *.c *.cc *.cpp *.C) \ - $(wildcard $(addprefix util/, *.c *.cc *.cpp *.C)) \ - $(wildcard $(addprefix utility/, *.c *.cc *.cpp *.C)) - -# automatically determine included libraries -LIBRARIES := $(filter $(notdir $(wildcard $(addsuffix /*, $(LIBRARYPATH)))), \ - $(shell sed -ne "s/^ *\# *include *[<\"]\(.*\)\.h[>\"]/\1/p" $(SOURCES))) - -endif - -# software -findsoftware = $(firstword $(wildcard $(addsuffix /$(1), $(AVRTOOLSPATH)))) -CC := $(call findsoftware,avr-gcc) -CXX := $(call findsoftware,avr-g++) -LD := $(call findsoftware,avr-ld) -AR := $(call findsoftware,avr-ar) -OBJCOPY := $(call findsoftware,avr-objcopy) -AVRDUDE := $(call findsoftware,avrdude) -AVRSIZE := $(call findsoftware,avr-size) - -# directories -ARDUINOCOREDIR := $(ARDUINODIR)/hardware/arduino/cores/arduino -LIBRARYDIRS := $(foreach lib, $(LIBRARIES), \ - $(firstword $(wildcard $(addsuffix /$(lib), $(LIBRARYPATH))))) -LIBRARYDIRS += $(addsuffix /utility, $(LIBRARYDIRS)) - -# files -TARGET := $(if $(TARGET),$(TARGET),a.out) -OBJECTS := $(addsuffix .o, $(basename $(SOURCES))) -DEPFILES := $(patsubst %, .dep/%.dep, $(SOURCES)) -ARDUINOLIB := .lib/arduino.a -ARDUINOLIBOBJS := $(foreach dir, $(ARDUINOCOREDIR) $(LIBRARYDIRS), \ - $(patsubst %, .lib/%.o, $(wildcard $(addprefix $(dir)/, *.c *.cpp)))) -BOOTLOADERHEX := $(addprefix \ - $(ARDUINODIR)/hardware/arduino/bootloaders/$(BOARD_BOOTLOADER_PATH)/, \ - $(BOARD_BOOTLOADER_FILE)) - -# avrdude confifuration -ifeq "$(AVRDUDECONF)" "" -ifeq "$(AVRDUDE)" "$(ARDUINODIR)/hardware/tools/avr/bin/avrdude" -AVRDUDECONF := $(ARDUINODIR)/hardware/tools/avr/etc/avrdude.conf -else -AVRDUDECONF := $(wildcard $(AVRDUDE).conf) -endif -endif - -# flags -BOARD_UPPER = $(shell tr '[:lower:]' '[:upper:]' <<< $(BOARD)) - -CPPFLAGS += -Os -Wall -fno-exceptions -ffunction-sections -fdata-sections -CPPFLAGS += -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -CPPFLAGS += -mmcu=$(BOARD_BUILD_MCU) -CPPFLAGS += -DF_CPU=$(BOARD_BUILD_FCPU) -DARDUINO=$(ARDUINOCONST) -CPPFLAGS += -DUSB_VID=$(BOARD_USB_VID) -DUSB_PID=$(BOARD_USB_PID) -CPPFLAGS += -DBOARD_$(BOARD_UPPER)=1 -CPPFLAGS += -I. -Iutil -Iutility -I $(ARDUINOCOREDIR) -CPPFLAGS += -I $(ARDUINODIR)/hardware/arduino/variants/$(BOARD_BUILD_VARIANT)/ -CPPFLAGS += $(addprefix -I , $(LIBRARYDIRS)) -CPPDEPFLAGS = -MMD -MP -MF .dep/$<.dep -CPPINOFLAGS := -x c++ -include $(ARDUINOCOREDIR)/Arduino.h -AVRDUDEFLAGS += $(addprefix -C , $(AVRDUDECONF)) -DV -AVRDUDEFLAGS += -p $(BOARD_BUILD_MCU) -P $(SERIALDEV) -AVRDUDEFLAGS += -c $(BOARD_UPLOAD_PROTOCOL) -b $(BOARD_UPLOAD_SPEED) -LINKFLAGS += -Os -Wl,--gc-sections -mmcu=$(BOARD_BUILD_MCU) - -# figure out which arg to use with stty (for OS X, GNU and busybox stty) -STTYFARG := $(shell stty --help 2>&1 | \ - grep -q 'illegal option' && echo -f || echo -F) - -# include dependencies -ifneq "$(MAKECMDGOALS)" "clean" --include $(DEPFILES) -endif - -# default rule -.DEFAULT_GOAL := all - -#_______________________________________________________________________________ -# RULES - -.PHONY: all target upload clean boards monitor size bootloader - -all: target - -target: $(TARGET).hex - -upload: target - @echo "\nUploading to board..." - @test -n "$(SERIALDEV)" || { \ - echo "error: SERIALDEV could not be determined automatically." >&2; \ - exit 1; } - @test 0 -eq $(SERIALDEVGUESS) || { \ - echo "*GUESSING* at serial device:" $(SERIALDEV); \ - echo; } -ifeq "$(BOARD_BOOTLOADER_PATH)" "caterina" - stty $(STTYFARG) $(SERIALDEV) speed 1200 - sleep 1 -else - stty $(STTYFARG) $(SERIALDEV) hupcl -endif - $(AVRDUDE) $(AVRDUDEFLAGS) -U flash:w:$(TARGET).hex:i - -clean: - rm -f $(OBJECTS) - rm -f $(TARGET).elf $(TARGET).hex $(ARDUINOLIB) *~ - rm -rf .lib .dep - -boards: - @echo "Available values for BOARD:" - @sed -nEe '/^#/d; /^[^.]+\.name=/p' $(BOARDSFILE) | \ - sed -Ee 's/([^.]+)\.name=(.*)/\1 \2/' \ - -e 's/(.{12}) *(.*)/\1 \2/' - -monitor: - @test -n "$(SERIALDEV)" || { \ - echo "error: SERIALDEV could not be determined automatically." >&2; \ - exit 1; } - @test -n `which screen` || { \ - echo "error: can't find GNU screen, you might need to install it." >&2 \ - exit 1; } - @test 0 -eq $(SERIALDEVGUESS) || { \ - echo "*GUESSING* at serial device:" $(SERIALDEV); \ - echo; } - screen $(SERIALDEV) - -size: $(TARGET).elf - echo && $(AVRSIZE) --format=avr --mcu=$(BOARD_BUILD_MCU) $(TARGET).elf - -bootloader: - @echo "Burning bootloader to board..." - @test -n "$(SERIALDEV)" || { \ - echo "error: SERIALDEV could not be determined automatically." >&2; \ - exit 1; } - @test 0 -eq $(SERIALDEVGUESS) || { \ - echo "*GUESSING* at serial device:" $(SERIALDEV); \ - echo; } - stty $(STTYFARG) $(SERIALDEV) hupcl - $(AVRDUDE) $(AVRDUDEFLAGS) -U lock:w:$(BOARD_BOOTLOADER_UNLOCK):m - $(AVRDUDE) $(AVRDUDEFLAGS) -eU lfuse:w:$(BOARD_BOOTLOADER_LFUSES):m - $(AVRDUDE) $(AVRDUDEFLAGS) -U hfuse:w:$(BOARD_BOOTLOADER_HFUSES):m -ifneq "$(BOARD_BOOTLOADER_EFUSES)" "" - $(AVRDUDE) $(AVRDUDEFLAGS) -U efuse:w:$(BOARD_BOOTLOADER_EFUSES):m -endif -ifneq "$(BOOTLOADERHEX)" "" - $(AVRDUDE) $(AVRDUDEFLAGS) -U flash:w:$(BOOTLOADERHEX):i -endif - $(AVRDUDE) $(AVRDUDEFLAGS) -U lock:w:$(BOARD_BOOTLOADER_LOCK):m - -# building the target - -$(TARGET).hex: $(TARGET).elf - $(OBJCOPY) -O ihex -R .eeprom $< $@ - -.INTERMEDIATE: $(TARGET).elf - -$(TARGET).elf: $(ARDUINOLIB) $(OBJECTS) - $(CC) $(LINKFLAGS) $(OBJECTS) $(ARDUINOLIB) -lm -o $@ - -%.o: %.c .dep - echo "Build $@" - mkdir -p .dep/$(dir $<) - $(COMPILE.c) $(CPPDEPFLAGS) -Werror -o $@ $< - -%.o: %.cpp .dep - echo "Build $@" - mkdir -p .dep/$(dir $<) - $(COMPILE.cpp) $(CPPDEPFLAGS) -Werror -o $@ $< - -%.o: %.cc .dep - echo "Build $@" - mkdir -p .dep/$(dir $<) - $(COMPILE.cpp) $(CPPDEPFLAGS) -Werror -o $@ $< - -%.o: %.C .dep - echo "Build $@" - mkdir -p .dep/$(dir $<) - $(COMPILE.cpp) $(CPPDEPFLAGS) -o $@ $< - -%.o: %.ino .dep - echo "Build $@ ino"; echo `pwd` - mkdir -p .dep/$(dir $<) - $(COMPILE.cpp) $(CPPDEPFLAGS) -Werror -o $@ $(CPPINOFLAGS) $< - -%.o: %.pde .dep - echo "Build $@ pde" - mkdir -p .dep/$(dir $<) - $(COMPILE.cpp) $(CPPDEPFLAGS) -o $@ $(CPPINOFLAGS) $< - -.dep: - mkdir -p $@ - -# building the arduino library - -$(ARDUINOLIB): $(ARDUINOLIBOBJS) - $(AR) rcs $@ $? - -.lib/%.c.o: %.c - mkdir -p $(dir $@) - $(COMPILE.c) -o $@ $< - -.lib/%.cpp.o: %.cpp - mkdir -p $(dir $@) - $(COMPILE.cpp) -o $@ $< - -.lib/%.cc.o: %.cc - mkdir -p $(dir $@) - $(COMPILE.cpp) -o $@ $< - -.lib/%.C.o: %.C - mkdir -p $(dir $@) - $(COMPILE.cpp) -o $@ $< - -# Local Variables: -# mode: makefile -# tab-width: 4 -# End: diff --git a/arduino/kegboard/OneWire.cpp b/arduino/kegboard/OneWire.cpp deleted file mode 100644 index 6ee0008..0000000 --- a/arduino/kegboard/OneWire.cpp +++ /dev/null @@ -1,403 +0,0 @@ -/* -Copyright (c) 2007, Jim Studt - -Updated to work with arduino-0008 and to include skip() as of -2007/07/06. --RJL20 - -Modified to calculate the 8-bit CRC directly, avoiding the need for -the 256-byte lookup table to be loaded in RAM. Tested in arduino-0010 --- Tom Pollard, Jan 23, 2008 - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -Much of the code was inspired by Derek Yerger's code, though I don't -think much of that remains. In any event that was.. - (copyleft) 2006 by Derek Yerger - Free to distribute freely. - -The CRC code was excerpted and inspired by the Dallas Semiconductor -sample code bearing this copyright. -//--------------------------------------------------------------------------- -// Copyright (C) 2000 Dallas Semiconductor Corporation, All Rights Reserved. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -// IN NO EVENT SHALL DALLAS SEMICONDUCTOR BE LIABLE FOR ANY CLAIM, DAMAGES -// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// Except as contained in this notice, the name of Dallas Semiconductor -// shall not be used except as stated in the Dallas Semiconductor -// Branding Policy. -//-------------------------------------------------------------------------- -*/ - -#include "OneWire.h" -#include "Arduino.h" - -extern "C" { -#include -#include "pins_arduino.h" -} - - -OneWire::OneWire( uint8_t pinArg) -{ - pin = pinArg; - port = digitalPinToPort(pin); - bitmask = digitalPinToBitMask(pin); - outputReg = portOutputRegister(port); - inputReg = portInputRegister(port); - modeReg = portModeRegister(port); -#if ONEWIRE_SEARCH - reset_search(); -#endif -} - -// -// Perform the onewire reset function. We will wait up to 250uS for -// the bus to come high, if it doesn't then it is broken or shorted -// and we return a 0; -// -// Returns 1 if a device asserted a presence pulse, 0 otherwise. -// -uint8_t OneWire::reset() { - uint8_t r; - uint8_t retries = 125; - - // wait until the wire is high... just in case - pinMode(pin,INPUT); - do { - if ( retries-- == 0) return 0; - delayMicroseconds(2); - } while( !digitalRead( pin)); - - digitalWrite(pin,0); // pull low for 500uS - pinMode(pin,OUTPUT); - delayMicroseconds(500); - pinMode(pin,INPUT); - delayMicroseconds(65); - r = !digitalRead(pin); - delayMicroseconds(490); - return r; -} - -// -// Write a bit. Port and bit is used to cut lookup time and provide -// more certain timing. -// -void OneWire::write_bit(uint8_t v) { - static uint8_t lowTime[] = { 55, 5 }; - static uint8_t highTime[] = { 5, 55}; - - v = (v&1); - *modeReg |= bitmask; // make pin an output, do first since we - // expect to be at 1 - *outputReg &= ~bitmask; // zero - delayMicroseconds(lowTime[v]); - *outputReg |= bitmask; // one, push pin up - important for - // parasites, they might start in here - delayMicroseconds(highTime[v]); -} - -// -// Read a bit. Port and bit is used to cut lookup time and provide -// more certain timing. -// -uint8_t OneWire::read_bit() { - uint8_t r; - - *modeReg |= bitmask; // make pin an output, do first since we expect to be at 1 - *outputReg &= ~bitmask; // zero - delayMicroseconds(1); - *modeReg &= ~bitmask; // let pin float, pull up will raise - delayMicroseconds(5); // A "read slot" is when 1mcs > t > 2mcs - r = (*inputReg & bitmask) ? 1 : 0; // check the bit - delayMicroseconds(50); // whole bit slot is 60-120uS, need to give some time - - return r; -} - -// -// Write a byte. The writing code uses the active drivers to raise the -// pin high, if you need power after the write (e.g. DS18S20 in -// parasite power mode) then set 'power' to 1, otherwise the pin will -// go tri-state at the end of the write to avoid heating in a short or -// other mishap. -// -void OneWire::write(uint8_t v, uint8_t power) { - uint8_t bitMask; - - for (bitMask = 0x01; bitMask; bitMask <<= 1) { - OneWire::write_bit((bitMask & v) ? 1 : 0); - } - if (!power) { - pinMode(pin,INPUT); - digitalWrite(pin,0); - } -} - -// -// Read a byte -// -uint8_t OneWire::read() { - uint8_t bitMask; - uint8_t r = 0; - - for (bitMask = 0x01; bitMask; bitMask <<= 1) { - if (OneWire::read_bit()) { - r |= bitMask; - } - } - return r; -} - -// -// Do a ROM select -// -void OneWire::select(uint8_t rom[8]) -{ - int i; - write(0x55,0); // Choose ROM - for(i = 0; i < 8; i++) { - write(rom[i],0); - } -} - -// -// Do a ROM skip -// -void OneWire::skip() -{ - write(0xCC, 0); // Skip ROM -} - -void OneWire::depower() -{ - pinMode(pin, INPUT); -} - -#if ONEWIRE_SEARCH - -// -// You need to use this function to start a search again from the beginning. -// You do not need to do it for the first search, though you could. -// -void OneWire::reset_search() -{ - uint8_t i; - - last_discrepancy = -1; - searchExhausted = 0; - for(i = 7; ; i--) { - address[i] = 0; - if (i == 0) { - break; - } - } -} - -// -// Perform a search. If this function returns a '1' then it has -// enumerated the next device and you may retrieve the ROM from the -// OneWire::address variable. If there are no devices, no further -// devices, or something horrible happens in the middle of the -// enumeration then a 0 is returned. If a new device is found then -// its address is copied to newAddr. Use OneWire::reset_search() to -// start over. -// -uint8_t OneWire::search(uint8_t *newAddr) -{ - uint8_t i; - - if (searchExhausted) { - return 0; - } - - if (!reset()) { - return 0; - } - - write(0xf0, 0); - last_zero = 0; - - for(i = 0; i < 64; i++) { - uint8_t id_bit = read_bit(); - uint8_t cmp_id_bit = read_bit(); - uint8_t ibyte = i/8; - uint8_t ibit = 1<<(i&7); - uint8_t search_direction; - - if ((id_bit == 1) && (cmp_id_bit == 1)) { - // Participaing device stopped responding (ie removed during search); - // search should be terminated and bus reset. - // Reference: http://www.maxim-ic.com/appnotes.cfm/an_pk/187 - reset_search(); - searchExhausted = 1; - return 0; - } else if ((id_bit == 0) && (cmp_id_bit == 0)) { - if (i == last_discrepancy) { - search_direction = 1; - } else if (i > last_discrepancy) { - search_direction = 0; - } else { - search_direction = (address[ibyte] >> (i%8)) & 0x1; - } - if (search_direction == 0) { - last_zero = i; - } - } else { - search_direction = id_bit; - } - - if (search_direction) { - address[ibyte] |= ibit; - } else { - address[ibyte] &= ~ibit; - } - - write_bit(search_direction); - } - - last_discrepancy = last_zero; - - if (last_discrepancy == 0) { - searchExhausted = 1; - } - - if (OneWire::crc8(address, 7) != address[7]) { - reset_search(); - return 0; - } - - for (i = 0; i < 8; i++) { - newAddr[i] = address[i]; - } - return 1; -} -#endif - -#if ONEWIRE_CRC -// The 1-Wire CRC scheme is described in Maxim Application Note 27: -// "Understanding and Using Cyclic Redundancy Checks with Maxim iButton Products" -// - -#if ONEWIRE_CRC8_TABLE -// This table comes from Dallas sample code where it is freely reusable, -// though Copyright (C) 2000 Dallas Semiconductor Corporation -static uint8_t dscrc_table[] = { - 0, 94,188,226, 97, 63,221,131,194,156,126, 32,163,253, 31, 65, - 157,195, 33,127,252,162, 64, 30, 95, 1,227,189, 62, 96,130,220, - 35,125,159,193, 66, 28,254,160,225,191, 93, 3,128,222, 60, 98, - 190,224, 2, 92,223,129, 99, 61,124, 34,192,158, 29, 67,161,255, - 70, 24,250,164, 39,121,155,197,132,218, 56,102,229,187, 89, 7, - 219,133,103, 57,186,228, 6, 88, 25, 71,165,251,120, 38,196,154, - 101, 59,217,135, 4, 90,184,230,167,249, 27, 69,198,152,122, 36, - 248,166, 68, 26,153,199, 37,123, 58,100,134,216, 91, 5,231,185, - 140,210, 48,110,237,179, 81, 15, 78, 16,242,172, 47,113,147,205, - 17, 79,173,243,112, 46,204,146,211,141,111, 49,178,236, 14, 80, - 175,241, 19, 77,206,144,114, 44,109, 51,209,143, 12, 82,176,238, - 50,108,142,208, 83, 13,239,177,240,174, 76, 18,145,207, 45,115, - 202,148,118, 40,171,245, 23, 73, 8, 86,180,234,105, 55,213,139, - 87, 9,235,181, 54,104,138,212,149,203, 41,119,244,170, 72, 22, - 233,183, 85, 11,136,214, 52,106, 43,117,151,201, 74, 20,246,168, - 116, 42,200,150, 21, 75,169,247,182,232, 10, 84,215,137,107, 53 -}; - -// -// Compute a Dallas Semiconductor 8 bit CRC. These show up in the ROM -// and the registers. (note: this might better be done without to -// table, it would probably be smaller and certainly fast enough -// compared to all those delayMicrosecond() calls. But I got -// confused, so I use this table from the examples.) -// -uint8_t OneWire::crc8(uint8_t *addr, uint8_t len) -{ - uint8_t i; - uint8_t crc = 0; - - for (i = 0; i < len; i++) { - crc = dscrc_table[crc ^ addr[i]]; - } - return crc; -} -#else -// -// Compute a Dallas Semiconductor 8 bit CRC directly. -// -uint8_t OneWire::crc8( uint8_t *addr, uint8_t len) -{ - uint8_t i, j; - uint8_t crc = 0; - - for (i = 0; i < len; i++) { - uint8_t inbyte = addr[i]; - for (j = 0; j < 8; j++) { - uint8_t mix = (crc ^ inbyte) & 0x01; - crc >>= 1; - if (mix) crc ^= 0x8C; - inbyte >>= 1; - } - } - return crc; -} -#endif - -#if ONEWIRE_CRC16 -static short oddparity[16] = { 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 }; - -// -// Compute a Dallas Semiconductor 16 bit CRC. I have never seen one of -// these, but here it is. -// -unsigned short OneWire::crc16(unsigned short *data, unsigned short len) -{ - unsigned short i; - unsigned short crc = 0; - - for (i = 0; i < len; i++) { - unsigned short cdata = data[len]; - - cdata = (cdata ^ (crc & 0xff)) & 0xff; - crc >>= 8; - - if (oddparity[cdata & 0xf] ^ oddparity[cdata >> 4]) crc ^= 0xc001; - - cdata <<= 6; - crc ^= cdata; - cdata <<= 1; - crc ^= cdata; - } - return crc; -} -#endif - -#endif diff --git a/arduino/kegboard/OneWire.h b/arduino/kegboard/OneWire.h deleted file mode 100644 index ee890b5..0000000 --- a/arduino/kegboard/OneWire.h +++ /dev/null @@ -1,107 +0,0 @@ -#ifndef OneWire_h -#define OneWire_h - -#include - -// you can exclude onewire_search by defining that to 0 -#ifndef ONEWIRE_SEARCH -#define ONEWIRE_SEARCH 1 -#endif - -// You can exclude CRC checks altogether by defining this to 0 -#ifndef ONEWIRE_CRC -#define ONEWIRE_CRC 1 -#endif - -// Select the table-lookup method of computing the 8-bit CRC by setting this to 1 -#ifndef ONEWIRE_CRC8_TABLE -#define ONEWIRE_CRC8_TABLE 0 -#endif - -// You can allow 16-bit CRC checks by defining this to 1 -// (Note that ONEWIRE_CRC must also be 1.) -#ifndef ONEWIRE_CRC16 -#define ONEWIRE_CRC16 0 -#endif - -class OneWire -{ - private: -#if ONEWIRE_SEARCH - uint8_t address[8]; - char last_discrepancy; - char last_zero; - uint8_t searchExhausted; -#endif - uint8_t pin; - uint8_t port; - uint8_t bitmask; - volatile uint8_t *outputReg; - volatile uint8_t *inputReg; - volatile uint8_t *modeReg; - - public: - OneWire( uint8_t pin); - - // Perform a 1-Wire reset cycle. Returns 1 if a device responds - // with a presence pulse. Returns 0 if there is no device or the - // bus is shorted or otherwise held low for more than 250uS - uint8_t reset(); - - // Issue a 1-Wire rom select command, you do the reset first. - void select( uint8_t rom[8]); - - // Issue a 1-Wire rom skip command, to address all on bus. - void skip(); - - // Write a byte. If 'power' is one then the wire is held high at - // the end for parasitically powered devices. You are responsible - // for eventually depowering it by calling depower() or doing - // another read or write. - void write( uint8_t v, uint8_t power = 0); - - // Read a byte. - uint8_t read(); - - // Write a bit. The bus is always left powered at the end, see - // note in write() about that. - void write_bit( uint8_t v); - - // Read a bit. - uint8_t read_bit(); - - // Stop forcing power onto the bus. You only need to do this if - // you used the 'power' flag to write() or used a write_bit() call - // and aren't about to do another read or write. You would rather - // not leave this powered if you don't have to, just in case - // someone shorts your bus. - void depower(); - -#if ONEWIRE_SEARCH - // Clear the search state so that if will start from the beginning again. - void reset_search(); - - // Look for the next device. Returns 1 if a new address has been - // returned. A zero might mean that the bus is shorted, there are - // no devices, or you have already retrieved all of them. It - // might be a good idea to check the CRC to make sure you didn't - // get garbage. The order is deterministic. You will always get - // the same devices in the same order. - uint8_t search(uint8_t *newAddr); -#endif - -#if ONEWIRE_CRC - // Compute a Dallas Semiconductor 8 bit CRC, these are used in the - // ROM and scratchpad registers. - static uint8_t crc8( uint8_t *addr, uint8_t len); - -#if ONEWIRE_CRC16 - // Compute a Dallas Semiconductor 16 bit CRC. Maybe. I don't have - // any devices that use this so this might be wrong. I just copied - // it from their sample code. - static unsigned short crc16(unsigned short *data, unsigned short len); -#endif -#endif -}; - -#endif diff --git a/arduino/kegboard/PCInterrupt.cpp b/arduino/kegboard/PCInterrupt.cpp deleted file mode 100644 index 1fa007a..0000000 --- a/arduino/kegboard/PCInterrupt.cpp +++ /dev/null @@ -1,153 +0,0 @@ -// -// PCInterrupt.cpp -// KegBoard -// -// An extension to the interrupt support for Arduino. -// Adds pin change interrupts to the external interrupts, allowing -// any pin to support external interrupts efficiently. -// -// Theory: all IO pins on Atmega168 are covered by Pin Change Interrupts. -// The PCINT corresponding to the pin must be enabled and masked, and -// an ISR routine provided. Since PCINTs are per port, not per pin, the ISR -// must use some logic to actually implement a per-pin interrupt service. -// -// Pin to interrupt map: -// D0-D7 = PCINT 16-23 = PCIR2 = PD = PCIE2 = pcmsk2 -// D8-D13 = PCINT 0-5 = PCIR0 = PB = PCIE0 = pcmsk0 -// A0-A5 (D14-D19) = PCINT 8-13 = PCIR1 = PC = PCIE1 = pcmsk1 -// -// Originally by ckiick at http://www.arduino.cc/playground/Main/PcInt -// Modified to support RISING/FALLING by John Boiles 9/30/10 -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -#include "Arduino.h" -#include "pins_arduino.h" -#include "kegboard_config.h" - -volatile uint8_t *PCintPortToInputMask[] = { - &PCMSK0, - &PCMSK1, - &PCMSK2 -}; - -static int PCintMode[24]; - -typedef void (*voidFuncPtr)(void); - -volatile static voidFuncPtr PCintFunc[24] = { NULL }; - -volatile static uint8_t PCintLast[3]; - -void PCattachInterrupt(uint8_t pin, void (*userFunc)(void), int mode) { - uint8_t bit = digitalPinToBitMask(pin); - uint8_t port = digitalPinToPort(pin); - uint8_t slot; - volatile uint8_t *pcmask; - - pinMode(pin, INPUT); - // map pin to PCIR register - if (port == NOT_A_PORT) { - return; - } - - port -= 2; - pcmask = PCintPortToInputMask[port]; - - // Fix by Baziki. In the original sources there was a little bug, - // which caused analog ports to work incorrectly. - if (port == 1) { - slot = port * 8 + (pin - 14); - } - else { - slot = port * 8 + (pin % 8); - } - - PCintMode[slot] = mode; - PCintFunc[slot] = userFunc; - // set the mask - *pcmask |= bit; - // enable the interrupt - PCICR |= 0x01 << port; -} - -void PCdetachInterrupt(uint8_t pin) { - uint8_t bit = digitalPinToBitMask(pin); - uint8_t port = digitalPinToPort(pin); - volatile uint8_t *pcmask; - - // map pin to PCIR register - if (port == NOT_A_PORT) { - return; - } else { - port -= 2; - pcmask = PCintPortToInputMask[port]; - } - - // disable the mask. - *pcmask &= ~bit; - // if that's the last one, disable the interrupt. - if (*pcmask == 0) { - PCICR &= ~(0x01 << port); - } -} - -#if KB_ENABLE_WIEGAND_RFID - -// common code for isr handler. "port" is the PCINT number. -// there isn't really a good way to back-map ports and masks to pins. -static void PCint(uint8_t port) { - uint8_t bit; - uint8_t curr; - uint8_t mask; - uint8_t pin; - - // get the pin states for the indicated port. - curr = *portInputRegister(port+2); - mask = curr ^ PCintLast[port]; - // mask is pins that have changed. screen out non pcint pins. - if ((mask &= *PCintPortToInputMask[port]) == 0) { - return; - } - // mask is pcint pins that have changed. - for (uint8_t i=0; i < 8; i++) { - bit = 0x01 << i; - if (bit & mask) { - pin = port * 8 + i; - // Trigger interrupt if mode is CHANGE, or if mode is RISING and - // the bit is currently high, or if mode is FALLING and bit is low. - if ((PCintMode[pin] == CHANGE - || ((PCintMode[pin] == RISING) && (curr & bit)) - || ((PCintMode[pin] == FALLING) && !(curr & bit))) - && (PCintFunc[pin] != NULL)) { - PCintFunc[pin](); - } - } - } - // Save current pin values - PCintLast[port] = curr; -} - -SIGNAL(PCINT0_vect) { - PCint(0); -} -SIGNAL(PCINT1_vect) { - PCint(1); -} -SIGNAL(PCINT2_vect) { - PCint(2); -} -#endif - diff --git a/arduino/kegboard/PCInterrupt.h b/arduino/kegboard/PCInterrupt.h deleted file mode 100644 index 0eb4b81..0000000 --- a/arduino/kegboard/PCInterrupt.h +++ /dev/null @@ -1,46 +0,0 @@ -// -// PCInterrupt.h -// KegBoard -// -// An extension to the interrupt support for Arduino. -// Adds pin change interrupts to the external interrupts, allowing -// any pin to support external interrupts efficiently. -// -// Theory: all IO pins on Atmega168 are covered by Pin Change Interrupts. -// The PCINT corresponding to the pin must be enabled and masked, and -// an ISR routine provided. Since PCINTs are per port, not per pin, the ISR -// must use some logic to actually implement a per-pin interrupt service. -// -// Pin to interrupt map: -// D0-D7 = PCINT 16-23 = PCIR2 = PD = PCIE2 = pcmsk2 -// D8-D13 = PCINT 0-5 = PCIR0 = PB = PCIE0 = pcmsk0 -// A0-A5 (D14-D19) = PCINT 8-13 = PCIR1 = PC = PCIE1 = pcmsk1 -// -// Originally by ckiick at http://www.arduino.cc/playground/Main/PcInt -// Modified to support RISING/FALLING by John Boiles 9/30/10 -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -#include - -/* - * Dttach an interrupt to a specific pin using pin change interrupts. - */ -void PCattachInterrupt(uint8_t pin, void (*userFunc)(void), int mode); - -/* - * Detach an pin change interrupt from a specific pin. - */ -void PCdetachInterrupt(uint8_t pin); diff --git a/arduino/kegboard/Wiegand.cpp b/arduino/kegboard/Wiegand.cpp deleted file mode 100644 index d02774c..0000000 --- a/arduino/kegboard/Wiegand.cpp +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2012 Mike Wakerly - * - * This file is part of the Kegboard package of the Kegbot project. - * For more information on Kegboard or Kegbot, see http://kegbot.org/ - * - * Kegboard is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * Kegboard is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Kegboard. If not, see . - */ - -#include "Arduino.h" -#include "Wiegand.h" -#include "pins_arduino.h" - -Wiegand::Wiegand() { - bitpos_ = 0; -} - -void Wiegand::handleData0Pulse() { - bitpos_++; -} - -void Wiegand::handleData1Pulse() { - int index = bitpos_ / 8; - if (index >= WIEGAND_BUFSIZ) { - return; - } - int offset = bitpos_ % 8; - buf_[index] |= (uint8_t) (1 << offset); - bitpos_++; -} - -int Wiegand::getData(uint8_t* data) { - memcpy(data, buf_, WIEGAND_BUFSIZ); - return bitpos_; -} - -void Wiegand::reset() { - bitpos_ = 0; - memset(buf_, 0, WIEGAND_BUFSIZ); -} - diff --git a/arduino/kegboard/Wiegand.h b/arduino/kegboard/Wiegand.h deleted file mode 100644 index bc49595..0000000 --- a/arduino/kegboard/Wiegand.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2012 Mike Wakerly - * - * This file is part of the Kegboard package of the Kegbot project. - * For more information on Kegboard or Kegbot, see http://kegbot.org/ - * - * Kegboard is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * Kegboard is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Kegboard. If not, see . - */ - -#define WIEGAND_BUFSIZ 5 - -class Wiegand { - public: - Wiegand(); - void handleData0Pulse(); - void handleData1Pulse(); - int getData(uint8_t* data); - void reset(); - private: - uint8_t data0_pin_; // Wiegand DATA0 pin - uint8_t data1_pin_; // Wiegand DATA1 pin - volatile int bitpos_; // Current bit position - uint8_t buf_[WIEGAND_BUFSIZ]; -}; diff --git a/arduino/kegboard/buzzer.cpp b/arduino/kegboard/buzzer.cpp deleted file mode 100644 index 21af061..0000000 --- a/arduino/kegboard/buzzer.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/** - * buzzer.cpp - Arduino buzzer routines - * Copyright 2014 Mike Wakerly - * - * This file is part of the Kegbot package of the Kegbot project. - * For more information on Kegbot, see http://kegbot.org/ - * - * Kegbot is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * Kegbot is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Kegbot. If not, see . - */ - -#include "buzzer.h" - -#include - -// Play a sequence of MelodyNotes -// Sequence must terminate with octave == -1 -void play_notes(uint32_t* notes, int pin) -{ - int i=0; - noTone(pin); - - while (true) { - uint32_t note = notes[i++]; - if (note == MELODY_END) { - break; - } - - uint32_t frequency = FREQUENCY(note); - uint32_t duration = DURATION(note); - - if (frequency == 0) { - noTone(pin); - } else { - tone(pin, frequency); - } - - delay(duration); - } - noTone(pin); -} diff --git a/arduino/kegboard/buzzer.h b/arduino/kegboard/buzzer.h deleted file mode 100644 index 4fbde33..0000000 --- a/arduino/kegboard/buzzer.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef BUZZER_H -#define BUZZER_H - -#include "Arduino.h" - -// Note is: -// 16 bits for frequency -// 16 bits for duration -#define NOTE(frequency, duration) \ - (((frequency & 0xffffUL) << 16) | (duration & 0xffffUL)) -#define DURATION(note) (note & 0xffffUL) -#define FREQUENCY(note) ((note >> 16) & 0xffffUL) -#define SILENT(duration) NOTE(0, duration) -#define MELODY_END 0 - -void play_notes(uint32_t* notes, int pin); - -#endif // BUZZER_H diff --git a/arduino/kegboard/ds1820.cpp b/arduino/kegboard/ds1820.cpp deleted file mode 100644 index 55ec511..0000000 --- a/arduino/kegboard/ds1820.cpp +++ /dev/null @@ -1,181 +0,0 @@ -/** - * Copyright 2003-2010 Mike Wakerly - * - * This file is part of the Kegbot package of the Kegbot project. - * For more information on Kegbot, see http://kegbot.org/ - * - * Kegbot is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * Kegbot is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Kegbot. If not, see . - */ - -#include -#include - -#include "kegboard.h" -#include "ds1820.h" -#include "OneWire.h" - -#define REFRESH_MS 5000 - -DS1820Sensor::DS1820Sensor() { - m_bus = 0; - m_initialized = false; - Reset(); -} - -void DS1820Sensor::Reset() { - m_converting = false; - m_conversion_start_clock = 0; - m_temp = INVALID_TEMPERATURE_VALUE; - m_temp_is_valid = false; - - for (int i=0; i<8; i++) { - m_addr[i] = 0; - } -} - -void DS1820Sensor::Initialize(OneWire* bus, uint8_t* addr) -{ - m_bus = bus; - for (int i=0; i<8;i++) { - m_addr[i] = addr[i]; - } - m_initialized = true; -} - -bool DS1820Sensor::Initialized() { - return m_initialized; -} - -bool DS1820Sensor::Update(unsigned long clock) -{ - if (!m_initialized) { - return false; - } - - if (clock < m_conversion_start_clock) { // overflow of clock - m_conversion_start_clock = 0; - } - - if (!m_converting) { - // we're not converting, and it is time to start - m_converting = true; - m_conversion_start_clock = clock; - StartConversion(); - } else if (m_converting && (clock - m_conversion_start_clock) >= 1000) { - // we're converting and it is time to fetch - m_converting = false; - - if (FetchConversion()) { - m_temp_is_valid = true; - } else { - m_temp_is_valid = false; - } - return true; - } else { - // we're either in the middle of a conversion, or it is too soon to start - // the next cycle. - } - return false; - -} - -bool DS1820Sensor::ResetAndSelect() -{ - if (!m_bus->reset()) { - return false; - } - m_bus->select(m_addr); - return true; -} - -bool DS1820Sensor::StartConversion() -{ - if (!ResetAndSelect()) - return false; - m_bus->write(0x44, 1); - return true; -} - -bool DS1820Sensor::FetchConversion() -{ - if (!ResetAndSelect()) { - return false; - } - - uint8_t data[9]; - m_bus->write(0xBE); // read scratchpad - - bool null_data = true; - for (int i = 0; i < 9; i++) { - data[i] = m_bus->read(); - if (data[i] != 0) { - null_data = false; - } - } - - if (null_data) { - return false; - } - - if (OneWire::crc8(data, 8) != data[8]) { - // bad CRC, drop reading. - return false; - } - - m_temp = ((data[1] << 8) | data[0]); - return true; -} - -long DS1820Sensor::GetTemp(void) -{ - if (!m_temp_is_valid) - return INVALID_TEMPERATURE_VALUE; - - // The value returned by the DS18B20 is a 16-bit 2's complement sign-extended - // value. The value is the temperature, either increments of 1/16th deg C - // (DS18B20 default) or 1/2 deg C (DS18S20 default). - // - // This method returns the temperature in 1/10^6 deg C, so the value is scaled - // up by (10^6/16) = 62500 or (10^6/2) = 500000. - long res = (long)m_temp; - switch (m_addr[0]) { - case ONEWIRE_FAMILY_DS18B20: - return res * 62500L; - case ONEWIRE_FAMILY_DS18S20: - return res * 500000L; - default: - return INVALID_TEMPERATURE_VALUE; - } -} - -bool DS1820Sensor::Busy() { - return m_converting; -} - -int DS1820Sensor::CompareId(uint8_t* other) { - for (int i = 0; i < 8; i++) { - if (m_addr[i] == other[i]) { - continue; - } else { - return (m_addr[i] < other[i]) ? -1 : 1; - } - } - return 0; -} - -void DS1820Sensor::PrintTemp(void) -{ - long temp = GetTemp() / 1000000; - Serial.print(temp); -} diff --git a/arduino/kegboard/ds1820.h b/arduino/kegboard/ds1820.h deleted file mode 100644 index d8911ff..0000000 --- a/arduino/kegboard/ds1820.h +++ /dev/null @@ -1,37 +0,0 @@ -#include - -#define ONEWIRE_FAMILY_DS18B20 0x28 -#define ONEWIRE_FAMILY_DS18S20 0x10 -#define INVALID_TEMPERATURE_VALUE INT_MIN - -class OneWire; - -class DS1820Sensor { - public: - DS1820Sensor(); - bool Update(unsigned long clock); - void PrintTemp(void); - long GetTemp(); - bool Busy(); - bool Initialized(); - void Reset(); - int CompareId(uint8_t* other); - void Initialize(OneWire* bus, uint8_t* addr); - - uint8_t m_addr[8]; - - private: - bool ResetAndSelect(); - bool StartConversion(); - bool FetchConversion(); - void Reset(uint8_t *addr); - - private: - OneWire* m_bus; - - bool m_initialized; - bool m_converting; - unsigned long m_conversion_start_clock; - int m_temp; - bool m_temp_is_valid; -}; diff --git a/arduino/kegboard/kegboard.h b/arduino/kegboard/kegboard.h deleted file mode 100644 index 40e40e4..0000000 --- a/arduino/kegboard/kegboard.h +++ /dev/null @@ -1,94 +0,0 @@ -#include "HardwareSerial.h" - -#define LOG(s) Serial.println(s); - -#define KB_BOARDNAME_MAXLEN 8 - -#define KBM_HELLO_ID 0x01 -#define KBM_HELLO_TAG_FIRMWARE_VERSION 0x01 -#define KBM_HELLO_TAG_PROTOCOL_VERSION 0x02 -#define KBM_HELLO_TAG_SERIAL_NUMBER 0x03 -#define KBM_HELLO_TAG_UPTIME_MILLIS 0x04 -#define KBM_HELLO_TAG_UPTIME_DAYS 0x05 - -#define KBM_THERMO_READING 0x11 -#define KBM_THERMO_READING_TAG_SENSOR_NAME 0x01 -#define KBM_THERMO_READING_TAG_SENSOR_READING 0x02 - -#define KBM_METER_STATUS 0x10 -#define KBM_METER_STATUS_TAG_METER_NAME 0x01 -#define KBM_METER_STATUS_TAG_METER_READING 0x02 - -#define KBM_OUTPUT_STATUS 0x12 -#define KBM_OUTPUT_STATUS_TAG_OUTPUT_NAME 0x01 -#define KBM_OUTPUT_STATUS_TAG_OUTPUT_READING 0x02 - -#define KBM_ONEWIRE_PRESENCE 0x13 -#define KBM_ONEWIRE_PRESENCE_TAG_DEVICE_ID 0x01 -#define KBM_ONEWIRE_PRESENCE_TAG_STATUS 0x02 - -#define KBM_AUTH_TOKEN 0x14 -#define KBM_AUTH_TOKEN_TAG_DEVICE 0x01 -#define KBM_AUTH_TOKEN_TAG_TOKEN 0x02 -#define KBM_AUTH_TOKEN_TAG_STATUS 0x03 - -#define KBM_PING 0x81 - -#define KBM_SET_OUTPUT 0x84 -#define KBM_SET_OUTPUT_TAG_OUTPUT_ID 0x01 -#define KBM_SET_OUTPUT_TAG_OUTPUT_MODE 0x02 - -#define OUTPUT_DISABLED 0 -#define OUTPUT_ENABLED 1 - -#define KBM_SET_SERIAL_NUMBER 0x85 -#define KBM_SET_SERIAL_NUMBER_TAG_SERIAL 0x01 - -#define KBSP_PREFIX "KBSP v1:" -#define KBSP_PREFIX_CRC 0xe3af -#define KBSP_TRAILER "\r\n" - -#define KBSP_HEADER_LEN 12 -#define KBSP_HEADER_PREFIX_LEN 8 -#define KBSP_HEADER_ID_LEN 2 -#define KBSP_HEADER_PAYLOADLEN_LEN 2 - -#define KBSP_FOOTER_LEN 4 -#define KBSP_FOOTER_CRC_LEN 2 -#define KBSP_FOOTER_TRAILER_LEN 2 - -#define KBSP_PAYLOAD_MAXLEN 112 - -// Milliseconds/day -#define MS_PER_DAY 86400000UL -// Interval between test pulse trains -#define KB_SELFTEST_INTERVAL_MS 500 - -// Number of pulses per test pulse train -#define KB_SELFTEST_PULSES 10 - -// Minimum time, in MS, between meter update packets. Setting this to zero will -// cause the kegboard to send a meter update message for nearly every tick; this -// is not recommended. -#define KB_METER_UPDATE_INTERVAL_MS 100 - -// Heartbeat interval. A "hello" packet will be emitted to the host this often. -#define KB_HEARTBEAT_INTERVAL_MS (10 * 1000) - -// Number of relay outputs -#define KB_NUM_RELAY_OUTPUTS 6 - -// Maximum time a relay will remain enabled after a "set_output" command. The -// timer is reset whenenver a new "set_output" command is received. -#define KB_RELAY_WATCHDOG_MS 10000 - -// RFID defines -#define STX 0x02 -#define ETX 0x03 -#define RFID_DATA_CHARS 10 -#define RFID_CHECKSUM_CHARS 2 -#define RFID_PARALLAX_PAYLOAD_CHARS 4 -#define RFID_PARALLAX_LEGACY_PAYLOAD_CHARS 10 -#define RFID_PAYLOAD_CHARS 12 -#define CR '\r' -#define LF '\n' diff --git a/arduino/kegboard/kegboard.ino b/arduino/kegboard/kegboard.ino deleted file mode 100644 index 59aaecc..0000000 --- a/arduino/kegboard/kegboard.ino +++ /dev/null @@ -1,1039 +0,0 @@ -/** - * kegboard.pde - Kegboard v3 Arduino project - * Copyright 2003-2011 Mike Wakerly - * - * This file is part of the Kegbot package of the Kegbot project. - * For more information on Kegbot, see http://kegbot.org/ - * - * Kegbot is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * Kegbot is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Kegbot. If not, see . - */ - -/** - * This firmware is intended for an Arduino Diecimila board (or similar) - * http://www.arduino.cc/en/Main/ArduinoBoardDiecimila - * - * This firmware implements the Kegboard Serial Protocol, version 1 (KBSP v1). - * For more information on what that means, see the kegbot docs: - * http://kegbot.org/docs/ - * - * You may change the pin configuration by editing kegboard_config.h; you should - * not need to change anything in this file. - * - * TODO: - * - get/set boardname with eeprom - * - Thermo: - * * check CRC - * * clean up code - * - leak detect circuit/alarm support - */ - -#include "Arduino.h" - -#include -#include -#include -#include -#include - -#include "kegboard.h" -#include "kegboard_config.h" -#include "kegboard_eeprom.h" -#include "ds1820.h" -#include "KegboardPacket.h" -#include "version.h" - -#if (KB_ENABLE_ONEWIRE_THERMO || KB_ENABLE_ONEWIRE_PRESENCE) -#include "OneWire.h" -#endif - -#if KB_ENABLE_BUZZER -#include "buzzer.h" -#include "tones.h" -#endif - -#if KB_ENABLE_ID12_RFID -#include -static SoftwareSerial gSerialRfid(KB_PIN_SERIAL_RFID_RX, -1); -int gRfidPos = -1; -unsigned char gRfidChecksum = 0; -unsigned char gRfidBuf[RFID_PAYLOAD_CHARS]; -#endif - -#if KB_ENABLE_PARALLAX_RFID -#include -static SoftwareSerial gSerialRfid(KB_PIN_SERIAL_RFID_RX, KB_PIN_SERIAL_RFID_TX); -int gRfidPos = -1; -unsigned char gRfidBuf[RFID_PARALLAX_LEGACY_PAYLOAD_CHARS]; -#endif - -#if KB_ENABLE_WIEGAND_RFID -#include "Wiegand.h" -#include "PCInterrupt.h" -#endif - -// -// Other Globals -// - -// Up to 6 meters supported if using Arduino Mega -static unsigned long volatile gMeters[] = {0, 0, 0, 0, 0, 0}; -static unsigned long volatile gLastMeters[] = {0, 0, 0, 0, 0, 0}; -static uint8_t gOutputPins[] = { - KB_PIN_RELAY_A, - KB_PIN_RELAY_B, - KB_PIN_RELAY_C, - KB_PIN_RELAY_D, - KB_PIN_LED_FLOW_A, - KB_PIN_LED_FLOW_B -}; - -static KegboardPacket gInputPacket; - -// Structure that holds the state of incoming serial bytes. -typedef struct { - uint8_t header_bytes_read; - uint8_t payload_bytes_remain; - bool have_packet; -} RxPacketStat; - -static RxPacketStat gPacketStat; - -// Relay output status. -typedef struct { - bool enabled; - unsigned long touched_timestamp_ms; -} RelayOutputStat; - -static RelayOutputStat gRelayStatus[KB_NUM_RELAY_OUTPUTS]; - -// Structure to keep information about this device's uptime. -typedef struct { - unsigned long uptime_ms; - unsigned long uptime_days; - unsigned long last_uptime_ms; - unsigned long last_meter_event; - unsigned long last_heartbeat; -} UptimeStat; - -static UptimeStat gUptimeStat; - -#if KB_ENABLE_CHIP_LED -static int gChipLedBrightness = 0xff; -#endif - -static uint8_t gSerialNumber[SERIAL_NUMBER_SIZE_BYTES]; - -#if KB_ENABLE_ONEWIRE_PRESENCE -// Structure used to cache information about devices on the onewire bus. -typedef struct { - uint64_t id; - bool valid; - uint8_t present_count; -} OnewireEntry; - -static OnewireEntry gOnewireCache[ONEWIRE_CACHE_SIZE]; -#endif - -#if KB_ENABLE_SELFTEST -static unsigned long gLastTestPulseMillis = 0; -#endif - -#if KB_ENABLE_BUZZER -uint32_t BOOT_MELODY[] = { - NOTE(FREQ_C6, 75), SILENT(25), - NOTE(FREQ_C6, 75), SILENT(25), - NOTE(FREQ_C6, 75), SILENT(25), - - NOTE(FREQ_F6, 200), - MELODY_END -}; - -uint32_t PING_MELODY[] = { - NOTE(FREQ_D6, 100), SILENT(25), - NOTE(FREQ_A6, 100), SILENT(25), - MELODY_END -}; - -#if (KB_ENABLE_ID12_RFID || KB_ENABLE_ONEWIRE_PRESENCE || KB_ENABLE_PARALLAX_RFID) -uint32_t AUTH_ON_MELODY[] = { - NOTE(FREQ_A6, 50), SILENT(10), - NOTE(FREQ_D6, 50), SILENT(10), - NOTE(FREQ_F6, 50), SILENT(10), - MELODY_END -}; -#endif // KB_ENABLE_ID12_RFID || KB_ENABLE_ONEWIRE_PRESENCE || KB_ENABLE_PARALLAX_RFID -#endif // KB_ENABLE_BUZZER - -#if KB_ENABLE_ONEWIRE_THERMO -static OneWire gOnewireThermoBus(KB_PIN_ONEWIRE_THERMO); -static DS1820Sensor gThermoSensor; -#endif - -#if KB_ENABLE_ONEWIRE_PRESENCE -static OneWire gOnewireIdBus(KB_PIN_ONEWIRE_PRESENCE); -#endif - -#if KB_ENABLE_WIEGAND_RFID -#define WIEGAND_TIMEOUT_MILLIS 200 -static Wiegand gWiegand; -static unsigned long gLastWiegandInterruptMillis = 0; -#endif - -// -// ISRs -// - -#if KB_ENABLE_SOFT_DEBOUNCE -#define CHECK_METER(pin, meter_index) \ - do { \ - delayMicroseconds(KB_SOFT_DEBOUNCE_MICROS); \ - if (digitalRead(pin) == 0) \ - gMeters[meter_index] += 1; \ - } while(0) -#else -#define CHECK_METER(pin, meter_index) gMeters[meter_index] += 1 -#endif - -void meterInterruptA() -{ - CHECK_METER(KB_PIN_METER_A, 0); -} - -#ifdef KB_PIN_METER_B -void meterInterruptB() -{ - CHECK_METER(KB_PIN_METER_B, 1); -} -#endif - -#ifdef KB_PIN_METER_C -void meterInterruptC() -{ - CHECK_METER(KB_PIN_METER_C, 2); -} -#endif - -#ifdef KB_PIN_METER_D -void meterInterruptD() -{ - CHECK_METER(KB_PIN_METER_D, 3); -} -#endif - -#ifdef KB_PIN_METER_E -void meterInterruptE() -{ - CHECK_METER(KB_PIN_METER_E, 4); -} -#endif - -#ifdef KB_PIN_METER_F -void meterInterruptF() -{ - CHECK_METER(KB_PIN_METER_F, 5); -} -#endif - -#if KB_ENABLE_WIEGAND_RFID -void wiegandData0() { - gWiegand.handleData0Pulse(); - gLastWiegandInterruptMillis = millis(); -} -void wiegandData1() { - gWiegand.handleData1Pulse(); - gLastWiegandInterruptMillis = millis(); -} -#endif - -// -// Serial I/O -// - -void writeHelloPacket() -{ - int firmware_version = FIRMWARE_VERSION; - KegboardPacket packet; - packet.SetType(KBM_HELLO_ID); - packet.AddTag(KBM_HELLO_TAG_FIRMWARE_VERSION, sizeof(firmware_version), (char*)&firmware_version); - packet.AddTag(KBM_HELLO_TAG_SERIAL_NUMBER, SERIAL_NUMBER_SIZE_BYTES, (char*)gSerialNumber); - packet.AddTag(KBM_HELLO_TAG_UPTIME_MILLIS, sizeof(gUptimeStat.uptime_ms), (char*)&gUptimeStat.uptime_ms); - packet.AddTag(KBM_HELLO_TAG_UPTIME_DAYS, sizeof(gUptimeStat.uptime_days), (char*)&gUptimeStat.uptime_days); - packet.Print(); -} - -#if KB_ENABLE_ONEWIRE_THERMO -void byteToChars(uint8_t byte, char* out) { - for (int i=0; i<2; i++) { - uint8_t val = (byte >> (4*i)) & 0xf; - if (val < 10) { - out[1-i] = (char) ('0' + val); - } else if (val < 16) { - out[1-i] = (char) ('a' + (val - 10)); - } - } -} - -void writeThermoPacket(DS1820Sensor *sensor) -{ - long temp = sensor->GetTemp(); - if (temp == INVALID_TEMPERATURE_VALUE) { - return; - } - - char name[23] = "thermo-"; - char* pos = (name + 7); - for (int i=7; i>=0; i--) { - byteToChars(sensor->m_addr[i], pos); - pos += 2; - } - KegboardPacket packet; - packet.SetType(KBM_THERMO_READING); - packet.AddTag(KBM_THERMO_READING_TAG_SENSOR_NAME, 23, name); - packet.AddTag(KBM_THERMO_READING_TAG_SENSOR_READING, sizeof(temp), (char*)(&temp)); - packet.Print(); -} -#endif - -void writeRelayPacket(int channel) -{ - char name[6] = "relay"; - int status = (int) (gRelayStatus[channel].enabled); - name[5] = 0x30 + channel; - KegboardPacket packet; - packet.SetType(KBM_OUTPUT_STATUS); - packet.AddTag(KBM_OUTPUT_STATUS_TAG_OUTPUT_NAME, 6, name); - packet.AddTag(KBM_OUTPUT_STATUS_TAG_OUTPUT_READING, sizeof(status), (char*)(&status)); - packet.Print(); -} - -void writeMeterPacket(int channel) -{ - char name[5] = "flow"; - unsigned long status = gMeters[channel]; - if (status == gLastMeters[channel]) { - return; - } else { - gLastMeters[channel] = status; - } - - switch (channel) { - case 0: -#if BOARD_KBPM - RXLED1; -#else - digitalWrite(KB_PIN_LED_FLOW_A, HIGH); -#endif - break; - case 1: -#if BOARD_KBPM - TXLED1; -#else - digitalWrite(KB_PIN_LED_FLOW_B, HIGH); -#endif - break; - default: - break; - } - - name[4] = 0x30 + channel; - KegboardPacket packet; - packet.SetType(KBM_METER_STATUS); - packet.AddTag(KBM_METER_STATUS_TAG_METER_NAME, 5, name); - packet.AddTag(KBM_METER_STATUS_TAG_METER_READING, sizeof(status), (char*)(&status)); - packet.Print(); -} - -void writeAuthPacket(const char* device_name, uint8_t* token, int token_len, - char status) { - KegboardPacket packet; - packet.SetType(KBM_AUTH_TOKEN); - packet.AddTag(KBM_AUTH_TOKEN_TAG_DEVICE, strlen(device_name), device_name); - packet.AddTag(KBM_AUTH_TOKEN_TAG_TOKEN, token_len, (char*)token); - packet.AddTag(KBM_AUTH_TOKEN_TAG_STATUS, 1, &status); - packet.Print(); -#if KB_ENABLE_BUZZER - if (status == 1) { - play_notes(AUTH_ON_MELODY, KB_PIN_BUZZER); - } -#endif -} - -#if KB_ENABLE_SELFTEST -void doTestPulse() -{ - // Strobes the test pin `KB_SELFTEST_PULSES` times, every - // `KB_SELFTEST_INTERVAL_MS` milliseconds - unsigned long now = millis(); - if ((now - gLastTestPulseMillis) >= KB_SELFTEST_INTERVAL_MS) { - gLastTestPulseMillis = now; - for (int i=0; i= 0) { - analogWrite(KB_PIN_LED_CHIP, gChipLedBrightness); - } else { - analogWrite(KB_PIN_LED_CHIP, 0); - } - gChipLedBrightness -= rate; - if (gChipLedBrightness < -32) { - gChipLedBrightness = 0xff; - } -} -#endif - -// -// Main -// - -void setup() -{ - memset(&gUptimeStat, 0, sizeof(UptimeStat)); - memset(&gPacketStat, 0, sizeof(RxPacketStat)); - memset(gSerialNumber, 0, SERIAL_NUMBER_SIZE_BYTES); - - if (eeprom_is_valid()) { - eeprom_read_serialno(gSerialNumber); - } - -#if KB_ENABLE_CHIP_LED - pinMode(KB_PIN_LED_CHIP, OUTPUT); - digitalWrite(KB_PIN_LED_CHIP, HIGH); -#endif - - // Flow meter steup. Enable internal weak pullup to prevent disconnected line - // from ticking away. - pinMode(KB_PIN_METER_A, INPUT); - digitalWrite(KB_PIN_METER_A, HIGH); - attachInterrupt(0, meterInterruptA, FALLING); - -#ifdef KB_PIN_METER_B - pinMode(KB_PIN_METER_B, INPUT); - digitalWrite(KB_PIN_METER_B, HIGH); - attachInterrupt(1, meterInterruptB, FALLING); -#endif - -#ifdef KB_PIN_METER_C - pinMode(KB_PIN_METER_C, INPUT); - digitalWrite(KB_PIN_METER_C, HIGH); - attachInterrupt(2, meterInterruptC, FALLING); -#endif - -#ifdef KB_PIN_METER_D - pinMode(KB_PIN_METER_D, INPUT); - digitalWrite(KB_PIN_METER_D, HIGH); - attachInterrupt(3, meterInterruptD, FALLING); -#endif - -#ifdef KB_PIN_METER_E - pinMode(KB_PIN_METER_E, INPUT); - digitalWrite(KB_PIN_METER_E, HIGH); - attachInterrupt(4, meterInterruptE, FALLING); -#endif - -#ifdef KB_PIN_METER_F - pinMode(KB_PIN_METER_F, INPUT); - digitalWrite(KB_PIN_METER_F, HIGH); - attachInterrupt(5, meterInterruptF, FALLING); -#endif - - pinMode(KB_PIN_RELAY_A, OUTPUT); - pinMode(KB_PIN_RELAY_B, OUTPUT); - pinMode(KB_PIN_RELAY_C, OUTPUT); - pinMode(KB_PIN_RELAY_D, OUTPUT); - pinMode(KB_PIN_LED_FLOW_A, OUTPUT); - pinMode(KB_PIN_LED_FLOW_B, OUTPUT); - pinMode(KB_PIN_ALARM, OUTPUT); - pinMode(KB_PIN_TEST_PULSE, OUTPUT); - - Serial.begin(115200); - - digitalWrite(KB_PIN_LED_FLOW_A, HIGH); - digitalWrite(KB_PIN_LED_FLOW_B, HIGH); - -#if KB_ENABLE_BUZZER - pinMode(KB_PIN_BUZZER, OUTPUT); - play_notes(BOOT_MELODY, KB_PIN_BUZZER); -#endif - - digitalWrite(KB_PIN_LED_FLOW_A, LOW); - digitalWrite(KB_PIN_LED_FLOW_B, LOW); - -#if KB_ENABLE_ID12_RFID - gSerialRfid.begin(9600); - pinMode(KB_PIN_RFID_RESET, OUTPUT); - digitalWrite(KB_PIN_RFID_RESET, HIGH); -#endif - -#if KB_ENABLE_PARALLAX_RFID - gSerialRfid.begin(9600); - pinMode(KB_PIN_SERIAL_RFID_RX, INPUT); - pinMode(KB_PIN_SERIAL_RFID_TX, OUTPUT); -#endif - -#if KB_ENABLE_WIEGAND_RFID - PCattachInterrupt(KB_PIN_WIEGAND_RFID_DATA0, wiegandData0, FALLING); - PCattachInterrupt(KB_PIN_WIEGAND_RFID_DATA1, wiegandData1, FALLING); - - pinMode(KB_PIN_WIEGAND_RFID_DATA0, OUTPUT); - pinMode(KB_PIN_WIEGAND_RFID_DATA1, OUTPUT); - digitalWrite(KB_PIN_WIEGAND_RFID_DATA0, HIGH); - digitalWrite(KB_PIN_WIEGAND_RFID_DATA1, HIGH); - digitalWrite(KB_PIN_WIEGAND_RFID_DATA0, LOW); - digitalWrite(KB_PIN_WIEGAND_RFID_DATA1, LOW); - pinMode(KB_PIN_WIEGAND_RFID_DATA0, INPUT); - pinMode(KB_PIN_WIEGAND_RFID_DATA1, INPUT); - digitalWrite(KB_PIN_WIEGAND_RFID_DATA0, HIGH); - digitalWrite(KB_PIN_WIEGAND_RFID_DATA1, HIGH); - gWiegand.reset(); - gLastWiegandInterruptMillis = 0; -#endif - - writeHelloPacket(); -} - -void updateTimekeeping() { - // TODO(mikey): it would be more efficient to take control of timer0 - unsigned long now = millis(); - gUptimeStat.uptime_ms += now - gUptimeStat.last_uptime_ms; - gUptimeStat.last_uptime_ms = now; - - if (gUptimeStat.uptime_ms >= MS_PER_DAY) { - gUptimeStat.uptime_days += 1; - gUptimeStat.uptime_ms -= MS_PER_DAY; - } - - if ((now - gUptimeStat.last_heartbeat) > KB_HEARTBEAT_INTERVAL_MS) { - gUptimeStat.last_heartbeat = now; - writeHelloPacket(); - } -} - -#if KB_ENABLE_ONEWIRE_THERMO -int stepOnewireThermoBus() { - uint8_t addr[8]; - unsigned long now = millis(); - - // Are we already working on a sensor? service it, possibly emitting a a - // thermo packet. - if (gThermoSensor.Initialized() || gThermoSensor.Busy()) { - if (gThermoSensor.Update(now)) { - // Just finished conversion - writeThermoPacket(&gThermoSensor); - gThermoSensor.Reset(); - } else if (gThermoSensor.Busy()) { - // More cycles needed on this sensor - return 1; - } else { - // finished or not started - } - } - - // First time, or finished with last sensor; clean up, and look more more - // devices. - int more_search = gOnewireThermoBus.search(addr); - if (!more_search) { - // Bus exhausted; start over - gOnewireThermoBus.reset_search(); - return 0; - } - - // New sensor. Initialize and start work. - gThermoSensor.Initialize(&gOnewireThermoBus, addr); - gThermoSensor.Update(now); - return 1; -} -#endif - -#if KB_ENABLE_ONEWIRE_PRESENCE -void stepOnewireIdBus() { - uint64_t addr; - uint8_t* addr_ptr = (uint8_t*) &addr; - - // No more devices on the bus; reset the bus. - if (!gOnewireIdBus.search(addr_ptr)) { - gOnewireIdBus.reset_search(); - - for (int i=0; i < ONEWIRE_CACHE_SIZE; i++) { - OnewireEntry* entry = &gOnewireCache[i]; - if (!entry->valid) { - continue; - } - - entry->present_count -= 1; - if (entry->present_count == 0) { - entry->valid = false; - writeAuthPacket("onewire", (uint8_t*)&(entry->id), 8, 0); - } - } - return; - } - - // We found a device; check the address CRC and ignore if invalid. - if (OneWire::crc8(addr_ptr, 7) != addr_ptr[7]) { - return; - } - - // Ignore the null address. TODO(mikey): Is there a bug in OneWire.cpp that - // causes this to be reported? - if (addr == 0) { - return; - } - - // Look for id in cache. If seen last time around, mark present (and do not - // emit packet). - for (int i=0; i < ONEWIRE_CACHE_SIZE; i++) { - OnewireEntry* entry = &gOnewireCache[i]; - if (entry->valid && entry->id == addr) { - entry->present_count = ONEWIRE_CACHE_MAX_MISSING_SEARCHES; - return; - } - } - - // Add id to cache and emit presence packet. - // NOTE(mikey): If the cache is full, no packet will be emitted. This is - // probably the best behavior; removing a device from the bus will clear up an - // entry in the cache. - for (int i=0; i < ONEWIRE_CACHE_SIZE; i++) { - OnewireEntry* entry = &gOnewireCache[i]; - if (!entry->valid) { - entry->valid = true; - entry->present_count = ONEWIRE_CACHE_MAX_MISSING_SEARCHES; - entry->id = addr; - writeAuthPacket("onewire", (uint8_t*)&(entry->id), 8, 1); - return; - } - } -} -#endif - -static void readSerialBytes(char *dest_buf, int num_bytes, int offset) { - while (num_bytes-- != 0) { - dest_buf[offset++] = Serial.read(); - } -} - -#if KB_ENABLE_ID12_RFID -static void doProcessRfid() { - if (gSerialRfid.available() == 0) { - return; - } - - if (gRfidPos == -1) { - if (gSerialRfid.read() != 0x02) { - return; - } else { - gRfidPos = 0; - gRfidChecksum = 0; - } - } - - while (gRfidPos < 12) { - unsigned char b; - int rfid_index = (RFID_PAYLOAD_CHARS/2 - 1) - gRfidPos / 2; - if (gSerialRfid.available() == 0) { - return; - } - - b = gSerialRfid.read(); - if (b == CR || b == LF || b == STX || b == ETX) { - goto out_reset; - } - - // ASCII to hex - if (b >= '0' && b <= '9') { - b -= '0'; - } else if (b >= 'A' && b <= 'F') { - b -= 'A'; - b += 10; - } - - if ((gRfidPos % 2) == 0) { - // Clears previous value. - gRfidBuf[rfid_index] = b << 4; - } else { - gRfidBuf[rfid_index] |= b; - gRfidChecksum ^= gRfidBuf[rfid_index]; - } - - gRfidPos++; - } - - if (gRfidPos == RFID_PAYLOAD_CHARS) { - if (gRfidChecksum == 0) { - writeAuthPacket("core.rfid", gRfidBuf+1, 5, 1); - writeAuthPacket("core.rfid", gRfidBuf+1, 5, 0); - } - } - - digitalWrite(KB_PIN_RFID_RESET, LOW); - delay(200); - digitalWrite(KB_PIN_RFID_RESET, HIGH); - -out_reset: - gRfidPos = -1; - gRfidChecksum = 0; -} -#endif - -#if KB_ENABLE_PARALLAX_RFID && !KB_ENABLE_PARALLAX_RFID_LEGACY_TAGS -static void doProcessParallaxRfid() { - if (gSerialRfid.available() > 0) { - int errorCode; - errorCode = gSerialRfid.read(); - if (errorCode != 0x01) { - return; - } else { - int pos = 0; - while (pos < RFID_PARALLAX_PAYLOAD_CHARS) { - if (gSerialRfid.available() > 0) { - gRfidBuf[pos] = gSerialRfid.read(); - pos++; - } - } - if (pos == RFID_PARALLAX_PAYLOAD_CHARS) { - writeAuthPacket("core.rfid", gRfidBuf, 4, 1 ); - } - } - } -} - -#elif KB_ENABLE_PARALLAX_RFID && KB_ENABLE_PARALLAX_RFID_LEGACY_TAGS -static void doProcessParallaxRfid() { - if (gSerialRfid.available() > 0) { - int errorCode; - errorCode = gSerialRfid.read(); - if (errorCode != 10) { - return; - } else { - int pos = 0; - while (pos < RFID_PARALLAX_LEGACY_PAYLOAD_CHARS) { - if (gSerialRfid.available() > 0) { - gRfidBuf[pos] = gSerialRfid.read(); - if ((gRfidBuf[pos] == 10) || (gRfidBuf[pos] == 13)) { - break; - } - pos++; - } - } - if (pos == RFID_PARALLAX_LEGACY_PAYLOAD_CHARS) { - writeAuthPacket("core.rfid", gRfidBuf, 10, 1 ); - } - } - - } -} -#endif - -#if KB_ENABLE_PARALLAX_RFID -long previousTimer = 0; -static void doProcessRfid() { - long timer = millis(); - if ((timer - previousTimer) > 400) { -#if KB_ENABLE_PARALLAX_RFID_LEGACY_TAGS - gSerialRfid.print("!RW"); - gSerialRfid.write(0x0F); -#else - gSerialRfid.print("!RW"); - gSerialRfid.write(0x01); - gSerialRfid.write(byte(32)); -#endif - previousTimer = timer; - } else { - doProcessParallaxRfid(); - } -} -#endif - -void resetInputPacket() { - memset(&gPacketStat, 0, sizeof(RxPacketStat)); - gInputPacket.Reset(); -} - -void readIncomingSerialData() { - char serial_buf[KBSP_PAYLOAD_MAXLEN]; - volatile uint8_t bytes_available = Serial.available(); - - // Do not read a new packet if we have one awiting processing. This should - // never happen. - if (gPacketStat.have_packet) { - return; - } - - // Look for a new packet. - if (gPacketStat.header_bytes_read < KBSP_HEADER_PREFIX_LEN) { - while (bytes_available > 0) { - char next_char = Serial.read(); - bytes_available -= 1; - - if (next_char == KBSP_PREFIX[gPacketStat.header_bytes_read]) { - gPacketStat.header_bytes_read++; - if (gPacketStat.header_bytes_read == KBSP_HEADER_PREFIX_LEN) { - // Found start of packet, break. - break; - } - } else { - // Wrong character in prefix; reset framing. - if (next_char == KBSP_PREFIX[0]) { - gPacketStat.header_bytes_read = 1; - } else { - gPacketStat.header_bytes_read = 0; - } - } - } - } - - // Read the remainder of the header, if not yet found. - if (gPacketStat.header_bytes_read < KBSP_HEADER_LEN) { - if (bytes_available < 4) { - return; - } - gInputPacket.SetType(Serial.read() | (Serial.read() << 8)); - gPacketStat.payload_bytes_remain = Serial.read() | (Serial.read() << 8); - bytes_available -= 4; - gPacketStat.header_bytes_read += 4; - - // Check that the 'len' field is not bogus. If it is, throw out the packet - // and reset. - if (gPacketStat.payload_bytes_remain > KBSP_PAYLOAD_MAXLEN) { - goto out_reset; - } - } - - // If we haven't yet found a frame, or there are no more bytes to read after - // finding a frame, bail out. - if (bytes_available == 0 || (gPacketStat.header_bytes_read < KBSP_HEADER_LEN)) { - return; - } - - // TODO(mikey): Just read directly into KegboardPacket. - if (gPacketStat.payload_bytes_remain) { - int bytes_to_read = (gPacketStat.payload_bytes_remain >= bytes_available) ? - bytes_available : gPacketStat.payload_bytes_remain; - readSerialBytes(serial_buf, bytes_to_read, 0); - gInputPacket.AppendBytes(serial_buf, bytes_to_read); - gPacketStat.payload_bytes_remain -= bytes_to_read; - bytes_available -= bytes_to_read; - } - - // Need more payload bytes than are now available. - if (gPacketStat.payload_bytes_remain > 0) { - return; - } - - // We have a complete payload. Now grab the footer. - if (!gPacketStat.have_packet) { - if (bytes_available < KBSP_FOOTER_LEN) { - return; - } - readSerialBytes(serial_buf, KBSP_FOOTER_LEN, 0); - - // Check CRC - - // Check trailer - if (strncmp((serial_buf + 2), KBSP_TRAILER, KBSP_FOOTER_TRAILER_LEN)) { - goto out_reset; - } - gPacketStat.have_packet = true; - } - - // Done! - return; - -out_reset: - resetInputPacket(); -} - -#if KB_ENABLE_WIEGAND_RFID -void doProcessWiegand() { - if (gLastWiegandInterruptMillis == 0) { - return; - } - unsigned long now = millis(); - - if ((now - gLastWiegandInterruptMillis) > WIEGAND_TIMEOUT_MILLIS) { - uint8_t buf[WIEGAND_BUFSIZ]; - int num_bits = gWiegand.getData(buf); - if (num_bits > 0) { - writeAuthPacket("core.rfid", buf, WIEGAND_BUFSIZ, 1); - writeAuthPacket("core.rfid", buf, WIEGAND_BUFSIZ, 0); - } - gWiegand.reset(); - gLastWiegandInterruptMillis = 0; - } -} -#endif - -void setRelayOutput(uint8_t id, uint8_t mode) { - gRelayStatus[id].touched_timestamp_ms = millis(); - if (mode == OUTPUT_DISABLED && gRelayStatus[id].enabled) { - digitalWrite(gOutputPins[id], LOW); - gRelayStatus[id].enabled = false; - } else if (mode == OUTPUT_ENABLED && !gRelayStatus[id].enabled) { - digitalWrite(gOutputPins[id], HIGH); - gRelayStatus[id].enabled = true; - } else { - return; - } - writeRelayPacket(id); -} - -void handleInputPacket() { - if (!gPacketStat.have_packet) { - return; - } - - // Process the input packet. - switch (gInputPacket.GetType()) { - case KBM_PING: -#if KB_ENABLE_BUZZER - play_notes(PING_MELODY, KB_PIN_BUZZER); -#endif - writeHelloPacket(); - break; - - case KBM_SET_OUTPUT: { - uint8_t id, mode; - - if (!gInputPacket.ReadTag(KBM_SET_OUTPUT_TAG_OUTPUT_ID, &id) - || !gInputPacket.ReadTag(KBM_SET_OUTPUT_TAG_OUTPUT_MODE, &mode)) { - break; - } - - if (id < KB_NUM_RELAY_OUTPUTS) { - setRelayOutput(id, mode); - } - break; - } - - case KBM_SET_SERIAL_NUMBER: { - // Serial number can only be set if not already set. - if (eeprom_is_valid()) { - break; - } - - if (gInputPacket.FindTagLength(KBM_SET_SERIAL_NUMBER_TAG_SERIAL) >= SERIAL_NUMBER_SIZE_BYTES) { - break; - } - - memset(gSerialNumber, 0, SERIAL_NUMBER_SIZE_BYTES); - gInputPacket.CopyTagData(KBM_SET_SERIAL_NUMBER_TAG_SERIAL, gSerialNumber); - eeprom_write_serialno(gSerialNumber); - writeHelloPacket(); - - break; - } - } - resetInputPacket(); -} - -void writeMeterPackets() { - unsigned long now = millis(); - - // Forcibly coalesce meter updates; we want to be responsive, but sending - // meter updates at every opportunity would cause too many messages to be - // sent. - if ((now - gUptimeStat.last_meter_event) > KB_METER_UPDATE_INTERVAL_MS) { - gUptimeStat.last_meter_event = now; - } else { - return; - } - - writeMeterPacket(0); -#ifdef KB_PIN_METER_B - writeMeterPacket(1); -#endif -#ifdef KB_PIN_METER_C - writeMeterPacket(2); -#endif -#ifdef KB_PIN_METER_D - writeMeterPacket(3); -#endif -#ifdef KB_PIN_METER_E - writeMeterPacket(4); -#endif -#ifdef KB_PIN_METER_F - writeMeterPacket(5); -#endif -} - -void stepRelayWatchdog() { - for (int i = 0; i < KB_NUM_RELAY_OUTPUTS; i++) { - if (gRelayStatus[i].enabled == true) { - unsigned long now = millis(); - if ((now - gRelayStatus[i].touched_timestamp_ms) > KB_RELAY_WATCHDOG_MS) { - setRelayOutput(i, OUTPUT_DISABLED); - } - } - } -} - -void loop() -{ - updateTimekeeping(); - -#if KB_ENABLE_CHIP_LED - pulseChipLed(); -#endif - - readIncomingSerialData(); - handleInputPacket(); - - writeMeterPackets(); - stepRelayWatchdog(); - -#if KB_ENABLE_ONEWIRE_THERMO - stepOnewireThermoBus(); -#endif - -#if KB_ENABLE_ONEWIRE_PRESENCE - stepOnewireIdBus(); -#endif - -#if KB_ENABLE_ID12_RFID - doProcessRfid(); -#endif - -#if KB_ENABLE_PARALLAX_RFID - doProcessRfid(); -#endif - -#if KB_ENABLE_WIEGAND_RFID - doProcessWiegand(); -#endif - -#if KB_ENABLE_SELFTEST - doTestPulse(); -#endif - - digitalWrite(KB_PIN_LED_FLOW_A, LOW); - digitalWrite(KB_PIN_LED_FLOW_B, LOW); - -#if BOARD_KBPM - if (!Serial) { - RXLED0; - TXLED0; - } -#endif -} - -// vim: syntax=c diff --git a/arduino/kegboard/kegboard_config.h b/arduino/kegboard/kegboard_config.h deleted file mode 100644 index 185f188..0000000 --- a/arduino/kegboard/kegboard_config.h +++ /dev/null @@ -1,150 +0,0 @@ -// -// Feature configuration -// - -// You may enable/disable kegboard features here as desired. The deafult are -// safe. - -// Check for & report 1-wire temperature sensors? -#define KB_ENABLE_ONEWIRE_THERMO 1 - -// Check for & report 1-wire devices on the ID bus? -#define KB_ENABLE_ONEWIRE_PRESENCE 1 - -// Enable a selftest pulse? -#define KB_ENABLE_SELFTEST 1 - -// Enable buzzer? -#define KB_ENABLE_BUZZER 1 - -// Enable PARALLAX RFID? -#define KB_ENABLE_PARALLAX_RFID 0 -#define KB_ENABLE_PARALLAX_RFID_LEGACY_TAGS 0 - -// Enable ID-12 RFID? -#define KB_ENABLE_ID12_RFID 1 - -// Enable Wiegand RFID reader? -// Note: Must set KB_ENABLE_ID12_RFID to 0 if enabling this. -#define KB_ENABLE_WIEGAND_RFID 0 - -// Enable software debounce? EXPERIMENTAL. Enabling this feature may negatively -// affect pour accuracy. In particular, a delay is added to each flow meter -// ISR, disabling all other interrupts during this time. -#define KB_ENABLE_SOFT_DEBOUNCE 0 - -// Approximate minimum pulse width required for incoming external interrupts. -#define KB_SOFT_DEBOUNCE_MICROS 1200 - -// Enable chip LED? -#define KB_ENABLE_CHIP_LED 0 - -#if BOARD_KBPM -#undef KB_ENABLE_BUZZER -#undef KB_ENABLE_ID12_RFID -#undef KB_ENABLE_CHIP_LED -#undef KB_ENABLE_SELFTEST -#define KB_ENABLE_BUZZER 0 -#define KB_ENABLE_ID12_RFID 0 -#define KB_ENABLE_CHIP_LED 1 -#define KB_ENABLE_SELFTEST 0 -#endif - -// -// Pin configuration - KEGBOARD VERSION -// - -// You may change values in this section if you know what you are doing -- -// though you ordinarily shouldn't need to change these. -// -// Digital pin allocation: -// 2 - flowmeter 0 pulse (input) -// 3 - flowmeter 1 pulse (input) -// 4 - flow 0 LED (output) -// 5 - flow 1 LED (output) -// 6 - rfid (input from ID-12) -// 7 - thermo onewire bus (1-wire, input/output) -// 8 - presence onewire bus (1-wire, input/output) -// 9 - gpio pin C -// 10 - rfid reset -// 11 - buzzer (output) -// 12 - test pulse train (output) -// 13 - alarm (output) -// Analog pin allocation: -// A0 - relay 0 control (output) -// A1 - relay 1 control (output) -// A2 - relay 2 control (output) -// A3 - relay 3 control (output) -// A4 - gpio pin A -// A5 - gpio pin B -// - -#define KB_PIN_METER_A 2 -//Parallax RFID needs two IO pins, using flow meter B's by default -#if KB_ENABLE_PARALLAX_RFID -#define KB_PIN_SERIAL_RFID_TX 3 -#else -#define KB_PIN_METER_B 3 -#endif -#define KB_PIN_LED_FLOW_A 4 -#define KB_PIN_LED_FLOW_B 5 -#define KB_PIN_SERIAL_RFID_RX 6 -#define KB_PIN_ONEWIRE_THERMO 7 -#define KB_PIN_ONEWIRE_PRESENCE 8 -#define KB_PIN_LED_CHIP 9 -#define KB_PIN_RFID_RESET 10 -#define KB_PIN_BUZZER 11 -#define KB_PIN_TEST_PULSE 12 -#define KB_PIN_ALARM 13 -#define KB_PIN_RELAY_A A0 -#define KB_PIN_RELAY_B A1 -#define KB_PIN_RELAY_C A2 -#define KB_PIN_RELAY_D A3 -#define KB_PIN_GPIO_A A4 -#define KB_PIN_GPIO_B A5 - - -#define KB_PIN_MAGSTRIPE_CLOCK 3 -#define KB_PIN_MAGSTRIPE_DATA A4 -#define KB_PIN_MAGSTRIPE_CARD_PRESENT A5 - -#define KB_PIN_WIEGAND_RFID_DATA0 A4 -#define KB_PIN_WIEGAND_RFID_DATA1 A5 - -// Atmega1280 (aka Arduino mega) section -#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) -#define KB_NUM_METERS 6 -#define KB_PIN_METER_C 21 -#define KB_PIN_METER_D 20 -#define KB_PIN_METER_E 19 -#define KB_PIN_METER_F 18 -#else -#define KB_NUM_METERS 2 -#endif // defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) - -// -// Device configuration defaults -// - -#define KB_DEFAULT_BOARDNAME "kegboard" -#define KB_DEFAULT_BOARDNAME_LEN 8 // must match #chars above -#define KB_DEFAULT_BAUD_RATE 115200 - -// Size in entries of the onewire presence bus cache. This many IDs can be -// concurrently tracked on the bus. -#define ONEWIRE_CACHE_SIZE 8 - -// Number of full onewire bus searches to complete before considering a -// non-responding onewire id missing. This is used to dampen against glitches -// where a device might be absent from a search. -#define ONEWIRE_CACHE_MAX_MISSING_SEARCHES 4 - -// -// Error checking -// - -#if (KB_ENABLE_ID12_RFID + KB_ENABLE_WIEGAND_RFID + KB_ENABLE_PARALLAX_RFID) > 1 -// TODO(mikey): work around pin change interrupt sharing issues. -#error "ID12 RFID and WIEGAND RFID cannot be used together." -#error "Please disable one of them in kegboard_config.h" -#endif diff --git a/arduino/kegboard/kegboard_eeprom.cpp b/arduino/kegboard/kegboard_eeprom.cpp deleted file mode 100644 index 39f1762..0000000 --- a/arduino/kegboard/kegboard_eeprom.cpp +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright 2014 Mike Wakerly - * - * This file is part of the Kegboard package of the Kegbot project. - * For more information on Kegbot, see http://kegbot.org/ - * - * Kegbot is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * Kegbot is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Kegbot. If not, see . - */ - -#include -#include - -#include "kegboard_eeprom.h" - -int eeprom_is_valid() { - return EEPROM.read(0) == (EEP_MAGIC >> 8) && - EEPROM.read(1) == (EEP_MAGIC & 0xff); -} - -int eeprom_read_serialno(uint8_t *buf) { - if (!eeprom_is_valid()) { - return 0; - } - int i = 0; - for (int off=2; i < SERIAL_NUMBER_SIZE_BYTES - 1; i++, off++) { - buf[i] = EEPROM.read(off); - if (buf[i] == '\0') { - break; - } - } - buf[i] = '\0'; - return i - 1; -} - -void eeprom_write_serialno(uint8_t *serialno) { - EEPROM.write(0, '\0'); - EEPROM.write(1, '\0'); - for (int i=0, off=2; i < SERIAL_NUMBER_SIZE_BYTES; i++, off++) { - EEPROM.write(off, serialno[i]); - } - EEPROM.write(0, EEP_MAGIC >> 8); - EEPROM.write(1, EEP_MAGIC & 0xff); -} diff --git a/arduino/kegboard/kegboard_eeprom.h b/arduino/kegboard/kegboard_eeprom.h deleted file mode 100644 index 5642e0c..0000000 --- a/arduino/kegboard/kegboard_eeprom.h +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright 2014 Mike Wakerly - * - * This file is part of the Kegboard package of the Kegbot project. - * For more information on Kegbot, see http://kegbot.org/ - * - * Kegbot is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * Kegbot is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Kegbot. If not, see . - */ - - // Kegboard EEPROM constants. - -// Memory Layout -// -// BYTES DESCRIPTION -// ----------- -------------------------------------------------------- -// 0-1 EEP_MAGIC flag (2 bytes); indicates programmed EEPROM. -// 2-31 Board serial number (8 bytes). -// 31-Max Reserved. - -#ifndef KEGBOARD_EEPROM_H_ -#define KEGBOARD_EEPROM_H_ - -#include - -#define EEP_MAGIC 0x4a1e -#define SERIAL_NUMBER_SIZE_BYTES 30 - -int eeprom_is_valid(); -int eeprom_read_serialno(uint8_t *buf); -void eeprom_write_serialno(uint8_t *serialno); - -#endif // KEGBOARD_EEPROM_H_ \ No newline at end of file diff --git a/arduino/kegboard/tones.h b/arduino/kegboard/tones.h deleted file mode 100644 index ad85424..0000000 --- a/arduino/kegboard/tones.h +++ /dev/null @@ -1,92 +0,0 @@ -// Buzzer note frequencies. -// Source: http://arduino.cc/en/Tutorial/tone - -#define FREQ_B0 31 -#define FREQ_C1 33 -#define FREQ_CS1 35 -#define FREQ_D1 37 -#define FREQ_DS1 39 -#define FREQ_E1 41 -#define FREQ_F1 44 -#define FREQ_FS1 46 -#define FREQ_G1 49 -#define FREQ_GS1 52 -#define FREQ_A1 55 -#define FREQ_AS1 58 -#define FREQ_B1 62 -#define FREQ_C2 65 -#define FREQ_CS2 69 -#define FREQ_D2 73 -#define FREQ_DS2 78 -#define FREQ_E2 82 -#define FREQ_F2 87 -#define FREQ_FS2 93 -#define FREQ_G2 98 -#define FREQ_GS2 104 -#define FREQ_A2 110 -#define FREQ_AS2 117 -#define FREQ_B2 123 -#define FREQ_C3 131 -#define FREQ_CS3 139 -#define FREQ_D3 147 -#define FREQ_DS3 156 -#define FREQ_E3 165 -#define FREQ_F3 175 -#define FREQ_FS3 185 -#define FREQ_G3 196 -#define FREQ_GS3 208 -#define FREQ_A3 220 -#define FREQ_AS3 233 -#define FREQ_B3 247 -#define FREQ_C4 262 -#define FREQ_CS4 277 -#define FREQ_D4 294 -#define FREQ_DS4 311 -#define FREQ_E4 330 -#define FREQ_F4 349 -#define FREQ_FS4 370 -#define FREQ_G4 392 -#define FREQ_GS4 415 -#define FREQ_A4 440 -#define FREQ_AS4 466 -#define FREQ_B4 494 -#define FREQ_C5 523 -#define FREQ_CS5 554 -#define FREQ_D5 587 -#define FREQ_DS5 622 -#define FREQ_E5 659 -#define FREQ_F5 698 -#define FREQ_FS5 740 -#define FREQ_G5 784 -#define FREQ_GS5 831 -#define FREQ_A5 880 -#define FREQ_AS5 932 -#define FREQ_B5 988 -#define FREQ_C6 1047 -#define FREQ_CS6 1109 -#define FREQ_D6 1175 -#define FREQ_DS6 1245 -#define FREQ_E6 1319 -#define FREQ_F6 1397 -#define FREQ_FS6 1480 -#define FREQ_G6 1568 -#define FREQ_GS6 1661 -#define FREQ_A6 1760 -#define FREQ_AS6 1865 -#define FREQ_B6 1976 -#define FREQ_C7 2093 -#define FREQ_CS7 2217 -#define FREQ_D7 2349 -#define FREQ_DS7 2489 -#define FREQ_E7 2637 -#define FREQ_F7 2794 -#define FREQ_FS7 2960 -#define FREQ_G7 3136 -#define FREQ_GS7 3322 -#define FREQ_A7 3520 -#define FREQ_AS7 3729 -#define FREQ_B7 3951 -#define FREQ_C8 4186 -#define FREQ_CS8 4435 -#define FREQ_D8 4699 -#define FREQ_DS8 4978 diff --git a/arduino/kegboard/version.h b/arduino/kegboard/version.h deleted file mode 100644 index 831f2d3..0000000 --- a/arduino/kegboard/version.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -// Version of the kegboard firmware. This is bumped whenever there's a -// significant new feature in the firmware. - -#define FIRMWARE_VERSION 18 diff --git a/components/kegboard/CORE.md b/components/kegboard/CORE.md new file mode 100644 index 0000000..d1ceeb7 --- /dev/null +++ b/components/kegboard/CORE.md @@ -0,0 +1,41 @@ +# Kegboard core + +The files listed below are the **framework-agnostic core**: the logic that is +genuinely kegboard-specific rather than ESPHome-specific. + +- `pour_session.h` / `.cpp` — pour detection state machine +- `tick_series.h` / `.cpp` — bounded `:` diagnostic series +- `kegbot_request.h` / `.cpp` — Kegbot Server API request construction +- `ring_queue.h` — bounded FIFO for offline report buffering + +## Rules + +1. **No framework headers.** Core files must not include anything from + `esphome/`, Arduino, or ESP-IDF. Only the C++ standard library. CI enforces + this (`script/check-core-purity.py`). +2. **No I/O, no clocks, no timers.** Time is passed in as arguments. This is + what lets the whole state machine run under host unit tests. +3. **Every core file has host tests** in `tests/core/`, run with plain `g++` + on every push. No hardware, no toolchain download. + +## Why + +Choosing ESPHome buys an enormous amount of infrastructure, but it is a +third-party framework that makes breaking changes to its external-component +API on a regular cadence. Keeping the kegboard-specific logic behind this +boundary means the ESPHome components stay thin adapters: if ESPHome ever +becomes untenable, porting to bare ESP-IDF is an adapter rewrite rather than a +firmware rewrite. + +It also has an immediate payoff, which is the real reason to bother: pour +detection, calibration, and the queue-and-retry path are the parts most likely +to have subtle bugs, and this is what makes them testable in milliseconds +without flashing a board. + +## Note on layout + +These files sit directly in `components/kegboard/` rather than a `core/` +subdirectory because ESPHome only copies source files found at the top level +of an external component — `ComponentManifest.resources` descends into +subdirectories only for manifests constructed with `recursive_sources=True`, +which is reserved for ESPHome's own internal components. diff --git a/components/kegboard/kegbot_request.cpp b/components/kegboard/kegbot_request.cpp new file mode 100644 index 0000000..2101269 --- /dev/null +++ b/components/kegboard/kegbot_request.cpp @@ -0,0 +1,121 @@ +#include "kegbot_request.h" + +#include +#include + +namespace kegboard { + +namespace { + +bool ends_with(const std::string &s, const std::string &suffix) { + return s.size() >= suffix.size() && s.compare(s.size() - suffix.size(), suffix.size(), suffix) == 0; +} + +void append_field(std::string *body, const char *key, const std::string &value) { + if (!body->empty()) + *body += '&'; + *body += key; + *body += '='; + *body += url_encode(value); +} + +void append_field(std::string *body, const char *key, uint32_t value) { + append_field(body, key, std::to_string(value)); +} + +} // namespace + +std::string url_encode(const std::string &value) { + static const char HEX[] = "0123456789ABCDEF"; + std::string out; + out.reserve(value.size()); + for (unsigned char c : value) { + if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || + c == '.' || c == '~') { + out += static_cast(c); + } else { + out += '%'; + out += HEX[c >> 4]; + out += HEX[c & 0x0f]; + } + } + return out; +} + +std::string format_float(float value, int decimals) { + if (std::isnan(value) || std::isinf(value)) + return "0"; + char buf[32]; + int n = snprintf(buf, sizeof(buf), "%.*f", decimals, static_cast(value)); + if (n < 0) + return "0"; + return std::string(buf, static_cast(n) < sizeof(buf) ? n : sizeof(buf) - 1); +} + +void KegbotRequestBuilder::set_base_url(const std::string &base_url) { + std::string url = base_url; + while (!url.empty() && url.back() == '/') + url.pop_back(); + // Accept a URL that already points at the API root, so users who paste + // either form from the Kegbot admin page get a working config. + if (ends_with(url, "/api")) + url.resize(url.size() - 4); + this->base_url_ = url; +} + +HttpCall KegbotRequestBuilder::drink_post(const DrinkReport &report, uint32_t now_unix, uint32_t now_uptime_s) const { + HttpCall call; + call.method = "POST"; + call.url = this->base_url_ + "/api/taps/" + url_encode(report.meter_name); + + append_field(&call.body, "ticks", report.ticks); + append_field(&call.body, "duration", report.duration_s); + + if (this->send_volume_) + append_field(&call.body, "volume_ml", format_float(report.volume_ml, 3)); + + // Only the difference between these two is used by the server, so send a + // consistent pair: wall time if the pour was stamped with a synced clock, + // monotonic uptime otherwise. + if (report.pour_time_unix != 0 && now_unix != 0) { + append_field(&call.body, "pour_time", report.pour_time_unix); + append_field(&call.body, "now", now_unix); + } else { + append_field(&call.body, "pour_time", report.pour_uptime_s); + append_field(&call.body, "now", now_uptime_s); + } + + if (!report.username.empty()) + append_field(&call.body, "username", report.username); + if (!report.tick_time_series.empty()) + append_field(&call.body, "tick_time_series", report.tick_time_series); + + return call; +} + +HttpCall KegbotRequestBuilder::thermo_post(const ThermoReport &report, uint32_t now_unix, uint32_t now_uptime_s) const { + HttpCall call; + call.method = "POST"; + call.url = this->base_url_ + "/api/thermo-sensors/" + url_encode(report.sensor_name); + + append_field(&call.body, "temp_c", format_float(report.temp_c, 3)); + + if (report.when_unix != 0 && now_unix != 0) { + append_field(&call.body, "when", report.when_unix); + append_field(&call.body, "now", now_unix); + } else { + append_field(&call.body, "when", report.when_uptime_s); + append_field(&call.body, "now", now_uptime_s); + } + + return call; +} + +HttpCall KegbotRequestBuilder::auth_token_get(const std::string &device, const std::string &token) const { + HttpCall call; + call.method = "GET"; + call.url = this->base_url_ + "/api/auth-tokens/" + url_encode(device) + "/" + url_encode(token); + return call; +} + +} // namespace kegboard diff --git a/components/kegboard/kegbot_request.h b/components/kegboard/kegbot_request.h new file mode 100644 index 0000000..97984d6 --- /dev/null +++ b/components/kegboard/kegbot_request.h @@ -0,0 +1,102 @@ +#pragma once + +// Kegbot Server request construction. +// +// Part of the framework-agnostic kegboard core: no ESPHome, Arduino, or +// ESP-IDF headers. See CORE.md. + +#include +#include + +namespace kegboard { + +/// A prepared HTTP call, ready for whatever transport the platform layer uses. +struct HttpCall { + std::string method; + std::string url; + /// Form-encoded (application/x-www-form-urlencoded) request body. Empty for + /// GET requests. + std::string body; +}; + +/// A finished pour, queued for delivery to Kegbot Server. +/// +/// Two clocks are carried deliberately. `pour_time_unix` is the wall time the +/// pour started, which is only meaningful if NTP had synced by then; +/// `pour_uptime_s` is monotonic seconds since boot, which is always valid. +/// See KegbotRequestBuilder::drink_post() for how they are used. +struct DrinkReport { + std::string meter_name; + uint32_t ticks{0}; + /// Volume in mL. Only sent when the reporter is configured to override the + /// server's own per-meter calibration; see `send_volume` on the builder. + float volume_ml{0.0f}; + uint32_t duration_s{0}; + uint32_t pour_time_unix{0}; + uint32_t pour_uptime_s{0}; + /// Username to attribute the pour to. Empty attributes it to the guest user. + std::string username; + /// Kegbot `:` series; empty to omit. + std::string tick_time_series; +}; + +/// A temperature reading queued for delivery. +struct ThermoReport { + std::string sensor_name; + float temp_c{0.0f}; + uint32_t when_unix{0}; + uint32_t when_uptime_s{0}; +}; + +/// Builds Kegbot Server API calls from reports. +/// +/// Stateless apart from the base URL and a couple of policy flags, so it is +/// straightforward to unit test the exact bytes we put on the wire. +class KegbotRequestBuilder { + public: + /// Set the server root, e.g. "https://kegbot.example.com". A trailing slash + /// and a trailing "/api" are both tolerated and normalized away. + void set_base_url(const std::string &base_url); + const std::string &base_url() const { return base_url_; } + + /// When true, include `volume_ml` in drink posts, overriding the server's + /// per-meter calibration. Off by default: Kegbot Server already stores + /// ml_per_tick per meter and exposes a calibration UI, and having two + /// sources of truth for volume is a reliable way to produce confusing data. + void set_send_volume(bool send_volume) { send_volume_ = send_volume; } + bool send_volume() const { return send_volume_; } + + /// POST a finished pour to /api/taps/. + /// + /// Kegbot Server reconstructs the pour time as + /// `server_now - (now - pour_time)`, so only the *difference* between the + /// two timestamps matters. That lets a queued pour be delivered long after + /// the fact with a correct timestamp, and it lets a device whose clock never + /// synced report accurately: when no wall time is available we send the + /// monotonic uptime pair instead, whose difference is equally valid. + /// + /// @param now_unix Current wall time, or 0 if the clock is not synced. + /// @param now_uptime_s Current monotonic seconds since boot. + HttpCall drink_post(const DrinkReport &report, uint32_t now_unix, uint32_t now_uptime_s) const; + + /// POST a reading to /api/thermo-sensors/. Uses the same + /// two-clock scheme as drink_post(). + HttpCall thermo_post(const ThermoReport &report, uint32_t now_unix, uint32_t now_uptime_s) const; + + /// GET /api/auth-tokens//, used to resolve a scanned token + /// to a Kegbot user. + HttpCall auth_token_get(const std::string &device, const std::string &token) const; + + private: + std::string base_url_; + bool send_volume_{false}; +}; + +/// Percent-encode a string for use in a URL path segment or form body. +std::string url_encode(const std::string &value); + +/// Format a float with the given number of decimal places, without pulling in +/// iostreams. Used for volume and temperature fields. +std::string format_float(float value, int decimals); + +} // namespace kegboard diff --git a/components/kegboard/pour_session.cpp b/components/kegboard/pour_session.cpp new file mode 100644 index 0000000..89c3f53 --- /dev/null +++ b/components/kegboard/pour_session.cpp @@ -0,0 +1,78 @@ +#include "pour_session.h" + +namespace kegboard { + +bool PourSession::add_ticks(uint32_t ticks, uint32_t now_ms, uint32_t now_unix) { + if (ticks == 0) + return false; + + this->total_ticks_ += ticks; + + bool started = false; + if (!this->pouring_) { + this->pouring_ = true; + this->session_ticks_ = 0; + this->start_ms_ = now_ms; + this->start_unix_ = now_unix; + this->series_.reset(this->series_resolution_ms_); + started = true; + } + + this->session_ticks_ += ticks; + this->last_tick_ms_ = now_ms; + this->series_.add(now_ms - this->start_ms_, ticks); + + return started; +} + +bool PourSession::poll(uint32_t now_ms, PourRecord *out) { + if (!this->pouring_) + return false; + + // Unsigned subtraction here is deliberate: it stays correct across the + // 32-bit millisecond rollover at ~49.7 days of uptime. + bool idle = (now_ms - this->last_tick_ms_) >= this->config_.idle_timeout_ms; + bool too_long = this->config_.max_duration_ms != 0 && (now_ms - this->start_ms_) >= this->config_.max_duration_ms; + + if (!idle && !too_long) + return false; + + return this->finish_(now_ms, out); +} + +bool PourSession::end_now(uint32_t now_ms, PourRecord *out) { + if (!this->pouring_) + return false; + return this->finish_(now_ms, out); +} + +bool PourSession::finish_(uint32_t now_ms, PourRecord *out) { + (void) now_ms; + + uint32_t ticks = this->session_ticks_; + + this->pouring_ = false; + this->session_ticks_ = 0; + + // Below the threshold this was a drip, a bump, or line noise. Drop it + // rather than reporting a pour nobody made. + if (ticks < this->config_.min_pour_ticks) { + this->series_.reset(this->series_resolution_ms_); + return false; + } + + if (out != nullptr) { + out->ticks = ticks; + out->volume_ml = ticks * this->config_.ml_per_tick; + out->start_unix = this->start_unix_; + // Measure to the last tick, not to the moment we noticed the pour ended, + // so the idle timeout is not counted as pour duration. + out->duration_ms = this->last_tick_ms_ - this->start_ms_; + out->series = this->series_; + } + + this->series_.reset(this->series_resolution_ms_); + return true; +} + +} // namespace kegboard diff --git a/components/kegboard/pour_session.h b/components/kegboard/pour_session.h new file mode 100644 index 0000000..a2a560a --- /dev/null +++ b/components/kegboard/pour_session.h @@ -0,0 +1,117 @@ +#pragma once + +// Pour session state machine. +// +// This file is part of the framework-agnostic kegboard core: it must not +// include any ESPHome (or Arduino, or ESP-IDF) headers, and must remain +// compilable and testable on a host with plain g++. See CORE.md. + +#include + +#include "tick_series.h" + +namespace kegboard { + +/// A pour that has ended and is ready to be reported. +struct PourRecord { + /// Ticks accumulated over the whole pour. + uint32_t ticks{0}; + /// Volume in milliliters, ticks * ml_per_tick at the time the pour ended. + float volume_ml{0.0f}; + /// Wall time the pour started, as a unix timestamp. Zero if the clock was + /// not synchronized when the pour began; consumers must handle that. + uint32_t start_unix{0}; + /// Duration from first to last tick, in milliseconds. + uint32_t duration_ms{0}; + /// Per-interval tick counts, oldest first. May be empty if the series was + /// disabled or overflowed. + TickSeries series; +}; + +/// Tunables for a single meter's pour detection. Times are milliseconds. +struct PourConfig { + /// A pour ends once this long passes with no ticks. + uint32_t idle_timeout_ms{10000}; + /// Pours shorter than this many ticks are discarded as drips or noise. + uint32_t min_pour_ticks{3}; + /// A pour is force-ended after this long, to bound a stuck or free-running + /// meter. Zero disables the cutoff. + uint32_t max_duration_ms{300000}; + /// Milliliters per tick. The SwissFlow SF800 default is ~0.185 (5.4 + /// ticks/mL); this is the value users calibrate. + float ml_per_tick{0.185f}; +}; + +/// Tracks one meter's transition between idle and pouring. +/// +/// The caller feeds it ticks (from an ISR, batched) and a monotonic clock, and +/// it reports when a pour begins and when one has ended. It owns no I/O and no +/// timers, which is what makes it testable on a host. +/// +/// All times passed in are monotonic milliseconds since boot. Wall-clock unix +/// times are passed separately and only used to stamp the resulting record, +/// because a device may pour before its clock is ever synchronized. +class PourSession { + public: + explicit PourSession(const PourConfig &config) : config_(config) {} + + const PourConfig &config() const { return config_; } + void set_config(const PourConfig &config) { config_ = config; } + void set_ml_per_tick(float ml_per_tick) { config_.ml_per_tick = ml_per_tick; } + + /// True while a pour is in progress. + bool is_pouring() const { return pouring_; } + + /// Ticks accumulated in the current pour. Zero when idle. + uint32_t session_ticks() const { return pouring_ ? session_ticks_ : 0; } + + /// Volume of the current pour in mL. Zero when idle. + float session_volume_ml() const { return session_ticks() * config_.ml_per_tick; } + + /// Lifetime tick count, which survives pour boundaries and never resets + /// except via reset_total(). + uint32_t total_ticks() const { return total_ticks_; } + void reset_total() { total_ticks_ = 0; } + + /// Feed ticks observed since the last call. + /// + /// @param ticks Ticks counted since the previous call; may be zero. + /// @param now_ms Monotonic milliseconds since boot. + /// @param now_unix Current unix time, or 0 if the clock is not synced. + /// @return true if this call started a new pour. + bool add_ticks(uint32_t ticks, uint32_t now_ms, uint32_t now_unix); + + /// Advance time without adding ticks, ending the pour if it has gone idle + /// or hit the duration cutoff. + /// + /// @param out Receives the finished pour when this returns true. + /// @return true if a pour ended on this call and passed min_pour_ticks. + /// A pour that ends below the threshold is discarded silently and + /// this returns false, so callers cannot mistake a drip for a pour. + bool poll(uint32_t now_ms, PourRecord *out); + + /// Force the current pour to end, e.g. because the tap was locked out. + /// Follows the same min_pour_ticks rule as poll(). + bool end_now(uint32_t now_ms, PourRecord *out); + + /// Configure the tick time series recorded during a pour. A resolution of + /// zero disables recording. + void set_series_resolution_ms(uint32_t resolution_ms) { series_resolution_ms_ = resolution_ms; } + + private: + bool finish_(uint32_t now_ms, PourRecord *out); + + PourConfig config_; + + bool pouring_{false}; + uint32_t session_ticks_{0}; + uint32_t total_ticks_{0}; + uint32_t start_ms_{0}; + uint32_t start_unix_{0}; + uint32_t last_tick_ms_{0}; + + uint32_t series_resolution_ms_{TickSeries::DEFAULT_RESOLUTION_MS}; + TickSeries series_; +}; + +} // namespace kegboard diff --git a/components/kegboard/ring_queue.h b/components/kegboard/ring_queue.h new file mode 100644 index 0000000..4265bb1 --- /dev/null +++ b/components/kegboard/ring_queue.h @@ -0,0 +1,78 @@ +#pragma once + +// Fixed-capacity FIFO. +// +// Part of the framework-agnostic kegboard core: no ESPHome, Arduino, or +// ESP-IDF headers. See CORE.md. + +#include +#include + +namespace kegboard { + +/// A bounded FIFO backed by a fixed array, for buffering reports that could +/// not be delivered yet. +/// +/// Capacity is fixed at compile time so the memory cost of an outage is known +/// up front and a long one cannot exhaust the heap. When the queue is full, +/// push() evicts the oldest entry and increments dropped(); callers are +/// expected to surface that counter, because it means data loss. +/// +/// Evicting the oldest rather than rejecting the newest is a deliberate +/// choice: during a prolonged outage the newest reports are the ones a user is +/// most likely to be watching for, and an unbounded backlog of stale pours is +/// not worth the pours being poured right now. +template class RingQueue { + public: + static constexpr size_t capacity() { return N; } + + size_t size() const { return this->size_; } + bool empty() const { return this->size_ == 0; } + bool full() const { return this->size_ == N; } + + /// Number of entries discarded because the queue was full. + uint32_t dropped() const { return this->dropped_; } + + /// Append an item. Returns false if an older item had to be evicted. + bool push(const T &item) { + bool evicted = false; + if (this->size_ == N) { + this->head_ = (this->head_ + 1) % N; + this->size_--; + this->dropped_++; + evicted = true; + } + size_t tail = (this->head_ + this->size_) % N; + this->items_[tail] = item; + this->size_++; + return !evicted; + } + + /// Oldest item, or nullptr when empty. Valid until the next mutation. + const T *peek() const { return this->size_ == 0 ? nullptr : &this->items_[this->head_]; } + + /// Remove the oldest item, optionally copying it to `out`. + bool pop(T *out = nullptr) { + if (this->size_ == 0) + return false; + if (out != nullptr) + *out = this->items_[this->head_]; + this->items_[this->head_] = T{}; + this->head_ = (this->head_ + 1) % N; + this->size_--; + return true; + } + + void clear() { + while (this->pop()) { + } + } + + private: + T items_[N]{}; + size_t head_{0}; + size_t size_{0}; + uint32_t dropped_{0}; +}; + +} // namespace kegboard diff --git a/components/kegboard/tick_series.cpp b/components/kegboard/tick_series.cpp new file mode 100644 index 0000000..9c1c362 --- /dev/null +++ b/components/kegboard/tick_series.cpp @@ -0,0 +1,90 @@ +#include "tick_series.h" + +namespace kegboard { + +void TickSeries::reset(uint32_t resolution_ms) { + this->count_ = 0; + this->resolution_ms_ = resolution_ms; + this->coarsened_ = false; +} + +void TickSeries::add(uint32_t offset_ms, uint32_t ticks) { + if (ticks == 0 || !this->enabled()) + return; + + // Snap to the start of the bucket this offset falls in, so repeated updates + // within one interval accumulate into a single entry. + uint32_t bucket = (offset_ms / this->resolution_ms_) * this->resolution_ms_; + + if (this->count_ > 0 && this->buckets_[this->count_ - 1].offset_ms == bucket) { + this->buckets_[this->count_ - 1].ticks += ticks; + return; + } + + if (this->count_ == CAPACITY) { + this->coarsen_(); + // Recompute against the new, wider resolution and retry the merge, since + // this offset may now belong to the last bucket. + bucket = (offset_ms / this->resolution_ms_) * this->resolution_ms_; + if (this->count_ > 0 && this->buckets_[this->count_ - 1].offset_ms == bucket) { + this->buckets_[this->count_ - 1].ticks += ticks; + return; + } + } + + this->buckets_[this->count_].offset_ms = bucket; + this->buckets_[this->count_].ticks = ticks; + this->count_++; +} + +void TickSeries::coarsen_() { + // Merge buckets pairwise: [0,1] -> 0, [2,3] -> 1, ... Each merged bucket + // keeps the earlier offset and the summed tick count, so no ticks are lost. + size_t out = 0; + for (size_t in = 0; in < this->count_; in += 2) { + uint32_t ticks = this->buckets_[in].ticks; + if (in + 1 < this->count_) + ticks += this->buckets_[in + 1].ticks; + this->buckets_[out].offset_ms = this->buckets_[in].offset_ms; + this->buckets_[out].ticks = ticks; + out++; + } + this->count_ = out; + this->resolution_ms_ *= 2; + this->coarsened_ = true; + + // Merging by position can leave two entries sharing a bucket once the + // resolution widens; collapse any such neighbours so offsets stay unique + // and strictly increasing. + size_t write = 0; + for (size_t read = 0; read < this->count_; read++) { + uint32_t bucket = (this->buckets_[read].offset_ms / this->resolution_ms_) * this->resolution_ms_; + if (write > 0 && this->buckets_[write - 1].offset_ms == bucket) { + this->buckets_[write - 1].ticks += this->buckets_[read].ticks; + continue; + } + this->buckets_[write].offset_ms = bucket; + this->buckets_[write].ticks = this->buckets_[read].ticks; + write++; + } + this->count_ = write; +} + +std::string TickSeries::to_string() const { + std::string out; + if (this->count_ == 0) + return out; + + // Rough reservation: offsets and counts are both short decimal strings. + out.reserve(this->count_ * 12); + for (size_t i = 0; i < this->count_; i++) { + if (i != 0) + out += ' '; + out += std::to_string(this->buckets_[i].offset_ms); + out += ':'; + out += std::to_string(this->buckets_[i].ticks); + } + return out; +} + +} // namespace kegboard diff --git a/components/kegboard/tick_series.h b/components/kegboard/tick_series.h new file mode 100644 index 0000000..0188d88 --- /dev/null +++ b/components/kegboard/tick_series.h @@ -0,0 +1,73 @@ +#pragma once + +// Bounded tick time series. +// +// Part of the framework-agnostic kegboard core: no ESPHome, Arduino, or +// ESP-IDF headers. See CORE.md. + +#include +#include +#include + +namespace kegboard { + +/// A bounded record of when ticks arrived during a pour. +/// +/// Kegbot Server accepts this as a Drink's `tick_time_series`, a +/// space-separated sequence of `:` pairs. It is diagnostic +/// data only, so this class prioritizes bounded memory over fidelity: ticks +/// are bucketed at a configurable resolution, and when the buffer fills, the +/// series is coarsened in place (adjacent buckets merged, resolution doubled) +/// rather than truncated. A long pour therefore stays fully represented, at +/// progressively lower time resolution. +class TickSeries { + public: + /// Maximum number of buckets held. At 8 bytes per bucket this caps the + /// series at a few hundred bytes per in-flight pour. + static constexpr size_t CAPACITY = 64; + + /// Default bucketing resolution, matching the legacy AVR firmware's + /// KB_METER_UPDATE_INTERVAL_MS. + static constexpr uint32_t DEFAULT_RESOLUTION_MS = 100; + + struct Bucket { + uint32_t offset_ms; + uint32_t ticks; + }; + + /// Discard all data and begin a new series at the given resolution. + /// A resolution of zero disables recording entirely. + void reset(uint32_t resolution_ms = DEFAULT_RESOLUTION_MS); + + /// Record ticks observed at `offset_ms` after the start of the pour. + void add(uint32_t offset_ms, uint32_t ticks); + + /// Serialize to Kegbot's `:` wire format. Returns an empty + /// string when the series is empty or recording is disabled. + std::string to_string() const; + + size_t size() const { return count_; } + bool empty() const { return count_ == 0; } + bool enabled() const { return resolution_ms_ != 0; } + + /// Current bucket width. Grows past the configured value if the series had + /// to be coarsened to fit. + uint32_t resolution_ms() const { return resolution_ms_; } + + /// True if the series was coarsened at least once, i.e. its resolution is + /// no longer the one it started with. + bool coarsened() const { return coarsened_; } + + const Bucket &operator[](size_t i) const { return buckets_[i]; } + + private: + /// Halve the bucket count by merging adjacent pairs, doubling resolution. + void coarsen_(); + + Bucket buckets_[CAPACITY]{}; + size_t count_{0}; + uint32_t resolution_ms_{DEFAULT_RESOLUTION_MS}; + bool coarsened_{false}; +}; + +} // namespace kegboard diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index 560b51e..0000000 --- a/docs/Makefile +++ /dev/null @@ -1,153 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = build - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - -rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Kegboard.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Kegboard.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/Kegboard" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Kegboard" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst deleted file mode 100644 index 9acdb79..0000000 --- a/docs/source/changelog.rst +++ /dev/null @@ -1,78 +0,0 @@ -.. _kegboard-changelog: - -Changelog -========= - -Arduino Firmware ------------------ - -v18 (2014-05-14) -^^^^^^^^^^^^^^^^ -* Added heartbeat: device will send a Hello message every 10 seconds. -* Hello message now includes uptime information. - -v17 (2014-02-12) -^^^^^^^^^^^^^^^^ -* Fixed a bug that broke serial communication (introduced in v16). - -v16 (2014-02-07) -^^^^^^^^^^^^^^^^ -* Added chip LED support for Kegboard Pro Mini. - -v15 (2014-01-16) -^^^^^^^^^^^^^^^^ -* Added `set_serial_number` command. - -v14 (2013-07-23) -^^^^^^^^^^^^^^^^ -* Flow LEDs are now toggled on system startup and during pours. -* Experimental debounce feature. -* Support for Parallax RFID readers. - -v13 (2012-10-28) -^^^^^^^^^^^^^^^^ -* Adds support for Wiegand RFID readers (HID ProxPro and similar). - -v12 (2012-07-07) -^^^^^^^^^^^^^^^^ -* Respond to ping with a short melody. - -v11 (2012-05-02) -^^^^^^^^^^^^^^^^ -* Updates for Arduino SDK v1.0; no functional changes. - -v10 (2011-06-19) -^^^^^^^^^^^^^^^^ -* Reverse ID-12 RFID endianness. - -v9 (2011-06-13) -^^^^^^^^^^^^^^^ -* Support ID-12 RFID input - -v8 (2011-06-11) -^^^^^^^^^^^^^^^ -* Expand 'set_output' to support onboard kegboard relay's, flow led's - -v7 (2011-03-16) -^^^^^^^^^^^^^^^ -* Added implementation of `set_output` command, relay output watchdog. - -v6 (2010-09-22) -^^^^^^^^^^^^^^^ -* Added auth_token message. - -v5 (2010-01-10) -^^^^^^^^^^^^^^^ -* Fix issue that caused flow events to be reported too frequently. - -v4 (2010-01-04) -^^^^^^^^^^^^^^^ -* Initial documented version. - -Support Library ---------------- - -v1.0.0 (2012-07-01) -^^^^^^^^^^^^^^^^^^^ -* Support library added to kegboard repository. -* Previous versions were located in the old master Kegbot repository: https://github.com/Kegbot/kegbot diff --git a/docs/source/conf.py b/docs/source/conf.py deleted file mode 100644 index f0c516e..0000000 --- a/docs/source/conf.py +++ /dev/null @@ -1,289 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Kegboard documentation build configuration file, created by -# sphinx-quickstart on Wed Jul 18 20:35:24 2012. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys, os - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) - -# -- General configuration ----------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'Kegboard' -copyright = u'2012, mike wakerly' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '0.0' -# The full version, including alpha/beta/rc tags. -release = '0.0' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = [] - -# The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - - -# -- Options for HTML output --------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -html_theme = 'default' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'Kegboarddoc' - - -# -- Options for LaTeX output -------------------------------------------------- - -latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [ - ('index', 'Kegboard.tex', u'Kegboard Documentation', - u'mike wakerly', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output -------------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'kegboard', u'Kegboard Documentation', - [u'mike wakerly'], 1) -] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------------ - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ('index', 'Kegboard', u'Kegboard Documentation', - u'mike wakerly', 'Kegboard', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' - - -# -- Options for Epub output --------------------------------------------------- - -# Bibliographic Dublin Core info. -epub_title = u'Kegboard' -epub_author = u'mike wakerly' -epub_publisher = u'mike wakerly' -epub_copyright = u'2012, mike wakerly' - -# The language of the text. It defaults to the language option -# or en if the language is not set. -#epub_language = '' - -# The scheme of the identifier. Typical schemes are ISBN or URL. -#epub_scheme = '' - -# The unique identifier of the text. This can be a ISBN number -# or the project homepage. -#epub_identifier = '' - -# A unique identification for the text. -#epub_uid = '' - -# A tuple containing the cover image and cover page html template filenames. -#epub_cover = () - -# HTML files that should be inserted before the pages created by sphinx. -# The format is a list of tuples containing the path and title. -#epub_pre_files = [] - -# HTML files shat should be inserted after the pages created by sphinx. -# The format is a list of tuples containing the path and title. -#epub_post_files = [] - -# A list of files that should not be packed into the epub file. -#epub_exclude_files = [] - -# The depth of the table of contents in toc.ncx. -#epub_tocdepth = 3 - -# Allow duplicate toc entries. -#epub_tocdup = True - - -# Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'http://docs.python.org/': None} diff --git a/docs/source/firmware.rst b/docs/source/firmware.rst deleted file mode 100644 index 06e1cfb..0000000 --- a/docs/source/firmware.rst +++ /dev/null @@ -1,84 +0,0 @@ -============================== -Building and Flashing Firmware -============================== - -This section describes how to build and flash the Kegboard firmware on a -standard Arduino device. - -Install Arduino software -======================== - -The Arduino project provides a free software development environment for Mac, -Windows, and Linux. It includes a basic text editor, avr microcontroller -toolchains, and many standard libraries. In short, it is everything you need to -program an arduino board. - -Download the software from the `Arduino Downloads Page -`_. Packages are available for Linux, -Mac OS X, and Windows. - -.. note:: - The latest tested version is Arduino 1.0.4 - -When unzipped you will have a single directory that contains all the arduino -software. The name will be something like ``arduino-1.0.4/`` (the version number -will be different for previous versions.) - -Place this directory somewhere appropriate. For Mac users, you can drag it to -your Applications folder. - - -Compile and flash the firmware -============================== - -A new Arduino board includes a basic bootloader on internal flash. The board -needs to be programmed with the custom Kegboard firmware. - -Binary versions of the Kegboard firmware are not provided, so you need to build -it yourself. This process isn't too hard. If you already have the Arduino -software installed, and you have a clone of the kegboard repository somewhere, -you're most of the way there - -The latest version of the Kegboard firmware is available in the **kegboard** -distribution, under the directory ``src/kegboard/``. - -You can also download the entire Kegboard github repository as a zip file: -`Download Kegboard repository `_. - -The file ``kegboard.ino`` is the main source to the firmware. This file is a -C source file, using the file extension preferred by the Arduino development -tools. - -Compile -------- - -Open the file ``kegboard.ino`` in the Arduino studio application. You should see -a listing of the source. You do not need to make any changes to the source. - -Next, configure the Arduino environment to match your Arduino. In particular: - -* Select the correct board type from menu :menuselection:`Tools --> Board` -* Select the serial port it is attached to from the menu - :menuselection:`Tools --> Serial Port` - -You now be ready to build the firmware. Select the menu item -:menuselection:`Sketch --> Verify/Compile`. - - -Flash ------ - -To install the firmware, should select the menu -:menuselection:`File --> Upload to I/O Board` in the Arduino software. The -firmware will be uploaded to you device. - -Depending on your hardware, it may be necessary to reset the board using the -reset pushbutton when starting the upload. - - -Test pin --------- - -To simulate a flow meter, you can connect Pin 12 to either of the two flow meter -pins with a short jumper wire. This pin continuously outputs a slow stream of -pulses, much like a flow meter would do. diff --git a/docs/source/index.rst b/docs/source/index.rst deleted file mode 100644 index 4548560..0000000 --- a/docs/source/index.rst +++ /dev/null @@ -1,22 +0,0 @@ -.. _kegboard-guide: - -Kegboard Firmware Manual -======================== - -This document describes how to program and hack on a Kegboard, our -Arduino-based keg controller board. - -Kegboard software works with the -`Kegboard Pro Mini `_, and can also be used -with a `do-it-yourself Arduino Kegboard `_. - -.. toctree:: - :maxdepth: 2 - - overview - operation - wiring - firmware - support-library - serial-protocol - changelog diff --git a/docs/source/operation.rst b/docs/source/operation.rst deleted file mode 100644 index d66901c..0000000 --- a/docs/source/operation.rst +++ /dev/null @@ -1,83 +0,0 @@ -Theory of Operation -=================== - -This chapter describes the internal design of the Kegboard firmware and how it -manages connected sensors. If you're not interested, you can safely skip to the -next chapter. - -Main event loop ---------------- - -Kegboard's two principle responsibilities are: - -* Monitors and report status and events from attached sensors. -* Accept commands from the host to enable and disable output relays. - -When the board is powered, it immediately begins listening to sensors and -sending events on the serial port. If temperature sensing is enabled, the board -also periodically polls attached sensors. Additionally, the host can send -commands the board at any time. (Commands and events are detailed in -:ref:`kegboard-serial-protocol`.) - - -Flow sensing ------------- - -Each flow meter is connected to one of the Arduino's external interrupt pins. -(On an Arduino, these are digital pins 2 and 3.) - -Kegboard supports "open collector" flow meters. These meters are typically -built using hall effect sensors. As liquid passes through the meter, a series -of pulses is emitted on its output pin. - -Every pulse emitted by the meter corresponds to the same fixed volume of fluid, -therefore volume is determined simply by counting the pulses. The exact volume -of a pulse is a physical property of the meter; the popular Vision 2000 meter -pulses 2200 times per liter. - -In the interrupt service routine for each of these pins, Kegboard increments a -counter every time there is a pulse, keeping a running total of each meter's -volume (similar to an odometer). - - -OneWire presence and temperature sensing ----------------------------------------- - -The Kegboard firmware supports two distinct OneWire (1-wire) sensor busses: the -"thermo" bus, and the "presence" bus. - -The "thermo" bus supports reading Dallas/Maxim DS18B20 and 18S20 OneWire -temperature sensors. This bus is reserved exclusively for temperature sensors; -OneWire devices not matching the DS18B20 or DS18S20 family codes will be ignored -on this bus. Any number of sensors may be attached. - -The firmware also supports a second OneWire bus, which is continuously polled -for OneWire devices. Whenever a OneWire device such as an iButton is connected, -its unique 64-bit OneWire device ID is reported as an authentication token using -:ref:`kegboard-serial-protocol`. - - -Relays ------- - -When a relay is enabled, Kegboard enables the corresponding output and starts a -timer. If the host has not re-activated the relay within that timer, Kegboard -automatically deactivated the output. This prevents prolonged relay operation -if the host crashes unexpectedly. - - -Piezo buzzer ------------- - -A low-cost piezo buzzer can be connected to the :ref:`buzzer output pin -`. When connected, Kegboard will serenade you with some sweet -tunes. - -+----------------------+-------------------------------------------------------+ -| Event | Sound | -+======================+=======================================================+ -| Board power up | Short musical tune (4 notes). | -+----------------------+-------------------------------------------------------+ -| Auth Token Added | Three-tone "added" sound. | -+----------------------+-------------------------------------------------------+ - diff --git a/docs/source/overview.rst b/docs/source/overview.rst deleted file mode 100644 index ba541f7..0000000 --- a/docs/source/overview.rst +++ /dev/null @@ -1,62 +0,0 @@ -================= -Kegboard Overview -================= - -This page describes *Kegboard*, the Arduino-based controller board for Kegbot. - -What is a Kegboard? -=================== - -*Kegboard* is the name we use for the microcontroller board used in a Kegbot -system. Kegboard is the device that monitors all sensors, including the flow -sensors that are essential in any Kegbot configuration. - -There are two commonly-used Kegboard targets: - -* `Kegboard Pro Mini `_, a fully-assembled board, - introduced in early 2014 and sold at the `Kegbot Store `_. -* `DIY Arduino Kegboard `_, a do-it-yourself - option that can be built using an `Arduino `_ - board. - -The Kegboard software package includes firmware and support libraries that -work with either kind of Kegboard. - -Features -======== - -Since not all Kegbots are alike, the Kegbot firmware is designed with -flexibility in mind: We try to support many features and add-on devices in the -core firmware, while still keeping basic functionality tight and fast for the -common configurations. - -Depending on hardware, the Kegboard firmware can support the following -features: - -* **Flow Sensing:** Two independent flow meter inputs (or 6 on Arduino Mega), - allowing you to monitor that many individual beer taps with just one board. -* **Temperature Sensing:** Dedicated OneWire bus for reading DS1820 (DS18S20 and - DS18B20) temperature sensors. An unlimited number of sensors can be - connected, allowing you to independently track keg temperature and ambient - temperature. -* **RFID Authentication:** Authenticate users with cheap 125kHz RFIDs by - connecting the optional ID-12 RFID reader. -* **OneWire Authentication:** Authenticate users with durable iButtons. -* **Relay/Value Control:** Four general purpose outputs can be used - to toggle external devices, such as a valve to prevent unauthorized access. - Relays are monitored by an internal watchdog. -* **Buzzer:** Kegboard will play a short melody whenever an - authentication token is connected or swiped. -* **Extensible Serial Protocol:** If you don't want to use the - rest of the Kegbot software, you can still use Kegboard by implementing its - simple and extensible serial protocol in your system. (See - :ref:`kegboard-serial-protocol`). - -.. note:: - - Because of its limited size, certain features (such as relay control and - RFID reading) are not available on Kegboard Pro Mini. - -Kegboard's firmware is designed to operate correctly even when a feature is -not being used. For example, if the temperature sensor input is not -connected, other features will continue to operate normally. diff --git a/docs/source/serial-protocol.rst b/docs/source/serial-protocol.rst deleted file mode 100644 index 3a93ca2..0000000 --- a/docs/source/serial-protocol.rst +++ /dev/null @@ -1,351 +0,0 @@ -.. _kegboard-serial-protocol: - -================================== -Kegboard Serial Protocol Reference -================================== - -About -===== - -This document describes the protocol implemented in the Kegboard firmware. The -protocol is a simple serial protocol for exchanging data and commands between a -host computer and the controller board. - -.. note:: - Most users don't need to be too familiar with the Kegboard protocol. This - document is intended for someone building a new type of controller board, or - attempting to extend the existing board. For example, if you build a new type - of controller board that speaks this protocol, you should be able to use the - rest of the Kegboard software without further modification. - -Protocol Overview -================= - -The Kegboard Serial Protocol is a binary protocol between the kegboard and the -host PC. Data is delivered from the board in the **message frame format** -(described later). The host can also control and configure the board by sending -messages in the same format. - -Messages arrive from the board *asynchronously*; the host does not need to -request updates to receive information about sensors. Similarly, the host can -issue commands to the board at any time. - - -Software Support -================ - -The pykeg software includes libraries for reading and writing KBSP packets. A -unittest with a sample packet capture is also included. See the code available -in ``pykeg/hw/kegboard``. - - -Message Frame Format -==================== - -Data from the Kegboard is always sent to the host in the `Kegboard Message` -frame format. All messages take the same basic format:: - -