Initial public release of WII5 Buoy firmware

Firmware for an autonomous wave-measurement buoy (ATmega2560-based
WII5 v2 board). Reads wave motion from a Sparton AHRS-M1/M2 IMU,
samples GPS and battery state, and reports back over Iridium SBD
satellite telemetry. Originally developed 2012-2024.

This is the first public release. Code, documentation, and field-tested
operating modes (Capture, Sleep, Position, ManualTest, SelfTest,
LowBattery) are licensed under Apache 2.0 — see LICENSE and NOTICE.

See README.md for an overview and build instructions, CONTRIBUTING.md
for how to contribute, and DEPLOYMENTS.md for the field-deployment log.
This commit is contained in:
2026-05-07 16:00:21 +10:00
commit 295abb37ee
122 changed files with 38142 additions and 0 deletions
+45
View File
@@ -0,0 +1,45 @@
---
name: Bug report
about: Something is broken or behaving unexpectedly
title: ''
labels: bug
assignees: ''
---
## Summary
<!-- One sentence: what is wrong? -->
## Firmware version
<!-- The WII5_SOFTWARE_VERSION reported on boot, plus the short commit if
known. e.g. WII5Buoy_5.5.12 (commit abc1234) -->
## Hardware
- Board: <!-- e.g. WII5 v2 -->
- Sensors / radios involved: <!-- e.g. Sparton AHRS-M2, Iridium 9603 -->
## Mode and configuration
<!-- Which mode were you in (Capture / Position / Sleep / ManualTest /
SelfTest)? Any non-default config (`@WII5,setting,...`)? -->
## Steps to reproduce
1.
2.
3.
## Expected behaviour
## Actual behaviour
<!-- Include relevant log lines. Please scrub IMEIs, GPS coordinates of real
deployments, and anything else you don't want public — see SECURITY.md. -->
```
<paste logs here>
```
## Additional context
+25
View File
@@ -0,0 +1,25 @@
---
name: Feature request
about: Suggest an enhancement or new capability
title: ''
labels: enhancement
assignees: ''
---
## What problem are you trying to solve?
<!-- The motivation, not the implementation. -->
## Proposed solution
<!-- Optional. If you have an idea of how to solve it. -->
## Constraints / hardware impact
<!-- The ATmega2560 has 256 KB program memory and 8 KB RAM. Does your
proposal touch those budgets? Does it require a new sensor/radio, new wiring
on the WII5 board, or an extra serial port? -->
## Alternatives you've considered
## Additional context
+31
View File
@@ -0,0 +1,31 @@
## Summary
<!-- 1-2 sentences on what changes and why. -->
## Linked issue
<!-- "Fixes #123" / "Refs #456" -->
## Testing performed
- [ ] Compiles for `WII:avr:wii5_v2_2560_3V`
- [ ] Tested on real hardware
- [ ] Tested in mode(s):
- [ ] Flash / RAM usage (`Sketch uses ...` line) was checked and is
acceptable
<!-- If "tested on real hardware" is unchecked, please explain why in the
notes below. -->
## Notes
## Pre-submission checklist
- [ ] I have read `CONTRIBUTING.md`
- [ ] My commits are signed off (DCO; `git commit -s`)
- [ ] No internal infrastructure references introduced (no real IPs,
hostnames, IMEIs, GPS coords, personal paths, or emails — see
`SECURITY.md`)
- [ ] Comments are accurate and use `TODO`/`FIXME` (not `XXX`)
- [ ] Any new `Serial.print` calls are inside `#ifdef WII5_DEBUG_*` blocks
or use the `console` abstraction
+29
View File
@@ -0,0 +1,29 @@
# Editor / OS junk
.DS_Store
Thumbs.db
*.swp
*.swo
.idea/
.vscode/
# Build artifacts
a.out
*.gch
*.o
*.a
*.elf
*.eep
*.hex
build/
# Tool / contributor-local config
.pio
logs
tools/upload.local.sh
tools/*.local.sh
# Doxygen output
doc/api/
# Personal scratch notes
SCOTT.md
+30
View File
@@ -0,0 +1,30 @@
v5.x - 2024
* TODO
v1.x
* New BinData for sending/receiving binary data on the Iridium
* Simplified and improved the button logic.
* New logging level controls
* GPS Debug and Passthrough in any mode
* Better beep for Maths Start
* Rugged Position Mode
* Calculate time until next...
v1.0.20
* Battery And Temperature encouraged to read near boot timed
* Hold/Click button cycle Improved
* Power for Buzzer
* Hold feedback messages for Displaying shutdown time of Maths
* Sleep period configurable in EEPROM
* Split non-essential loop out for Maths
* Mode timeouts
* Direct maths Hold command
* Fully working Seep Mode
v1.0.16
* statusDump - automatically shown at Voltage and Weather to allow Local Server updates
* Using @WII5,setting,mode,default,{STRING} has worked for a while, added "swap" which switches
to sleep if it was capture, otherwise to capture.
* Updated Test help to remove incorrect documentation.
* Large number of experimental changes in Maths to facilitate start/stop/reboot
+24
View File
@@ -0,0 +1,24 @@
# Code of Conduct
This project adopts the **Contributor Covenant, version 2.1**, as its code
of conduct. The full text is published at:
https://www.contributor-covenant.org/version/2/1/code_of_conduct/
In short: be respectful, be constructive, focus on the technical work.
Harassment, personal attacks, and discriminatory language are not welcome
in issues, pull requests, commit messages, code comments, or any other
project space.
## Reporting
If you experience or witness behaviour that violates this code, please
report it to the maintainers privately via email (see the contact in the
project README), or via a private GitHub message to a maintainer. Reports
will be handled confidentially and in good faith.
## Scope
This code applies to all project spaces — issues, pull requests, the code
itself, and any related communication channels. It also applies when an
individual is officially representing the project in public spaces.
+90
View File
@@ -0,0 +1,90 @@
# Contributing to WII5 Buoy Firmware
Thanks for your interest in contributing! This is firmware for an
Arduino/AVR-based wave-measurement buoy. Most contributors will be people
porting it to new hardware, fixing bugs found in the field, or adding sensor
support. The notes below are aimed at making PRs reviewable and avoiding the
classes of mistakes that have bitten us in the past.
## How to discuss before coding
For anything bigger than a typo or a small bug fix, **open an issue first**
to discuss the approach. Embedded firmware is full of subtle hardware
constraints (program-memory budget, real-time deadlines, ISR safety) and a
short up-front conversation usually saves a long review.
## Building locally
This is an Arduino IDE / `arduino-cli` project targeting the
ATmega2560 on a custom WII5 v2 board (see `WII5_board_v2.h`).
The build scripts in `tools/` are parameterized via env vars; copy
`tools/upload.sh` to `tools/upload.local.sh` (gitignored) and edit, or
export `WII5_BUILD_DIR`, `WII5_AVRDUDE_CONF`, `WII5_REMOTE`, etc. as needed.
See each script's preamble for the env vars it expects.
## Submitting changes
1. Fork the repository, create a topic branch off `main`.
2. Keep commits small and reviewable. One logical change per commit.
3. Use clear commit messages. The first line is a short summary
(`<area>: <verb> <object>`), followed by a blank line and the rationale
in prose if non-obvious.
4. Sign off your commits to certify the
[Developer Certificate of Origin](https://developercertificate.org/):
```
git commit -s
```
5. Verify the sketch still compiles for `WII:avr:wii5_v2_2560_3V` before
pushing.
6. Open a pull request. The PR template will prompt you for the things
reviewers care about (testing performed, hardware impact, etc.).
## Code conventions
- **No internal infrastructure references.** Don't introduce hardcoded
hostnames, IP addresses, IMEIs, GPS coordinates, personal email addresses,
or developer-specific paths. Use env vars in scripts and `#define`s in
`WII5_board_*.h` for hardware-specific values. See `SECURITY.md`.
- **Don't break the flash budget.** The ATmega2560 has 256 KB of program
memory; we already use a meaningful fraction. Watch the `Sketch uses ...`
line at the end of `arduino-cli compile`. New features that cost more than
~2 KB should mention the cost in the PR description.
- **Use the `console` abstraction**, not raw `Serial.print`. The console
routes output to whatever serial port the board is configured for, and
supports `LOG_DEBUG` / `LOG_INFO` / `LOG_WARN` / `LOG_ERROR` / `LOG_FATAL`
log levels. Raw `Serial.print` should only appear inside `#ifdef
WII5_DEBUG_*` blocks.
- **Use `F()` / `PSTR()` for string literals** to keep them in flash, not RAM.
- **TODO/FIXME convention.** `// TODO: ...` for known gaps, `// FIXME: ...`
for known-broken things that need attention. We previously used `XXX`
internally; new code should use `TODO`/`FIXME`.
- **Keep comments accurate.** A comment that contradicts the code is worse
than no comment.
## Adding a new sensor / driver
Subclass `WII5Power` (gives you `powerOn()` / `powerOff()` / `running` state).
Implement `controllerId()` and `driverId()` to return the appropriate
enumerators in `WII5Data.h`. Provide `begin()`, `loop()`, `start()`, `stop()`.
Wire it into `WII5Setup.cpp`'s `begin()` and `WII5Controller.cpp`'s
`loopOther()`. Look at `WII5GPS`, `WII5Battery`, or `WII5Weather_18B20` for
small worked examples.
## Testing
There is no automated test harness for the firmware itself — testing is done
by flashing onto real hardware and exercising the `wii5_buoy` and
`wii5_modes` sketches. The `test/` directory contains smaller sketches
demonstrating individual subsystems; please add a sketch there if you add a
new driver.
In your PR, describe:
- What you tested on (hardware or just compile?)
- Which mode(s) you exercised (Capture, Position, Sleep, ManualTest, etc.)
- Any known gaps (e.g. "didn't test on a battery-only run").
## License
By contributing, you agree that your contributions will be licensed under
the Apache License 2.0 (see `LICENSE`).
+37
View File
@@ -0,0 +1,37 @@
# Field Deployments
A running log of notable field deployments of the WII5 buoy firmware.
This file is for crediting partners, recording lessons learned, and
documenting which firmware version went out the door for each campaign.
Add a new section per deployment. Keep them short — link out to papers,
mission reports, or partner sites for the full story.
---
## Template
### YYYY — Deployment name
- **Partner / institution:** ...
- **Location:** ... (broad region; do not record exact buoy coordinates here)
- **Firmware version:** `vX.Y.Z` (commit `<short-sha>`)
- **Hardware:** WII5 v2 board, Sparton AHRS-M*, Iridium 9602 / 9603, etc.
- **Mode profile:** Capture / Position / Sleep schedule used in the field
- **Notes:** field issues observed, fixes that came out of the campaign,
links to publications.
---
<!--
Add real deployments below. Suggested first entry:
### 2024 — KOPRI deployment
- **Partner:** Korea Polar Research Institute (KOPRI)
- **Firmware version:** v5.5.x
- **Hardware:** WII5 v2 board with Sparton AHRS-M2, Iridium 9603
- **Notes:** ...
(Confirm with KOPRI before listing publicly.)
-->
+78
View File
@@ -0,0 +1,78 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
#
# Doxygen configuration for the WII5 Buoy firmware.
#
# This is a minimal Doxyfile listing only the non-default options. Run
# `doxygen Doxyfile` from the repo root; output lands in doc/api/ (which
# is gitignored). Doxygen >= 1.9 is recommended.
# --- Project identity --------------------------------------------------------
PROJECT_NAME = "WII5 Buoy Firmware"
PROJECT_BRIEF = "Firmware for the WII5 wave-measurement buoy (ATmega2560)."
# PROJECT_NUMBER is normally injected by the build script:
# doxygen <(sed "s/^PROJECT_NUMBER.*/PROJECT_NUMBER = $(cat VERSION | grep version | cut -d= -f2)/" Doxyfile)
# Hardcoded fallback follows.
PROJECT_NUMBER = v5.5.x
# --- Inputs ------------------------------------------------------------------
INPUT = . app/wii5_buoy
RECURSIVE = YES
FILE_PATTERNS = *.h *.cpp *.ino
EXCLUDE = doc test tools .git build .github
EXCLUDE_PATTERNS = */legacy/* */experimental/*
# --- Output ------------------------------------------------------------------
OUTPUT_DIRECTORY = doc/api
GENERATE_HTML = YES
GENERATE_LATEX = NO
GENERATE_XML = YES
HTML_OUTPUT = .
# --- Source extraction -------------------------------------------------------
EXTRACT_ALL = YES
EXTRACT_PRIVATE = NO
EXTRACT_STATIC = YES
EXTRACT_LOCAL_CLASSES = YES
EXTRACT_ANON_NSPACES = NO
# Recognise Javadoc-style /** ... */ as the brief if it's a single line.
JAVADOC_AUTOBRIEF = YES
QT_AUTOBRIEF = NO
# Source browser is useful for an exploratory codebase.
SOURCE_BROWSER = YES
INLINE_SOURCES = NO
REFERENCED_BY_RELATION = YES
REFERENCES_RELATION = YES
# Sort members the way they appear in the source.
SORT_MEMBER_DOCS = NO
SORT_BRIEF_DOCS = NO
# --- Warnings ----------------------------------------------------------------
QUIET = YES
WARN_IF_UNDOCUMENTED = NO # Tier-1 docs only; flip to YES once Tier-2 lands
WARN_IF_DOC_ERROR = YES
WARN_AS_ERROR = NO
# --- Preprocessor (so #ifdef'd code shows up correctly) ----------------------
ENABLE_PREPROCESSING = YES
MACRO_EXPANSION = YES
EXPAND_ONLY_PREDEF = YES
PREDEFINED = __WII5_V02__ \
WII5_COMMS_IRIDIUM \
WII5_GPS \
WII5_IMU_SPARTON \
WII5_STORAGE_SDBLOCK \
WII5_RTC \
F(x)=x \
PSTR(x)=x \
PROGMEM=
# --- Misc --------------------------------------------------------------------
HAVE_DOT = NO
GENERATE_TREEVIEW = YES
DISABLE_INDEX = NO
FULL_SIDEBAR = NO
TAB_SIZE = 2
+202
View File
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+11
View File
@@ -0,0 +1,11 @@
WII5 Buoy Firmware
Copyright 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
This product includes software developed by the WII5 Buoy contributors.
Licensed under the Apache License, Version 2.0 (the "License").
You may obtain a copy of the License at:
http://www.apache.org/licenses/LICENSE-2.0
See the LICENSE file in this repository for the full license text.
+129
View File
@@ -0,0 +1,129 @@
# WII5 Buoy Firmware
Firmware for an autonomous wave-measurement buoy. Reads wave motion from a
Sparton AHRS, samples GPS and battery state, and reports back over Iridium
SBD satellite telemetry. Targets the ATmega2560 on a custom WII5 v2 board.
> "WII5 = WII3 WDT + WII1 Buoy Combined" — the firmware merges the older
> WII3 watchdog/maths-CPU lineage with the WII1 buoy I/O lineage. Design
> notes and lineage live in `doc/`.
## Overview
The buoy runs in one of several modes:
- **Capture** — IMU/wave-motion data capture and SD logging
- **Position** — periodic GPS position + Iridium telemetry only (low power)
- **Sleep** — long sleeps between wake-ups, Maths CPU off
- **Manual Test** — operator-driven hardware exercise via console
- **Self Test** — automated boot-time hardware checks
- **Low Battery** — degraded operation when batteries are low
Mode is selected from EEPROM defaults, then can be overridden by Iridium
commands, the local console, or battery-management state. See
`doc/MODES.txt` and `WII5Controller.cpp::calculateCurrentMode()`.
## Hardware
- **Main board:** WII5 v2 (custom AVR carrier)
- **MCU:** ATmega2560 @ 11.0592 MHz, 256 KB flash, 8 KB RAM, 4 KB EEPROM
- **IMU / wave sensor:** Sparton AHRS-M1 or AHRS-M2 (NorthTek-programmable)
- **Sat radio:** Iridium 9602 / 9603 SBD
- **GPS:** u-blox NEO-6M / NEO-7M (NMEA, parsed via TinyGPS++)
- **Storage:** dual SD cards (one active, one reserve)
- **Sensors:** Dallas DS18B20 temperature, battery voltage analog inputs
External vendor documentation is not redistributed in this repo; see
`doc/EXTERNAL_REFERENCES.md` for links.
## Build & flash
This is an Arduino-IDE / `arduino-cli` project, not PlatformIO.
```sh
# Verify the sketch compiles
arduino-cli compile \
-b WII:avr:wii5_v2_2560_3V:cpu=atmega25603V3_11MHz230400 \
app/wii5_buoy/wii5_buoy.ino
# Or use the parameterized helper script
WII5_BUILD_DIR=~/Arduino/build \
WII5_AVRDUDE_CONF=~/.arduino15/.../avrdude.conf \
WII5_SERIAL=/dev/ttyUSB0 \
WII5_FULL_HEX=~/Arduino/build/wii5_buoy.ino.hex \
tools/build_local.sh
```
The `tools/` scripts are all parameterized via env vars — read each
script's preamble for the variables it expects. Copy any of them to
`*.local.sh` (gitignored) if you want to set defaults locally.
Library dependencies (install via Arduino Library Manager or
`arduino-cli lib install`): TimeLib, RTClib, elapsedMillis, TinyGPSPlus,
AvgStd, IridiumSBD, OneWire, DallasTemperature, SDBlock, CRC32,
PushButton, MemoryFree, LowPower, RadioHead.
## Architecture
Each subsystem is a class deriving from `WII5` (base) or `WII5Power`
(power-managed peripherals). Singleton instances hang off the global
namespace (`wii5Controller`, `wii5Iridium`, `wii5Sparton`, etc.) and are
called from `WII5Controller::loop()` according to the active mode.
Top-level entry points:
- `app/wii5_buoy/wii5_buoy.ino``setup()` / `loop()`
- `WII5Setup.cpp` — initial hardware bring-up
- `WII5Controller.cpp` — main loop, mode dispatch, watchdog
- `WII5Commands.cpp` — text-protocol command handler (console + Iridium)
## LED status codes
LEDs are the field-diagnostic language; preserve their meanings.
| Mode / state | LED 1 | Notes |
| --- | --- | --- |
| Capture (default after 5 min) | `LED_SHORT` | |
| Capture, waiting | `LED_SLOW` | |
| Capture, error | `LED_FAST` | |
| Capture, just started | `LED_TRIPPLE` | reverts to `LED_SHORT` after 5 min |
| Sleep | `LED_DOUBLE` | |
| Low Battery | `LED_TRIPPLE_GAP` | |
| Position | `LED_SHORT` | (same as Capture default — disambiguate by mode) |
| Off / asleep / broken | `LED_OFF` | not a "safe" indicator |
| On (no blink) | `LED_ON` | not safe — may have crashed |
| Idle / good | `LED_SLOW` | |
## Field deployments
A running log of notable real-world deployments lives in `DEPLOYMENTS.md`.
## Repository layout
```
app/wii5_buoy/ Arduino sketch entry point
WII5*.{h,cpp} Firmware source (board root, see Architecture)
WII5_board_v2.h Pin/peripheral map for the WII5 v2 board
doc/ Design notes, command reference, hardware schematics
test/ Smaller per-subsystem test sketches
tools/ Build, flash, and version helper scripts (env-var
driven; copy to *.local.sh to set local defaults)
```
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md). Please also read
[CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) and [SECURITY.md](SECURITY.md)
before opening issues or PRs.
## Contact
- Maintainer: Scott Penrose &lt;scottp@dd.com.au&gt;
- Bugs and feature requests: GitHub Issues
- Security issues: see [SECURITY.md](SECURITY.md)
## License
Apache License 2.0 — see [LICENSE](LICENSE) and [NOTICE](NOTICE).
Copyright 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors.
+46
View File
@@ -0,0 +1,46 @@
# Security Policy
## Reporting a vulnerability
If you discover a security issue in the WII5 Buoy firmware — for example,
a flaw in the Iridium SBD command-handling path, an authentication bypass
in the console protocol, or anything else that could let an attacker take
control of a deployed buoy — **please do not open a public GitHub issue**.
Instead, report it privately:
- Open a private security advisory via the GitHub repository's
**Security** tab → "Report a vulnerability", or
- Email the maintainer: Scott Penrose &lt;scottp@dd.com.au&gt;
Please include:
- A description of the issue and its potential impact
- Steps to reproduce, or a proof-of-concept
- The affected firmware version (`WII5_SOFTWARE_VERSION`) and hardware
variant if known
We will acknowledge receipt within a reasonable time, work with you on a
fix, and coordinate disclosure.
## What this project asks of contributors
When opening issues or pull requests, **please do not include**:
- Internal hostnames, IP addresses, or network paths from operational
deployments
- Iridium IMEIs, modem serial numbers, or device identifiers from real
deployments
- GPS coordinates of operational deployment sites
- Other contributors' personal information (emails, real names, paths) —
unless they have given explicit permission
If you need example values to demonstrate a problem, use obviously-fake
placeholders (e.g. `192.0.2.1` from RFC 5737, IMEI `300000000000000`,
generic lat/lng like `0,0`).
## Supported versions
Only the latest tagged release on `main` is actively supported. Older
deployed firmware may continue to function in the field but does not
receive backported fixes.
+2
View File
@@ -0,0 +1,2 @@
version=v5.5.1
short=c0b356d
+89
View File
@@ -0,0 +1,89 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5.cpp
* @brief Top-level base class and global buffer declarations.
*/
/*
WII5 - Base class for all - not much used here now
Main methods:
* debugDo ???
* setError(id, string); - includes auto update of time etc
*/
#include <Arduino.h>
#include <WII5.h>
/*
BUFFERS:
The buffers are defined here to allow for central control of
space, and potential to reuse them. They should be statically allocated.
Need to do more work on shared buffers. Especially for serial ports.
*/
#ifdef WII5_BUFFER_STRING
char wii5BufferString[WII5_BUFFER_STRING];
#endif
#ifdef WII5_BUFFER_CONSOLE_BINARY
char wii5BufferConsoleBinary[WII5_BUFFER_CONSOLE_BINARY];
#endif
#ifdef WII5_BUFFER_CONSOLE_PRINT
char wii5BufferConsolePrint[WII5_BUFFER_CONSOLE_PRINT];
#endif
#ifdef WII5_BUFFER_CONSOLE_CMD
char wii5BufferConsoleCmd[WII5_BUFFER_CONSOLE_CMD];
#endif
#ifdef WII5_RADIO_LORA
char wii5BufferRadio[WII5_BUFFER_RADIO];
#endif
#ifdef WII5_COMMS_IRIDIUM
char wii5BufferIridium[WII5_BUFFER_IRIDIUM]; // Used for Serial In / Out
char wii5BinaryIridium[WII5_IRIDIUM_BIN_MAX]; // Used to send and receive data to the modem
#endif
#ifdef WII5_BUFFER_SPARTON
char wii5BufferSparton[WII5_BUFFER_SPARTON];
#endif
#ifdef WII5_BUFFER_GPS
char wii5BufferGPS[WII5_BUFFER_GPS];
#endif
#ifdef WII5_RADIO_LORA
RH_RF95 wii5RadioRF95(RADIO_CS);
#endif
void debugDo(uint32_t n) {
static uint32_t count = 0;
count++;
SerialConsole.print(F("DEBUG POINT: "));
SerialConsole.print(n);
SerialConsole.print(" ");
SerialConsole.println(count);
SerialConsole.flush();
}
// Simple reusable class to help with multiple boards
WII5_DRIVERS WII5::driverId() {
return WII5DRIVER_UNKNOWN;
}
WII5_CONTROLLERS WII5::controllerId() {
return WII5CONTROLLER_UNKNOWN;
}
bool WII5::SJ(DdW when) {
if (when >= 1483511955)
return true;
else
return false;
}
+221
View File
@@ -0,0 +1,221 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5.h
* @brief Top-level base class and global buffer declarations.
*/
#ifndef WII5_h
#define WII5_h
#include <elapsedMillis.h>
#include <TimeLib.h>
// Load the Board file - see board for type
#include <WII5_board.h>
#include <WII5DataShared.h>
#include <WII5Data.h>
#include <WII5Strings.h>
#include <WII5Config.h>
#define WII5_TIMERLAPS 5
// Try bitRead
#define BitVal(data,y) ( (data>>y) & 1) /** Return Data.Y value **/
#define SetBit(data,y) data |= (1 << y) /** Set Data.Y to 1 **/
#define ClearBit(data,y) data &= ~(1 << y) /** Clear Data.Y to 0 **/
#ifdef WII5_BUFFER_STRING
extern char wii5BufferString[WII5_BUFFER_STRING];
#endif
#ifdef WII5_BUFFER_CONSOLE_BINARY
extern char wii5BufferConsoleBinary[WII5_BUFFER_CONSOLE_BINARY];
#endif
#ifdef WII5_BUFFER_CONSOLE_PRINT
extern char wii5BufferConsolePrint[WII5_BUFFER_CONSOLE_PRINT];
#endif
#ifdef WII5_BUFFER_CONSOLE_CMD
extern char wii5BufferConsoleCmd[WII5_BUFFER_CONSOLE_CMD];
#endif
#ifdef WII5_RADIO_LORA
extern char wii5BufferRadio[WII5_BUFFER_RADIO];
#endif
// Serial modules - (should have defines)
#ifdef WII5_COMMS_IRIDIUM
#define WII5_IRIDIUM_BIN_MAX 340
extern char wii5BufferIridium[WII5_BUFFER_IRIDIUM]; // Used for Serial In / Out
extern char wii5BinaryIridium[WII5_IRIDIUM_BIN_MAX]; // Used to send and receive data to the modem
#endif
#ifdef WII5_BUFFER_SPARTON
extern char wii5BufferSparton[WII5_BUFFER_SPARTON];
#endif
#ifdef WII5_BUFFER_GPS
extern char wii5BufferGPS[WII5_BUFFER_GPS];
#endif
/** @brief Debug breadcrumb: print "DEBUG POINT: <n> <count>" to SerialConsole. */
void debugDo(uint32_t n);
/**
* @brief Common base class for WII5 drivers and controllers.
*
* Provides controllerId/driverId virtuals so subclasses identify themselves
* to logging and status helpers. Concrete subclasses include WII5Power
* (power-managed peripherals), WII5Mode (run-time modes), and the various
* sensor / radio drivers.
*/
class WII5 {
public:
WII5() {
// lastStatus = WII5STATUS_UNKNOWN;
// lastErrorCount = 0;
// lastErrorRepeat = 0;
// lastErrorStatus = WII5STATUS_UNKNOWN;
// lastError = WII5ERROR_UNKNOWN;
// lastErrorString[0] = '\0';
}
/** @brief Identify the controller category for logging/status. */
virtual WII5_CONTROLLERS controllerId();
/** @brief Identify the driver category for logging/status. */
virtual WII5_DRIVERS driverId();
// void setStatus(WII5_STATUS s);
// void setErrorUnknown();
// void setErrorNone();
// void setError(WII5_ERRORS id, char *str);
// void setError(WII5_ERRORS id, const __FlashStringHelper *str);
// bool isError();
// Helpers to show values
// bool getStatusOff();
// bool getStatusOn();
// bool getStatusValid();
// virtual void displayStatus();
// virtual void displayError(bool hideNone = false);
// virtual void displayStatusFull(bool suprressNewLine = false);
// virtual void metadataPrint(File* fh);
/** @brief Sanity check: is the given timestamp in a plausible epoch range? */
virtual bool SJ(DdW when);
/*
virtual void displaySTATS();
void statsHighClear();
void statsHighPointStart();
void statsHighPointStop();
void statsHighDisplay();
*/
protected:
// STATUS - Last Status
// elapsedMillis lastStatusAge;
// time_t lastStatusT;
// WII5_STATUS lastStatus; // at time of error
// ERROR - Last Error
// elapsedMillis lastErrorAge;
// uint32_t lastErrorCount; // Count all errors... also consider repeats
// uint32_t lastErrorRepeat; // Count of repeats
// time_t lastErrorT;
// WII5_STATUS lastErrorStatus; // at time of error
// WII5_ERRORS lastError; // Error ID
// char lastErrorString[WII5_ERROR_STRING_MAX]; // Error string
/*
// Stats - mostly automatic, but lap timers need manual input, see also displaySTATS
void statsTimerStart();
uint8_t statsTimerLap();
void statsTimerStop();
elapsedMillis statsTimer;
uint32_t statsFinal;
uint32_t statsLap[WII5_TIMERLAPS];
uint8_t statsLap_count;
// High Speed stats loops used for some operations
uint32_t statsHigh_count;
uint32_t statsHigh_total;
elapsedMicros statsHigh_timer;
*/
};
#ifdef WII5_RADIO_LORA
// extern RH_RF95 wii5RadioRF95;
#endif
// Mostly dependency stuff !
#include <WII5Mode.h>
#include <WII5Power.h>
#include <WII5SerialManager.h>
#include <WII5ModeSleep.h>
#include <WII5ModeCapture.h>
#include <WII5ModeLowBattery.h>
#include <WII5ModeManualTest.h>
#include <WII5ModePosition.h>
#include <WII5ModeSelfTest.h>
#include <WII5Commands.h>
#include <WII5Controller.h>
#include <WII5Data.h>
#ifdef WII5_RADIO_LORA
#include <WII5RadioLoRa.h>
#endif
#ifdef WII5_GPS
#include <WII5GPS.h>
#endif
#ifdef WII5_COMMS_IRIDIUM
#include <WII5Iridium.h>
#endif
#include <WII5Maths.h>
#ifdef WII5_IMU_SPARTON
#include <WII5Sparton.h>
#endif
#ifdef WII5_STORAGE_SDBLOCK
#include <SDBlock.h>
#endif
#ifdef WII5_RTC
#include <WII5RTC.h>
#endif
#include <WII5Communications.h>
#include <WII5Battery.h>
#include <WII5Weather_18B20.h>
#include <WII5Help.h>
#include <WII5Display.h>
#include <WII5SerialDebug.h>
#include <WII5SerialStatus.h>
#include <WII5SerialMaths.h>
#include <WII5Setup.h>
// Sh3d shared code
#include <WII5Sh3dConfig.h>
#include <WII5Sh3dConsole.h>
#include <WII5Sh3dIO.h>
#include <WII5Sh3dUtil.h>
// Data
#include <WII5BinData.h>
// Usefu libs
#include <TimeLib.h>
// RTC
#ifdef WII5_RTC
#include <Wire.h>
#include "RTClib.h"
#endif
#endif
+199
View File
@@ -0,0 +1,199 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5Battery.cpp
* @brief Battery monitor: voltage sampling, running average, and threshold logic.
*/
/*
WII5Battery
*/
#include <Arduino.h>
#include <WII5Battery.h>
#include <WII5Sh3dConsole.h>
#include <TimeLib.h>
void WII5Battery::begin() {
// ??? powerSetPin(POWER_BATTERY_PIN, POWER_IMU_SERIAL_ON, POWER_BATTERY_PIN_OFF);
#ifdef BATTERY_1_VOLTS_PIN
pinMode(BATTERY_1_VOLTS_PIN, OUTPUT);
digitalWrite(BATTERY_1_VOLTS_PIN, LOW);
#endif
#ifdef BATTERY_1_VOLTS_ANALOG
pinMode(BATTERY_1_VOLTS_ANALOG, INPUT);
#endif
#ifdef BATTERY_2_VOLTS_ANALOG
pinMode(BATTERY_2_VOLTS_ANALOG, INPUT);
#endif
// Set the age high... never got it.
age = WII5TIME_1WEEK * 1000;
// Start by getting the voltage as early as possible
step = WII5BATTERY_ON; stepWait = 0; stepCount = 0;
};
bool WII5Battery::isRunning() {
return (step != WII5BATTERY_OFF);
}
void WII5Battery::start(bool force) {
step = WII5BATTERY_ON; stepWait = 300000; stepCount = 0;
}
void WII5Battery::stop(bool force) {
step = WII5BATTERY_OFF; stepWait = 0; stepCount = 0;
}
uint16_t WII5Battery::scale(uint16_t data, uint16_t d) {
return int(0 + (float)data * (float)((float)d / 1000));
}
uint32_t WII5Battery::scale(uint32_t data, uint32_t d) {
return (uint32_t)(0 + (float)data * (float)((float)d / 1000));
}
void WII5Battery::loop() {
// Check timeouts (e.g. how long blocked low voltage?)
// Testing Voltage Mode
switch (step) {
case WII5BATTERY_OFF:
// TODO step counter
// TIMEOUT - Not applicable
// off();
// Hard coded every 5 minutes for now
if (stepWait > 300000) {
step = WII5BATTERY_ON; stepWait = 0; stepCount = 0;
}
break;
case WII5BATTERY_ON:
digitalWrite(BATTERY_1_VOLTS_PIN, HIGH);
// on();
avgBattery1.reset();
avgBattery2.reset();
step = WII5BATTERY_MEASURE; stepWait = 0; stepCount = 0;
break;
case WII5BATTERY_MEASURE:
// How many should we measure?
if (stepCount > 100) { // TODO Hard coded?
step = WII5BATTERY_STORE; stepWait = 0; stepCount = 0;
}
else {
#ifdef BATTERY_1_VOLTS_ANALOG
// Should we scale n ow or later?
uint16_t in = analogRead(BATTERY_1_VOLTS_ANALOG);
avgBattery1.checkAndAddReading(scale((uint16_t) in, (uint16_t)BATTERY_1_VOLTS_MULT));
#endif
#ifdef BATTERY_2_VOLTS_ANALOG
// avgBattery2.checkAndAddReading(scale((uint16_t) analogRead(BATTERY_2_VOLTS_ANALOG), (uint16_t)BATTERY_2_VOLTS_MULT);
#endif
}
break;
case WII5BATTERY_STORE:
// TODO Where do we store this?
// - Current Metadata - sure - useful
// - SD card or SPIMemory - be nice?
// console.log(LOG_DEBUG, F("BATTERY: Storage not yet implemented"));
// TODO Check this is decivolts !
// TODO can we use openLogs etc to write log to disk
// #ifdef BATTERY_2_VOLTS_ANALOG
//int(avgBattery2.getMean() * 100);
// #endif
value = int(avgBattery1.getMean());
age = 0;
step = WII5BATTERY_ANALYSE; stepWait = 0; stepCount = 0;
break;
case WII5BATTERY_ANALYSE:
if (
(value < wii5Config.getBatteryLow())
&& (wii5Controller.getMode() != WII5MODE_LOWBATTERY)
) {
console.log(LOG_INFO, F("Low Battery detected V=%d, Low=%d, Mid=%d"),
value, wii5Config.getBatteryLow(), wii5Config.getBatteryMid()
);
wii5Controller.setMode(WII5MODE_LOWBATTERY);
}
else if (
(value > wii5Config.getBatteryMid())
&& (wii5Controller.getMode() == WII5MODE_LOWBATTERY)
) {
console.log(LOG_INFO, F("Mid Battery detected V=%d, Low=%d, Mid=%d"),
value, wii5Config.getBatteryLow(), wii5Config.getBatteryMid()
);
wii5Controller.setDefaultMode();
}
#ifdef BATTERY_1_VOLTS_ANALOG
wii5Display.atDataSend(
F("battery1"),
F("volts=%d,swing=%d,min=%d,max=%d"),
int(avgBattery1.getMean()),
int(avgBattery1.getStd()),
int(avgBattery1.getMin()),
int(avgBattery1.getMax())
);
#endif
#ifdef BATTERY_2_VOLTS_ANALOG
// TODO
console.printf(F("# battery: #=2 mean=%d +-%d min=%d max=%d\r\n"),
// avgAccelZ.getN(),
int(avgBattery2.getMean()),
int(avgBattery2.getStd()),
int(avgBattery2.getMin()),
int(avgBattery2.getMax())
);
#endif
wii5Display.atCommandSend(F("status"), F("voltage=%ld"),
wii5Battery.value
);
wii5Display.atCommandSend(F("status"), F("update"));
/*
RULES:
* Valid date tinme ?
* Check date time not too far future, and not in past
* Batter 2 - work out which to use (not this release)
* Confidition of battery - how long it has been on this average etc
if (
// Low Battery
(TOO LOW)
// Disabled still
&& (wii5Config.getDisableLowBattery() > now()
) {
}
*/
step = WII5BATTERY_FINISH; stepWait = 0; stepCount = 0;
break;
case WII5BATTERY_FINISH:
digitalWrite(BATTERY_1_VOLTS_PIN, LOW);
step = WII5BATTERY_OFF; stepWait = 0; stepCount = 0;
break;
default:
console.log(LOG_FATAL, F("BATTERY: Default step... step=%d"), step);
step = WII5BATTERY_OFF; stepWait = 0; stepCount = 0;
break;
}
stepCount++;
return;
}
WII5Battery wii5Battery;
+63
View File
@@ -0,0 +1,63 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5Battery.h
* @brief Battery monitor: voltage sampling, running average, and threshold logic.
*/
#ifndef WII5Battery_h
#define WII5Battery_h
#include <Arduino.h>
#include <TimeLib.h>
#include <elapsedMillis.h>
#include <WII5.h>
#include <AvgStd.h>
enum WII5BATTERY_STEPS {
WII5BATTERY_OFF,
WII5BATTERY_ON,
WII5BATTERY_MEASURE,
WII5BATTERY_STORE,
WII5BATTERY_ANALYSE,
WII5BATTERY_FINISH
};
class WII5Battery : public WII5Power {
public:
WII5Battery() {}
virtual WII5_CONTROLLERS controllerId() {return WII5CONTROLLER_DRIVER;}
virtual WII5_DRIVERS driverId() {return WII5DRIVER_BATTERY;}
void begin();
void loop();
void start(bool force = false);
void stop(bool force = false);
bool isRunning();
elapsedMillis age;
int32_t value;
protected:
WII5BATTERY_STEPS step;
elapsedMillis stepWait;
uint32_t stepCount;
AvgStd avgBattery1;
AvgStd avgBattery2;
uint16_t scale(uint16_t data, uint16_t d);
uint32_t scale(uint32_t data, uint32_t d);
};
extern WII5Battery wii5Battery;
#endif
+633
View File
@@ -0,0 +1,633 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5BinData.cpp
* @brief Binary message format for Iridium SBD: layout, packing, and split logic.
*/
/*
WII5BinData - Simple utitlity class to create and manage binary data for Iridium
How to manage the type requested. If it needs splitting, how to do, store etc.
NOTE: LSB 0
*/
#include <WII5BinData.h>
void WII5BinData::begin() {
}
// getSize - Literally the size of the object including the headers
uint16_t WII5BinData::getSize(uint32_t t) {
return sizeof(WII5_BINDATA_HEADER) + getSizeObj(t);
}
uint16_t WII5BinData::getSizeOne(uint8_t b) {
switch(b) {
case 0:
return sizeof(WII5_BINDATA_0); break;
case 1:
return sizeof(WII5_BINDATA_1); break;
case 2:
return sizeof(WII5_BINDATA_2); break;
case 3:
return sizeof(WII5_BINDATA_3); break;
case 4:
return sizeof(WII5_BINDATA_4); break;
case 5:
return sizeof(WII5_BINDATA_5); break;
case 6:
return sizeof(WII5_BINDATA_6); break;
case 7:
return sizeof(WII5_BINDATA_7); break;
case 8:
return sizeof(WII5_BINDATA_8); break;
case 9:
return sizeof(WII5_BINDATA_9); break;
case 10:
return sizeof(WII5_BINDATA_10); break;
case 11:
return sizeof(WII5_BINDATA_11); break;
case 12:
return sizeof(WII5_BINDATA_12); break;
case 13:
return sizeof(WII5_BINDATA_13); break;
case 14:
return sizeof(WII5_BINDATA_14); break;
case 15:
return sizeof(WII5_BINDATA_15); break;
};
return 0;
}
/*
Starting with a type of B01001011
uint8_t nextBit = 0;
uint32_t outType = 0;
uint32_t t = B01001011;
uint8_t count = 0;
while (getSplit(t, 300, &nextBit, %outType)) {
console.printf(F("Splitting #=%d, original type=%lu, new type=%lu, size=%d, nextBit=%d\r\n")
count, t, outType, getSize(outType), int(nextBit)
);
count++;
}
There should be no overlapping bits at the end of this
*/
bool WII5BinData::getSplit(uint32_t in_t, uint16_t max_size, uint8_t* start_bit, uint32_t* out_t) {
uint16_t ret_size = 0;
uint16_t part_size = 0;
ret_size = sizeof(WII5_BINDATA_HEADER);
*out_t = 0;
// TODO fix max bits
for (uint8_t b = *start_bit; b < 16; b++) {
if (BitVal(in_t, b)) {
part_size = getSizeOne(b);
if ((ret_size + part_size) > max_size) {
// That is it, we can't do any more.
*start_bit = b;
// NOTE: This is an error condition
if (*out_t == 0) {
console.log(LOG_FATAL, F("BinData: Failed to split BinData to this size"));
return false;
}
else
return true;
}
else {
SetBit(*out_t, b);
ret_size += part_size;
}
}
}
*start_bit = 16;
// Check if we have anything left
if (*out_t == 0)
return false;
else
return true;
}
uint16_t WII5BinData::setBit(uint32_t t, uint8_t b) {
SetBit(t, b);
return t;
}
// getSizeObj - The individual objects, added together but no header
uint16_t WII5BinData::getSizeObj(uint32_t t) {
uint16_t ret = 0;
for (uint8_t b = 0; b < 16; b++) {
if (BitVal(t, b))
ret += getSizeOne(b);
}
return ret;
}
// Create Data is called first - to initialize the Buffer
bool WII5BinData::createData(uint32_t t, void* buf, uint16_t maxSize, uint32_t recordCount) {
memset(buf, 0, maxSize);
WII5_BINDATA_HEADER* local = (WII5_BINDATA_HEADER*)buf;
// Set the Header
local->wii_binary_id = 60001; // Fixed ID for the old system - 60001
local->bindata_type = t; // Which packet type is this.
local->bindata_packet = 0; // 0 means only?
// NOTE:
local->deviceId = wii5Config.getDeviceId();
// Custom recordCount (eg. we are sending something old, else current)
if (recordCount > 0)
local->recordCount = recordCount;
else
local->recordCount = wii5Config.getRecordCount();
/*
console.log(LOG_DEBUG, F("*** (1) binary id = %d, binary type = %lu, packet = %d, recordCount = %lu, deviceId = %lu"),
local->wii_binary_id, local->bindata_type, int(local->bindata_packet), local->recordCount, local->deviceId
);
*/
}
// setData - literlaly set the data
// - However, it pulls data live from devices, therefore you need next to get old data
// - setBlockMetadata - Use a block to set the Metadata values
// - setBlockResults - Set the block for the results values
bool WII5BinData::setData(uint32_t t, void* buf, uint16_t maxSize) {
char* ptr = (char*)buf;
if (getSize(t) > maxSize) {
console.log(LOG_FATAL, F("BinData: Standard Data too big for buger provided. Try splitting."));
return false;
}
{
WII5_BINDATA_HEADER* local = (WII5_BINDATA_HEADER*)ptr;
if ( (local->wii_binary_id != 60001) || (local->bindata_type != t) ) {
// TODO fix the %d here, should be unsigned.
console.log(LOG_DEBUG, F("*** (2) binary id = %d, binary type = %lu, packet = %d, recordCount = %lu, deviceId = %lu"),
local->wii_binary_id, local->bindata_type, int(local->bindata_packet), local->recordCount, local->deviceId
);
console.log(LOG_FATAL, F("BinData: *** INTERNAL ERROR *** setData called without valid header first"));
}
}
// Header
ptr += sizeof(WII5_BINDATA_HEADER);
if (BitVal(t, 0)) {
WII5_BINDATA_0* local = (WII5_BINDATA_0*)ptr;
if (wii5Weather_18B20.age < (WII5TIME_12HOUR * 1000))
local->temperature = wii5Weather_18B20.value;
else
local->temperature = 25500; // TODO Hard coded
if (wii5Battery.age < (WII5TIME_12HOUR * 1000))
local->voltage = wii5Battery.value;
else
local->voltage = 25500; // TODO Hard coded
local->last = now();
local->mode = wii5Controller.getMode();
// TODO Check valid ???
local->gps_lat = wii5Gps.gps->location.lat();
local->gps_lon = wii5Gps.gps->location.lng();
ptr += sizeof(WII5_BINDATA_0);
}
if (BitVal(t, 1)) {
WII5_BINDATA_1* local = (WII5_BINDATA_1*)ptr;
local->altitude = wii5Gps.gps->altitude.meters();
local->hdop = wii5Gps.gps->hdop.value();
local->satellites = wii5Gps.gps->satellites.value();
local->when = wii5Gps.when;
local->lastRunTime = wii5Gps.lastRunTime;
ptr += sizeof(WII5_BINDATA_1);
}
if (BitVal(t, 2)) {
WII5_BINDATA_2* local = (WII5_BINDATA_2*)ptr;
// TODO 2024 - signalQuality ?
ptr += sizeof(WII5_BINDATA_2);
}
if (BitVal(t, 3)) {
WII5_BINDATA_3* local = (WII5_BINDATA_3*)ptr;
local->status = 0; // Special status bit fields to tell us what is going on
local->command = wii5Commands.lastCommandCmd; // Commands as usual, top bit (32) is on for ACK
// TODO local->id = wii5Commands.lastCommandId; // Resposne must contain request id in case we have many
local->id = 0;
local->response = wii5Commands.lastCommandResult;
strncpy(local->message, wii5Commands.lastCommandMessage, sizeof(local->message));
ptr += sizeof(WII5_BINDATA_3);
}
if (BitVal(t, 4)) {
WII5_BINDATA_4* local = (WII5_BINDATA_4*)ptr;
strncpy(local->message, wii5Commands.lastCommandMessage, sizeof(local->message));
ptr += sizeof(WII5_BINDATA_4);
}
if (BitVal(t, 5)) {
WII5_BINDATA_5* local = (WII5_BINDATA_5*)ptr;
local->runCount = sh3dNodeConfig.getRunCount();
local->integerVersion = WII5_SOFTWARE_INTVER;
ptr += sizeof(WII5_BINDATA_5);
}
if (BitVal(t, 6)) {
ptr += sizeof(WII5_BINDATA_6);
}
if (BitVal(t, 7)) {
ptr += sizeof(WII5_BINDATA_7);
}
if (BitVal(t, 8)) {
ptr += sizeof(WII5_BINDATA_8);
}
if (BitVal(t, 9)) {
ptr += sizeof(WII5_BINDATA_8);
}
if (BitVal(t, 10)) {
ptr += sizeof(WII5_BINDATA_10);
}
if (BitVal(t, 11)) {
ptr += sizeof(WII5_BINDATA_11);
}
if (BitVal(t, 12)) {
ptr += sizeof(WII5_BINDATA_12);
}
if (BitVal(t, 13)) {
ptr += sizeof(WII5_BINDATA_13);
}
if (BitVal(t, 14)) {
ptr += sizeof(WII5_BINDATA_14);
}
if (BitVal(t, 15)) {
ptr += sizeof(WII5_BINDATA_15);
}
return true;
}
bool WII5BinData::setBlockMetadata(uint32_t t, void* buf, uint16_t maxSize, WII5MetaDataObject* metadata) {
if (getSize(t) > maxSize) {
console.log(LOG_FATAL, F("BinData: Results Object too big for buger provided. Try splitting."));
return false;
}
char* ptr = (char*)buf;
ptr += sizeof(WII5_BINDATA_HEADER);
if (BitVal(t, 0)) {
WII5_BINDATA_0* local = (WII5_BINDATA_0*)ptr;
local->temperature = metadata->temperatureValue;
local->voltage = metadata->batteryValue;
local->mode = metadata->mode;
local->gps_lat = metadata->gpsLat;
local->gps_lon = metadata->gpsLon;
ptr += sizeof(WII5_BINDATA_0);
}
if (BitVal(t, 1)) {
WII5_BINDATA_1* local = (WII5_BINDATA_1*)ptr;
/*
local->altitude = wii5Gps.gps->altitude.meters();
local->hdop = wii5Gps.gps->hdop.value();
local->satellites = wii5Gps.gps->satellites.value();
local->when = wii5Gps.when;
local->lastRunTime = wii5Gps.lastRunTime;
*/
ptr += sizeof(WII5_BINDATA_1);
}
if (BitVal(t, 2)) {
ptr += sizeof(WII5_BINDATA_2);
}
if (BitVal(t, 3)) {
WII5_BINDATA_3* local = (WII5_BINDATA_3*)ptr;
ptr += sizeof(WII5_BINDATA_3);
}
if (BitVal(t, 4)) {
ptr += sizeof(WII5_BINDATA_4);
}
if (BitVal(t, 5)) {
ptr += sizeof(WII5_BINDATA_5);
}
if (BitVal(t, 6)) {
ptr += sizeof(WII5_BINDATA_6);
}
if (BitVal(t, 7)) {
ptr += sizeof(WII5_BINDATA_7);
}
if (BitVal(t, 8)) {
ptr += sizeof(WII5_BINDATA_8);
}
if (BitVal(t, 9)) {
ptr += sizeof(WII5_BINDATA_8);
}
if (BitVal(t, 10)) {
ptr += sizeof(WII5_BINDATA_10);
}
if (BitVal(t, 11)) {
ptr += sizeof(WII5_BINDATA_11);
}
if (BitVal(t, 12)) {
ptr += sizeof(WII5_BINDATA_12);
}
if (BitVal(t, 13)) {
ptr += sizeof(WII5_BINDATA_13);
}
if (BitVal(t, 14)) {
ptr += sizeof(WII5_BINDATA_14);
}
if (BitVal(t, 15)) {
ptr += sizeof(WII5_BINDATA_15);
}
}
bool WII5BinData::setBlockResults(uint32_t t, void* buf, uint16_t maxSize, WII5Processed* processed) {
char* ptr = (char*)buf;
ptr += sizeof(WII5_BINDATA_HEADER);
if (BitVal(t, 0)) { ptr += sizeof(WII5_BINDATA_0); }
if (BitVal(t, 1)) { ptr += sizeof(WII5_BINDATA_1); }
if (BitVal(t, 2)) { ptr += sizeof(WII5_BINDATA_2); }
if (BitVal(t, 3)) { ptr += sizeof(WII5_BINDATA_3); }
if (BitVal(t, 4)) { ptr += sizeof(WII5_BINDATA_4); }
if (BitVal(t, 5)) { ptr += sizeof(WII5_BINDATA_5); }
if (BitVal(t, 6)) { ptr += sizeof(WII5_BINDATA_6); }
// processed1
if (BitVal(t, 7)) {
WII5_BINDATA_7* local = (WII5_BINDATA_7*)ptr;
/* local->tz_max[4]; local->htm_max[4]; local->hcm_max[4]; local->hz_max[4]; */
memcpy(local, &processed->processed1, sizeof(float) * 16); // 4 groups of floats
ptr += sizeof(WII5_BINDATA_7);
}
// processed1
if (BitVal(t, 8)) {
WII5_BINDATA_8* local = (WII5_BINDATA_8*)ptr;
local->processed1 = processed->processed1;
ptr += sizeof(WII5_BINDATA_8);
}
// processed2
if (BitVal(t, 9)) {
WII5_BINDATA_9* local = (WII5_BINDATA_9*)ptr;
local->processed2 = processed->processed2;
ptr += sizeof(WII5_BINDATA_8);
}
if (BitVal(t, 11)) {
WII5_BINDATA_11* local = (WII5_BINDATA_11*)ptr;
memcpy(&local->direction, &processed->processed2.part2int[8], sizeof(int16_t) * 54);
ptr += sizeof(WII5_BINDATA_11);
}
if (BitVal(t, 12)) {
WII5_BINDATA_12* local = (WII5_BINDATA_12*)ptr;
ptr += sizeof(WII5_BINDATA_12);
}
if (BitVal(t, 13)) {
WII5_BINDATA_13* local = (WII5_BINDATA_13*)ptr;
memcpy(&local->moments, &processed->processed2.part2float[0], sizeof(float) * 7);
ptr += sizeof(WII5_BINDATA_13);
}
if (BitVal(t, 14)) {
WII5_BINDATA_14* local = (WII5_BINDATA_14*)ptr;
memcpy(&local->psd, &processed->processed1.part1float[18], sizeof(float) * 55);
ptr += sizeof(WII5_BINDATA_14);
}
}
WII5_BINDATA_3* WII5BinData::getCommand(void* buf, uint16_t maxSize) {
uint32_t t = 0;
SetBit(t, 3);
char* ptr = (char*)buf;
{
WII5_BINDATA_HEADER* local = (WII5_BINDATA_HEADER*)ptr;
if (local->wii_binary_id != 60001) {
console.log(LOG_WARN, F("BinData - not a valid header"));
return NULL;
}
// Should upport multiple, but simplistic for now
if (local->bindata_type != t) {
console.log(LOG_WARN, F("BinData - could not find valid command"));
return NULL;
}
ptr += sizeof(WII5_BINDATA_HEADER);
}
console.log(LOG_INFO, F("BinData - found valid command"));
return (WII5_BINDATA_3*)ptr;
}
// dumpData - show the data to the console for debugging
void WII5BinData::dumpData(void* buf, uint16_t maxSize, bool values) {
uint32_t t = 0;
char* ptr = (char*)buf;
{
WII5_BINDATA_HEADER* local = (WII5_BINDATA_HEADER*)ptr;
// local->wii_binary_id = 60001;
t = local->bindata_type;
ptr += sizeof(WII5_BINDATA_HEADER);
console.printf(F("# BinData Header found\r\n"));
console.printf(F("# deviceId = %lu\r\n"), local->deviceId);
console.printf(F("# recordCount = %lu\r\n"), local->recordCount);
}
if (BitVal(t, 0)) {
WII5_BINDATA_0* local = (WII5_BINDATA_0*)ptr;
console.printf(F("# BinData 0 - title=%s\r\n"), strTitle(0, false));
if (values) {
console.printf(F("# temperature = %d\r\n"), int(local->temperature));
console.printf(F("# voltage = %d\r\n"), int(local->voltage));
console.printf(F("# last = %ld\r\n"), long(local->last));
console.printf(F("# mode = %d\r\n"), int(local->mode));
console.printf(F("# gps_lat = %ld\r\n"), long(local->gps_lat * GPS_POS_MULT));
console.printf(F("# gps_lon = %ld\r\n"), long(local->gps_lon * GPS_POS_MULT));
}
ptr += sizeof(WII5_BINDATA_0);
}
if (BitVal(t, 1)) {
WII5_BINDATA_1* local = (WII5_BINDATA_1*)ptr;
console.printf(F("# BinData 1 - title=%s\r\n"), strTitle(1, false));
if (values) {
console.printf(F("# gps_altitude = %ld\r\n"), long(local->altitude));
console.printf(F("# gps_hdop = %d\r\n"), int(local->hdop));
console.printf(F("# gps_satellites = %d\r\n"), int(local->satellites));
console.printf(F("# gps_when = %ld\r\n"), long(local->when));
console.printf(F("# gps_lastRunTime = %ld\r\n"), long(local->lastRunTime));
}
ptr += sizeof(WII5_BINDATA_1);
}
if (BitVal(t, 2)) {
WII5_BINDATA_2* local = (WII5_BINDATA_2*)ptr;
console.printf(F("# BinData 2 - title=%s\r\n"), strTitle(2, false));
if (values) {
console.printf(F("# lastRunTime = %d\r\n"), int(local->lastRunTime));
console.printf(F("# signalQuality = %d\r\n"), int(local->signalQuality));
}
ptr += sizeof(WII5_BINDATA_2);
}
if (BitVal(t, 3)) {
console.printf(F("# BinData 3 - title=%s\r\n"), strTitle(3, false));
ptr += sizeof(WII5_BINDATA_3);
}
if (BitVal(t, 4)) {
console.printf(F("# BinData 4 - title=%s\r\n"), strTitle(4, false));
ptr += sizeof(WII5_BINDATA_4);
}
if (BitVal(t, 5)) {
console.printf(F("# BinData 5 - title=%s\r\n"), strTitle(5, false));
ptr += sizeof(WII5_BINDATA_5);
}
if (BitVal(t, 6)) {
console.printf(F("# BinData 6 - title=%s\r\n"), strTitle(6, false));
ptr += sizeof(WII5_BINDATA_6);
}
if (BitVal(t, 7)) {
console.printf(F("# BinData 7 - title=%s\r\n"), strTitle(7, false));
ptr += sizeof(WII5_BINDATA_7);
}
if (BitVal(t, 8)) {
console.printf(F("# BinData 8 - title=%s\r\n"), strTitle(8, false));
ptr += sizeof(WII5_BINDATA_8);
}
if (BitVal(t, 9)) {
console.printf(F("# BinData 9 - title=%s\r\n"), strTitle(9, false));
ptr += sizeof(WII5_BINDATA_8);
}
if (BitVal(t, 10)) {
console.printf(F("# BinData 10 - title=%s\r\n"), strTitle(10, false));
ptr += sizeof(WII5_BINDATA_10);
}
if (BitVal(t, 11)) {
console.printf(F("# BinData 11 - title=%s\r\n"), strTitle(11, false));
ptr += sizeof(WII5_BINDATA_11);
}
if (BitVal(t, 12)) {
console.printf(F("# BinData 12 - title=%s\r\n"), strTitle(12, false));
ptr += sizeof(WII5_BINDATA_12);
}
if (BitVal(t, 13)) {
console.printf(F("# BinData 13 - title=%s\r\n"), strTitle(13, false));
ptr += sizeof(WII5_BINDATA_13);
}
if (BitVal(t, 14)) {
console.printf(F("# BinData 14 - title=%s\r\n"), strTitle(14, false));
ptr += sizeof(WII5_BINDATA_14);
}
if (BitVal(t, 15)) {
console.printf(F("# BinData 15 - title=%s\r\n"), strTitle(15, false));
ptr += sizeof(WII5_BINDATA_15);
}
}
void WII5BinData::showBlocks(uint32_t t) {
console.printf(F("BinData Type=%lu, Blocks="), t);
for (uint8_t b = 0; b < 16; b++) {
if (BitVal(t, b)) {
console.printf(F("%d, "), int(b));
}
}
console.printf(F(" size=%d"), getSize(t));
}
void WII5BinData::showSplit(uint32_t in_t, uint16_t max_size, char* bufout, uint16_t bufsize) {
uint8_t nextBit = 0;
uint32_t outType = 0;
// TODO: also fill bufout/bufsize with the split summary so callers receive
// structured data, not just console output.
(void)bufout; (void)bufsize;
console.printf(F("# Orig "));
showBlocks(in_t);
console.printNewLine();
uint8_t count = 0;
while (getSplit(in_t, max_size, &nextBit, &outType)) {
console.printf(F("# New %d "), count);
showBlocks(outType);
console.printNewLine();
count++;
if (count > 25) {
console.log(LOG_FATAL, F("BinData: More than 25 splits, not supported"));
return;
}
}
}
// showSizes - all sizes to console.
void WII5BinData::showSizes(char* bufout, uint16_t bufsize) {
console.printf(F("# BinData Size overhead=%d\r\n"), sizeof(WII5_BINDATA_HEADER));
// TODO: also fill bufout/bufsize with the size summary so callers receive
// structured data, not just console output.
(void)bufout; (void)bufsize;
for (uint8_t x = 0; x < 16; x++) {
console.printf(F("# BinData Size bit=%d, size=%d, title=%s\r\n"),
int(x),
getSizeOne(x),
strTitle(x, false)
);
if (getSizeOne(x) > 300) {
console.printf(F("# *** WARNING *** Packet > 300 bytes ***\r\n"));
}
}
}
// strTitle - get the title for this object
char* WII5BinData::strTitle(uint8_t bit, bool ext) {
switch(bit) {
case 0: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_0_TEXT)); break;
case 1: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_1_TEXT)); break;
case 2: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_2_TEXT)); break;
case 3: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_3_TEXT)); break;
case 4: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_4_TEXT)); break;
case 5: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_5_TEXT)); break;
case 6: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_6_TEXT)); break;
case 7: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_7_TEXT)); break;
case 8: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_8_TEXT)); break;
case 9: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_9_TEXT)); break;
case 10: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_10_TEXT)); break;
case 11: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_11_TEXT)); break;
case 12: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_12_TEXT)); break;
case 13: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_13_TEXT)); break;
case 14: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_14_TEXT)); break;
default: strcpy_P(wii5BufferString, (PGM_P) F("unknown")); break;
};
return wii5BufferString;
}
bool WII5BinData::hasType(uint8_t bit, void* buf, uint16_t maxSize) {
// TODO Check bit
}
void* WII5BinData::getData(uint8_t bit, void* buf, uint16_t maxSize) {
// Find location in buffer
}
WII5BinData wii5BinData;
+239
View File
@@ -0,0 +1,239 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5BinData.h
* @brief Binary message format for Iridium SBD: layout, packing, and split logic.
*/
#ifndef WII5BinData_h
#define WII5BinData_h
#include <WII5.h>
// Header Size = 15
typedef struct {
// 0
uint16_t wii_binary_id; // Fixed ID for the old system - 60001
// 2
uint32_t bindata_type; // Which packet type is this.
// 6
uint8_t bindata_packet; // Which part of the packet is this - note we don't need total, can claculate from type
// Essential data to allow packets to be put back together
// 7
uint32_t deviceId; // Ideally move back to uint32_t
// 11
uint32_t recordCount;
} WII5_BINDATA_HEADER;
#define WII5_BINDATA_0_TEXT "Status,Temp,Volts,GPS"
typedef struct {
// uint8_t deviceId_SHORT; // Ideally move back to uint32_t
uint32_t status; // Special status bit fields to tell us what is going on
time_t last;
int16_t voltage; // 0..16 with 2 decimal places -10000 .. + 10000
int16_t temperature; // -100 .. +100 with two decimal places 2452
uint8_t mode;
float gps_lat;
float gps_lon;
// Ideally status - number or reboots/resets and other errors
// How many messages sent, disk space and lots more.
} WII5_BINDATA_0;
#define WII5_BINDATA_1_TEXT "GPS Extended"
// Detailed GPS data
typedef struct {
uint16_t lastRunTime; // Number of seconds to lock last time
time_t when; // When we last got a time lock
int16_t altitude; // Altitutde in meters
uint8_t satellites;
float hdop;
} WII5_BINDATA_1;
#define WII5_BINDATA_2_TEXT "Iridium Extended"
// Detailed Iridium data
typedef struct {
uint16_t lastRunTime; // How long did the last one take
uint8_t signalQuality;
// uint32_t lastId; (the last id sent etc)
// total_sent
// total_received (I think the Iridium knows)
// imei - very large
} WII5_BINDATA_2;
#define WII5_BINDATA_3_TEXT "Message - Small (<50 bytes)"
// Commands - Tiny version
typedef struct {
uint32_t status; // Special status bit fields to tell us what is going on
uint32_t command; // Commands as usual, top bit (32) is on for ACK
uint8_t id; // Resposne must contain request id in case we have many
uint8_t response;
// TODO 2024 - This is a BREAKING CHANGE for any old buoys - could be fixed on WII3_Server BinData by supporting 30 or 25 text chars
char message[25]; // On ACK/NACK it can contain error data... TODO 2024 Changed from 30 to 25 - where else / what else
} WII5_BINDATA_3;
#define WII5_BINDATA_4_TEXT "Message - large (< 300 bytes)"
// Commands - full size version
typedef struct {
// uint32_t status; // Special status bit fields to tell us what is going on
// uint32_t command; // Commands as usual, top bit (32) is on for ACK
// uint8_t id;
// uint8_t response;
char message[235]; // On ACK/NACK it can contain error data
// Here we may use this to send a script, by including in this
// message tag - sh/1/3: - ie. write to shell script, Part 1 of 3
// Rest is concat to the end of the scipt
} WII5_BINDATA_4;
// TODO Idea - Results/Processed/Run sumary - HMO etc?
#define WII5_BINDATA_5_TEXT "Extra Buoy Information"
typedef struct {
uint32_t runCount;
uint32_t integerVersion; // 16
// char version[20];
} WII5_BINDATA_5;
#define WII5_BINDATA_6_TEXT "Processed - QF"
typedef struct {
float qf_mvar;
float qf_p_accel;
// (gyro not being used) float qf_p_gyro;
// (mag not being used) float qf_p_mag;
// (kistler not being used) float qf_kist",
// (kistler not being used) qf_p_kist",
float qf_imu;
float qf_head;
float a;
float b;
} WII5_BINDATA_6;
#define WII5_BINDATA_7_TEXT "Processed - _max"
typedef struct {
float tz_max[4];
float htm_max[4];
float hcm_max[4];
float hz_max[4];
} WII5_BINDATA_7;
#define WII5_BINDATA_8_TEXT "WII5Processed1"
typedef struct {
WII5Processed1 processed1;
} WII5_BINDATA_8;
#define WII5_BINDATA_9_TEXT "WII5Processed2"
typedef struct {
WII5Processed2 processed2;
} WII5_BINDATA_9;
#define WII5_BINDATA_10_TEXT "WII5MetaDataObject"
typedef struct {
WII5MetaDataObject metadata;
} WII5_BINDATA_10;
// Processed - Direction
#define WII5_BINDATA_11_TEXT "Processed - Direction array"
typedef struct {
int direction[54];
} WII5_BINDATA_11;
#define WII5_BINDATA_12_TEXT "Processed - Misc and Quality"
// Processed - Metadatas full (excluding kistler, mag, gyro)
// TODO: trim BinData record set to active fields
typedef struct {
float hmo;
float theta;
float dp;
float s;
float r;
float nstd;
float f2;
float yaw_std;
float open_water;
float power_diff;
float tp;
} WII5_BINDATA_12;
// Moments
#define WII5_BINDATA_13_TEXT "Processed - Moment array"
typedef struct {
float moments[7];
} WII5_BINDATA_13;
// PSD
#define WII5_BINDATA_14_TEXT "Processed - PSD array"
typedef struct {
float psd[55];
} WII5_BINDATA_14;
typedef struct {
} WII5_BINDATA_15;
/**
* @brief Binary message format for Iridium SBD.
*
* Each SBD message starts with a WII5_BINDATA_HEADER (binary id 60001,
* record metadata, a 32-bit `bindata_type` bitmap), then a sequence of
* fixed-size payload structs (WII5_BINDATA_0 .. _15) selected by which
* bits are set in `bindata_type`. The `getSplit()` helper splits an
* over-sized type bitmap across multiple 340-byte SBD frames.
*/
class WII5BinData {
public:
WII5BinData() {}
/** @brief One-time bring-up. */
void begin();
/** @brief Pop the next chunk that fits in `max_size` bytes; advances start_bit. */
bool getSplit(uint32_t in_t, uint16_t max_size, uint8_t* start_bit, uint32_t* out_t);
/** @brief Size of a single payload struct selected by bit `b`. */
uint16_t getSizeOne(uint8_t b);
/** @brief Set bit `b` in `t` and return the modified value. */
uint16_t setBit(uint32_t t, uint8_t b);
/** @brief Total wire size for a type bitmap, including the header. */
uint16_t getSize(uint32_t t);
/** @brief Total payload size for a type bitmap, excluding the header. */
uint16_t getSizeObj(uint32_t t);
/** @brief Initialize a binary buffer with header for type `t`. */
bool createData(uint32_t t, void* buf, uint16_t maxSize, uint32_t recordCount = 0);
/** @brief Fill the payload structs in `buf` from live device state. */
bool setData(uint32_t t, void* buf, uint16_t maxSize);
/** @brief Fill the payload structs from a pre-loaded SD metadata block. */
bool setBlockMetadata(uint32_t t, void* buf, uint16_t maxSize, WII5MetaDataObject* metadata);
/** @brief Fill the payload structs from a pre-loaded SD results (processed) block. */
bool setBlockResults(uint32_t t, void* buf, uint16_t maxSize, WII5Processed* processed);
/** @brief Print a human-readable dump of the buffer to the console. */
void dumpData(void* buf, uint16_t maxSize, bool values);
/** @brief Print the size of every BinData payload type. */
void showSizes(char* bufout = NULL, uint16_t bufsize = 0);
/** @brief Print the split sequence for a given type bitmap. */
void showSplit(uint32_t in_t, uint16_t max_size, char* bufout = NULL, uint16_t bufsize = 0);
/** @brief Human-readable title for payload bit `bit`. */
char* strTitle(uint8_t bit, bool ext);
/** @brief Does this buffer's header type-bitmap include bit `bit`? */
bool hasType(uint8_t bit, void* buf, uint16_t maxSize);
/** @brief Pointer to the payload struct for bit `bit` inside `buf`. */
void* getData(uint8_t bit, void* buf, uint16_t maxSize);
/** @brief Validate header and return pointer to the embedded command struct. */
WII5_BINDATA_3* getCommand(void* buf, uint16_t maxSize);
/** @brief Print the list of bits set in a type bitmap. */
void showBlocks(uint32_t t);
protected:
bool testDone;
};
extern WII5BinData wii5BinData;
#endif
+1504
View File
File diff suppressed because it is too large Load Diff
+114
View File
@@ -0,0 +1,114 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5Commands.h
* @brief Command protocol handler: parses and dispatches @-prefixed commands.
*/
#ifndef WII5Commands_h
#define WII5Commands_h
#include <Arduino.h>
#include <WII5.h>
/**
* @brief Text-protocol command parser and dispatcher.
*
* Accepts commands from multiple inputs (local console, Iridium SBD,
* radio, buttons) in two syntaxes:
* - "@AREA,cmd,arg1,arg2,..." (the AT-style protocol)
* - "X;" single-letter "standard" style (used in manual test mode)
*
* Records the most recent command in lastCommand* members so result and
* acknowledge replies can refer back to it.
*/
class WII5Commands : public WII5 {
public:
virtual WII5_CONTROLLERS controllerId() {return WII5CONTROLLER_COMMANDS;}
/** @brief One-time bring-up. */
void begin();
/** @brief Feed an AT-style command into the command pipeline as if it came from the console. */
bool injectCommand(char *sendCmd);
/** @brief Drain pending "X;" single-letter commands from the console. */
bool processConsoleStandard();
/** @brief Drain pending "@AREA,cmd,..." AT-style commands from the console. */
bool processConsoleAT();
/** @brief Translate physical button events (click/long-hold) into commands. */
bool processButtons();
/** @brief Drain pending packets from the LoRa network (if enabled). */
bool processNetwork();
/** @brief Process any incoming BinData payload (typically from Iridium). */
bool processBinData();
/** @brief Execute a parsed command; returns true on success. */
bool runCommand(WII5_COMMANDS cmd, char** params = NULL, uint8_t paramsLen = 0);
/** @brief Record the source/area of the command currently being processed. */
void setupLast(WII5_DEVICES device, WII5_PORTS port, WII5_AREAS area, WII5_COMMANDS cmd = WII5COMMAND_NONE);
/** @brief Finalize lastCommand bookkeeping (call after runCommand). */
void doneLast();
/** @brief Set the result + message for the last command (mutable buffer). */
void resultLast(WII5_RESULTS result, char *message);
/** @brief Set the result + message for the last command (PROGMEM string). */
void resultLast(WII5_RESULTS result, const __FlashStringHelper *area);
/** @brief Set the result for the last command without altering the message. */
void resultLast(WII5_RESULTS result);
/** @brief Print a debug dump of command state to the console. */
void dump();
elapsedMillis disableButtons;
// ------------------------------------------------------------------------------
// lastCommand!
// When?
elapsedMillis lastCommandAge;
time_t lastCommandT;
// Who sent this?
WII5_PORTS lastCommandPort;
WII5_DEVICES lastCommandDevice;
// Which area and commands was parsed (e.g. area=WII5)
WII5_AREAS lastCommandArea;
// Main command - e.g. SETTINGS_DEVICEID
WII5_COMMANDS lastCommandCmd;
uint32_t lastCommandParam1; // Keep just the params in int format - should be enough
uint32_t lastCommandParam2; // Keep just the params in int format - should be enough
uint32_t lastCommandParam3; // Keep just the params in int format - should be enough
// Result of command - success = yes/no
WII5_RESULTS lastCommandResult;
char lastCommandMessage[WII5_RESULT_MESSAGE]; // Error string
// Acknowldge reply
bool lastCommandAckComplete;
protected:
elapsedMillis delayWait;
time_t when;
// ------------------------------------------------------------------------------
// Button managmement
elapsedMillis buttonHeld;
elapsedMillis buttonReleaseWait;
bool buttonActive;
bool buttonReleased;
uint8_t buttonMode;
// ------------------------------------------------------------------------------
};
extern WII5Commands wii5Commands;
#endif
+657
View File
@@ -0,0 +1,657 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5Communications.cpp
* @brief High-level communications dispatcher: orchestrates Iridium send/receive flows.
*/
/*
TODO 2024 - This seems a little over complicated, any simplification possible
* List of results to send with current SD card???
* Status - what to send what not to?
TODO: Full testing, especially:
. Receiving multiple messages when only sending one
. New sdBlock code for loading and finding records
Communications - Really in control of Iridium
However, it may become more, startup of Weather and Voltage.
Possibly even read/write SD cared for data
When is a message sent:
* By request - e.g. sendBinModeData
* Automatically - e.g. looking for unset results
* By time - if start is called and we get to JUMP, and it has been more than 12 hours.
(this last one covers the case where something has gotten stuck - e.g. Sparton Failure and no results to post)
Template in another Mode / State machine
Simple Mode - Just send messages requested, no looking at SD
Start sending a message (don't call again until stopped)
wii5Communications.setSimpleMode();
wii5Communications.sendBinModeType(1, 0);
wii5Communications.start();
while (!wii5Communications.isRunning()) {
Wait...
Complex Mode:
Start the background processing
wii5Communications.setAutoMode();
Be very careful of SD changes, power on/off/swap
Ideally, once you start - don't turn it off.
However, this may not always be practical.
Send signalSD when an SD Card is changed.
*/
#include <Arduino.h>
#include <TimeLib.h>
#include <WII5.h>
void WII5Communications::begin() {
step = WII5COMMUNICATIONS_IDLE;
lastStep = WII5COMMUNICATIONS_END;
wait = 0;
autoMode = false;
metadataScan = false;
testMode = false;
mainBinType = 0;
metadataBlock = 0;
resultsBlock = 0;
mainRecordCount = 0;
// Clear the request
requestBinType = 0;
requestMetadataBlock = 0;
requestResultsBlock = 0;
requestRecordCount = 0;
updateStatus = false;
countSucces = 0;
countFailed = 0;
// Find Request - these should happen before other requests or auto searches
findBinType = 0;
findRecordCount = 0; // When it finds this record it will select this address and the correct Results record
// TODO 2024 - Disabled - this is unsafe. Can we make it so there is a maimum - look at other loops
disabled = false;
}
// NOTE: Call start to clear
void WII5Communications::setTESTING() { testMode = true; }
// TODO 2024 - New code to disable - must timeout !!!
bool WII5Communications::setDisabled(bool in) {
disabled = in;
if (disabled) {
step = WII5COMMUNICATIONS_DISABLED;
console.log(LOG_INFO, F("Communications: disabled on (do nothing)"));
}
else {
step = WII5COMMUNICATIONS_IDLE;
console.log(LOG_INFO, F("Communications: disabled off (move to idle)"));
}
wait = 0;
return disabled;
}
void WII5Communications::setAutoMode() { autoMode = true; }
void WII5Communications::setSimpleMode() { autoMode = false; }
bool WII5Communications::getAutoMode() { return autoMode; }
bool WII5Communications::getSimpleMode() { return !autoMode; }
void WII5Communications::signalSD() {
// Reset that we definitely want a metadata scan
metadataScan = true;
// disable existing scans - set them to 0 - as this is a new SD
sdCardId = 0;
sdStartBlock = 0;
sdCurrentBlock = 0;
// Check the mode, and put it in appropriate place.
start();
}
void WII5Communications::findRecord(uint32_t t, uint32_t rc) {
// Set a type and Record Count to find, it will override the previous
findRecordCount = rc;
findBinType = t;
}
void WII5Communications::sendText(void *buf = NULL, uint16_t bufSize = 0) {
if (buf && bufSize > 0) {
strncpy(wii5Commands.lastCommandMessage, buf, bufSize); // < 235 ? bufSize, 235);
}
uint32_t replyType = wii5BinData.setBit(0, 4);
sendBinModeType(replyType, 0, 0, 0);
setSimpleMode();
console.log(LOG_INFO, F("Communications: sendText() message=%s"), wii5Commands.lastCommandMessage);
start();
}
void WII5Communications::sendError() {
uint32_t replyType = wii5BinData.setBit(0, 3);
wii5Commands.lastCommandResult = WII5RESULTS_ERROR;
sendBinModeType(replyType, 0, 0, 0);
setSimpleMode();
console.log(LOG_INFO, F("Communications: sendError() message=%s"), wii5Commands.lastCommandMessage);
start();
}
void WII5Communications::sendBinModeType(uint32_t t, uint32_t recordCount, uint32_t mdBlock, uint32_t resultsBlock) {
updateStatus = false;
requestWait = 0;
requestBinType = t;
requestRecordCount = recordCount; // Note - this does not look up the count - uses it to check Block
// Leave this at 0 to load currecnt record count
requestMetadataBlock = mdBlock; // Leave this at zero to not load a Block
requestResultsBlock = resultsBlock; // Leave this at zero to not load a Block
}
/*
How to use this library.
setType - The BinData type to be sent
setResultsRecord - Pick and SD Card and record to read.
- Or could have a setResultsBuf
- Not sure how to choose SD card - as it may be locked by maths etc
- Not sure how to keep copy of data - 512 bytes too much
. readRecord, just before BinData.set ?
- How to deal with queues and wrong cards.
Advanced use
Instead of just a type with record etc, this library should handle
things like sending raw data.
Simple Mode
- Like used in Position. Just send message requested.
Queue Mode
- Find missing entries, not yet sent
*/
void WII5Communications::start() {
// Note: Test mode is turned to false at each start. So to enable test mode, start then setTESTING
testMode = false;
sdDone = 0;
sdTried = 0;
if (step == WII5COMMUNICATIONS_IDLE) {
step = WII5COMMUNICATIONS_PREPARE; wait = 0;
}
}
void WII5Communications::stop() {
// TODO I think we should consider a shutdown, or just use first?
if (step != WII5COMMUNICATIONS_IDLE) {
step = WII5COMMUNICATIONS_END; wait = 0;
}
}
bool WII5Communications::isRunning() {
return (step != WII5COMMUNICATIONS_IDLE);
}
void WII5Communications::loop() {
bool first = (lastStep != step);
lastStep = step;
switch (step) {
case WII5COMMUNICATIONS_DISABLED:
if (first) {
console.log(LOG_DEBUG, F("Communications: step disabled"));
}
break;
case WII5COMMUNICATIONS_IDLE:
if (first) {
if (disabled) {
step = WII5COMMUNICATIONS_DISABLED; wait = 0;
}
console.log(LOG_DEBUG, F("Communications: step idle - manual waiting for start"));
wii5Iridium.stop();
countSend = 0; // We use this to count how many in this run
}
// More than 14 hours since last messsage - send out a ping
else if (lastSuccess > ((uint32_t)14*3600000)) {
console.log(LOG_DEBUG, F("Communications: Not sent a successful message in 14 hours - send pinkg"));
sendBinModeType(1,1,0,0);
// TODO Custom text for the error/reason
lastSuccess = 0; // To stop repeating every loop
}
break;
case WII5COMMUNICATIONS_PREPARE:
step = WII5COMMUNICATIONS_IRIDIUM_START; wait = 0;
break;
case WII5COMMUNICATIONS_IRIDIUM_START:
if (first) {
console.log(LOG_DEBUG, F("Communications: step IRIDIUM_START (firmware, imei, signalQuality)"));
if (testMode) {
console.log(LOG_FATAL, F("FAKE: skipping iridium request Firmware"));
step = WII5COMMUNICATIONS_JUMP; wait = 0;
}
else {
wii5Iridium.requestFirmware();
}
binNextBit = 0; // Important to start split
binCount = 0;
}
// Waiting - we are ready to send the text
else if (wii5Iridium.waiting()) {
step = WII5COMMUNICATIONS_JUMP; wait = 0;
}
// TODO Hard coded
else if (wait > 600000) {
console.log(LOG_ERROR, F("Communications: Iridium Timed out after 600 seconds"));
step = WII5COMMUNICATIONS_JUMP; wait = 0;
}
break;
// Decide what to do
case WII5COMMUNICATIONS_JUMP:
if (first) {
}
// Already got one moving on
else if (mainBinType > 0) {
console.log(LOG_DEBUG, F("Communications: step JUMP - existing message in mainBinType"));
step = WII5COMMUNICATIONS_IRIDIUM_SEND; wait = 0;
}
// New request - set it and move on
else if (requestBinType > 0) {
console.log(LOG_DEBUG, F("Communications: step JUMP - new message in requestBiType"));
// One in the background - must clearit, so not used again
mainBinType = requestBinType;
metadataBlock = requestMetadataBlock;
resultsBlock = requestResultsBlock;
mainRecordCount = requestRecordCount;
countSucces = 0;
countFailed = 0;
// Clear the request
requestBinType = 0;
requestMetadataBlock = 0;
requestResultsBlock = 0;
requestRecordCount = 0;
step = WII5COMMUNICATIONS_IRIDIUM_SEND; wait = 0;
}
else if (autoMode) {
// TODO - autoMode - decide what to do
// Only when we have nothing else to do
step = WII5COMMUNICATIONS_SD_NEXT; wait = 0;
}
// else if (Old!) {
// TODO Send a minimum packet
// }
else {
step = WII5COMMUNICATIONS_END; wait = 0;
}
// TODO Decide on loading data from SD
// 1. requestBlock or similar manuarl request (Which SD Card?)
// 2. Auto Mode - find a record not sent and send it
break;
// SD Next - find a record
case WII5COMMUNICATIONS_SD_NEXT:
// Find out the SD Card and Block we started at - if either change, start again
// TODO - consider openging the SD Card ourselves
// No card is open.... quit
if (!sdBlock.cardIsOpen()) {
// TODO - consider opening one? - Nah - Capture mode only
console.log(LOG_DEBUG, F("Communications: card is not open - finishing"));
sdCardId = 0;
sdStartBlock = 0;
sdCurrentBlock = 0;
step = WII5COMMUNICATIONS_END; wait = 0;
}
// Nothing processed - lets get that data from the SD card
else if ( (sdCardId == 0) || (sdStartBlock == 0) ) {
console.log(LOG_DEBUG, F("Communications: new card... getting locations"));
sdCardId = sdBlock.getCardId();
sdStartBlock = sdBlock.getMetadataNext(); // 1 record past where to read
sdCurrentBlock = sdBlock.getMetadataNext(); // Current record read, starts past because never reads. Sub one first
}
// Card has changed, blank these and move on
else if ( (sdBlock.getCardId() != sdCardId) || (sdBlock.getMetadataNext() != sdStartBlock)) {
console.log(LOG_DEBUG, F("Communications: card changed ! - trying to read new card"));
console.log(LOG_DEBUG, F("Communications: OldSD=%d, NewCard=%d"), int(sdCardId), int(sdBlock.getCardId()));
console.log(LOG_DEBUG, F("Communications: OldBlock=%lu, NewBlock=%lu"), sdCurrentBlock, sdBlock.getMetadataNext());
sdCardId = 0;
sdStartBlock = 0;
sdCurrentBlock = 0;
}
// Is the next block we have to read before the start?
else if ((sdCurrentBlock - 1) < sdBlock.getMetadataFirst()) {
console.log(LOG_DEBUG, F("Communications: Read all metadata records to start"));
// Finsihed readin as far as we can.
sdCardId = 0;
sdStartBlock = 0;
sdCurrentBlock = 0;
step = WII5COMMUNICATIONS_END; wait = 0;
}
// TODO - check if we have gone back far enough - e.g. 100 records.
// TODO - FIND ! This is where we can do the find.
// Do 100 max tests
else if (sdTried > 100) {
console.log(LOG_DEBUG, F("Communications: Tried 100 records, Finishing"));
// Finsihed readin as far as we can.
sdCardId = 0;
sdStartBlock = 0;
sdCurrentBlock = 0;
step = WII5COMMUNICATIONS_END; wait = 0;
}
// Do 10 transmitts
else if (sdDone > 10) {
console.log(LOG_DEBUG, F("Communications: Done 10 transmitts. Finsihing"));
// Finsihed readin as far as we can.
sdCardId = 0;
sdStartBlock = 0;
sdCurrentBlock = 0;
step = WII5COMMUNICATIONS_END; wait = 0;
}
// Read record and use now - cos might loose it
else {
sdTried++;
sdCurrentBlock--;
console.log(LOG_DEBUG, F("Communications: reading block: %lu"), sdCurrentBlock);
if (!sdBlock.read(sdCurrentBlock)) {
console.log(LOG_FATAL, F("Communications: Error reading this block. Skipping to next"));
// TODO what next afer error?
}
else {
WII5MetaDataObject* local_metadata = (WII5MetaDataObject*)(sdBlock.metadata->data);
// TODO Print this out to test ?
if (
// Status is blank, not sent or tried
(sdBlock.metadata->status == 0)
&& (sdBlock.metadata->resultsBlockStart != 0)
&& (sdBlock.metadata->recordId != 0)
&& (sdBlock.metadata->recordId == local_metadata->recordCount)
) {
console.log(LOG_DEBUG, F("Communications: Metadata readyy, but is Result?"));
// Copy the values across, but zero if next record fails - because we loose the data
mainBinType = wii5Config.getCaptureBinaryType();
metadataBlock = sdCurrentBlock;
resultsBlock = sdBlock.metadata->resultsBlockStart;
mainRecordCount = sdBlock.metadata->recordId;
updateStatus = true;
bool badResults = false;
// Read the block
if (!sdBlock.read(resultsBlock)) {
badResults = true;
console.log(LOG_ERROR, F("Communications: Failed to read results for testing"));
}
// All records should match
else if (sdBlock.block->recordId != mainRecordCount) {
badResults = true;
console.log(LOG_ERROR, F("Communications: Results bock record ID does not match, skipping"));
}
// This is a way I know the RESULTS block has been written to
else if (sdBlock.block->status != mainRecordCount) {
badResults = true;
console.log(LOG_ERROR, F("Communications: Results status is not the record count - it isn't ready to send"));
}
if (badResults) {
// Abandon ship
console.log(LOG_FATAL, F("Communications: Error reading this block. Skipping to next"));
mainBinType = 0;
metadataBlock = 0;
resultsBlock = 0;
mainRecordCount = 0;
updateStatus = false;
}
else {
console.log(LOG_FATAL, F("Communications: Block ready to send = %lu"), resultsBlock);
step = WII5COMMUNICATIONS_IRIDIUM_SEND; wait = 0;
}
}
else {
console.log(LOG_DEBUG, F("Communications: Skipping - Status = %lu"), sdBlock.metadata->status);
}
}
}
break;
// SD Load Block
case WII5COMMUNICATIONS_SD_BLOCK:
step = WII5COMMUNICATIONS_IRIDIUM_SEND; wait = 0;
break;
// SEND a message
case WII5COMMUNICATIONS_IRIDIUM_SEND:
if (first) {
console.log(LOG_DEBUG, F("Communications: step IRIDIUM_SEND"));
}
// Safety
else if (binCount > 25) {
console.log(LOG_FATAL, F("Communications: More than 25 data splits - cancelling Iridium"));
step = WII5COMMUNICATIONS_END; wait = 0;
}
// if (wii5BinData.getSplit(wii5Config.getCommunicationsBinaryType(), 300, &binNextBit, &binType)) {
else {
// NOTE: Stil hard coded iridium number
if (wii5BinData.getSplit(mainBinType, 340, &binNextBit, &binType)) {
console.log(LOG_INFO, F("Communications: Sending BinData Splitting #=%d, original type/size=%lu/%d, new type/size=%lu/%d, nextBit=%d"),
binCount,
mainBinType, wii5BinData.getSize(mainBinType),
binType, wii5BinData.getSize(binType),
int(binNextBit)
);
binCount++;
// NOTE: setData will not set data from Blocks, call the necessary after steps.
// see BinData for more information.
wii5BinData.createData(binType, wii5BinaryIridium, 340, mainRecordCount);
wii5BinData.setData(binType, wii5BinaryIridium, 340); // TODO Hard coded
// NOTE: Performance. This should be split up into steps in this state machine, card open, read metadata, etc... This will do for now
if (metadataBlock) {
if (sdBlock.read(metadataBlock)) {
console.log(LOG_INFO, F("Communications: Loaded metadata=%lu and setting Iridium data"), metadataBlock);
// TODO Consider checking metadta block wth mainRecordCount
wii5BinData.setBlockMetadata(binType, wii5BinaryIridium, 340, (WII5MetaDataObject*)sdBlock.metadata->data);
}
else {
console.log(LOG_FATAL, F("Communications: FAILED TO LOAD Load metadata=%lu"), metadataBlock);
}
}
if (resultsBlock) {
if (sdBlock.read(resultsBlock)) {
console.log(LOG_INFO, F("Communications: Loaded results=%lu"), resultsBlock);
wii5BinData.setBlockResults(binType, wii5BinaryIridium, 340, (WII5Processed*)sdBlock.block->data);
}
else {
console.log(LOG_FATAL, F("Communications: FAILED TO LOAD Load results=%lu"), resultsBlock);
}
}
wii5Iridium.setBinSize(
wii5BinData.getSize(binType)
);
// Send it !
if (testMode)
console.log(LOG_FATAL, F("FAKE: Not requestSendBinary"));
else {
wii5Iridium.requestSendBinary();
countSend++;
}
waitStatus = true; // We are waiting for the status, unlike command replies
step = WII5COMMUNICATIONS_IRIDIUM_SEND_WAIT; wait = 0;
}
// Clear them and move on
else {
console.log(LOG_DEBUG, F("Communications: Iridium send complete, moving to end"));
if (
updateStatus
&& metadataBlock
&& (countSucces > 0)
// && (countFailed < 1) - TODO add this test
) {
// TODO how about some better meaning to the number?
sdDone++;
console.log(LOG_DEBUG, F("Communications: Updating status block=%lu, record=%lu, status=1"), metadataBlock, mainRecordCount);
sdBlock.metadataUpdateStatus(metadataBlock, mainRecordCount, 1);
sdBlock.read(metadataBlock);
console.log(LOG_INFO, F("Communications: TODO Updated status and read for block = %lu, record = %lu and status = %lu"),
metadataBlock, mainRecordCount, sdBlock.metadata->status
);
}
else {
console.log(LOG_INFO, F("Communications: Message sent but status not updated - metadataBlock=%lu,countSucces=%d,update=%d"), metadataBlock, int(countSucces), int(updateStatus));
}
updateStatus = false;
binNextBit = 0; // Important to start split
binCount = 0;
mainBinType = 0;
metadataBlock = 0;
resultsBlock = 0;
step = WII5COMMUNICATIONS_JUMP; wait = 0;
}
}
break;
// Wait for send and check reply
case WII5COMMUNICATIONS_IRIDIUM_SEND_WAIT:
if (first) {
console.log(LOG_DEBUG, F("Communications: step IRIDIUM_SEND_WAIT"));
if (testMode) {
console.log(LOG_FATAL, F("FAKE: Not waiting for Iridium"));
updateStatus = true;
countSucces++;
step = WII5COMMUNICATIONS_JUMP; wait = 0; first = true;
}
}
else if (wii5Iridium.waiting()) {
// Wait for status success/fail for counts. This allos us to ignore command messages
if (waitStatus) {
if (wii5Iridium.lastSend == WII5IRIDIUMSENDRESULT_OK) {
countSucces++;
lastSuccess = 0; // To stop repeating every loop
}
else
countFailed++;
waitStatus = false;
}
// TODO - check this isn't a race condition.
// Check incoming messages
if (wii5Iridium.lastReceive == WII5IRIDIUMRECEIVERESULT_OK) {
if (wii5Iridium.lastSend == WII5IRIDIUMSENDRESULT_OK)
console.log(LOG_INFO, F("Communications: (Send success) Received Iridium Data length=%d"), wii5Iridium.lastReceiveLen);
else
console.log(LOG_ERROR, F("Communications: (Send failed) Received Iridium Data length=%d"), wii5Iridium.lastReceiveLen);
wii5Commands.processBinData();
if ( (wii5Commands.lastCommandCmd != WII5COMMAND_NONE) && (wii5Commands.lastCommandResult != WII5RESULTS_UNKNOWN) ) {
console.log(LOG_ERROR, F("Communications: Sending reply command=%d, result=%d"), int(wii5Commands.lastCommandCmd), int(wii5Commands.lastCommandResult));
// Always send a result, allowing for next message too - 3 or 4 depending on lenght reply
uint32_t replyType = wii5BinData.setBit(0, 3);
wii5BinData.createData(replyType, wii5BinaryIridium, 340, 1);
wii5BinData.setData(replyType, wii5BinaryIridium, 340); // TODO Hard coded
wii5Iridium.setBinSize(wii5BinData.getSize(replyType));
wii5Iridium.requestSendBinary();
// Blank last command
wii5Commands.lastCommandCmd = WII5COMMAND_NONE;
wii5Commands.lastCommandResult = WII5RESULTS_UNKNOWN;
memset(wii5Commands.lastCommandMessage, 0, sizeof(wii5Commands.lastCommandMessage));
}
else {
// TODO Send error !
console.log(LOG_ERROR, F("Communications: Unable to reply command=%d, result=%d"), int(wii5Commands.lastCommandCmd), int(wii5Commands.lastCommandResult));
step = WII5COMMUNICATIONS_JUMP; wait = 0; first = true;
}
// No change of step - as we are sending again
}
else {
if (wii5Iridium.lastSend == WII5IRIDIUMSENDRESULT_OK)
console.log(LOG_INFO, F("Communications: (Send success) nothing received"));
else
console.log(LOG_ERROR, F("Communications: (Send failed) nothing received"));
// Check any further waiting messages - send same message again.
step = WII5COMMUNICATIONS_JUMP; wait = 0; first = true;
}
}
// TODO Configurables
else if (wait > 300000) {
console.log(LOG_ERROR, F("Communications: Iridium Timed out after 5 minutes"));
step = WII5COMMUNICATIONS_END; wait = 0;
}
break;
case WII5COMMUNICATIONS_END:
if (first) {
console.log(LOG_INFO, F("Communications: step END - checking"));
}
else if (metadataScan && (countSend < 1)) {
// Turn it off, so we don't get in here twice on no send.
metadataScan = false;
console.log(LOG_INFO, F("Communications: NO TRANSMISSIONS - Sending a default"));
uint32_t replyType = wii5BinData.setBit(1, 5);
sendBinModeType(replyType, 0, 0, 0);
setSimpleMode();
step = WII5COMMUNICATIONS_JUMP; wait = 0;
}
else {
metadataScan = false;
console.log(LOG_INFO, F("Communications: Sent %d messages"), countSend);
// Noithing left to do - lets shut it down.
wii5Iridium.stop();
step = WII5COMMUNICATIONS_IDLE; wait = 0;
}
break;
default:
console.log(LOG_FATAL, F("COMMUNICATIONS: Default step... step=%d"), step);
step = WII5COMMUNICATIONS_IDLE; wait = 0;
}
}
WII5Communications wii5Communications;
+136
View File
@@ -0,0 +1,136 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5Communications.h
* @brief High-level communications dispatcher: orchestrates Iridium send/receive flows.
*/
#ifndef WII5Communications_h
#define WII5Communications_h
#include <Arduino.h>
#include <WII5.h>
enum WII5COMMUNICATIONS_STEPS {
WII5COMMUNICATIONS_DISABLED,
WII5COMMUNICATIONS_IDLE,
WII5COMMUNICATIONS_PREPARE,
WII5COMMUNICATIONS_IRIDIUM_START,
WII5COMMUNICATIONS_JUMP,
WII5COMMUNICATIONS_FIND,
WII5COMMUNICATIONS_SD_NEXT,
WII5COMMUNICATIONS_SD_BLOCK,
WII5COMMUNICATIONS_IRIDIUM_SEND,
WII5COMMUNICATIONS_IRIDIUM_SEND_WAIT,
WII5COMMUNICATIONS_END
};
/**
* @brief Communications orchestration: manages a state machine across
* Iridium send/receive cycles, including pulling messages from SD blocks
* and splitting them across multiple SBD frames when too large.
*
* Three orthogonal modes:
* - autoMode: scan SD for unsent metadata and push it
* - simpleMode: only send what's explicitly requested
* - testMode: smoke-test the comms path without burning satellite minutes
*
* Plus a `disabled` master switch.
*/
class WII5Communications : public WII5Mode {
public:
WII5Communications() {}
/** @brief Is the comms state machine actively in a send/receive cycle? */
bool isRunning();
/** @brief One-time bring-up. */
void begin();
/** @brief State-machine tick: drives Iridium and SD interactions. */
void loop();
/** @brief Begin a new comms cycle (send pending data). */
void start();
/** @brief Abort the current comms cycle and idle. */
void stop();
/** @brief Hint to autoMode that the SD card has fresh data worth scanning. */
void signalSD();
/** @brief Queue a BinData message of type `t`, optionally pulled from given SD blocks. */
void sendBinModeType(uint32_t t, uint32_t recordCount, uint32_t mdBlock = 0, uint32_t resultsBlock = 0);
/** @brief Send arbitrary buffered text (bypasses the BinData frame). */
void sendText(void *buf = NULL, uint16_t bufSize = 0);
/** @brief Send the most recent error as an SBD message. */
void sendError();
/** @brief Find an SD record matching (binType, recordCount) for retransmission. */
void findRecord(uint32_t t, uint32_t recordCount);
/** @brief Enable test mode (no real Iridium traffic). */
void setTESTING();
/** @brief Enable autoMode: periodically scan SD and push new data. */
void setAutoMode();
/** @brief Enable simpleMode: send only what is explicitly requested. */
void setSimpleMode();
/** @brief Is autoMode currently the active mode? */
bool getAutoMode();
/** @brief Is simpleMode currently the active mode? */
bool getSimpleMode();
/** @brief Master enable/disable; returns previous state. */
bool setDisabled(bool in);
protected:
// Mode - These are actually mutually exclusive - disabled | noiridium | auto | test
bool disabled;
bool autoMode;
bool metadataScan;
bool testMode;
elapsedMillis lastSuccess;
// Send a message
elapsedMillis requestWait;
uint32_t requestBinType;
uint32_t requestRecordCount;
uint32_t requestMetadataBlock;
uint32_t requestResultsBlock;
uint8_t sdCardId;
bool updateStatus;
uint32_t sdStartBlock;
uint32_t sdCurrentBlock;
uint8_t countSucces;
uint8_t countFailed;
uint16_t countSend;
bool waitStatus;
uint32_t sdDone;
uint32_t sdTried;
uint32_t findBinType;
uint32_t findRecordCount;
// Loop
bool first;
elapsedMillis displayWait;
elapsedMillis wait;
WII5COMMUNICATIONS_STEPS step;
WII5COMMUNICATIONS_STEPS lastStep;
uint32_t minutes; // since midnight.
// State of the split to smaller chunks
uint8_t binNextBit;
uint8_t binCount;
uint32_t binType;
// The block we are working on
uint32_t mainBinType;
uint32_t metadataBlock;
uint32_t resultsBlock;
uint32_t mainRecordCount;
};
extern WII5Communications wii5Communications;
#endif
+358
View File
@@ -0,0 +1,358 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5Config.cpp
* @brief Persistent configuration in EEPROM: device ID, mode defaults, calibrations.
*/
/*
WII5Config
*/
#include <Arduino.h>
#include <WII5Config.h>
#include <WII5Sh3dConsole.h>
void WII5Config::begin() {
sh3dNodeConfig.begin();
_deviceId = (uint32_t)(sh3dNodeConfig.getNodeId());
readConfigData();
readStatusData();
#ifdef WII5_DEBUG_SERIAL
SerialDebug.print(F("CONFIG:ID:"));
SerialDebug.print(getDeviceId());
SerialDebug.println();
#endif
console.log(LOG_DEBUG, F("Config: id=%lu"), getDeviceId());
}
void WII5Config::resetStatusData() {
// Tag it
statusData.type = WII5_DATA_STATUS_TYPE;
statusData.version = WII5_DATA_STATUS_VERSION;
// statusData.xxxCount = 0;
}
void WII5Config::resetConfigData() {
// Tag it
configData.type = WII5_DATA_CONFIG_TYPE;
configData.version = WII5_DATA_CONFIG_VERSION;
// Sensible defaults
configData.gpsTimeout = 300;
configData.flags = 0x0;
configData.disableLowBattery = 0;
// MODE: Capture
configData.capturePeriod = 20;
configData.captureBinaryType = 769;
configData.captureRecords = 5120;
//
// 1 = Packet 0 - dates, times, lat, lon, etc.
// 769 = 0,8,9
//
// MODE: Position
configData.positionPeriod = 5;
configData.positionBinaryType = 1; // TODO Bogus
// MODE: Sleep
configData.sleepPeriod = 12 * 60; // 12 hours
configData.sleepBinaryType = 1; // TODO Bogus
configData.sleepUntil = 0; //
// MODE: Default and Temporary
configData.defaultMode = WII5MODE_CAPTURE;
configData.temporaryMode = WII5MODE_NONE;
configData.temporaryModeExpires = 0;
// Battery
configData.batteryLow = 900;
configData.batteryMid = 1050;
// Save it
updateConfigData();
}
// WARNING: Don't want to do this without clearning SD Card !
void WII5Config::resetCounters() {
sh3dNodeConfig.clearRecordCount();
sh3dNodeConfig.clearRunCount();
}
void WII5Config::readStatusData() {
if (sizeof(statusData) > WII5CONFIG_MAXBLOCK) {
console.log(LOG_ERROR, F("Config: Size of Status too big. Size=%d, Maxiumum=%d"), sizeof(statusData), WII5CONFIG_MAXBLOCK);
}
EEPROM.readBlock(WII5CONFIG_OFFSET + WII5CONFIG_STATUSDATA, statusData);
// TODO checking data etc.
}
void WII5Config::updateStatusData() {
EEPROM.updateBlock(WII5CONFIG_OFFSET + WII5CONFIG_STATUSDATA, statusData);
}
void WII5Config::readConfigData() {
if (sizeof(configData) > WII5CONFIG_MAXBLOCK) {
console.log(LOG_ERROR, F("Config: Size of Config too big. Size=%d, Maxiumum=%d"), sizeof(configData), WII5CONFIG_MAXBLOCK);
}
EEPROM.readBlock(WII5CONFIG_OFFSET + WII5CONFIG_CONFIGDATA, configData);
// TODO checking data etc.
}
void WII5Config::updateConfigData() {
EEPROM.updateBlock(WII5CONFIG_OFFSET + WII5CONFIG_CONFIGDATA, configData);
}
uint32_t WII5Config::getDeviceId() {
return _deviceId;
}
void WII5Config::setDeviceId(uint32_t newId) {
sh3dNodeConfig.setNodeId(newId);
_deviceId = newId;
}
void WII5Config::setDisableLowBattery(time_t exp) {
// TODO Check values
// lowBatteryDisable = exp;
}
time_t WII5Config::getDisableLowBattery() {
return 0; // lowBatteryDisable;
}
bool WII5Config::_checkMode(WII5_MODES newMode, uint32_t v1, uint32_t v2, uint32_t v3, uint32_t v4) {
// TODO check mode, and v1,2,3 dependeing on mode
// TODO check expiry ?
return true;
}
void WII5Config::setDefaultMode(WII5_MODES newMode) {
configData.defaultMode = newMode;
updateConfigData();
}
WII5_MODES WII5Config::getDefaultMode() {
return configData.defaultMode;
}
void WII5Config::setTemporaryMode(time_t exp, WII5_MODES newMode) {
configData.temporaryMode = newMode;
configData.temporaryModeExpires = exp;
updateConfigData();
}
WII5_MODES WII5Config::getTemporaryMode() {
return configData.temporaryMode;
}
uint32_t WII5Config::updateRecordCount() {
return (uint32_t)sh3dNodeConfig.updateRecordCount();
}
uint32_t WII5Config::getRecordCount() {
return (uint32_t)sh3dNodeConfig.getRecordCount();
}
bool WII5Config::getFlag(WII5_FLAGS f) {
return (BitVal(configData.flags, f) == 1);
}
void WII5Config::setFlag(WII5_FLAGS f, bool state) {
if (state)
SetBit(configData.flags, f);
else
ClearBit(configData.flags, f);
updateConfigData();
}
uint32_t WII5Config::getGpsTimeout() {
// Minimum 1 minute
if (configData.gpsTimeout < 60)
return 60;
// Maximum 10 minutes
else if (configData.gpsTimeout > 600)
return 600;
else
return configData.gpsTimeout;
}
void WII5Config::setGpsTimeout(uint32_t t) {
configData.gpsTimeout = t;
updateConfigData();
}
void WII5Config::setCaptureRecords(uint32_t in) {
configData.captureRecords = in;
updateConfigData();
}
uint32_t WII5Config::getCaptureRecords() {
if (configData.captureRecords < 100)
return 5120;
else if (configData.captureRecords > 57600)
return 57600;
else
return configData.captureRecords;
}
void WII5Config::setCapturePeriod(uint32_t in) {
configData.capturePeriod = in;
updateConfigData();
}
uint32_t WII5Config::getCapturePeriod() {
// Envorce limits 5 - 720 (12 hours)
// 12 Hours
if (configData.capturePeriod > 720)
return 720;
// 5 Minutes
else if (configData.capturePeriod < 5)
return 5;
else
return configData.capturePeriod;
}
void WII5Config::setCaptureBinaryType(uint32_t in) {
configData.captureBinaryType = in;
updateConfigData();
}
uint32_t WII5Config::getCaptureBinaryType() {
return configData.captureBinaryType;
}
void WII5Config::setPositionPeriod(uint32_t in) {
configData.positionPeriod = in;
updateConfigData();
}
uint32_t WII5Config::getPositionPeriod() {
return configData.positionPeriod;
}
void WII5Config::setPositionBinaryType(uint32_t in) {
configData.positionBinaryType = in;
updateConfigData();
}
uint32_t WII5Config::getPositionBinaryType() {
return configData.positionBinaryType;
}
void WII5Config::setSleepUntil(uint32_t in) {
configData.sleepUntil = in;
updateConfigData();
}
uint32_t WII5Config::getSleepUntil() {
return configData.sleepBinaryType;
}
void WII5Config::setSleepBinaryType(uint32_t in) {
configData.sleepBinaryType = in;
updateConfigData();
}
uint32_t WII5Config::getSleepBinaryType() {
return configData.sleepBinaryType;
}
void WII5Config::setSleepPeriod(uint32_t in) {
configData.sleepPeriod = in;
updateConfigData();
}
uint32_t WII5Config::getSleepPeriod() {
// Envorce limits 5 - 720 (12 hours)
// 6 Hours
if (configData.sleepPeriod > 360)
return 360;
// 1 minute minimum, becomes 1 hour if nothing
else if (configData.sleepPeriod < 1)
return 1;
else
return configData.sleepPeriod;
}
void WII5Config::dump(bool toConsole, Print* toOther) {
snprintf_P(
wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT,
PSTR("@Data,config,deviceid=%lu,recordid=%lu\r\n"),
getDeviceId(), getRecordCount()
);
if (toOther) toOther->print(wii5BufferConsolePrint);
if (toConsole) console.print(wii5BufferConsolePrint);
snprintf_P(
wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT,
PSTR("@Data,config,defaultMode=%d/%s\r\n"),
int(getDefaultMode()), wii5Strings.strMode(getDefaultMode())
);
if (toOther) toOther->print(wii5BufferConsolePrint);
if (toConsole) console.print(wii5BufferConsolePrint);
// TODO Temporary mode
snprintf_P(
wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT,
PSTR("@Data,config,capturePeriod=%lu,captureBinaryType=%lu,captureRecords=%lu\r\n"),
getCapturePeriod(), getCaptureBinaryType(), getCaptureRecords()
);
if (toOther) toOther->print(wii5BufferConsolePrint);
if (toConsole) console.print(wii5BufferConsolePrint);
snprintf_P(
wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT,
PSTR("@Data,config,positionPeriod=%lu,positionBinaryType=%lu\r\n"),
getPositionPeriod(), getPositionBinaryType()
);
if (toOther) toOther->print(wii5BufferConsolePrint);
if (toConsole) console.print(wii5BufferConsolePrint);
snprintf_P(
wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT,
PSTR("@Data,config,sleepPeriod=%lu,sleepBinaryType=%lu,sleepUntil=%lu\r\n"),
getSleepPeriod(), getSleepBinaryType(), getSleepUntil()
);
if (toOther) toOther->print(wii5BufferConsolePrint);
if (toConsole) console.print(wii5BufferConsolePrint);
snprintf_P(
wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT,
PSTR("@Data,config,batteryLow=%d,batteryMid=%d\r\n"),
getBatteryLow(), getBatteryMid()
);
if (toOther) toOther->print(wii5BufferConsolePrint);
if (toConsole) console.print(wii5BufferConsolePrint);
// time_t getDisableLowBattery();
// uint32_t getRecordCount();
// bool getFlag(WII5_FLAGS f);
// uint32_t getGpsTimeout();
}
uint16_t WII5Config::getBatteryLow() {
if (configData.batteryLow > 1500)
return 1100;
return configData.batteryLow;
}
void WII5Config::setBatteryLow(uint16_t in) {
configData.batteryLow = in;
updateConfigData();
}
uint16_t WII5Config::getBatteryMid() {
if (configData.batteryMid < getBatteryLow())
return getBatteryLow() + 100;
if (configData.batteryMid > 1500)
return getBatteryLow() + 100;
return configData.batteryMid;
}
void WII5Config::setBatteryMid(uint16_t in) {
configData.batteryMid = in;
updateConfigData();
}
WII5Config wii5Config;
+140
View File
@@ -0,0 +1,140 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5Config.h
* @brief Persistent configuration in EEPROM: device ID, mode defaults, calibrations.
*/
#ifndef WII5Config_h
#define WII5Config_h
#ifdef WII5_EEPROMex
#include <EEPROMex.h>
#endif
#include <WII5.h>
/*
WII5 Configuration we must keeping
Big Areas:
* Default Mode
* Temorary Mode
* Disable LowBattery
Identifiers & Counters
* DeviceId - Unique device ID - see WII Server for names. Normally 5 digits
* RunCount - A counter that incremembers every time the AVR is booted
* RecordCount - A number that is incremented every time a Capture is done
*
*/
// Highly Oversimplified structured EEPROM storage (from past Sh3d managed code)
// Limitations:
// - Assume EEPROMex
// - Assume 384 bytes from where it says
// - Assume 64 bytes per struct (config for example is currently only 32)
// - Gives us 6 areas to write to
// - If we need more advanced, we can make a struct of structs
// TEMP: Manually copied from Sh3d Config
#define WII5CONFIG_MAXBLOCK 64
#define WII5CONFIG_OFFSET 128
// 0 - Config
#define WII5CONFIG_CONFIGDATA 0
// 1 - Status (TODO Maybe split Status, Counts, Errors)
#define WII5CONFIG_STATUSDATA 64
// 2 - ?
#define WII5CONFIG_RESERVED_2 128
// 3 - ?
#define WII5CONFIG_RESERVED_3 192
// 4 - ?
#define WII5CONFIG_RESERVED_4 256
// 5 - ?
#define WII5CONFIG_RESERVED_5 320
class WII5Config {
public:
WII5Config() {}
virtual WII5_CONTROLLERS controllerId() {return WII5CONTROLLER_CONFIG;}
void begin();
uint32_t getDeviceId(); // getNodeId
void setDeviceId(uint32_t newId); // setNodeId
// MODES: - do we have valid ones?
bool _checkMode(WII5_MODES newMode, uint32_t v1, uint32_t v2, uint32_t v3, uint32_t v4);
void setDefaultMode(WII5_MODES newMode);
WII5_MODES getDefaultMode();
void setTemporaryMode(time_t exp, WII5_MODES newMode);
WII5_MODES getTemporaryMode();
void setCapturePeriod(uint32_t in);
uint32_t getCapturePeriod();
void setCaptureRecords(uint32_t in);
uint32_t getCaptureRecords();
void setCaptureBinaryType(uint32_t in);
uint32_t getCaptureBinaryType();
void setPositionPeriod(uint32_t in);
uint32_t getPositionPeriod();
void setPositionBinaryType(uint32_t in);
uint32_t getPositionBinaryType();
// MODE Sleep
void setSleepBinaryType(uint32_t in);
uint32_t getSleepBinaryType();
void setSleepPeriod(uint32_t in);
uint32_t getSleepPeriod();
void setSleepUntil(uint32_t in);
uint32_t getSleepUntil();
void setDisableLowBattery(time_t exp);
time_t getDisableLowBattery();
uint32_t updateRecordCount();
uint32_t getRecordCount();
void resetCounters(); // clearRunCount(), clearRecordCount()
void readConfigData();
void updateConfigData();
void resetConfigData();
void readStatusData();
void updateStatusData();
void resetStatusData();
bool getFlag(WII5_FLAGS f);
void setFlag(WII5_FLAGS f, bool state);
uint32_t getGpsTimeout();
void setGpsTimeout(uint32_t t);
void dump(bool toConsole, Print* toOther);
uint16_t getBatteryLow();
void setBatteryLow(uint16_t in);
uint16_t getBatteryMid();
void setBatteryMid(uint16_t in);
protected:
uint32_t _deviceId;
WII5_Data_Config configData;
// TODO Data_Config - why is status same as config ?
WII5_Data_Config statusData;
};
extern WII5Config wii5Config;
#endif
+521
View File
@@ -0,0 +1,521 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5Controller.cpp
* @brief Main controller: mode dispatch, watchdog, and the top-level loop.
*/
/*
Controller - Main controller, for loops and processing within WII5.
Basically the controller is in charge of what is called for setup and loop.
SPLIT:
* Setup - helpers and stuff to setup code - doesn't not need to be here
* Mode, setMode, calculateMode etc
* Console and Settings
HOWTO USE "yield" Here
NOT READY - Causes infinite loops
* create a "yield" in the sketch and call
* wii5Controller.yield()l
* And any other important entries, e.g. Network
* And in loop() call yield at the start of loop
TEMPLATE:
LOOPS:
loop:
- This is the actual loop and it just calls the others, depending on mode
- Calls loopWDT
- Based on mode, calls
.
- Call loopSafe - always?
loopWDT:
- Just update the WDT and spin on
loopSafe:
- All the commands that must be always run
- Calls
. sh3dNodeIO.loop();
. wii5Commands.processButtons();
. console.loop();
. wii5Commands.processConsoleAT();
. wii5Commands.processConsoleStandard();
. wii5Iridium.loop();
. wii5Communications.loop();
loopDefault:
- Old - everything, instead, this is now loopOther
loopOther:
- Everything else
- Calls
. wii5Sparton.loop();
. wii5RadioLoRa.loop();
. wii5Commands.processNetwork();
. wii5Gps.loop();
. wii5Battery.loop();
. wii5Weather_18B20.loop();
loopNotCapturing:
Other places loop* is called from
safeDelay (not complete)
*/
#include <Arduino.h>
#include <TimeLib.h>
#include <WII5.h>
#include <WII5Controller.h>
#include <WII5Strings.h>
#include <MemoryFree.h>
#include <avr/wdt.h>
// Debugging etc - move to header file
#define DEBUG_SLEEP
void WII5Controller::begin() {
uptime = 0;
minuteWait = 0;
wii5ModeSleep.begin();
wii5ModeCapture.begin();
wii5ModeLowBattery.begin();
wii5ModeManualTest.begin();
wii5ModePosition.begin();
wii5ModeSelfTest.begin();
mode = WII5MODE_BOOT; // Not using setMode here
currentSD = 0;
#ifdef WII5_DEBUG_SERIAL
SerialDebug.print(F("CONTROL:BOOTUP:freememory="));
SerialDebug.print(freeMemory());
SerialDebug.println();
#endif
// SD Card pins - should use Power object
pinMode(POWER_STORAGE_1_PIN, OUTPUT);
pinMode(STORAGE_SD1_MATHS_PIN, OUTPUT);
pinMode(STORAGE_SD2_MATHS_PIN, OUTPUT);
#ifdef WII5_WDT_INTERNAL
wdt_enable(WDTO_8S);
#endif
}
// Delay with SafeLoop
void WII5Controller::safeDelay(uint32_t waits) {
delayWait = 0;
while (delayWait < waits) {
loopWDT();
loopSafe();
}
}
void WII5Controller::setLastHello(WII5_PORTS p, WII5_DEVICES d) {
lastHelloReceived = now();
lastHelloElapsed = 0;
lastHelloPort = p;
lastHelloDevice = d;
}
bool WII5Controller::checkLastHello(WII5_DEVICES d, uint32_t msOld) {
// Expected device, and time is newer than we needed - TRUE!
if ((d == lastHelloDevice) && (lastHelloElapsed < msOld)) {
return true;
}
return false;
}
// Main Loop - broken up for easy reading
void WII5Controller::loop() {
loopWDT();
loopSafe();
// The *reasonably* accurate minute count
if (minuteWait > 60000) {
// TODO - use uptime += (minuteWait / 60000)
// minuteWait = (minuteWait % 60000);
// And log if it is over 5 minutes.
uptime++;
minuteWait = minuteWait - 60000;
}
// TODO timeout
switch (mode) {
case WII5MODE_BOOT:
// Safety: Automatically moves on
console.log(LOG_DEBUG, F("WII5: Boot up process complete"));
// Pretty safe to run
loopOther();
statusDump();
setMode(WII5MODE_START);
break;
case WII5MODE_START:
// Safety: Automatically moves on
loopOther();
setMode(calculateCurrentMode());
break;
case WII5MODE_DEMO:
// Safety: TODO This one never exits. Needs work.
// modeWait is screwed
loopOther();
if (modeWait > 60000) {
console.log(LOG_DEBUG, F("Demonstration mode, try T; for test"));
modeWait = 0;
}
if (modeWait > (WII5TIME_1DAY * 1000)) {
// Stuck here... now what. Move to sleep?
mode = WII5MODE_SLEEP; modeWait = 0;
}
break;
case WII5MODE_SLEEP:
// Safety: Sleep mode handles exit
wii5ModeSleep.loop();
// TODO Maybe?
loopOther();
break;
case WII5MODE_LOWBATTERY:
// Safety: Battery mode handles exit
wii5ModeLowBattery.loop();
loopOther();
break;
case WII5MODE_POSITION:
// Safety: Either temporary timed mode, or changed by sattelite comms
wii5ModePosition.loop();
loopOther();
break;
case WII5MODE_CAPTURE:
wii5ModeCapture.loop();
loopOther();
break;
case WII5MODE_MANUALTEST:
wii5ModeManualTest.loop();
if (modeWait > (WII5TIME_1DAY * 1000)) {
// Stuck here... now what. Move to sleep? Check saved mode is sleep or capture
console.log(LOG_FATAL, F("FATAL * FATAL * FATAL * reset is being called from manual test loop"));
console.flush();
sh3dNodeUtil.reset();
}
loopOther();
break;
case WII5MODE_SELFTEST:
wii5ModeSelfTest.loop();
// loopSafe plus some
loopOther();
if (modeWait > (WII5TIME_1DAY * 1000)) {
// Stuck here... now what. Move to sleep? Check saved mode is sleep or capture
console.log(LOG_FATAL, F("FATAL * FATAL * FATAL * reset is being called from self test loop"));
console.flush();
sh3dNodeUtil.reset();
}
break;
default:
console.log(LOG_FATAL, F("Invalide Mode, old=%d, new=%d"), mode, WII5MODE_CAPTURE);
mode = WII5MODE_CAPTURE;
}
}
// loopSafe - Minimal safe loop, to allow for callback from Iridium etc
void WII5Controller::loopSafe() {
// Buttons, LEDs etc
sh3dNodeIO.loop();
wii5Commands.processButtons();
// Serial port
console.loop();
wii5Commands.processConsoleAT();
wii5Commands.processConsoleStandard();
// Undesirable: Do not use these in loopSafe
// - Gps - lots of data
// - Sparton - lots of data
// - Iridium - lots of data and potential cost
// - Battery - Changes mode on us
wii5Iridium.loop();
wii5Communications.loop();
wii5Maths.loop();
}
void WII5Controller::loopWDT() {
#ifdef WDT_RESET
// TODO consider compile this out and have specific version
// TODO or don't call loopWDT inside TestMode and have own version
// TODO manual test mode timeout
// TODO Test the buoy - has it been more than 3 days since we sent a message - maybe reboot?
if (tempDisableWDT) return;
if (wdtWait > 5100) {
digitalWrite(WDT_RESET, LOW);
wdtWait = 0;
}
// TODO setting pin all the time for 100 ms
else if (wdtWait > 5000) {
digitalWrite(WDT_RESET, HIGH);
}
#endif
#ifdef WII5_WDT_INTERNAL
wdt_reset();
#endif
}
void WII5Controller::loopOther() {
#ifdef WII5_IMU_SPARTON
wii5Sparton.loop();
#endif
// LORA or Other Modems
#ifdef WII5_RADIO_LORA
wii5RadioLoRa.loop();
wii5Commands.processNetwork();
#endif
#ifdef WII5_GPS
wii5Gps.loop();
#endif
wii5Battery.loop();
wii5Weather_18B20.loop();
}
WII5_MODES WII5Controller::getMode() {
return mode;
}
bool WII5Controller::setMode(WII5_MODES newMode, bool nodelay) {
// Ignore same mode change - really this is an error
if (newMode == mode)
return false;
console.log(LOG_INFO, F("Controller: new mode set %d:%s"), newMode, wii5Strings.strMode(newMode));
#ifdef WII5_DEBUG_SERIAL
SerialDebug.print(F("CONTROL:MODE:mode="));
SerialDebug.print(newMode);
SerialDebug.print(F(":"));
SerialDebug.print(wii5Strings.strMode(newMode));
SerialDebug.println();
#endif
// TODO Delay ???
// - Put in a "when ready or timeout" to change mode
// Set the mode
mode = newMode;
modeWait = 0;
// Reset all STEPS to start
wii5ModeCapture.reset();
wii5ModeLowBattery.reset();
wii5ModePosition.reset();
wii5ModeManualTest.reset();
wii5ModeSelfTest.reset();
wii5ModeSleep.reset();
// Update status to tell users of mode change
statusDump();
return true;
}
// TODO move to SPI
void WII5Controller::disableChipSelect() {
#ifdef RADIO_CS
digitalWrite(RADIO_CS, HIGH);
#endif
#ifdef STORAGE_CS
digitalWrite(RADIO_CS, HIGH);
#endif
#ifdef SPIMEMORY_CS
digitalWrite(SPIMEMORY_CS, HIGH);
#endif
}
// TODO move to Status Dump
void WII5Controller::statusDump(bool human) {
// Basic status - mode, time, free memory
// TODO 2024 - Add run count - check this still works in node? (yes)
wii5Display.atCommandSend(F("status"), F("record=%lu,uptime=%lu,freeMemory=%d,run=%lu"),
wii5Config.getRecordCount(),
uptime, freeMemory(),
sh3dNodeConfig.getRunCount()
);
period = 0;
next = 0;
size = 0;
// CURRENT mode if we have one.
if (mode != wii5Config.getDefaultMode()) {
next = 0;
// Calculate next depending on noce
if (getMode() == WII5MODE_CAPTURE) {
period = wii5Config.getCapturePeriod();
size = wii5BinData.getSize(wii5Config.getCaptureBinaryType());
}
else if (getMode() == WII5MODE_POSITION) {
period = wii5Config.getPositionPeriod();
size = wii5BinData.getSize(wii5Config.getPositionBinaryType());
}
else if (getMode() == WII5MODE_SLEEP) {
period = wii5Config.getSleepPeriod();
size = wii5BinData.getSize(wii5Config.getSleepBinaryType());
}
next = wii5Display.minutesUntilNext(period);
// Only show current if not the same as default
wii5Display.atCommandSend(F("status"), F("modeType=current,mode=%s,modeTime=%lu,modePeriod=%lu,modeNext=%lu,binSize=%d,sd=%d"),
wii5Strings.strMode(mode),
(uint32_t)(modeWait / 60000), // Convert to Minutes
period, next, size, currentSD
);
}
// DEFAULT Mode
next = 0;
if (wii5Config.getDefaultMode() == WII5MODE_CAPTURE) {
period = wii5Config.getCapturePeriod();
size = wii5BinData.getSize(wii5Config.getCaptureBinaryType());
}
else if (wii5Config.getDefaultMode() == WII5MODE_POSITION) {
period = wii5Config.getPositionPeriod();
size = wii5BinData.getSize(wii5Config.getPositionBinaryType());
}
else if (wii5Config.getDefaultMode() == WII5MODE_SLEEP) {
period = wii5Config.getSleepPeriod();
size = wii5BinData.getSize(wii5Config.getSleepBinaryType());
}
next = wii5Display.minutesUntilNext(period);
wii5Display.atCommandSend(F("status"), F("modeType=default,mode=%s,modeTime=%lu,modePeriod=%lu,modeNext=%lu,binSize=%d,sd=%d"),
wii5Strings.strMode(wii5Config.getDefaultMode()),
(uint32_t)(modeWait / 60000), // Convert to Minutes
period, next, size, currentSD
);
wii5Display.atCommandSend(F("status"), F("version=%S,imei=%s"), F(WII5_SOFTWARE_VERSION), wii5Iridium.imei);
wii5Display.atCommandSend(F("status"), F("voltage=%ld,temperature=%ld,lat=%ld,lon=%ld,alt=%ld"),
wii5Battery.value, wii5Weather_18B20.value,
long(wii5Gps.gps->location.lat() * GPS_POS_MULT),
long(wii5Gps.gps->location.lng() * GPS_POS_MULT),
long(wii5Gps.gps->altitude.meters() * GPS_ALT_MULT)
);
// Calculate Maths mode.
wii5Display.atCommandSend(F("status"), F("maths_on=%d,maths_held_remaining=%lu,maths_power_remaining=%lu"),
wii5Maths.isRunning(), wii5Maths.remainingHold() / 60, wii5Maths.remainingUntil() / 60
);
// Tell the Maths CPU to advertise this info
wii5Display.atCommandSend(F("status"), F("update"));
if (human) {
console.log(LOG_INFO, F("# WII5: Waves In Ice 5 - 2019. Board:%S Version:%S ID=%lu"),
F(WII5_BOARD_NAME),
F(WII5_SOFTWARE_VERSION),
wii5Config.getDeviceId()
);
}
}
WII5_MODES WII5Controller::calculateDefaultMode() {
return wii5Config.getDefaultMode();
}
// TODO move to Status Dump
WII5_MODES WII5Controller::calculateCurrentMode() {
// TODO Urgent - temporary Modes
// TODO Urgent - find everywhere setDefaultMode is called, probably should be setCurrentMode
// TODO check we have valid date/time
// TODO check temporaryMode
// TODO check defaultMode
// return WII5MODE_CAPTURE;
// return WII5MODE_DEMO;
return wii5Config.getDefaultMode();
}
// What was the last default mode - for reboot and post sleep
// TODO - Proabably move to sh3dNodeConfig (EEPROM) data
void WII5Controller::setDefaultMode() {
// Get default mode from EEPROM and disable the Temporary Mode there
setMode(calculateDefaultMode());
}
void WII5Controller::setCurrentMode() {
// Get temporary mode from EEPROM, check it is valid, else fall back to Default
setMode(calculateCurrentMode());
}
void WII5Controller::shared5On() {
// Turn it on - and feel free to
digitalWrite(SHARED_5VOLT_PIN, HIGH);
}
void WII5Controller::shared5Off() {
// Turn 5 Volts off - ONLY if safe to do so
if (wii5Maths.isRunning() || wii5Iridium.isRunning() || wii5Sparton.isRunning() || (sh3dNodeIO.led2Get() != LED_OFF)) {
// Keep it on !
digitalWrite(SHARED_5VOLT_PIN, HIGH);
}
else {
// Yep, power off time
digitalWrite(SHARED_5VOLT_PIN, LOW);
}
}
uint8_t WII5Controller::getSD() {
return currentSD;
}
void WII5Controller::setSDOff() {
sdBlock.cardForceClose();
digitalWrite(POWER_STORAGE_1_PIN, LOW);
digitalWrite(STORAGE_SD1_MATHS_PIN, HIGH);
digitalWrite(STORAGE_SD2_MATHS_PIN, LOW);
currentSD = 0;
}
// TODO 2024 - delay - really ! Probably ok for now - consider safeDelay
void WII5Controller::setSD1() {
digitalWrite(POWER_STORAGE_1_PIN, LOW);
delay(500);
digitalWrite(STORAGE_SD1_MATHS_PIN, HIGH);
digitalWrite(STORAGE_SD2_MATHS_PIN, LOW);
digitalWrite(STORAGE_CS, HIGH);
delay(500);
digitalWrite(POWER_STORAGE_1_PIN, HIGH);
delay(500);
currentSD = 1;
}
// TODO 2024 - delay - really ! Probably ok for now - consider safeDelay
void WII5Controller::setSD2() {
digitalWrite(POWER_STORAGE_1_PIN, LOW);
delay(500);
digitalWrite(STORAGE_SD1_MATHS_PIN, LOW);
digitalWrite(STORAGE_SD2_MATHS_PIN, HIGH);
digitalWrite(STORAGE_CS, HIGH);
delay(500);
digitalWrite(POWER_STORAGE_1_PIN, HIGH);
delay(500);
currentSD = 2;
}
WII5Controller wii5Controller;
+136
View File
@@ -0,0 +1,136 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5Controller.h
* @brief Main controller: mode dispatch, watchdog, and the top-level loop.
*/
#ifndef WII5Controller_h
#define WII5Controller_h
#include <Arduino.h>
#include <WII5.h>
/**
* @brief Top-level firmware controller.
*
* Owns the main loop(), watchdog tick, and dispatch to the active mode
* (Capture / Sleep / Position / ManualTest / SelfTest / LowBattery).
* Also tracks SD-card hand-off, the shared 5V rail, and "last hello" state
* used by mode transitions.
*/
class WII5Controller : public WII5 {
public:
virtual WII5_CONTROLLERS controllerId() {return WII5CONTROLLER_CONTROLLER;}
/** @brief One-time bring-up: zero counters, call mode-class begin()s, enable WDT. */
void begin();
/** @brief Spin in loopSafe() / loopWDT() for `waits` milliseconds. */
void safeDelay(uint32_t waits);
/** @brief Current run-time mode. */
WII5_MODES getMode();
/** @brief Switch mode; returns false if the transition was rejected. */
bool setMode(WII5_MODES newMode, bool nodelay = false);
/** @brief Reset to the EEPROM-configured default mode. */
void setDefaultMode();
/** @brief Move to whatever calculateCurrentMode() returns right now. */
void setCurrentMode();
/** @brief Compute the mode the buoy should be in given clock + battery + temp overrides. */
WII5_MODES calculateCurrentMode();
/** @brief Compute the persistent default mode from EEPROM configuration. */
WII5_MODES calculateDefaultMode();
/** @brief Disable both SD-card chip selects. */
void setSDOff();
/** @brief Hand SD card 1 to the AVR side. */
void setSD1();
/** @brief Hand SD card 2 to the AVR side. */
void setSD2();
/** @brief Which SD card is currently selected (0/1/2). */
uint8_t getSD();
/** @brief Enable the shared 5V rail (used by GPS, Iridium, Sparton, etc.). */
void shared5On();
/** @brief Disable the shared 5V rail. */
void shared5Off();
/** @brief Record that a peer device replied to a Hello. */
void setLastHello(WII5_PORTS p, WII5_DEVICES d);
/** @brief Has device `d` been heard from within the last `msOld` ms? */
bool checkLastHello(WII5_DEVICES d, uint32_t msOld);
// TODO These should not be contains ! Why dynamic generate?
// - Move them global - use #define instead
WII5ModeSleep *modeSleep;
WII5ModeCapture *modeCapture;
WII5ModeLowBattery *modeLowBattery;
WII5ModeManualTest *modeManualTest;
WII5ModePosition *modePosition;
WII5ModeSelfTest *modeSelfTest;
/** @brief Main tick: runs WDT, the safe loop, and dispatches the active mode. */
void loop();
/** @brief Minimal-cost loop: console, buttons, Iridium, Communications. */
void loopSafe();
/** @brief Heavier-cost loop: Sparton, GPS, Battery, Weather, etc. */
void loopOther();
/** @brief Pet the watchdog (external pin and/or AVR internal WDT). */
void loopWDT();
/** @brief Print a one-line summary of controller state to the console. */
void dump();
/** @brief Print the long-form `@Status` block to the console (human=true for human-readable). */
void statusDump(bool human = false);
/** @brief Tri-state both SD chip-select lines. */
void disableChipSelect();
time_t lastHelloReceived;
WII5_PORTS lastHelloPort;
WII5_DEVICES lastHelloDevice;
elapsedMillis lastHelloElapsed;
bool tempDisableWDT;
// Uptime in minutes - manually updated by minnute counter
uint32_t uptime;
protected:
uint8_t currentSD;
// Used in status calculate
uint32_t next;
uint16_t size;
uint32_t period;
elapsedMillis minuteWait;
elapsedMillis delayWait;
elapsedMillis wdtWait;
// Main mode, and a mode for each of the sub modes
elapsedMillis modeWait;
WII5_MODES mode;
WII5_MODES lastMode;
// TODO how to get all last mode data and swtich back after tempo
time_t modeUntil;// TODO ?
// TODO Data to store
// - Location, Date & Time - inside WII5GPS
// . Captured directly inside module
// . wii5GPS.status - ON, OFF, BUSY, ERROR
// . wii5GPS.error - Error object = time, number, string
// - Iridium Status - Inside WII5Iridium
// . Captured directly inside module
// - Maths Status - Either WII5Maths or WII5Controller
// . Captured via Console and processed in Controller
// - Capture Status - Inside WII5Capture
// -
};
extern WII5Controller wii5Controller;
#endif
+506
View File
@@ -0,0 +1,506 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5Data.h
* @brief Shared data type definitions: enums, status codes, error codes.
*/
#ifndef WII5Data_h
#define WII5Data_h
/*
WII5 - Data Structures
*/
#if !defined(__time_t_defined) // avoid conflict with newlib or other posix libc
typedef unsigned long time_t;
#endif
// Manual from Sh3d
#define SH3D_TYPE_COUNTER uint32_t
#define SH3D_TYPE_UNIQUEID uint64_t
#define SH3D_TYPE_PACKET_TYPE uint8_t
#define SH3D_TYPE_PACKET_VERSION uint8_t
// Number of seconds in...
#define WII5TIME_1WEEK (uint32_t)7*(uint32_t)24*(uint32_t)3600
#define WII5TIME_1DAY (uint32_t)24*(uint32_t)3600
#define WII5TIME_12HOUR (uint32_t)12*(uint32_t)3600
#define WII5TIME_1HOUR (uint32_t)3600
/*
Basic: Max 50 bytes
* Buoy ID (16 or 32 bit LSB of 64 bit NodeID)
* Date time
* Location Lat, Lon, Age (in minutes, 1 byte)
* Battery Voltage, Age (in miutes)
* Temperature Internal, Age (in minutes)
* Sleep information - ?
Full: Max 100 bytes
* More data?
Error:
* Basic + Error Number and String
NOTE: Other types, such as Binary processed Maths come from the Maths CPU
*/
enum WII5SELFTEST_DEVICES {
WII5STD_BOARD = 0,
WII5STD_GPS,
WII5STD_EEPROM,
WII5STD_SD1,
WII5STD_SD2,
WII5STD_IRIDIUM,
WII5STD_BATTERY,
WII5STD_MATHS,
WII5STD_RTC,
WII5STD_SPARTON,
WII5STD_18B20,
WII5STD_BUTTON,
WII5STD_LED,
WII5STD_BUZZER,
WII5STD_5VOLT,
WII5STD_DEVICES
};
enum WII5SELFTEST_STATUS {
WII5STS_NONE, // Never run
WII5STS_NC, // Not connected, not enabled, or not applicable
WII5STS_SUCCESS, // SUCCCESS - literally nothing to report
WII5STS_HUMAN, // HUMAN - we assume success, but need a human to confirm
WII5STS_SLOW, // SLOW - SUCCESS in all ways, but slower than expected. Good for SD, Maths boot, GPS lock etc
WII5STS_POWER, // Works, but power management may have failed (e.g. serial data still coming in when power off)
WII5STS_TIMEOUT, // Literally timed out. No failure we can actually understand.
WII5STS_OUTOFRANGE, // Out of range - voltage, or GPS or something out of range
WII5STS_UNKNOWN, // Some sort of bad, unknown error
};
enum WII5_DEVICES {
WII5DEVICE_UNKNOWN,
WII5DEVICE_WAVEBUOY, // This AVR - myself
WII5DEVICE_MATHS, // Maths CPU on the Console
WII5DEVICE_CONTROLLER, // Dedicated controller - e.g. handheld Wifi Accessed device
WII5DEVICE_CLI, // Simple CLI Serial port controller
WII5DEVICE_SERVER // Via the Server - e.g. remote command sent from Web Server
};
enum WII5_CONTROLLERS {
WII5CONTROLLER_UNKNOWN,
WII5CONTROLLER_DRIVER, // ie. See Driver below
WII5CONTROLLER_CONFIG,
WII5CONTROLLER_CONTROLLER,
WII5CONTROLLER_COMMANDS,
WII5CONTROLLER_DISPLAY,
WII5CONTROLLER_HELP,
WII5CONTROLLER_SETUP,
WII5CONTROLLER_STRINGS,
WII5CONTROLLER_METADATA,
};
enum WII5_DRIVERS {
WII5DRIVER_UNKNOWN,
WII5DRIVER_GPS,
WII5DRIVER_MATHS,
WII5DRIVER_COMMS,
WII5DRIVER_COMMS_IRIDIUM,
WII5DRIVER_MPU9250,
WII5DRIVER_POWER,
WII5DRIVER_SPARTON,
WII5DRIVER_STORAGE,
WII5DRIVER_WEATHER_18B20,
WII5DRIVER_BATTERY,
WII5DRIVER_RADIO,
WII5DRIVER_RTC,
WII5DRIVER_WDT,
};
enum WII5_PORTS {
WII5PORT_UNKNOWN,
WII5PORT_RADIO, // Came from the radio, probably LoRa
WII5PORT_COMMS, // Came from main comms, e.g. Iridium or similar
WII5PORT_CONSOLE, // Via the console, probably an end user (or Maths)
WII5PORT_MATHS // From the maths CPU - probably a processed command via web interface
};
enum WII5_MATHSMODE {
WII5MATHSMODE_UNKNOWN,
WII5MATHSMODE_AUTO, // Run in auto mode, start and stop as normal
WII5MATHSMODE_BUTTON, // Button was pressed, internally uses setHold
WII5MATHSMODE_COMMS, // Communication data came in
};
enum WII5_AREAS {
WII5AREA_UNKNOWN,
WII5AREA_WII5,
WII5AREA_HELP,
WII5AREA_SH3D
};
enum WII5_COMMANDS {
WII5COMMAND_NONE = 0, // Not yet parsed
WII5COMMAND_NA = 1, // Parsed but no good
WII5COMMAND_HELP = 10, // @WII5,help - see @Help
WII5COMMAND_LOG_DEBUG = 11, // Show debugging
WII5COMMAND_LOG_DEFAULT = 12,// Normal level of logging
WII5COMMAND_LOG_FATAL = 13, // Fatals only
WII5COMMAND_LOG_ERROR = 14, // Error only
WII5COMMAND_LOG_TEST = 19, // Test out the log
// Hello and ACK - mostly about chatting to Maths
WII5COMMAND_HELLO = 100,
WII5COMMAND_HELLOACK = 101,
// Do it !
WII5COMMAND_RESET = 102, // Reset the AVR now
WII5COMMAND_STORAGE_FORMAT = 103, // Format 1 or more storage devices, all data lost from there
WII5COMMAND_ECHO = 110,
WII5COMMAND_PEOPLE = 111,
WII5COMMAND_BATTERY_START = 112,
WII5COMMAND_WEATHER_READ = 113,
WII5COMMAND_SET = 114,
WII5COMMAND_SEND = 115,
WII5COMMAND_WEATHER_TEST = 116,
// Set modes now - temporary until reboot etc
WII5COMMAND_MODE_SELFTEST = 200,
WII5COMMAND_MODE_MANUALTEST = 201,
WII5COMMAND_MODE_SLEEP = 202,
WII5COMMAND_MODE_DEFAULT = 203,
WII5COMMAND_MODE_POSITION = 204,
WII5COMMAND_MODE_CAPTURE = 205,
// Status and Dumps
WII5COMMAND_STATUS = 300, // Optional Device Name
WII5COMMAND_DUMP = 301,
WII5COMMAND_WAITX = 302,
WII5COMMAND_WAITY = 303,
WII5COMMAND_WAITZ = 304,
WII5COMMAND_WAITQ = 305,
WII5COMMAND_WAITS = 306,
// Settings - stored
WII5COMMAND_SETTING_LIST = 400, // List current settings
WII5COMMAND_SETTING_DEVICEID = 401, // Include Device ID to change
WII5COMMAND_SETTING_GPSTIMEOUT = 402, // GPSTimeout
WII5COMMAND_SETTING_DEFAULTS = 403, // optionally add DeviceId
WII5COMMAND_SETTING_RESET = 404, // Reset counters..... Danger
WII5COMMAND_SETTING_MODE = 405, // Saved Mode
WII5COMMAND_SETTING_TIME = 406,
WII5COMMAND_SETTING_DISABLELOWBATTERY = 407,
WII5COMMAND_SETTING_FLAGS = 408,
WII5COMMAND_SETTING_CAPTUREOPTIONS = 409,
WII5COMMAND_SETTING_POSITIONOPTIONS = 410,
WII5COMMAND_SETTING_SLEEPOPTIONS = 411,
WII5COMMAND_SETTING_BATTERYOPTIONS = 412,
// Device - MIsc
WII5COMMAND_GPS_MODE = 500,
WII5COMMAND_GPS_DUMP = 501,
WII5COMMAND_GPS_PASSTHROUGH = 502,
WII5COMMAND_GPS_DEBUG = 503,
WII5COMMAND_GPS_START = 504,
WII5COMMAND_GPS_STOP = 505,
WII5COMMAND_GPS_TIME = 506,
WII5COMMAND_GPS_POS = 507,
WII5COMMAND_GPS_ACCURATE = 508,
WII5COMMAND_SPARTON_MODE = 510,
WII5COMMAND_SPARTON_DUMP = 511,
WII5COMMAND_SPARTON_DEBUG = 513,
WII5COMMAND_SPARTON_PASSTHROUGH = 514,
WII5COMMAND_SPARTON_INFO = 515,
// Device - Maths
WII5COMMAND_MATHS_START = 550, // Turn On maths
WII5COMMAND_MATHS_STOP = 551, // Stop maths
WII5COMMAND_MATHS_BEEP = 552, // Beep !
WII5COMMAND_MATHS_RESTART = 553, // Maths informing that it is restarting
WII5COMMAND_MATHS_SHUTDOWN = 554, // Maths being requested to shutdown
WII5COMMAND_MATHS_HALT = 555, // Maths informing that it is now halting
WII5COMMAND_MATHS_BOOTED = 556, // Maths tells us it is bootet d
WII5COMMAND_MATHS_KEEPALIVE = 557, // Maths informing "keep alive" (aka not shut shutdown)
WII5COMMAND_MATHS_WIFI_LOCAL = 558, // Tell Maths to try Local WIFI
WII5COMMAND_MATHS_WIFI_WAP = 559, // Tell Maths to create a WAP
WII5COMMAND_MATHS_SHELL = 560, // Send a direct shell command (can do anything).
WII5COMMAND_MATHS_HOLD = 561, // Hold vs Keep Alive.... hmmmm
WII5COMMAND_MATHS_FLIP = 562, // We are running all the time. Request a flip of the AVR
// TODO Create new file, append, run - so we can send multiple packets, to create arbitrary long file
// WII5COMMAND_MATHS_S, // Send a direct shell command (can do anything).
WII5COMMAND_MATHS_REPROCESS = 563, // Send maths request to reprocess a file
WII5COMMAND_MATHS_BUILD = 564, // Rebuild the maths code with some optional make directives
WII5COMMAND_MATHS_FIRMWARE = 565, // Update the firmware, inputs will be 'dev', 'local', 'latest, 'vn.n' (version)
WII5COMMAND_MATHS_PATCHLOCAL = 566, // Patch the local Firmware code and rebuild
WII5COMMAND_MATHS_UNTIL = 567, // Set the UNTIL - forced power switch on control
WII5COMMAND_MATHS_INFO = 568, // Show some info - what is the Maths CPU doing
WII5COMMAND_MATHS_DO = 569,
WII5COMMAND_MATHS_MAKE = 570,
WII5COMMAND_MATHS_SCRIPT = 571, // Actually - use this to add to QUEUE
WII5COMMAND_MATHS_DONE = 572, // Actually - use this to add to QUEUE
WII5COMMAND_MATHS_RETURN = 573, // RETURN line Turn on or off
// Device - Storage cards
WII5COMMAND_STORAGE_START = 580, // Mode 1,2,3,4
WII5COMMAND_STORAGE_STOP = 581,
WII5COMMAND_STORAGE_TESTFILE = 582, // TODO ls, chdir, get (filename) etc
WII5COMMAND_STORAGE_CONSOLEFILE = 583,
WII5COMMAND_STORAGE_SERIALFILE = 584,
WII5COMMAND_STORAGE_SETSTATUS = 589,
WII5COMMAND_STORAGE_OFF = 590,
WII5COMMAND_STORAGE_SD1 = 591,
WII5COMMAND_STORAGE_SD2 = 592,
WII5COMMAND_STORAGE_LIST = 593,
WII5COMMAND_STORAGE_VIEW = 594,
WII5COMMAND_STORAGE_RESULTS = 595,
WII5COMMAND_STORAGE_DEBUG = 596,
WII5COMMAND_STORAGE_STATUS = 597,
WII5COMMAND_STORAGE_RAW = 598,
WII5COMMAND_STORAGE_METADATA = 599,
// Device - Network / Radio
WII5COMMAND_NETWORK_ECHO = 600,
WII5COMMAND_NETWORK_ECHOACK = 601,
WII5COMMAND_NETWORK_FAKEECHO = 602,
WII5COMMAND_NETWORK_FAKEECHOACK = 603,
// Instant commands for modes
WII5COMMAND_CAPTURE_START = 700,
WII5COMMAND_CAPTURE_STOP = 701,
WII5COMMAND_POSITION_START = 702,
WII5COMMAND_POSITION_STOP = 703,
WII5COMMAND_POSITION_PASSTHROUGH = 704,
WII5COMMAND_BINDATA_LIST = 800,
WII5COMMAND_BINDATA_SPLIT = 801,
// Iridium (90x) & Communications (91x)
WII5COMMAND_IRIDIUM_SEND = 901,
WII5COMMAND_COMMUNICATIONS_DISABLED = 911,
WII5COMMAND_COMMUNICATIONS_TEST = 912,
WII5COMMAND_COMMUNICATIONS_START = 913,
WII5COMMAND_COMMUNICATIONS_STOP = 914,
WII5COMMAND_COMMUNICATIONS_SIGNAL = 915,
};
// Results of a command request
#define WII5_RESULT_MESSAGE 250
enum WII5_RESULTS {
WII5RESULTS_UNKNOWN = 0, // Not great, no idea what happened
WII5RESULTS_SUCCESS = 1, // Success - everything worked as you expected
WII5RESULTS_INVALID = 2, // Invalid - probably paramers, see message
WII5RESULTS_RETRY = 3, // Retry later
WII5RESULTS_ERROR = 4, // Error, see message
WII5RESULTS_NOREPLY = 5, // This command can't get a reply - e.g. Help
};
// Status of a Driver / Device
enum WII5_STATUS {
WII5STATUS_UNKNOWN,
WII5STATUS_OFF,
WII5STATUS_ON,
WII5STATUS_ON_VALID,
WII5STATUS_BUSY,
WII5STATUS_WAITING,
WII5STATUS_ERROR
};
// Error for device
// 50 = about 1K
// 10 = about 190 bytes
#define WII5_ERROR_STRING_MAX 5
enum WII5_ERRORS {
WII5ERROR_UNKNOWN,
WII5ERROR_NONE,
WII5ERROR_RETRY,
WII5ERROR_INIT,
WII5ERROR_MEMORY,
WII5ERROR_TIMEOUT,
WII5ERROR_FATAL
};
enum WII5_SERIALCMDS {
WII5SERIALCMD_NONE,
WII5SERIALCMD_OK,
WII5SERIALCMD_WAITING,
WII5SERIALCMD_TIMEOUT,
WII5SERIALCMD_ERROR
};
enum WII5_MODES {
WII5MODE_NONE, /// Invalid !
// Boot up
WII5MODE_BOOT, // First time only - after boot
WII5MODE_START, // First time only - after boot
WII5MODE_DEMO, // Special mode which kind of does nothing
// Normal modes
WII5MODE_SLEEP, // Sleeping (long term mode).
WII5MODE_LOWBATTERY, // Super low power mode
WII5MODE_POSITION, // Send Position Mode
WII5MODE_CAPTURE, // Capture - the main mode WII5MODE_MANUAL_TEST, // Special mode where we can turn things on and off.
// Testing
WII5MODE_MANUALTEST,
WII5MODE_SELFTEST
};
// Other Config - keep order, and add to end, and check values in read
#define WII5_DATA_CONFIG_TYPE 5
#define WII5_DATA_CONFIG_VERSION 2
typedef struct {
uint8_t type;
uint8_t version;
// Useful generic options
time_t disableLowBattery; // Disable until?
uint32_t flags;
uint32_t gpsTimeout;
// TODO Battery calibration and modes?
// TODO Temperature sensor mapping?
// MODE Capture options
// Periods:
// Minutes
// - 15 (0, 15, 30, 45 ...)
// - 30 (0, 30)
// - 60 (every hour)
// - 120 (once per hour)
// NOTE: Auto sleep when over 30 minutes? (or configurable)
// Number of Records ?
// What to process - ? Can we make this automatic
// What to return
// Could we "return data" only in certain circumstances
uint32_t capturePeriod;
uint32_t captureBinaryType;
uint32_t captureRecords;
// MODE LowBattery
// None - see disableLowBattery above
// MODE Position
uint32_t positionPeriod;
uint32_t positionBinaryType;
// MODE Sleep
uint32_t sleepPeriod; // 0 or Invalid = 12 hours
uint32_t sleepBinaryType;
uint32_t sleepMaths; // Flags and Times for Maths CPU when waking from sleep
// So... what mode and for how long
WII5_MODES defaultMode;
WII5_MODES temporaryMode;
WII5_MODES temporaryModeExpires;
uint16_t batteryLow;
uint16_t batteryMid;
uint32_t sleepUntil; // When to auto go back to capture
} WII5_Data_Config;
// Other Config - keep order, and add to end, and check values in read
#define WII5_DATA_STATUS_TYPE 6
#define WII5_DATA_STATUS_VERSION 1
/*
typedef struct {
uint8_t type;
uint8_t version;
// Sleep information
uint16_t sleepCountTotal;
uint16_t sleepCountThis;
time_t sleepLast;
// Capture information
uint32_t captureCountTotal;
uint16_t captureCountThis;
// Keep error counts and times to allow for temporary disabe etc
uint16_t buttonErrorCountTotal;
uint8_t buttonErrorCountThis;
time_t buttonErrorCountLast;
uint16_t gpsErrorCountTotal;
uint8_t gpsErrorCountThis;
time_t gpsErrorCountLast;
uint32_t rtcErrorCountTotal;
uint16_t rtcErrorCountThis;
time_t rtcErrorCountLast;
uint32_t commsErrorCountTotal;
uint16_t commsErrorCountThis;
time_t commsErrorCountLast;
uint32_t captureErrorCountTotal;
uint16_t captureErrorCountThis;
time_t captureErrorCountLast;
uint32_t commsSendTries;
uint32_t commsSendSuccess;
} WII5_Data_Status;
*/
enum WII5_FLAGS {
WII5FLAGS_GPSDISABLE, // GPS causing problems, turn it OFF
WII5FLAGS_RTCDISABLE, // RTC issues, turn it off
WII5FLAGS_RADIOOFF, // Radio issues, turn it off
WII5FLAGS_ENABLELOGS, // Store logs at record Id
WII5FLAGS_DEBUGLOGS, // Higher information
WII5FLAGS_DISABLEBEEP, // Hide them beeps away
};
enum WII5GPS_MODES {
WII5GPS_UNKNOWN, // aka Invalid
WII5GPS_OFF, // Turn it off - power and all
WII5GPS_ON, // On, but just collecting data - no serial out except errors
WII5GPS_TIME, // Wait just for first valid time stamp, set, keep, and send special NMEA and set to OFF
WII5GPS_POS, // Wait for position then off
WII5GPS_ACCURATE, // Like POS Once but more accurate data - ie. wait longer etc
WII5GPS_REPEAT // Send position evern "n" minutes (see setTime) (note, self manage on, off, power modes etc)
};
enum WII5_SWITCH {
WII5SWITCH_NONE,
WII5SWITCH_ON,
WII5SWITCH_OFF,
WII5SWITCH_TOGGLE
};
typedef struct {
uint32_t deviceId;
uint32_t recordCount;
// 12. Date, Time etc
time_t last;
uint32_t age; // Update as written to disk - (now() - last)
uint32_t uptime; // Uptime in minutes (not sure how this goes wih sleep)
} WII5Command;
enum WII5IRIDIUM_SEND_RESULT {
WII5IRIDIUMSENDRESULT_NONE,
WII5IRIDIUMSENDRESULT_OK,
WII5IRIDIUMSENDRESULT_FAILED_TIMEOUT,
WII5IRIDIUMSENDRESULT_FAILED_OTHER,
};
enum WII5IRIDIUM_RECEIVE_RESULT {
WII5IRIDIUMRECEIVERESULT_NONE,
WII5IRIDIUMRECEIVERESULT_OK,
};
#define DdW time_t
#endif
+146
View File
@@ -0,0 +1,146 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5DataShared.h
* @brief Cross-class shared data structures and constants.
*/
// **********************************************************************
// *** This file belongs in WII5_SD_Block ***
// **********************************************************************
// NOTE: Manually copied from WII5_Buoy - but unlikely to change
// NOTE: Not aligned - must manually extract characters
typedef struct {
uint8_t byteCount;
uint8_t status; // 3 bits error, 5 bits protocol
uint8_t channel;
float pose_x;
float pose_y;
float pose_z;
// float mag_x;
// float mag_y;
// float mag_z;
float accel_x;
float accel_y;
float accel_z;
// float gyro_x;
// float gyro_y;
// float gyro_z;
uint32_t stamp;
uint16_t crc;
} WII5_DATA_SpartonBinary;
// Max size in Block Store = 488
// NOTE: Manually copied from WII5_Buoy - Very likely to change !!!
typedef struct {
// 4. This device and Current Record
uint32_t deviceId;
uint32_t recordCount;
// 12. Date, Time etc
time_t last;
uint32_t age; // Update as written to disk - (now() - last)
uint32_t uptime; // Uptime in minutes (not sure how this goes wih sleep)
// 4. Software Versions
uint32_t version; // Bytes.... 0,n.n.n = vn.n.n
// 8. Temperature
int32_t temperatureValue;
uint32_t temperatureAge;
// 8. Battery
int32_t batteryValue;
uint32_t batteryAge;
// 24. GPS
float gpsLat;
float gpsLon;
float gpsAlt;
int32_t gpsSat;
int32_t gpsHdop;
uint32_t gpsFixTime;
uint32_t gpsAge;
// Iridium ?
// 24. Capture
uint32_t captureWriteMax;
uint32_t captureWriteMin;
uint32_t captureWriteOver;
uint32_t captureTimeError;
uint32_t captureSizeError;
uint32_t captureStartTime;
// 1. mode - what is current
uint8_t mode;
// TODO more !!!
uint32_t iridiumFixTime;
uint32_t iridiumSignalQuality;
// CAPTURE Info - e.g. number of errors etc
} WII5MetaDataObject;
// **********************************************************************
// *** This file belongs in WII5_SD_Block ***
// **********************************************************************
// Processed output data
typedef struct {
// Reordered version
float part1float[73]; // 73*4 = 292 bytes
/*
float hz_max[4];
float hcm_max[4];
float htm_max[4];
float tz_max[4];
float tp;
float hmo;
float psd[55];
*/
} WII5Processed1;
typedef struct {
// Reordered version
float part2float[20]; // 20*4 = 80
int part2int[62]; // 62*2 = 124
// Total = 204
/*
float moments[7];
float theta;
float dp;
float s;
float r;
float hs_dir;
float a;
float b;
float nstd;
float f2;
float qf_mvar;
int qf_kist;
int qf_imu;
int qf_p_kist;
int qf_p_accel;
int qf_p_gyro;
int qf_p_mag;
int qf_head;
float power_diff;
float yaw_std;
int open_water;
int direction[54];
*/
} WII5Processed2;
typedef struct {
WII5Processed1 processed1;
WII5Processed2 processed2;
} WII5Processed;
// **********************************************************************
// *** This file belongs in WII5_SD_Block ***
// **********************************************************************
+339
View File
@@ -0,0 +1,339 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5Display.cpp
* @brief Status display helpers: SD-block view, formatted metadata dumps.
*/
/*
WII5Display
*/
#include <Arduino.h>
#include <WII5Display.h>
// TODO Move to utils (could also be pinMode by different inputs)
uint8_t bit;
uint8_t port;
volatile uint8_t *reg;
volatile uint8_t *out;
int wii5_getPinMode(uint8_t pin) {
if (pin >= NUM_PINS) return (-1);
// TODO analog?
bit = digitalPinToBitMask(pin);
port = digitalPinToPort(pin);
reg = portModeRegister(port);
if (*reg & bit) return (OUTPUT);
out = portOutputRegister(port);
return ((*out & bit) ? INPUT_PULLUP : INPUT);
}
uint32_t minutes;
uint32_t used;
uint32_t WII5Display::minutesUntilNext(uint32_t period) {
minutes = (hour() * 60) + minute();
used = minutes % period;
return period - used;
}
// SD / Storage Display Helpers
void WII5Display::sdBlockView(uint32_t block_in, bool show_results, bool show_raw) {
// Read block
#ifdef WII5_DEBUG_DISPLAY
Serial.print(F("# BLOCK="));
Serial.println(block_in);
#endif
if (!sdBlock.read(block_in)) {
console.log(LOG_ERROR, F("Unable to view block - %lu"), block_in);
return;
}
uint32_t dataBlockStart = sdBlock.metadata->dataBlockStart;
uint32_t dataBlocks = sdBlock.metadata->dataBlocks;
uint32_t resultsBlockStart = sdBlock.metadata->resultsBlockStart;
uint32_t resultsBlocks = sdBlock.metadata->resultsBlocks;
console.printf(F("@Block,metadata,address=%lu,deviceId=%lu,recordId=%lu\r\n"),
block_in, sdBlock.metadata->deviceId, sdBlock.metadata->recordId
);
console.printf(F("@Block,metadata,dataBlockStart=%lu,dataBlocks=%lu"),
dataBlockStart, dataBlocks
);
console.printf(F("@Block,metadata,resultsBlockStart=%lu,resultsBlocks=%lu"),
resultsBlockStart, resultsBlocks
);
printMetadataBlock((WII5MetaDataObject*)&sdBlock.metadata->data);
if (show_results) {
console.printf(F("# Loading results block\r\n"));
if (!sdBlock.read(resultsBlockStart)) {
console.log(LOG_ERROR, F("Unable to view resultsBlock - %lu"), resultsBlockStart);
return;
}
WII5Processed* processed = (WII5Processed*)sdBlock.block->data;
#ifdef WII5_DEBUG_DISPLAY
void* a = &processed->processed1;
void* b = &processed->processed1.part1float;
void* c = &processed->processed2.part2float;
void* d = &processed->processed2.part2int;
Serial.println(int(processed));
Serial.println(int(a));
Serial.println(int(b));
Serial.println(int(c));
Serial.println(int(d));
Serial.println(int(sizeof(WII5Processed)));
Serial.println(int(sizeof(WII5Processed1)));
Serial.println(int(sizeof(WII5Processed2)));
for (uint8_t i = 0; i < 73; i++) {
Serial.println(processed->processed1.part1float[i]);
}
for (uint8_t i = 0; i < 20; i++) {
Serial.println(processed->processed2.part2float[i]);
}
for (uint8_t i = 0; i < 62; i++) {
Serial.println(processed->processed2.part2int[i]);
}
#else
(void)processed;
#endif
console.print("# END out/processed.out");
console.printNewLine();
}
if (show_raw) {
console.log(LOG_ERROR, F("Raw view not yet implemented"));
}
}
void WII5Display::printMetadataBlock(WII5MetaDataObject* metadata) {
// TODO MAke this use metadtat abvoe but look the same
console.printf(F("@Metadata,start,print block\r\n"));
console.printf(F("@Metadata,deviceId=%ld,recordId=%ld\r\n"),
metadata->deviceId, metadata->recordCount
);
console.printf(F("@Metadata,time=%ld,uptime=%ld\r\n"),
metadata->last, metadata->uptime
);
console.printf(F("@Metadata,temperature=%ld,battery=%ld\r\n"),
metadata->temperatureValue, metadata->batteryValue
);
console.printf(F("@Metadata,lat=%ld,lon=%ld,alt=%ld,sats=%ld,hdop=%ld\r\n"),
long(wii5Gps.gps->location.lat() * GPS_POS_MULT), long(wii5Gps.gps->location.lng() * GPS_POS_MULT), long(wii5Gps.gps->altitude.meters() * GPS_POS_MULT),
metadata->gpsLat, metadata->gpsLon, metadata->gpsHdop
);
/*
console.printf(F("@Metadata,capture,timeError=%ld,sizeError=-%ld,writeMin=%ld,writeMax=%ld,writeOver=%ld\r\n"),
wii5Sparton.captureWriteMax, wii5Sparton.captureWriteMin, wii5Sparton.captureWriteOver,
wii5Sparton.statsTimeError, wii5Sparton.serialSizeError
);
*/
console.printf(F("@Metadata,end,print block\r\n"));
}
uint8_t a_start;
uint8_t a_end;
uint8_t p;
void WII5Display::dumpPins() {
a_start = analogInputToDigitalPin(0);
a_end = a_start + NUM_ANALOG_INPUTS - 1;
console.printf(F("# Pins:\r\n"));
for (p = 0; p < NUM_PINS; p++) {
console.printf(F("# D%02d "), int(p));
console.printf(F("%s "), wii5Strings.strPinMode(wii5_getPinMode(p)));
console.printf(F(" "));
// Long term - if analog - analogRead too or instead
if ((p >= a_start) && (p <= a_end)) {
console.printf(F("%04d"), analogRead(p));
}
else {
console.printf(F("%s"), wii5Strings.strState(digitalRead(p)));
}
console.printf(F(" "));
console.printf(F("%s"), wii5Strings.strPinArduinoName(p));
console.printf(F(" "));
console.printf(F("%s"), wii5Strings.strPinWII5Name(p));
console.printf(F(" | "));
// TODO - Arduino name, D3, A4, MOSI, etc
// TODO - WII5 name - e.g. GPS Power Switch
// 4 per line?
if ( ((p+1) % 3) == 0) {
console.printNewLine();
}
}
console.printNewLine();
}
void WII5Display::atMathsSend(const __FlashStringHelper *area, const __FlashStringHelper *out, ... ){
console.printf(F("@Maths,"));
console.printf(area);
console.printf(F(","));
va_list argptr;
va_start(argptr, out);
VSNPRINTF(wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT, (const char *)out, argptr);
va_end(argptr);
console.print(wii5BufferConsolePrint);
// console.printf(F(",%lu/%lu,"), wii5Config.getDeviceId(), wii5Config.getRecordCount());
// console.printDateTime(),
console.printNewLine();
}
void WII5Display::atStatsSend(const __FlashStringHelper *area, const __FlashStringHelper *out, ... ){
console.printf(F("@Stats,"));
console.printf(area);
console.printf(F(",%lu/%lu,"), wii5Config.getDeviceId(), wii5Config.getRecordCount());
console.printDateTime(),
console.printf(F(","));
va_list argptr;
va_start(argptr, out);
VSNPRINTF(wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT, (const char *)out, argptr);
va_end(argptr);
console.print(wii5BufferConsolePrint);
console.printNewLine();
}
void WII5Display::atDataSend(const __FlashStringHelper *area, const __FlashStringHelper *out, ... ){
console.printf(F("@Data,"));
console.printf(area);
console.printf(F(",%lu/%lu,"), wii5Config.getDeviceId(), wii5Config.getRecordCount());
console.printDateTime(),
console.printf(F(","));
va_list argptr;
va_start(argptr, out);
VSNPRINTF(wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT, (const char *)out, argptr);
va_end(argptr);
console.print(wii5BufferConsolePrint);
console.printNewLine();
}
// NOTE ! This is the StatusSerial - not the console
void WII5Display::statusSend(const __FlashStringHelper *area, const __FlashStringHelper *out, ... ){
#ifdef WII5_STATUS_SERIAL
SerialStatus.print(area);
SerialStatus.print(",");
// Low level - assume we know device id - see if we can send it
// SerialStatus.print(wii5Config.getDeviceId());
// SerialStatus.print("/");
SerialStatus.print(wii5Config.getRecordCount());
// SerialStatus.printDateTime(),
SerialStatus.print(F(","));
va_list argptr;
va_start(argptr, out);
VSNPRINTF(wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT, (const char *)out, argptr);
va_end(argptr);
SerialStatus.print(wii5BufferConsolePrint);
SerialStatus.println();
#endif
}
void WII5Display::atCommandSend(const __FlashStringHelper *area, const __FlashStringHelper *out, ... ){
console.printf(F("@WII5,"));
console.printf(area);
// TODO @WII5 commands do not normally have device and date details
console.printf(F(",%lu/%lu,"), wii5Config.getDeviceId(), wii5Config.getRecordCount());
console.printDateTime(),
console.printf(F(","));
va_list argptr;
va_start(argptr, out);
VSNPRINTF(wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT, (const char *)out, argptr);
va_end(argptr);
console.print(wii5BufferConsolePrint);
console.printNewLine();
}
void WII5Display::atCommsSend(const __FlashStringHelper *area, const __FlashStringHelper *out, ... ){
console.printf(F("@Comms,"));
console.printf(area);
console.printf(F(",%lu/%lu,"), wii5Config.getDeviceId(), wii5Config.getRecordCount());
console.printDateTime(),
console.printf(F(","));
va_list argptr;
va_start(argptr, out);
VSNPRINTF(wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT, (const char *)out, argptr);
va_end(argptr);
console.print(wii5BufferConsolePrint);
console.printNewLine();
}
void WII5Display::printMetadata() {
console.printf(F("@Metadata,deviceId=%ld,recordId=%ld\r\n"),
wii5Config.getDeviceId(), wii5Config.getRecordCount()
);
console.printf(F("@Metadata,time=%ld,uptime=%ld\r\n"),
now(), wii5Controller.uptime
);
console.printf(F("@Metadata,temperature=%ld,battery=%ld\r\n"),
wii5Weather_18B20.value, wii5Battery.value
);
#ifdef WII5_GPS
console.printf(F("@Metadata,lat=%ld,lon=%ld,alt=%ld,sats=%ld,hdop=%ld\r\n"),
long(wii5Gps.gps->location.lat() * GPS_POS_MULT), long(wii5Gps.gps->location.lng() * GPS_POS_MULT), long(wii5Gps.gps->altitude.meters() * GPS_POS_MULT),
wii5Gps.gps->satellites.value(), wii5Gps.gps->hdop.value()
);
#endif
console.printf(F("@Metadata,capture,timeError=%ld,sizeError=-%ld,writeMin=%ld,writeMax=%ld,writeOver=%ld\r\n"),
wii5Sparton.captureWriteMax, wii5Sparton.captureWriteMin, wii5Sparton.captureWriteOver,
wii5Sparton.statsTimeError, wii5Sparton.serialSizeError
);
}
void WII5Display::updateMetadata(void* ref = NULL) {
if (ref) {
metadata = (WII5MetaDataObject*)ref;
}
if (metadata) {
memset(metadata, 0, sizeof(WII5MetaDataObject));
metadata->deviceId = wii5Config.getDeviceId();
metadata->recordCount = wii5Config.getRecordCount();
// TODO last is now? What about start of capture time?
metadata->last = now();
metadata->age = 0;
metadata->uptime = wii5Controller.uptime;
metadata->temperatureValue = wii5Weather_18B20.value;
metadata->temperatureAge = wii5Weather_18B20.age;
metadata->batteryValue = wii5Battery.value;
metadata->batteryAge = wii5Battery.age;
#ifdef WII5_GPS
// TODO move to float
metadata->gpsLat = wii5Gps.gps->location.lat();
metadata->gpsLon = wii5Gps.gps->location.lng();
metadata->gpsAlt = wii5Gps.gps->altitude.meters();
metadata->gpsSat = wii5Gps.gps->satellites.value();
metadata->gpsHdop = wii5Gps.gps->hdop.value();
// fixtime
// age
metadata->gpsAge = wii5Gps.gps->time.age();
#endif
metadata->captureWriteMax = wii5Sparton.captureWriteMax;
metadata->captureWriteMin = wii5Sparton.captureWriteMin;
metadata->captureWriteOver = wii5Sparton.captureWriteOver;
metadata->captureTimeError = wii5Sparton.statsTimeError;
metadata->captureSizeError = wii5Sparton.serialSizeError;
metadata->captureStartTime = wii5ModeCapture.startTime;
metadata->mode = (uint8_t)wii5Controller.getMode();
}
}
WII5Display wii5Display;
+46
View File
@@ -0,0 +1,46 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5Display.h
* @brief Status display helpers: SD-block view, formatted metadata dumps.
*/
#ifndef WII5Display_h
#define WII5Display_h
#include <WII5.h>
class WII5Display {
public:
WII5Display() {}
virtual WII5_CONTROLLERS controllerId() {return WII5CONTROLLER_DISPLAY;}
void dumpPins();
// TODO rename - very confusing
void statusSend(const __FlashStringHelper *area, const __FlashStringHelper *out, ... );
uint32_t minutesUntilNext(uint32_t period);
// Standard commands
void atMathsSend(const __FlashStringHelper *area, const __FlashStringHelper *out, ... );
void atDataSend(const __FlashStringHelper *area, const __FlashStringHelper *out, ... );
void atStatsSend(const __FlashStringHelper *area, const __FlashStringHelper *out, ... );
void atCommandSend(const __FlashStringHelper *area, const __FlashStringHelper *out, ... );
void atCommsSend(const __FlashStringHelper *area, const __FlashStringHelper *out, ... );
void updateMetadata(void* ref = NULL);
void printMetadata();
void printMetadataBlock(WII5MetaDataObject* metadata);
void sdBlockView(uint32_t block, bool view_md, bool view_raw);
protected:
WII5MetaDataObject* metadata;
};
extern WII5Display wii5Display;
#endif
+394
View File
@@ -0,0 +1,394 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5GPS.cpp
* @brief GPS driver: NMEA parsing via TinyGPS++, time/position updates.
*/
/*
WII5 GPS
*/
#include <Arduino.h>
#include <WII5.h>
#include <WII5GPS.h>
#include <WII5Sh3dConsole.h>
// HARDWARE begin - Just once to configure
void WII5GPS::begin() {
enableNmea();
powerSetPin(POWER_GPS_PIN, POWER_GPS_ON);
powerOff(true); // Forced off at boot
#ifdef WII5_BUFFER_GPS
setBuffer(wii5BufferGPS, WII5_BUFFER_GPS);
#endif
step = WII5GPS_OFF;
stepWait = 0;
running = false;
minutes = DEFAULT_MINUTES;
processMode = WII5SERIALPARSER_LINE;
lastTime = 864000000; // 10 days ago
sinceOnLast = 864000000;
};
// True if last GPS waas more than 12 hours ago
bool WII5GPS::old() {
return (lastTime > (WII5TIME_12HOUR * 1000));
}
void WII5GPS::off() {
step = WII5GPS_OFF; stepWait = 0;
stop(true);
}
void WII5GPS::on() {
step = WII5GPS_ON; stepWait = 0;
start(true);
sinceOnLast = 0;
}
void WII5GPS::autoTime() {
// Set mode to turn off after a Time, then step = OFF
step = WII5GPS_TIME; stepWait = 0;
start(true);
}
void WII5GPS::autoPos() {
// Set mode to turn off after a position, then step = OFF
step = WII5GPS_POS; stepWait = 0;
start(true);
}
void WII5GPS::autoAccurate() {
// Set mode to turn off after a more accurate position, then step = OFF
step = WII5GPS_ACCURATE; stepWait = 0;
start(true);
}
void WII5GPS::autoRepeat() {
// Set mode to control GPS power on/off every N minutes to get a good time and position
// keep step here.
step = WII5GPS_REPEAT; stepWait = 0;
start(true);
}
// start - and Stop - can be manually called but might get overwritten by modes in loop
void WII5GPS::start(bool force) {
if (!running || !stream || force) {
powerOn(force);
SerialGPS.begin(SerialGPS_Baud);
stream = &SerialGPS;
// waitTime = UPDATE_TIME; // Update as soon as ready
waitTime = 0; // TODO 2024 - Change to 0 and shorter UPDATE TIME
beginSerialManager();
// NOTE: This requires modified TinyGPS++ now
/* TODO 2024 Removed - how to change?
gps->encodedCharCount = 0;
gps->sentencesWithFixCount = 0;
gps->failedChecksumCount = 0;
gps->passedChecksumCount = 0;
*/
finished = false;
running = true;
lastError = WII5ERROR_UNKNOWN;
runTime = 0;
sinceOnLast = 0; // Kept how long even with errors
}
}
// stop
void WII5GPS::stop(bool force) {
if (running || force) {
stream = NULL;
SerialGPS.end();
endSerialManager();
powerOff(force);
running = false;
}
}
// set and get minutes
void WII5GPS::setMinutes(uint8_t newMinutes) {
minutes = newMinutes;
}
uint8_t WII5GPS::getMinutes() {
return minutes;
}
// loop
void WII5GPS::loop() {
if (running) {
WII5SerialManager::loop();
if (
// Wait - usually 5 minutes before updating time again
(waitTime > UPDATE_TIME)
&& (gps->date.isValid())
&& (gps->date.age() < 5000)
&& (gps->time.isValid())
&& (gps->time.age() < 5000)
&& (gps->satellites.isValid())
&& (gps->satellites.value() > 4) // 2024 moved back to 4 ? 2024 - Moved from 4 to 2, aka 5 satellites down to 3
) {
/* TODO 2024
console.log(LOG_INFO, F("GPS: set time before %d-%d-%d %d:%d:%d"),
gps->date.year(), gps->date.month(), gps->date.day(),
gps->time.hour(), gps->time.minute(), gps->time.second()
);
*/
console.log(LOG_INFO, F("GPS: Before setting time loop"));
when = now();
setTime(
gps->time.hour(), gps->time.minute(), gps->time.second(),
gps->date.day(), gps->date.month(), gps->date.year()
);
console.log(LOG_INFO, F("GPS: After setting time loop"));
// Yay - we have a valid time - lets set this
lastTime = 0;
// Ignore anything less than 1 second (is there a way to do before setTime) (10 secconds)
// TODO configurable... 10 seconds safe?
// TODO abs - does it work with long
//if ( ( (now() - when) > 10 ) || ( when - now() > 10) ) {
#ifdef WII5_RTC
wii5RTC.setRTC();
#endif
//}
waitTime = 0;
}
}
first = (step != stepLast);
stepLast = step;
switch (step) {
// WII5GPS_OFF - Completely off, powered down, disabled.
case WII5GPS_OFF:
// NOTE: This is called every time, but does nothing if already stoppped. Just safety
if (first)
stop();
// When to do more ?
// - e..g no DateTime so trigger GPS
break;
// WII5GPS_QUIET - Turn on GPS and capture data normally, but don't print anything
// (passthrough now separate)
case WII5GPS_ON:
if (first)
start();
break;
case WII5GPS_TIME:
if (first)
start();
// Valid and age is < 10 seconds???
if (
(stepWait > 5000)
&& gps->date.isValid()
&& (gps->date.age() < 10000)
&& gps->time.isValid()
&& (gps->time.age() < 10000)
&& (gps->satellites.isValid())
&& (gps->satellites.value() > 4) // 2024 moved from 4 to 2
) {
lastError = WII5ERROR_NONE;
// Callback or other things?
finished = true;
lastRunTime = (uint32_t)(runTime / 1000);
// TODO Log and Display
step = WII5GPS_OFF; stepWait = 0;
}
else if (stepWait > (wii5Config.getGpsTimeout() * 1000)) {
lastError = WII5ERROR_TIMEOUT;
finished = true;
lastRunTime = (uint32_t)(runTime / 1000);
step = WII5GPS_OFF; stepWait = 0;
}
break;
case WII5GPS_POS:
if (first)
start();
// Valid and age is < 30 seconds???
if (
(stepWait > 5000)
&& gps->location.isValid()
&& (gps->time.age() < 20000)
&& (gps->satellites.isValid())
&& (gps->satellites.value() >= 4) // 2024 Keep 3 for position
) {
lastError = WII5ERROR_NONE;
// Callback or other things?
finished = true;
lastRunTime = (uint32_t)(runTime / 1000);
// TODO Log and Display
step = WII5GPS_OFF; stepWait = 0;
}
else if (stepWait > (wii5Config.getGpsTimeout() * 1000)) {
lastError = WII5ERROR_TIMEOUT;
finished = true;
lastRunTime = (uint32_t)(runTime / 1000);
step = WII5GPS_OFF; stepWait = 0;
}
break;
case WII5GPS_ACCURATE:
if (first)
start();
if (
// TODO HDOP, VDOP, TDOP - and possibly other average
(stepWait > 5000)
&& gps->location.isValid()
&& (gps->date.isValid())
&& (gps->date.age() < 5000)
&& (gps->time.isValid())
&& (gps->time.age() < 5000)
&& (gps->satellites.isValid())
&& (gps->satellites.value() >= 4) // 2024 - moved from >4 to >=4 aka 5 to 4 for accurate
) {
lastError = WII5ERROR_NONE;
// Callback or other things?
finished = true;
lastRunTime = (uint32_t)(runTime / 1000);
// TODO Log and Display
console.log(LOG_INFO, F("GPS: Accurate Complete"));
console.log(LOG_INFO, F("GPS: Before setting time accurate"));
when = now();
setTime(
gps->time.hour(), gps->time.minute(), gps->time.second(),
gps->date.day(), gps->date.month(), gps->date.year()
);
console.log(LOG_INFO, F("GPS: After setting time accurate"));
lastTime = 0;
waitTime = 0;
step = WII5GPS_OFF; stepWait = 0;
}
else if (stepWait > (wii5Config.getGpsTimeout() * 1000)) {
lastError = WII5ERROR_TIMEOUT;
finished = true;
lastRunTime = (uint32_t)(runTime / 1000);
step = WII5GPS_OFF; stepWait = 0;
}
break;
case WII5GPS_REPEAT:
if (first)
start();
if (
(stepWait > 60000)
&& gps->location.isValid()
&& (gps->time.age() < 20000)
&& (gps->satellites.isValid())
&& (gps->satellites.value() >= 4) // 2024 - moved from >4 to >=4
) {
stepWait = 0;
// TODO Log and Display
}
break;
default:
step = WII5GPS_OFF; stepWait = 0;
break;
}
return;
}
bool WII5GPS::isTimeValid() {
// Less than 12 hours since we had a good lock?
return (
(lastTime < (WII5TIME_12HOUR * 1000))
&& (year() >= 2019)
// TODO EWWWW YEAH EWWW I KNOW !
&& (year() < 2045)
);
}
bool WII5GPS::ready() {
return finished;
}
bool WII5GPS::isError() {
return (lastError != WII5ERROR_NONE);
}
bool WII5GPS::isRunning() {
return (step != WII5GPS_OFF);
}
void WII5GPS::dump(bool toConsole, Print* toOther) {
// Error
if (isError()) {
snprintf_P(
wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT,
PSTR("@Metadata,gps,error=timeout\r\n")
);
}
else {
snprintf_P(
wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT,
PSTR("@Metadata,gps,error=none\r\n")
);
}
if (toOther) toOther->print(wii5BufferConsolePrint);
if (toConsole) console.print(wii5BufferConsolePrint);
// Position
snprintf_P(
wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT,
PSTR("@Metadata,gps,position,%ld,%ld,%ld,%lu\r\n"),
long(gps->location.lat() * GPS_POS_MULT),
long(gps->location.lng() * GPS_POS_MULT),
long(gps->altitude.meters() * GPS_ALT_MULT),
gps->location.age()
);
if (toOther) toOther->print(wii5BufferConsolePrint);
if (toConsole) console.print(wii5BufferConsolePrint);
// Speed and Course
snprintf_P(
wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT,
PSTR("@Metadata,gps,direction,%ld,%ld,%lu\r\n"),
long(gps->course.deg() * 100),
long(gps->speed.kmph() * 100),
gps->speed.age()
);
if (toOther) toOther->print(wii5BufferConsolePrint);
if (toConsole) console.print(wii5BufferConsolePrint);
// Datetime
snprintf_P(
wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT,
PSTR("@Metadata,gps,datetime,%04d-%02d-%02dT%02d:%02d:%02d,%lu,%lu\r\n"),
int(gps->date.year()), int(gps->date.month()), int(gps->date.day()),
int(gps->time.hour()), int(gps->time.minute()), int(gps->time.second()),
gps->date.value(), gps->date.age()
);
if (toOther) toOther->print(wii5BufferConsolePrint);
if (toConsole) console.print(wii5BufferConsolePrint);
// Quality
snprintf_P(
wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT,
PSTR("@Metadata,gps,quality,satellites=%lu,hdop=%lu,age=%lu\r\n"),
gps->satellites.value(), gps->hdop.value(),
gps->date.age()
);
if (toOther) toOther->print(wii5BufferConsolePrint);
if (toConsole) console.print(wii5BufferConsolePrint);
// Stats
snprintf_P(
wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT,
PSTR("@Metadata,gps,stats,locktime=%lu,chars=%lu,sentences=%lu\r\n"),
lastRunTime, gps->charsProcessed(), gps->sentencesWithFix()
);
if (toOther) toOther->print(wii5BufferConsolePrint);
if (toConsole) console.print(wii5BufferConsolePrint);
}
WII5GPS wii5Gps;
+107
View File
@@ -0,0 +1,107 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5GPS.h
* @brief GPS driver: NMEA parsing via TinyGPS++, time/position updates.
*/
#ifndef WII5GPS_h
#define WII5GPS_h
/*
So one second after the module is powered on normally,
you can have the actual RTC time in the $GPRMC NMEA sentence,
even if the fixing tells 'V' like 'void' (invalid). But the time is valid
RTC time, if your battery backup did not run out of
power since you powered the system off.
*/
#include <Arduino.h>
#include <WII5.h>
#define DEFAULT_MINUTES 5
#define UPDATE_TIME 60000 // TODO 2024 1 minute
/**
* @brief GPS driver: u-blox NMEA via TinyGPS++.
*
* Manages power-on, fix acquisition, and clock-update flows. NMEA sentences
* are parsed off the WII5SerialManager base; the result feeds the global
* `wii5Gps.gps` (TinyGPSPlus) instance for location/altitude/HDOP queries.
*/
class WII5GPS : public WII5Power, public WII5SerialManager {
public:
WII5GPS() {}
virtual WII5_CONTROLLERS controllerId() {return WII5CONTROLLER_DRIVER;}
virtual WII5_DRIVERS driverId() {return WII5DRIVER_GPS;}
/** @brief One-time bring-up; pin config comes from the active board file. */
void begin();
/** @brief State-machine tick: drains NMEA, advances mode. */
void loop();
/** @brief Set how long the GPS stays on each acquisition window. */
void setMinutes(uint8_t newMinutes);
/** @brief Current acquisition window length, in minutes. */
uint8_t getMinutes();
/** @brief Print a status dump; `toOther` optionally mirrors to a second Print. */
void dump(bool toConsole = true, Print* toOther = NULL);
/** @brief Power off the GPS module. */
void off();
/** @brief Power on the GPS module. */
void on();
/** @brief One-shot acquisition tuned for time only. */
void autoTime();
/** @brief One-shot acquisition tuned for position. */
void autoPos();
/** @brief Long acquisition for high-quality fix. */
void autoAccurate();
/** @brief Periodic re-acquisition (continuous mode). */
void autoRepeat();
/** @brief True once a valid fix is available. */
bool ready();
/** @brief True if the most recent acquisition failed. */
bool isError();
/** @brief True if the time fix is valid (independent of position). */
bool isTimeValid();
/** @brief True if a state-machine cycle is in flight. */
bool isRunning();
uint32_t lastRunTime;
time_t when;
/** @brief True if the last fix is older than the configured timeout. */
bool old();
elapsedMillis sinceOnLast; // Simple - check how long since it has last been on
protected:
virtual void start(bool force = false);
void stop(bool force = false);
WII5_ERRORS lastError;
bool finished; // The reqeust - e.g. wait for position
bool running;
WII5GPS_MODES step;
WII5GPS_MODES stepLast;
bool first;
elapsedMillis stepWait;
uint8_t minutes;
elapsedMillis waitTime;
elapsedMillis runTime;
elapsedMillis lastTime; // Last time it was valid
};
extern WII5GPS wii5Gps;
#endif
+237
View File
@@ -0,0 +1,237 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5Help.cpp
* @brief Help-text generator for the `@Help` console command.
*/
/*
WII5Help
Top Commands
* Inputs:
@Help
@WII5
* Outputs:
@List
@File
*/
#include <Arduino.h>
#include <WII5Help.h>
bool WII5Help::processConsoleCsv() {
// Top page
if (console.getCsvCount() < 2)
return displayIndex();
if (console.getCsvCount() < 3) {
if (strcmp_P(console.getCsvBuffer(1),(PGM_P) F("info")) == 0)
return displayInfo();
else if (strcmp_P(console.getCsvBuffer(1),(PGM_P) F("general")) == 0)
return displayGeneral();
else if (strcmp_P(console.getCsvBuffer(1),(PGM_P) F("advanced")) == 0)
return displayAdvanced();
else if (strcmp_P(console.getCsvBuffer(1),(PGM_P) F("setting")) == 0)
return displaySettings();
else if (strcmp_P(console.getCsvBuffer(1),(PGM_P) F("maths")) == 0)
return displayMaths();
else if (strcmp_P(console.getCsvBuffer(1),(PGM_P) F("storage")) == 0)
return displayStorage();
else if (strcmp_P(console.getCsvBuffer(1),(PGM_P) F("gps")) == 0)
return displayGps();
else if (strcmp_P(console.getCsvBuffer(1),(PGM_P) F("iridium")) == 0)
return displayIridium();
else if (strcmp_P(console.getCsvBuffer(1),(PGM_P) F("sparton")) == 0)
return displaySparton();
else if (strcmp_P(console.getCsvBuffer(1),(PGM_P) F("hardware")) == 0)
return displayHardware();
else if (strcmp_P(console.getCsvBuffer(1),(PGM_P) F("data")) == 0)
return displayData();
else if (strcmp_P(console.getCsvBuffer(1),(PGM_P) F("testing")) == 0)
return displayTesting();
}
// 404 - Not found
return display404();
}
// TODO Look at help modules Arduino
bool WII5Help::displayIndex() {
console.printf(F("WII5: Help - page = Index\r\n"));
console.printf(F("Commands list:\r\n"));
return displayList();
}
// Not found !
bool WII5Help::display404() {
console.printf(F("NOT FOUND!"));
return false;
}
// List help pages
bool WII5Help::displayList() {
console.printf(F("# @Help COMMANDS:\r\n"));
console.printf(F("# - @Help - Here\r\n"));
console.printf(F("# - @Help,info - how to get information\r\n"));
console.printf(F("# - @Help,general - display general commands\r\n"));
console.printf(F("# - @Help,testing - how to run manual and self testing modes\r\n"));
console.printf(F("# - @Help,hardware - brief information on hardware\r\n"));
console.printf(F("# - @Help,data - brief information on data formats\r\n"));
console.printf(F("# - @Help,setting\r\n"));
console.printf(F("# - @Help,storage\r\n"));
console.printf(F("# - @Help,gps\r\n"));
console.printf(F("# - @Help,iridium - iridium and communications menu\r\n"));
console.printf(F("# - @Help,sparton\r\n"));
return true;
}
bool WII5Help::displayInfo() {
console.printf(F("# @Help info:\r\n"));
console.printf(F("# * @WII5,hello - TODO 2024\r\n"));
console.printf(F("# * @WII5,people - List people\r\n"));
console.printf(F("# * @WII5,status - TODO 2024\r\n"));
console.printf(F("# * @WII5,dump - TODO 2024\r\n"));
return true;
}
bool WII5Help::displayGeneral() {
console.printf(F("# @Help general:\r\n"));
console.printf(F("# * @WII5,General\r\n"));
console.printf(F("# * @WII5,reset,666 - Reset now - must have 666\r\n"));
console.printf(F("# * @WII5,status - List status of all objects\r\n"));
console.printf(F("# * @WII5,log,default - Log level set to default\r\n"));
console.printf(F("# * @WII5,log,debug - Log level set to debug\r\n"));
console.printf(F("# * @WII5,log,error - Log level set to error\r\n"));
console.printf(F("# * @WII5,log,fatal - Log level set to fatal\r\n"));
console.printf(F("# * @WII5,log,test - Test the log levels\r\n"));
// TODO 2024 change this to date in #define? TODO 2024 better copyright
console.printf(F("# * Copyright PAS Consultants Pty. Ltd. 2024\r\n"));
return true;
}
bool WII5Help::displayAdvanced() {
console.printf(F("# @Help advanced:\r\n"));
console.printf(F("# * @WII5,help\r\n"));
return true;
}
bool WII5Help::displaySettings() {
console.printf(F("# @Help setting:\r\n"));
console.printf(F("# * @WII5,setting,list\r\n"));
console.printf(F("# * @WII5,setting,deviceid,{n} (10000 to 99999)\r\n"));
console.printf(F("# This is written to EEPROM and used for all directories\r\n"));
console.printf(F("# * @WII5,setting,time,yyyy,mm,dd,hh,mm,ss\r\n"));
console.printf(F("# eg: @WII5,setting,time,2017,01,07,11,01,02\r\n"));
console.printf(F("# to set the time from an authoratataive source (e.g. NTPD)"));
console.printf(F("# * @WII5,setting,mode,default|yyyy-mm-ddThh:mm|time_t,v1,v2,v3,v4\r\n"));
console.printf(F("# If you add a date, time it will be saved to temporary mode\r\n"));
console.printf(F("# * @WII5,(TODO)mode,default|time_t future,mode,x,y,z\r\n"));
console.printf(F("# * @WII5,setting,defaults\r\n")); // NOTE, add 3759 to reset counters too
console.printf(F("# * @WII5,setting,gpstimeout,{seconds}\r\n"));
console.printf(F("# * @WII5,setting,captureoptions,{minutes},{binaryType}\r\n"));
console.printf(F("# Type and Records can be 0 or left off, minutes is required.\r\n"));
console.printf(F("# eg: @WII5,setting,captureoptions,15,03,5120\r\n"));
console.printf(F("# Minutes in caption must be between 15 (1/4 hr) and 720 (12 hr)\r\n"));
console.printf(F("# * @WII5,setting,positionoptions,{minutes},{binaryType}\r\n"));
console.printf(F("# eg: @WII5,setting,positionoptions,5,01\r\n"));
console.printf(F("# * @WII5,setting,sleepoptions,{minutes},{binaryType}\r\n"));
console.printf(F("# eg: @WII5,setting,sleepoptions,720,01\r\n"));
console.printf(F("# Minutes in sleep must be between 60 (1hr) and 1440 (24 hr)\r\n"));
console.printf(F("# * @WII5,help\r\n"));
return true;
}
bool WII5Help::displayMaths() {
console.printf(F("# @Help maths:\r\n"));
console.printf(F("# Controlling the maths CPU\r\n"));
console.printf(F("# * @WII5,maths,status - current status\r\n"));
console.printf(F("# * @WII5,maths,start - startup the CPU\r\n"));
console.printf(F("# * @WII5,maths,stop - stop the CPU\r\n"));
console.printf(F("# * @WII5,maths,restart - Restart cpu (forced)\r\n"));
console.printf(F("# * @WII5,maths,flip - Stop CPU, flip SD cards and restart CPU\n"));
console.printf(F("# * @WII5,maths,beep - Beep this AVR - sent from the Maths CPU\r\n"));
console.printf(F("# * @WII5,maths,hold,{n} - Hold maths on for {n} minutes\r\n"));
return true;
}
bool WII5Help::displayStorage() {
console.printf(F("# @Help storage:\r\n"));
console.printf(F("# Storage class is used to manuall control and retrieve data\r\n"));
console.printf(F("# from the storage devices.\r\n"));
console.printf(F("# * @WII5,storage,status\r\n"));
console.printf(F("# * @WII5,storage,off\r\n"));
console.printf(F("# * @WII5,storage,sd1\r\n"));
console.printf(F("# * @WII5,storage,sd2\r\n"));
console.printf(F("# * @WII5,storage,list\r\n"));
console.printf(F("# * @WII5,storage,view,{block_id}\r\n"));
console.printf(F("# * @WII5,storage,results,{id}\r\n"));
console.printf(F("# * @WII5,storage,debug,{0|1}\r\n"));
console.printf(F("# * @WII5,storage,format,1\r\n"));
console.printf(F("# * @WII5,storage,format,2\r\n"));
/*
console.printf(F("# * @WII5,storage,this1maths2\r\n"));
console.printf(F("# * @WII5,storage,this2maths1\r\n"));
console.printf(F("# * @WII5,storage,dir,{new} - Get direcotry or set directory\r\n"));
console.printf(F("# * @WII5,storage,ls - List files\r\n"));
console.printf(F("# * @WII5,storage,records - List record IDs\r\n"));
console.printf(F("# * @WII5,storage,record,{id} - Get {current}, {next} or {n}\r\n"));
console.printf(F("# * @WII5,storage,read,{filename} - start reading {filename}\r\n"));
console.printf(F("# * @WII5,storage,read! - stop reading\r\n"));
*/
return true;
}
bool WII5Help::displayGps() {
console.printf(F("# @Help gps:\r\n"));
console.printf(F("# * @WII5,gps,status\r\n"));
console.printf(F("# * @WII5,gps,dump\r\n"));
console.printf(F("# * @WII5,gps,start\r\n"));
console.printf(F("# * @WII5,gps,stop\r\n"));
console.printf(F("# * @WII5,gps,passthrug,0|1\r\n"));
console.printf(F("# * @WII5,gps,debug,0|1\r\n"));
console.printf(F("# * @WII5,gps,autopos\r\n"));
return true;
}
bool WII5Help::displayIridium() {
console.printf(F("# @Help iridium:\r\n"));
console.printf(F("# * @WII5,communications,disabled,{0|1} - Set communications to be disabled or enabled\r\n"));
console.printf(F("# * @WII5,iridium,status - TODO 2024 most of these are not written, fix\r\n"));
console.printf(F("# * @WII5,iridium,off - force fully off, including power\r\n"));
console.printf(F("# * @WII5,iridium,last - Show again last data, inclding in progress\r\n"));
console.printf(F("# * @WII5,iridium,metadata - Firmware, Time, Signal Quality\r\n"));
console.printf(F("# * @WII5,iridium,sendtext,{rest is text}\r\n"));
console.printf(F("# * @WII5,iridium,sendbinary,{length} - must already be set in binary console\r\n"));
return true;
}
bool WII5Help::displaySparton() {
console.printf(F("# @Help sparton:\r\n"));
console.printf(F("# * @WII5,sparton,off - power off sparton\r\n"));
return true;
}
bool WII5Help::displayHardware() {
console.printf(F("# @Help hardware:\r\n"));
// TODO 2024 add
return true;
}
bool WII5Help::displayData() {
console.printf(F("# @Help data:\r\n"));
console.printf(F("# @WII5,bindata,<id>\r\n"));
return true;
}
bool WII5Help::displayTesting() {
console.printf(F("# @Help testing:\r\n"));
// TODO 2024 add
console.printf(F("# * @WII5,weather,read - read the temperature sensor and display\r\n"));
console.printf(F("# * @WII5,TODO 2024\r\n"));
return true;
}
WII5Help wii5Help;
+44
View File
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5Help.h
* @brief Help-text generator for the `@Help` console command.
*/
#ifndef WII5Help_h
#define WII5Help_h
#include <WII5.h>
#include <WII5Help.h>
class WII5Help {
public:
WII5Help() {}
virtual WII5_CONTROLLERS controllerId() {return WII5CONTROLLER_HELP;}
bool processConsoleCsv();
protected:
bool displayIndex();
bool display404();
bool displayList();
bool displayInfo();
bool displayGeneral();
bool displayMaths();
bool displayHardware();
bool displayData();
bool displayTesting();
bool displayAdvanced();
bool displaySettings();
bool displaySparton();
bool displayIridium();
bool displayGps();
bool displayStorage();
};
extern WII5Help wii5Help;
#endif
+851
View File
@@ -0,0 +1,851 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5Iridium.cpp
* @brief Iridium 9602/9603 SBD modem driver: AT-command state machine.
*/
/*
WII5Iridium:
TODO 2024 - Seems fairly stable, check TODO and review code
TOOO
Status
Examoe=oles, abny reference= etc.
Foundby=xm ... Daee
How to structure a message:
* Adding up size of Structs - if < 340 (Iridium Size)
WII5_IRIDIUM_BIN_MAX
Then just build it up into that binary entry
* If larger....
NFI
* Receive - assume one - and goes into bin buffer then processed
* Where is it proessed
Code Source:
* WII2_Board/WII2_WaveSim/WII2_WaveSim.ino
* NA
* WII2_Board/WII2_WaveBuoy/WII2_WaveBuoy.ino
* IridiumSBD Library
#include <IridiumSBD.h>
IridiumSBD isbd(SerialIridium); // , onPin);
* WII2_Board/Iridium/
* NA
* States/Iridium_Library
* NA
* Tracker/IridumData/IridiumData.ino
* IridiumSBD Library
* WII2_Board/Iridium
* NA
= What do we want to do =
== Automatic processing ==
* loop()
* Processes any mode it is in, including off
*
== Manual Helpers ==
* bool off()
* Returns true if able to switch off and turns to off mode.
* Returns false, if busy and unable to change mode
* bool status()
* Start status if you can. Will change to off when complete, see lastStatus
* Read lastStatus
* bool sendSimple(Messaage)
* Send a simple message, text or binary
* Returns true to say it is prepared
* bool sendStart(); - Setup to send messages - only if not already there
* uint8_t sendStatus(); - Return status so you can send a message
* bool sendMessage(Message); - Return true if buffered and trying, false if not sendStart, or sendStatus is busy
* bool sendEnd(); - End send - return true if we can. Will turn off automatically.
== Examples ==
* setup()
* loop()
iridum.loop();
Want to get the updated status
// If older than 5 minutes
if (lastStatusMillis > 300000)
.status()
*/
#include <Arduino.h>
#include <WII5Iridium.h>
#include <WII5Sh3dConsole.h>
#include <MemoryFree.h>
#ifdef WII5_COMMS_IRIDIUM
void WII5Iridium::begin() {
SerialComms.begin(SerialComms_Baud);
#ifdef WII5_BUFFER_IRIDIUM
setBuffer(wii5BufferIridium, WII5_BUFFER_IRIDIUM);
#else
console.log(LOG_ERROR, F("Iridium: Error, not setting buffer"));
#endif
powerSetPin(POWER_COMMS_PIN, POWER_COMMS_ON);
powerOff(true); // Forced off at boot
step = WII5IRIDIUM_OFF;
running = false;
initialized = false;
imei[0] = '\0';
sigQualRetry = 0;
sigQualMin = 2;
}
void WII5Iridium::start(bool force) {
if (debug)
console.log(LOG_DEBUG, F("IRIDIUM: start current=%d"), step);
if (step == WII5IRIDIUM_OFF) {
recordCount = 0; sendCount = 0;
step = WII5IRIDIUM_POWER; stepWait = 0;
}
powerOn();
}
void WII5Iridium::stop(bool force) {
if (debug)
console.log(LOG_DEBUG, F("IRIDIUM: stop current=%d"), step);
if (step != WII5IRIDIUM_OFF) {
// Delays - request off, that sort of thing... ! YES ! TODO
step = WII5IRIDIUM_OFF; stepWait = 0;
}
powerOff(force);
}
bool WII5Iridium::isRunning() {
return running;
}
// on
void WII5Iridium::powerOn(bool force) {
if (!running || force) {
if (debug)
console.log(LOG_DEBUG, F("IRIDIUM: power ON"));
running = true;
wii5Controller.shared5On();
WII5Power::powerOn();
SerialComms.begin(SerialComms_Baud);
stream = &SerialComms;
beginSerialManager();
}
}
// off
void WII5Iridium::powerOff(bool force) {
if (running || force) {
if (debug)
console.log(LOG_DEBUG, F("IRIDIUM: power off"));
running = false;
SerialComms.end();
stream = NULL;
WII5Power::powerOff();
endSerialManager();
wii5Controller.shared5Off();
}
}
void WII5Iridium::loop() {
WII5SerialManager::loop();
bool first = (step != stepLast);
stepLast = step;
switch (step) {
// WII5Iridium_OFF - Completely off, powered down, disabled.
case WII5IRIDIUM_OFF:
if (first) {
currentType = WII5IRIDIUM_REQUEST_NONE;
initialized = false;
wii5Display.atCommsSend(F("status"), F("off"));
powerOff();
/*
if (strlen(imei[0]) == 0) {
// TODO No IMEI - Turn on Iridium to get
// TODO Don't continue to do this. If done - stop, even if we dont' have one
requestFirmware();
}
*/
}
break;
case WII5IRIDIUM_POWER:
initialized = false;
powerOn();
// Wait half a second then spin on
if (stepWait > 1000) {
step = WII5IRIDIUM_ALIVE; stepWait = 0;
if (debug)
console.log(LOG_DEBUG, F("step power->alive"));
}
break;
case WII5IRIDIUM_ALIVE:
initialized = false;
sendLine(0);
// This probably needs to be _AT (not yet written)
processMode = WII5SERIALPARSER_OKHUH;
// Boot up config
last = WII5SERIALLAST_NONE; sendCount = 0;
step = WII5IRIDIUM_BOOT; stepWait = 0;
if (debug)
console.log(LOG_DEBUG, F("step boot_stop->boot_wait"));
break;
// Boot up config
case WII5IRIDIUM_BOOT:
if (first) {
programCount = 0; programTotal = 3;
setTimeout(15000);
}
switch (sendAll()) {
// Still waiting
case WII5SERIALCMD_WAITING:
break;
// Error
case WII5SERIALCMD_TIMEOUT:
console.log(LOG_ERROR, F("SERIAL: TIMEOUT sending (sendAll:0-3): %s"), buffer);
case WII5SERIALCMD_ERROR:
console.log(LOG_ERROR, F("SERIAL: ERROR sending (sendAll:0-3): %s"), buffer);
// TODO Do a retry count here, and possible power cycle
// Success
case WII5SERIALCMD_OK:
default:
// TODO - Consider starting again with OFF, then step back to READY etc etc
step = WII5IRIDIUM_FIRMWARE; stepWait = 0;
if (debug)
console.log(LOG_DEBUG, F("step program_base->program_start"));
break;
}
break;
// Firmware
case WII5IRIDIUM_FIRMWARE:
if (first) {
sendCount = 0;
}
else if (stepWait > 500) {
// TODO This is because we have shared buffer. FFS
// TODO also... this could blat half read in data ?
if (sendCount == 0) {
programLine(4);
setTimeout(15000);
}
switch (sendAndWait(WII5SERIALLAST_BOOTVERSION)) {
// Still waiting
case WII5SERIALCMD_WAITING:
break;
// Move on
case WII5SERIALCMD_TIMEOUT:
console.log(LOG_ERROR, F("SERIAL: TIMEOUT sending (programLine:4): %s"), buffer);
case WII5SERIALCMD_ERROR:
console.log(LOG_ERROR, F("SERIAL: ERROR sending (programLine:4): %s"), buffer);
case WII5SERIALCMD_OK:
default:
// TODO How to keep the return?
step = WII5IRIDIUM_IMEI; stepWait = 0;
if (debug) console.log(LOG_DEBUG, F("step firmware->signalquality"));
break;
};
}
break;
// IMEI
case WII5IRIDIUM_IMEI:
if (first) {
sendCount = 0;
}
else if (stepWait > 500) {
// TODO This is because we have shared buffer. FFS
if (sendCount == 0) {
programLine(5);
setTimeout(15000);
}
switch (sendAndWait(WII5SERIALLAST_NUMBER)) {
// Still waiting
case WII5SERIALCMD_WAITING:
break;
// Move on
case WII5SERIALCMD_TIMEOUT:
console.log(LOG_ERROR, F("SERIAL: TIMEOUT sending (programLine:5): %s"), buffer);
step = WII5IRIDIUM_SIGNALQUALITY; stepWait = 0;
break;
case WII5SERIALCMD_ERROR:
console.log(LOG_ERROR, F("SERIAL: ERROR sending (programLine:5): %s"), buffer);
step = WII5IRIDIUM_SIGNALQUALITY; stepWait = 0;
break;
case WII5SERIALCMD_OK:
default:
// TODO Woooh.... should be checking length etc.
strcpy(imei, buffer);
wii5Display.atCommsSend(F("info"), F("imei=%s"), imei);
step = WII5IRIDIUM_SIGNALQUALITY; stepWait = 0;
}
}
break;
// SYSTEMTIME
// case WII5IRIDIUM_SYSTEMTIME:
case WII5IRIDIUM_SIGNALQUALITY_WAIT:
if (first) {
if (debug)
console.log(LOG_ERROR, F("Irigin: Waiting n seconds before retry signal quality"));
}
// 15 second delay to retry Signal Quality
// TODO for now 5 seconds, alo... should be able check this
// TODO 2024 - Making this 20 seconds
else if (stepWait > 20000) {
if (debug)
console.log(LOG_ERROR, F("Irigin: Waiting complete"));
step = WII5IRIDIUM_SIGNALQUALITY; stepWait = 0;
}
break;
// Signal Quality
case WII5IRIDIUM_SIGNALQUALITY:
if (first) {
sendCount = 0;
}
else if (stepWait > 500) {
// TODO This is because we have shared buffer. FFS
if (sendCount == 0) {
programLine(7);
setTimeout(30000);
}
switch (sendAndWait(WII5SERIALLAST_ATCSQ)) {
// Still waiting
case WII5SERIALCMD_WAITING:
break;
// Move on
case WII5SERIALCMD_TIMEOUT:
console.log(LOG_ERROR, F("SERIAL: TIMEOUT sending (programLine:7): %s"), buffer);
case WII5SERIALCMD_ERROR:
console.log(LOG_ERROR, F("SERIAL: ERROR sending (programLine:7): %s"), buffer);
case WII5SERIALCMD_OK:
default:
// TODO Keep this data lastCSQ
if (lastVal < sigQualMin) {
if (sigQualRetry < IRIDIUM_RETRY_MAX) {
sigQualRetry++;
wii5Display.atCommsSend(F("info"), F("signalquality=%d,retry=%d"), int(lastVal), int(sigQualRetry));
// Setting sendCount to zero, will trigger sending Line 7 again
// (not necessary now using signal quality wait) sendCount = 0;
step = WII5IRIDIUM_SIGNALQUALITY_WAIT; stepWait = 0;
}
else {
wii5Display.atCommsSend(F("info"), F("signalquality=%d,retry max"), int(lastVal));
lastSince = 0;
lastSignalQuality = int(lastVal);
step = WII5IRIDIUM_SEND_JUMP; stepWait = 0;
}
}
else {
lastSince = 0;
lastSignalQuality = lastVal;
wii5Display.atCommsSend(F("info"), F("signalquality=%d"), int(lastSignalQuality));
step = WII5IRIDIUM_SEND_JUMP; stepWait = 0;
}
}
}
else if (stepWait > 120000) {
lastSince = 0;
lastSignalQuality = 0;
wii5Display.atCommsSend(F("info"), F("signalquality=0,timeout"));
step = WII5IRIDIUM_SEND_JUMP; stepWait = 0;
}
break;
// Wait here for a retry.
case WII5IRIDIUM_RETRY:
if (first) {
if (debug)
console.log(LOG_ERROR, F("Irigin: Waiting n seconds before retry send"));
}
// 15 second delay to retry Signal Quality
else if (stepWait > 20000) {
step = WII5IRIDIUM_SEND_JUMP; stepWait = 0;
}
break;
// Choose - send Text or Binary - always returns data
case WII5IRIDIUM_SEND_JUMP:
initialized = true;
if (currentType == WII5IRIDIUM_REQUEST_SIGNALQUALITY) {
// TODO - should be done
step = WII5IRIDIUM_WAITING; stepWait = 0;
}
else if (currentType == WII5IRIDIUM_REQUEST_TIME) {
// TODO - should be done
step = WII5IRIDIUM_WAITING; stepWait = 0;
}
else if (currentType == WII5IRIDIUM_REQUEST_FIRMWARE) {
// TODO - should be done
step = WII5IRIDIUM_WAITING; stepWait = 0;
}
else if (currentType == WII5IRIDIUM_REQUEST_SENDTEXT) {
step = WII5IRIDIUM_PREPARE_TEXT; stepWait = 0;
if (debug)
console.log(LOG_DEBUG, F("step send_jump->prepare_text"));
}
else if (currentType == WII5IRIDIUM_REQUEST_SENDBIN) {
step = WII5IRIDIUM_PREPARE_BIN1; stepWait = 0;
if (debug)
console.log(LOG_DEBUG, F("step send_jump->prepare_bin1"));
}
else {
step = WII5IRIDIUM_OFF; stepWait = 0;
if (debug)
console.log(LOG_DEBUG, F("step send_jump->off"));
}
break;
// Send Text
case WII5IRIDIUM_PREPARE_TEXT:
if (first) {
if (debug) console.log(LOG_DEBUG, F("Iridium: Setting PrepareText Sentence"));
wii5Display.atCommsSend(F("send"), F("prepare=text,length=%d"), int(strlen(wii5BinaryIridium)));
sendCount = 0;
}
else if (stepWait > 500) {
//
// AT+SBDWT=hello - Text send, maxium length = 120 bytes
//
if (sendCount == 0) {
// strncpy_P(buffer ,(PGM_P) F("AT+SBDWT=My first nonBlocking Iridium attempt"), bufMax);
// TODO will this be big enough
sprintf_P(buffer, PSTR("AT+SBDWT=%s"), wii5BinaryIridium);
setTimeout(15000);
}
switch (sendAndWait()) {
// Still waiting
case WII5SERIALCMD_WAITING:
break;
// Move on
case WII5SERIALCMD_TIMEOUT:
console.log(LOG_ERROR, F("SERIAL: TIMEOUT sending (AT+SBDWT=X): %s"), buffer);
case WII5SERIALCMD_ERROR:
console.log(LOG_ERROR, F("SERIAL: ERROR sending (AT+SBDWT=X): %s"), buffer);
case WII5SERIALCMD_OK:
default:
if (debug)
console.log(LOG_DEBUG, F("SERIAL: OK setting up (AT+SBDWT=X)"));
step = WII5IRIDIUM_SEND; stepWait = 0;
if (debug) console.log(LOG_DEBUG, F("step prepare_text->send_text"));
break;
};
}
break;
case WII5IRIDIUM_RECEIVE:
if (first) {
if (debug) console.log(LOG_DEBUG, F("Iridium: Receiving Data"));
sendCount = 0;
// Use the shared Binary buffer for incoming now
memset(wii5BinaryIridium, 0, 340);
binBuffer = wii5BinaryIridium;
processMode = WII5SERIALPARSER_BINARY1;
binBufferExpect = 0; // Important - must set to 0
// SEND THIS - AT+SBDRB
strncpy_P(buffer ,(PGM_P) F("AT+SBDRB"), bufMax);
stream->print(buffer);
sendNewLine();
wii5Display.atCommsSend(F("receive"), F("start"));
}
else if (stepWait > 500) {
if (processMode != WII5SERIALPARSER_BINARY1) {
// OK So we have a message !
// Put it somewhere
// Swith process Mode back and move on
// - We can send here, no delay, then switch to command wait
// - Command wait will be looking for reply in Command TODO
// - or Timeout
// - At which point it will send a reply, and receive next command
// - On receiveing command can be go three ways.
// . Didn't understand the command - ignore / log locally
// . Understood - got a reply, and send a replly
// . Undertsood but timeout... send reply timeout
lastReceive = WII5IRIDIUMRECEIVERESULT_OK;
lastReceiveLen = binBufferExpect;
lastReceiveBuff = wii5BinaryIridium[0]; // First 2 are length;
wii5BinaryIridium[lastReceiveLen] = '\0';
// TODO Should we strip last 2 to zero?
// TODO - What if the queue says there is more to receive. Maybe just wait
// since we are alwqys sending lots, and receiving little ?
processMode = WII5SERIALPARSER_OKHUH;
wii5Display.atCommsSend(F("receive"), F("complete,size=%d"), lastReceiveLen);
step = WII5IRIDIUM_WAITING; stepWait = 0;
}
}
else if (stepWait > 5000) {
wii5Display.atCommsSend(F("receive"), F("timeout"));
// TODO Timeout... Force back clear etc (how to clear buffers etc
// TODO Show errors
processMode = WII5SERIALPARSER_OKHUH;
step = WII5IRIDIUM_WAITING; stepWait = 0;
}
break;
case WII5IRIDIUM_SEND:
if (first) {
if (debug) console.log(LOG_DEBUG, F("Iridium: Setting SendToSat Sentence"));
sendCount = 0;
}
else if (stepWait > 500) {
if (sendCount == 0) {
strncpy_P(buffer ,(PGM_P) F("AT+SBDI"), bufMax);
setTimeout(45000);
}
switch (sendAndWait(WII5SERIALLAST_ATSBDI)) {
// Still waiting
case WII5SERIALCMD_WAITING:
break;
// Move on
case WII5SERIALCMD_TIMEOUT:
console.log(LOG_ERROR, F("SERIAL: TIMEOUT sending (AT+SBDI): %s"), buffer);
case WII5SERIALCMD_ERROR:
console.log(LOG_ERROR, F("SERIAL: ERROR sending (AT+SBDI): %s"), buffer);
wii5Display.atCommsSend(F("send"), F("failed,during +SBDI"));
lastSend = WII5IRIDIUMSENDRESULT_FAILED_OTHER;
step = WII5IRIDIUM_WAITING; stepWait = 0;
currentType = WII5IRIDIUM_REQUEST_NONE;
break;
case WII5SERIALCMD_OK:
default:
// TODO CHeck for return value of +SBDI or +SBDIX incase we need to retry
wii5Display.atCommsSend(F("send"), F("success,mo_status=%lu,mo_MSN=%lu,mt_status=%lu,mt_MSN=%lu,mt_len=%lu,mt_queued=%lu"),
recordVals[0], recordVals[1],
recordVals[2], recordVals[3], recordVals[4], recordVals[5]
);
lastMOMSN = recordVals[1];
lastSince = 0;
switch(recordVals[0]) {
// Success fully sent or nothing to send
case 0:
case 1:
// LOG SUCCESS !!!
console.log(LOG_INFO, F("Iridium: SEND: Success"));
lastSend = WII5IRIDIUMSENDRESULT_OK;
currentType = WII5IRIDIUM_REQUEST_NONE;
// Do we have a waiting message, download it off the Iridium
// 2 = Number of messages, 4 = Length of this one
if ( (recordVals[2] > 0) && (recordVals[4] > 0) ) {
wii5Display.atCommsSend(F("receive"), F("mt_len=%lu"),
recordVals[4]
);
step = WII5IRIDIUM_RECEIVE; stepWait = 0;
if (debug) console.log(LOG_DEBUG, F("step send_text->receive`"));
}
// No waiting messages.
else {
step = WII5IRIDIUM_WAITING; stepWait = 0;
// TODO Include Iridium message ID etc etc
wii5Display.atCommsSend(F("send"), F("complete"));
// TODO Capture etc
if (debug) console.log(LOG_DEBUG, F("step send_text->waiting`"));
}
break;
// Other error
case 2:
default:
// Retry
retry++;
if (retry < IRIDIUM_RETRY_MAX) {
// LOG RETRY !!!
wii5Display.atCommsSend(F("send"), F("failed,retry=%d"), int(retry));
step = WII5IRIDIUM_RETRY; stepWait = 0;
}
else {
// LOG FAILED !!!
wii5Display.atCommsSend(F("send"), F("failed,exit"));
lastSend = WII5IRIDIUMSENDRESULT_FAILED_OTHER;
step = WII5IRIDIUM_WAITING; stepWait = 0;
currentType = WII5IRIDIUM_REQUEST_NONE;
}
break;
};
// TODO - check for receive stuff
break;
};
}
break;
// Send Binary
case WII5IRIDIUM_PREPARE_BIN1:
//
// +SBDWB=<SBD message length>
//
// {binary SBD message} + {2-byte checksum}
//
// The checksum is the least signi cant 2-bytes of the summation of the entire SBD message. The high order byte will be sent rst. For example if the 9602 were to send the word “hello” encoded in ASCII to the DTE the binary stream would be hex 00 05 68 65 6c 6c 6f 02 14.
//
// Response:
// 0 SBD message successfully written to the 9602.
// 1 SBD message write timeout. An insu cient number of bytes were transferred to 9602 during the transfer period of 60 seconds.
// 2 SBD message checksum sent from DTE does not match the checksum calculated by the 9602.
// 3 SBD message size is not correct. The maximum mobile originated SBD message length is 340 bytes. The minimum mobile originated SBD message length is 1 byte.
//
if (first) {
if (debug) console.log(LOG_DEBUG, F("Iridium: Setting PrepareBin Sentence"));
wii5Display.atCommsSend(F("send"), F("prepare=binary,length=%d"), int(binSize));
sendCount = 0;
}
else if (stepWait > 500) {
//
// AT+SBDWB=bytes
//
if (sendCount == 0) {
sprintf_P(buffer, PSTR("AT+SBDWB=%d"), int(binSize));
setTimeout(15000);
}
switch (sendAndWait(WII5SERIALLAST_READY)) {
case WII5SERIALCMD_WAITING:
break;
case WII5SERIALCMD_TIMEOUT:
console.log(LOG_ERROR, F("SERIAL: TIMEOUT sending (AT+SBDWB=X): %s"), buffer);
case WII5SERIALCMD_ERROR:
console.log(LOG_ERROR, F("SERIAL: ERROR sending (AT+SBDWB=X): %s"), buffer);
case WII5SERIALCMD_OK:
default:
if (debug)
console.log(LOG_INFO, F("SERIAL: OK setting up (AT+SBDWB=X)"));
step = WII5IRIDIUM_PREPARE_BIN2; stepWait = 0;
if (debug) console.log(LOG_DEBUG, F("step prepare_text->prepare_bin2"));
break;
};
}
break;
case WII5IRIDIUM_PREPARE_BIN2:
if (first) {
// Logging?
}
else if (stepWait > 500) {
checksum = 0;
if (passthrough)
console.printBinary(wii5BinaryIridium, binSize);
for (loopcount = 0; loopcount < binSize; loopcount++) {
stream->write(wii5BinaryIridium[loopcount]);
checksum += (uint8_t)wii5BinaryIridium[loopcount];
}
stream->write(checksum >> 8);
stream->write(checksum & 0xFF);
// TODO how to find Status
// 0 success
// 1 TIMEOUT
// 2 checksum errors
// 3 size errors
step = WII5IRIDIUM_SEND; stepWait = 0;
}
break;
// Complete
case WII5IRIDIUM_STOP_PREPARE:
break;
case WII5IRIDIUM_END:
break;
// Wait here... just in case Maths wants to do some more. Give them 1 min
case WII5IRIDIUM_WAITING:
// TODO Configure time etc (should we send reminder?)
if (first) {
wii5Display.atCommsSend(F("status"), F("waiting"));
}
if (stepWait > 60000) {
step = WII5IRIDIUM_OFF; stepWait = 0;
currentType = WII5IRIDIUM_REQUEST_NONE;
if (debug) console.log(LOG_DEBUG, F("step waiting->off`"));
}
break;
default:
console.log(LOG_FATAL, F("IRIDIUM: Default step... step=%d"), step);
step = WII5IRIDIUM_OFF;
break;
}
return;
}
// Manually set the binary data - note, we have no buffer, so must have external for hhis
void WII5Iridium::setBinary(uint8_t *buf, uint16_t size) {
// Memcopy data size or max iridium size
binSize = size <= WII5_IRIDIUM_BIN_MAX ? size : WII5_IRIDIUM_BIN_MAX;
memcpy(wii5BinaryIridium, buf, binSize);
}
void WII5Iridium::setBinSize(uint16_t size) {
binSize = size <= WII5_IRIDIUM_BIN_MAX ? size : WII5_IRIDIUM_BIN_MAX;
}
void WII5Iridium::setText(char *str) {
strncpy(wii5BinaryIridium, *(char *)str, WII5_IRIDIUM_BIN_MAX); // WII5_STORAGE_FILENAME_LEN);
}
// Allow fprintf params like console etc.
void WII5Iridium::setText(const __FlashStringHelper *str) {
strncpy_P(wii5BinaryIridium, (const char *)str, WII5_IRIDIUM_BIN_MAX);
}
// Get current
WII5IRIDIUM_REQUEST WII5Iridium::currentRequest() {
return currentType;
}
bool WII5Iridium::requestSendText() {
// If no text, faile - return false,
currentType = WII5IRIDIUM_REQUEST_SENDTEXT;
recordCount = 0; sendCount = 0; stepWait = 0;
lastSend = WII5IRIDIUMSENDRESULT_NONE;
lastReceive = WII5IRIDIUMRECEIVERESULT_NONE;
retry = 0;
sigQualRetry = 0;
if (initialized)
step = WII5IRIDIUM_SEND_JUMP;
else
step = WII5IRIDIUM_POWER;
return true;
}
bool WII5Iridium::requestSendBinary() {
// If we have binary data.... else return false
currentType = WII5IRIDIUM_REQUEST_SENDBIN;
lastSend = WII5IRIDIUMSENDRESULT_NONE;
lastReceive = WII5IRIDIUMRECEIVERESULT_NONE;
recordCount = 0; sendCount = 0; stepWait = 0;
retry = 0;
sigQualRetry = 0;
if (initialized)
step = WII5IRIDIUM_SEND_JUMP;
else
step = WII5IRIDIUM_POWER;
return true;
}
bool WII5Iridium::requestFirmware() {
currentType = WII5IRIDIUM_REQUEST_FIRMWARE;
lastSend = WII5IRIDIUMSENDRESULT_NONE;
lastReceive = WII5IRIDIUMRECEIVERESULT_NONE;
recordCount = 0; sendCount = 0; stepWait = 0;
step = WII5IRIDIUM_POWER;
retry = 0;
sigQualRetry = 0;
}
// Waiting - Feel free to check the last status and send another
bool WII5Iridium::waiting() {
return ((step == WII5IRIDIUM_OFF) || (step == WII5IRIDIUM_WAITING));
}
/*
void WII5Iridium::clearLast() {
}
*/
void WII5Iridium::programLine(uint16_t l) {
// Main Programming
switch (l) {
// BOOT=0..3,
case 0:
strncpy_P(buffer ,(PGM_P) F("ATE0"), bufMax);
break;
case 1:
strncpy_P(buffer ,(PGM_P) F("AT&D0"), bufMax);
break;
case 2:
strncpy_P(buffer ,(PGM_P) F("AT&K0"), bufMax);
break;
case 3:
strncpy_P(buffer ,(PGM_P) F("AT+SBDMTA=0"), bufMax);
break;
// Firmware
case 4:
strncpy_P(buffer ,(PGM_P) F("AT+CGMR"), bufMax);
break;
// IMEI
case 5:
strncpy_P(buffer ,(PGM_P) F("AT+CGSN"), bufMax);
break;
// System Time
case 6:
strncpy_P(buffer ,(PGM_P) F("AT-MSSTM"), bufMax);
break;
// Signal Quality
case 7:
strncpy_P(buffer ,(PGM_P) F("AT+CSQ"), bufMax);
break;
case 8:
strncpy_P(buffer ,(PGM_P) F("AT"), bufMax);
break;
// send Text
// send Binary
// HOW TO: Retries
// HOW TO: Work around firmware issues
default:
console.log(LOG_FATAL, F("IRIDIUM: request invalid programming string"));
buffer[0] = '\0';
break;
}
if (debug)
console.log(LOG_DEBUG, F("IRIDIUM: set command=%d buffer=%s"), l, buffer);
}
void WII5Iridium::sendNewLine() {
stream->print(F("\r"));
}
WII5Iridium wii5Iridium;
#endif
+265
View File
@@ -0,0 +1,265 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5Iridium.h
* @brief Iridium 9602/9603 SBD modem driver: AT-command state machine.
*/
#ifndef WII5Iridium_h
#define WII5Iridium_h
#include <Arduino.h>
#include <WII5.h>
#include <elapsedMillis.h>
#ifdef WII5_COMMS_IRIDIUM
/*
How to make request ?
How to know when complete?
void loop() {
wii5Iridium.loop();
if (wii5Iridium.lastStatus) {
// Check the Status of the last thing we sent
}
if (wii5Iridium.lastReceive) {
// TODO
}
}
DATA STRUCTURE TO Send
* Wrap WII5 Binary Packet in Iridium specific data
* Time it took to send (updated just before send each time)
* Number of retries
* Last Signal quality
Useful References:
https://docs.rockblock.rock7.com/reference#sbdix
*/
enum WII5IRIDIUM_REQUEST {
WII5IRIDIUM_REQUEST_NONE,
WII5IRIDIUM_REQUEST_FIRMWARE,
WII5IRIDIUM_REQUEST_TIME,
WII5IRIDIUM_REQUEST_SIGNALQUALITY,
WII5IRIDIUM_REQUEST_SENDTEXT,
WII5IRIDIUM_REQUEST_SENDBIN,
};
enum WII5IRIDIUM_STATUS {
WII5IRIDIUM_STATUS_READY, // Ready to send, no errors
WII5IRIDIUM_STATUS_BUSY, // Busy, still sending, need to wait
WII5IRIDIUM_STATUS_FAILED // Reason for failure?
};
// TODO request - need to know what the input is and how to stop...
enum WII5IRIDIUM_STEPS {
// Just off.
WII5IRIDIUM_OFF, // Turn it off - power and all
WII5IRIDIUM_POWER, // Turn on power and wait 500ms
WII5IRIDIUM_ALIVE, // Send "AT" until get OK or timeout
WII5IRIDIUM_BOOT, // ATE1, AT&D0, AT&K0, AT+SBDMTA=0
// Firmware
WII5IRIDIUM_FIRMWARE,// AT+CGMR
// IMEI
WII5IRIDIUM_IMEI, // AT-MSSTM
// System Time
WII5IRIDIUM_SYSTERMTIME, // AT-MSSTM
// Signal Quality
WII5IRIDIUM_SIGNALQUALITY_WAIT,
WII5IRIDIUM_SIGNALQUALITY, // AT+CSQ
// Choose - send Text or Binary - always returns data
WII5IRIDIUM_RETRY, // Wait here a bit before rety
WII5IRIDIUM_SEND_JUMP, // Are we TEXT or BINARY
// Send Text
WII5IRIDIUM_PREPARE_TEXT, // AT+SBDWT=RockBlocks1
WII5IRIDIUM_SEND, // AT+SBDI ... +SBDI: 1, 3, 0, 0, 0, 0
// Send Binary
WII5IRIDIUM_PREPARE_BIN1, // AT+SBDWB={n} (size of data)
WII5IRIDIUM_PREPARE_BIN2, // AT+SBDWB={n} (size of data)
// Receive Message
WII5IRIDIUM_RECEIVE, // AT+SBDRB ... AT+SBDRB (echo back)
WII5IRIDIUM_RECEIVE_BIN, // first 2 bytes = size, then binary, then checksum
WII5IRIDIUM_WAITING, // Waiting - hurry up and absorb
// Complete
WII5IRIDIUM_STOP_PREPARE, // END ! Jump to STOP_PREPARE,
WII5IRIDIUM_END // Very end
};
#define DEFAULT_MINUTES 5
#define IRIDIUM_RETRY_MIN 2 // Used for low power mode, how many times to rety minimum
#define IRIDIUM_RETRY_MAX 20 // Maximum number of retries for normal modes
// FROM IridiumSBD by Rock Blocks
#define ISBD_LIBRARY_REVISION 2
#define ISBD_DEFAULT_AT_TIMEOUT 30
#define ISBD_MSSTM_RETRY_INTERVAL 10
#define ISBD_DEFAULT_SBDIX_INTERVAL 10
#define ISBD_USB_SBDIX_INTERVAL 30
#define ISBD_DEFAULT_SENDRECEIVE_TIME 300
#define ISBD_STARTUP_MAX_TIME 240
#define ISBD_MAX_MESSAGE_LENGTH 340
#define ISBD_MSSTM_WORKAROUND_FW_VER 13001
#define ISBD_SUCCESS 0
#define ISBD_ALREADY_AWAKE 1
#define ISBD_SERIAL_FAILURE 2
#define ISBD_PROTOCOL_ERROR 3
#define ISBD_CANCELLED 4
#define ISBD_NO_MODEM_DETECTED 5
#define ISBD_SBDIX_FATAL_ERROR 6
#define ISBD_SENDRECEIVE_TIMEOUT 7
#define ISBD_RX_OVERFLOW 8
#define ISBD_REENTRANT 9
#define ISBD_IS_ASLEEP 10
#define ISBD_NO_SLEEP_PIN 11
#define ISBD_NO_NETWORK 12
#define ISBD_MSG_TOO_LONG 13
enum WII5IRIDIUM_RETURNS {
WII5IRIDIUM_RETURN_AT,
WII5IRIDIUM_RETURN_OK,
WII5IRIDIUM_RETURN_READY,
WII5IRIDIUM_RETURN_ERROR,
WII5IRIDIUM_RETURN_SBDI, // +SBDI, +SBDIX, +SBDIXA (moCode, &moMSN, &mtCode, &mtMSN, &mtLen, &mtRemaining)
WII5IRIDIUM_RETURN_CSQ, // +CSQ:{n}
WII5IRIDIUM_RETURN_MSSTM, // -MSSTM
WII5IRIDIUM_RETURN_BINARY // Hard coded the binary
};
/**
* @brief Driver for the Iridium 9602/9603 SBD modem.
*
* AT-command state machine: power on, wait for the modem to come alive,
* boot up (ATE1, AT&D0, AT&K0, AT+SBDMTA=0), then drive a sequence of
* +CSQ / +SBDWB / +SBDIX / +SBDRB cycles to send and receive 340-byte
* SBD messages. State is in WII5IRIDIUM_STEPS; see step transitions in
* WII5Iridium.cpp::loop().
*/
class WII5Iridium : public WII5Power, public WII5SerialManager{
public:
WII5Iridium() {}
virtual WII5_CONTROLLERS controllerId() {return WII5CONTROLLER_DRIVER;}
virtual WII5_DRIVERS driverId() {return WII5DRIVER_COMMS_IRIDIUM;}
/** @brief One-time bring-up. */
void begin();
/** @brief State-machine tick: parses serial, advances steps. */
void loop();
/** @brief Queue a firmware-version query (AT+CGMR). */
bool requestFirmware();
/** @brief Queue a binary send (uses buffer set by setBinary/setBinSize). */
bool requestSendBinary();
/** @brief Queue a text send (uses buffer set by setText). */
bool requestSendText();
/** @brief Currently-queued request type. */
WII5IRIDIUM_REQUEST currentRequest();
/** @brief Set the binary buffer + length for the next send. */
void setBinary(uint8_t* buf, uint16_t size);
/** @brief Set just the size of the binary buffer (caller writes the bytes). */
void setBinSize(uint16_t size);
/** @brief Set the text payload for the next send (RAM string). */
void setText(char* str);
/** @brief Set the text payload for the next send (PROGMEM string). */
void setText(const __FlashStringHelper *str);
/** @brief Begin a comms cycle (`force` skips initialized check). */
virtual void start(bool force = false);
/** @brief Abort the current cycle. */
void stop(bool force = false);
/** @brief Apply power to the Iridium modem. */
void powerOn(bool force = false);
/** @brief Cut power to the Iridium modem. */
void powerOff(bool force = false);
/** @brief Are we waiting on the modem to finish a step? */
bool waiting();
/** @brief Is the state machine currently in a non-idle step? */
bool isRunning();
// TODO Storage? why can't we use a 64 bit (8 byte) number?
char imei[18]; // 14 or up to 16 digits?
// uint32_t ; // 14 or up to 16 digits?
elapsedMillis lastSince;
uint8_t lastSignalQuality;
uint16_t lastMOMSN;
WII5IRIDIUM_SEND_RESULT lastSend;
WII5IRIDIUM_RECEIVE_RESULT lastReceive;
uint16_t lastReceiveLen;
uint8_t* lastReceiveBuff;
virtual void sendNewLine();
protected:
bool running;
WII5IRIDIUM_STEPS step;
WII5IRIDIUM_STEPS stepLast;
elapsedMillis stepWait;
void programLine(uint16_t l);
bool initialized;
WII5IRIDIUM_REQUEST currentType;
uint8_t retry; // How many times did we retry ?
uint8_t sigQualRetry; // How many times did we retry ?
uint8_t sigQualMin; // How many times did we retry ?
uint32_t binSize; // Size of binary data to send
uint16_t checksum;
uint16_t loopcount;
uint8_t *txData;
// Firmware (and age)
// Signal Quality (and age)
// DateTime (and age)
// Send Status (and age) (WAITING, COMPLETE, ERROR)
// Send Queue ? (or external)
// Receive Queue (or external)
// lastCSQ
// lastIMEI
// lastSystemTime
// lastID???
// lastStatus??
// Last Status =
};
extern WII5Iridium wii5Iridium;
#endif
#endif
+588
View File
@@ -0,0 +1,588 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5Maths.cpp
* @brief Maths CPU controller: power, hold, restart, and SD-card hand-off.
*/
/*
WII5Maths - Manage the power and serial to the Maths Compyter
TODO Seems Communications or Capture mode or something overrides hold - fix
Who is in charge ?
* This applies to Pi vs AVR - they can both reset each other,
who gets to say - nah you are being turned off, like it or
not.
* Also applies to within the Pi. Who decides to keep it turned
on, and if it is turned on, who decides when to turn off
and even more importantly - where is that triggered?
since we don't rely on the daemon.
* And - if no daemon - how do we receive commands
Scenario 1: Regular, Power on Pi to process SD Card
* Power up, wii5auto runs as per normal
* End of that wii5auto sends a "serial" command
to tell the AVR that it will be shutting down soon.
* End of that timeout, Pi shutsdown, one final serial
before self shutdown/power off (time how long it takes
before power is off)
* Q:
- How to run something, indipendent of the Daemon
(another listener, or proxy through server)
(complicated)
Scenario 2: Power Up boot without reason
@WII5,maths,booted
@WII5,maths!,task=mainreboot
Scenario 3: Power Up boot where Pi did the reboot/firmware etc
Pi won't know it to send a booted command, hello etc still
But AVR can send
@WII5,maths,ask <- Ask what you want instead of telling
@WII5,maths!,thanks <- lol
Could just, on getting ask, decide to ignore (Beause in
on mode for more minutes yet) or go straight to shutdown
COMMAND Utility:
* Write a command utitily, for senging @ based commands
to the wave buoy. In each case, the cli does not exit,
until it gets a reply (or timeout)
* Utility should do serial direct, but could potentially
use the API to send and recevive commands, falling back
to serial direct, when the daemon is non-responsive.
* Boot scenraio:
- Send a "why am I awake" - @WII5,maths,booted
Reply with @WII5,maths!,task=on,min=15
Boot Sequence Issues:
* AVR boot - even if WDT or on prupse at the end of sleep/Testing
* Turns on the Pi sets it to ask and waits a long time.
* LOT OF TIME and power.
setUntil - Not currently used, but allows a number to be set way off info
the future - not ideal though, because it could therefore be always on,
and dates change.
setHold - is simple and perfect reply to shut down.
*/
#include <Arduino.h>
#include <WII5Maths.h>
#include <WII5Sh3dConsole.h>
#include <TimeLib.h>
void WII5Maths::begin() {
retry = 0;
firstboot = false;
holdFor = 0;
mathsMode = WII5MATHSMODE_UNKNOWN;
mathsUntil = 0;
// NOTE: This is a two pin version - check it exists
#ifdef POWER_MATHS_PIN_OFF
powerSetPin(POWER_MATHS_PIN, POWER_MATHS_ON, POWER_MATHS_PIN_OFF);
#else
#ifdef POWER_MATHS_PIN
powerSetPin(POWER_MATHS_PIN, POWER_MATHS_ON);
#endif
#endif
// Make sure off at boot to allow capacitor time
// #ifdef POWER_MATHS_PIN
// WII5Power::powerOff(force);
// #endif
#ifdef MATHS_RETURNLINE
// TODO 2024 - Using this to tell Maths to stay on !!!
pinMode(MATHS_RETURNLINE, OUTPUT);
digitalWrite(MATHS_RETURNLINE, LOW);
#endif
// NOTE: Do not set the off or state - wait until we work out what mode this is in
// e.g. Maths may be already on and require to stay on
step = WII5MATHS_ASK;
stepLast = WII5MATHS_UNKNOWN;
stepWait = 0;
};
void WII5Maths::setReturnLine() {
pinMode(MATHS_RETURNLINE, OUTPUT);
digitalWrite(MATHS_RETURNLINE, HIGH);
returnlineWait = 0;
}
void WII5Maths::cancelReturnLine() {
pinMode(MATHS_RETURNLINE, OUTPUT);
digitalWrite(MATHS_RETURNLINE, LOW);
}
void WII5Maths::gpioClick() {
// elapsedGpio = 0;
}
// Already on - we don't want to boot up.... assume it is on. Lets assume ASK
void WII5Maths::woops_on() {
// Over simplified - only when off. Race condition here from WARN to OFF, but safe
switch(step) {
case WII5MATHS_OFF:
wii5Display.statusSend(F("Maths"), F("woopson"));
console.log(LOG_DEBUG, F("Maths woops_on - got a hello or similar, switch to ready"));
step = WII5MATHS_READY; stepWait = 0;
}
}
bool WII5Maths::isMode(WII5_MATHSMODE mm) {
return (mathsMode == mm);
}
void WII5Maths::start(WII5_MATHSMODE mm) {
mathsMode = mm;
console.log(LOG_DEBUG, F("Maths: start, step=%d off status=%d"), step, WII5MATHS_OFF);
switch(step) {
case WII5MATHS_OFF:
case WII5MATHS_WARN:
case WII5MATHS_ASK:
case WII5MATHS_HOLD_ON:
wii5Display.statusSend(F("Maths"), F("ModeSwitch=boot"));
console.log(LOG_DEBUG, F("Maths: Changing to BOOT, step=%d off status=%d"), step, WII5MATHS_OFF);
step = WII5MATHS_BOOT; stepWait = 0;
retry = 0;
}
}
void WII5Maths::stop(bool force) {
if (step != WII5MATHS_OFF) {
// Delays - request off, that sort of thing... ! YES
console.log(LOG_INFO, F("Maths: stop called, hold was=%lu, now=0"), remainingHold());
holdFor = 0;
step = WII5MATHS_WARN; stepWait = 0;
}
}
bool WII5Maths::isOff() {
return (step == WII5MATHS_OFF);
}
bool WII5Maths::isRunning() {
return (step != WII5MATHS_OFF);
}
void WII5Maths::setDebug(bool in) {
debug = in;
}
bool WII5Maths::getDebug() {
return debug;
}
// NOTE: Turn on/off shared - even when not necessary, when Maths is on
void WII5Maths::powerOn(bool force) {
// Turn on shared here always, but at boot, turn on shared and delay for capacitor charge
wii5Controller.shared5On();
WII5Power::powerOn(force);
}
// Block powerOff maybe
void WII5Maths::powerOff(bool force) {
// Leaves it i the mode, e.g. OFF, but set powerOn
if ((mathsUntil == 0) || (mathsUntil < now())) {
wii5Display.statusSend(F("Maths"), F("power=off,DISABLED"));
mathsUntil = 0;
wii5Display.atMathsSend(F("shutdown"), F("actual,power off"));
#ifdef POWER_MATHS_PIN
WII5Power::powerOff(force);
#endif
wii5Controller.shared5Off();
}
}
// Set hold time and switch to HOLD mode
// - allowLower is false by default, so it won't change unless bigger
void WII5Maths::setHold(uint32_t in, bool allowLower) {
// TODO - check Maths CPU is on. Reset hold count. Set how long for, and tell it to HOLD
if ((in == 0) || allowLower || (remainingHold() < in)) {
holdWait = 0;
holdFor = in; // Seconds, not milliseconds
}
// if state is maths off - fix it
switch(step) {
case WII5MATHS_OFF:
case WII5MATHS_WARN:
case WII5MATHS_ASK:
step = WII5MATHS_HOLD_ON; stepWait = 0;
break;
}
// Always make sure power is on.
powerOn(true);
}
void WII5Maths::cancelHold() {
if (holdFor > 0) {
console.log(LOG_INFO, F("Maths: cancelHold called, hold was=%lu, now=0"), remainingHold());
holdFor = 0;
powerOff();
step = WII5MATHS_OFF; stepWait = 0;
}
}
// SUPER simple send Queue
void WII5Maths::sendQueue() {
// TODO how to know that it has been done... lets just try three times.
if (queueCount > 0) {
console.log(LOG_FATAL, F("MathsQeuue: SENDING %s. Retry=%d"), queueMessage[queueCount - 1], queueRetry);
console.printf(F("@Maths,%s\n"), queueMessage[queueCount - 1]);
queueRetry++;
if (queueRetry > 3) {
queueDone();
}
}
}
// Mark first as done
void WII5Maths::queueDone() {
queueRetry = 0;
if (queueCount > 1) {
// Copy the message 1 to 0
strncpy(queueMessage[0], queueMessage[1], MATHS_QUEUE_BUFFER);
queueCount = 1;
console.log(LOG_FATAL, F("MathsQueue: Second queued messaged moved to first"));
}
else {
queueCount = 0;
console.log(LOG_FATAL, F("MathsQueue: All messages done"));
}
}
bool WII5Maths::queueCommand(char *val, uint8_t valSize) {
// Not accessed queue in over 15 hours - lets clear it
if (queueLast > 54000000) {
if (queueCount != 0) {
console.log(LOG_INFO, F("NOTE: Maths queue was cleared - not used in 15 hours"));
queueCount = 0;
}
}
queueLast = 0;
if (queueCount == 0)
queueRetry = 0;
if (queueCount >= MATHS_QUEUE_MAX) {
console.log(LOG_INFO, F("Maximum queued messages still waiting"));
return false;
}
strncpy(queueMessage[queueCount], val, valSize < MATHS_QUEUE_BUFFER ? valSize : MATHS_QUEUE_BUFFER);
queueMessage[queueCount][MATHS_QUEUE_BUFFER - 1] = '\0';
console.log(LOG_INFO, F("MathsQueue: Added new request = @Maths,%s"), queueMessage[queueCount]);
queueCount++;
// Turn on and hold to do the above
setHold(900);
return true;
}
// Manual Hold Processing
// - Check if Maths is still on - if off, close off hold and exit
// - Manually check if we are still within hold period.
// - If inside - just wait longer
// - If outside - shut down Maths
// . We assume this is only called during non normal maths loop modes.
bool WII5Maths::processHold() {
// First time booted, we ASK the maths cpu - this should not last more than a minute
if (step == WII5MATHS_ASK) {
return true;
}
// Not actually running
if (holdFor == 0) {
return false;
}
// TODO console check for Maths held longer, or power OFF
if (holdWait > (1000 * holdFor)) {
cancelHold();
return false;
}
return true;
}
uint32_t WII5Maths::remainingHold() {
if (holdFor > MATHS_MAXHOLD) holdFor = MATHS_MAXHOLD;
// NOTE: Safety, if checking remaining then break out if error
if (holdWait > (MATHS_MAXHOLD * 1000)) {
console.log(LOG_INFO, F("Maths: error holdWait too long = %lu, holdFor=%lu"), holdWait, holdFor);
holdFor = 0;
holdWait = 0;
return 0;
}
return holdFor > 0 ? (holdFor - (holdWait / 1000)) : 0;
}
uint32_t WII5Maths::remainingUntil() {
return mathsUntil > now() ? (mathsUntil - now()) : 0;
}
void WII5Maths::loop() {
// Every hour cancel return line
if ((returnlineWait/1000) > MATHS_RETURNHOLD) {
cancelReturnLine();
returnlineWait = 0;
}
bool first = (stepLast != step);
stepLast = step;
switch (step) {
// ASK: This mode will ask the Maths CPU what state to be in
// - Timeout/noanswer will move on to OFF
// exit = hello response or timeout
case WII5MATHS_ASK:
// Setup as if in HOLD mode, but DO NOT turn on the power (this allows reboots without Pi)
// TODO Mayube we want it
holdWait = 0;
if (holdFor < 900)
holdFor = 900;
step = WII5MATHS_HOLD_ON; stepWait = 0;
break;
// WARN: - Show Maths that it is about to be shut down then wait...
case WII5MATHS_WARN:
if (first) {
if (remainingHold() > 0) {
step = WII5MATHS_HOLD_ON; stepWait = 0;
}
else {
wii5Display.statusSend(F("Maths"), F("mode=warn,power off,10s"));
wii5Display.atMathsSend(F("shutdown"), F("now,15 seconds"));
stepWait = 0;
}
}
// TODO - consider sending updates every 5 seconds
else if (stepWait > 10000) {
// Direct to OFF - beacuse this is WARN
step = WII5MATHS_OFF; stepWait = 0;
}
break;
// OFF: Sends a message going off but then powers off !
// exit = NA - Stays here forever, until outside source changes step
case WII5MATHS_OFF:
if (first) {
wii5Display.atMathsSend(F("shutdown"), F("now,power off"));
wii5Display.statusSend(F("Maths"), F("mode=off"));
console.log(LOG_DEBUG, F("MathsLoop. step=OFF first=%d"), first);
powerOff(true);
mathsMode = WII5MATHSMODE_UNKNOWN;
}
// TODO Normally maths is turned on as needed, but here we can have
// maybe a situation where it hasn't been on for too long? And consider it?
break;
// REBOOT: Powers off the device, waits 10 seconds then back to BOOT
// exit = 10 secoonds back to BOOT
case WII5MATHS_REBOOT:
if (first) {
powerOff(true);
wii5Display.atMathsSend(F("reboot"), F("now"));
wii5Display.statusSend(F("Maths"), F("reboot"));
console.log(LOG_DEBUG, F("MathsLoop. step=REBOOT first=%d"), first);
}
// Reboot - turn off for 10 seconds then back to BOOT
if (stepWait > 10000) {
console.log(LOG_DEBUG, F("MATHS: step reboot -> boot"));
step = WII5MATHS_BOOT; stepWait = 0;
}
break;
// BOOT: Turn the power back on, and jump to BOOT_WAIT
case WII5MATHS_BOOT:
if (first) {
console.log(LOG_INFO, F("MathsLoop. step=BOOT first=%d"), first);
wii5Display.statusSend(F("Maths"), F("boot"));
wii5Controller.shared5On();
// setStatus(WII5STATUS_WAITING);
}
// TODO 2024 - Look at if this is required or else immediate?
// TODO 200ms for capacitor charge? TODO 10 seconds for now for testing
// else if (stepWait > 10000)
else {
#ifdef POWER_MATHS_PIN
powerOn();
#endif
console.log(LOG_DEBUG, F("MATHS: step boot -> wait"));
step = WII5MATHS_BOOT_WAIT; stepWait = 0;
}
break;
// BOOT_WAIT - We twiddlge our thumbs, waiting for the CPu to respond to us
// exit - Hello or similar command from the Maths, or Timeout
// NOTE: On timeout, will probably try reboot, but this is
// where we may get into far more complex code checking for
// limited retries etc
case WII5MATHS_BOOT_WAIT:
if (first) {
console.log(LOG_INFO, F("MathsLoop. step=BOOT_WAIT first=%d"), first);
wii5Display.statusSend(F("Maths"), F("wait"));
displayWait = 30000;
}
// Do we have a recent hello from the Maths
else if (
wii5Controller.checkLastHello(WII5DEVICE_MATHS, powerElapsed())
) {
// SUCCESS - Got a hello from a Maths CPU since we have turned power pin on
console.log(LOG_DEBUG, F("MATHS: step wait -> ready"));
step = WII5MATHS_READY; stepWait = 0;
}
else if (displayWait > 30000) {
// Send Mode
if (mathsMode == WII5MATHSMODE_BUTTON) {
wii5Display.atMathsSend(F("mode"), F("button,no hello"));
}
else if (mathsMode == WII5MATHSMODE_COMMS) {
wii5Display.atMathsSend(F("mode"), F("comms,no hello"));
}
else if (remainingHold() > 0) {
wii5Display.atMathsSend(F("mode"), F("hold,no hello"));
}
else if (mathsMode == WII5MATHSMODE_AUTO) {
wii5Display.atMathsSend(F("mode"), F("auto,no hello"));
}
else {
wii5Display.atMathsSend(F("mode"), F("unknown,no hello"));
}
sendTime();
displayWait = 0;
}
else if (stepWait > 120000) {
retry++;
if (retry > 2) {
console.log(LOG_ERROR, F("MATHS: Timeout waiting for boot, switching off (multiple retries)"));
step = WII5MATHS_OFF; stepWait = 0;
}
else {
console.log(LOG_ERROR, F("MATHS: Timeout retry=%d"), retry);
step = WII5MATHS_REBOOT; stepWait = 0;
}
}
break;
// READY: and waiting patiently for you to tell it what to do.
// - Maths may shut itself down, or send hold commands
case WII5MATHS_READY:
if (first) {
console.log(LOG_DEBUG, F("MathsLoop. step=READY first=%d"), first);
wii5Display.statusSend(F("Maths"), F("ready"));
displayWait = 15000;
}
else if (displayWait > 15000) {
if (mathsMode == WII5MATHSMODE_BUTTON) {
wii5Display.atMathsSend(F("mode"), F("button,waiting"));
}
else if (mathsMode == WII5MATHSMODE_COMMS) {
wii5Display.atMathsSend(F("mode"), F("comms,waiting"));
}
else if (remainingHold() > 0) {
wii5Display.atMathsSend(F("mode"), F("hold,waiting"));
}
else if (mathsMode == WII5MATHSMODE_AUTO) {
wii5Display.atMathsSend(F("mode"), F("auto,waiting"));
}
else {
wii5Display.atMathsSend(F("mode"), F("unknown,waiting"));
}
sendQueue();
sendTime();
displayWait = 0;
}
// Wait 15 minutes - this is our worst case waiting - something gone wrong but looks right
else if (stepWait > (900000)) {
console.log(LOG_DEBUG, F("MATHS: step ready -> shutdown_wait"));
step = WII5MATHS_WARN; stepWait = 0;
}
break;
// HOLD_ON: Keep Maths CPU ON - there needs to be a maximum timeout, and maybe switch to ASK
// e.g. wait 1 hour, then ASK then 1 hour?
case WII5MATHS_HOLD_ON:
if (first) {
wii5Display.atMathsSend(F("mode"), F("hold,remaining=%lu,seconds"), remainingHold());
console.log(LOG_DEBUG, F("MathsLoop. step=HOLD_ON first=%d, holding for %lu seconds"), first, holdFor);
wii5Display.statusSend(F("maths"), F("holding,%lu,seconds"), remainingHold());
displayWait = 0;
}
else if (holdWait > (1000 * holdFor)) {
// Have we told them we are shutting down? Maybe that is in the OFF
holdFor = 0;
wii5Display.statusSend(F("maths"), F("holding,end"));
wii5Display.atMathsSend(F("mode"), F("auto"));
step = WII5MATHS_WARN; stepWait = 0;
}
else if (displayWait > 15000) {
wii5Display.atMathsSend(F("mode"), F("hold,remaining=%lu"), remainingHold());
wii5Display.statusSend(F("maths"), F("holding,%lu,seconds"), remainingHold());
sendQueue();
sendTime();
displayWait = 0;
}
// if (stepWait > (60 * 60 * 1000)) {
// TODO Force OFF !!!
// }
break;
case WII5MATHS_FINISH:
if (first)
console.log(LOG_INFO, F("MathsLoop. step=FINISH first=%d"), first);
console.log(LOG_DEBUG, F("MATHS: step finish -> off"));
step = WII5MATHS_WARN; stepWait = 0;
break;
default:
console.log(LOG_FATAL, F("MathsLoop: Default step... step=%d"), step);
step = WII5MATHS_WARN; stepWait = 0;
break;
// TODO Error?
}
return;
}
void WII5Maths::sendTime() {
if (timeWait < 60000) {
return;
}
timeWait = 0;
if (wii5Gps.isTimeValid()) {
console.log(LOG_DEBUG, F("Sending time - valid from GPS"));
wii5Display.atMathsSend(F("time"), F("%04d,%02d,%02d,%02d,%02d,%02d"),
int(year()), int(month()), int(day()),
int(hour()), int(minute()), int(second())
);
}
else {
console.log(LOG_DEBUG, F("Not sending time - not valid from GPS"));
}
}
// Set time to be held on, and force ON if not already
void WII5Maths::setMathsUntil(time_t t) {
if (t > now()) {
mathsUntil = t;
}
else {
mathsUntil = 0;
}
}
time_t WII5Maths::getMathsUntil() {
return mathsUntil;
}
WII5Maths wii5Maths;
+119
View File
@@ -0,0 +1,119 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5Maths.h
* @brief Maths CPU controller: power, hold, restart, and SD-card hand-off.
*/
#ifndef WII5Maths_h
#define WII5Maths_h
#include <Arduino.h>
#include <WII5Data.h>
#include <elapsedMillis.h>
#include <TimeLib.h>
#include <WII5.h>
enum WII5MATHS_STEPS {
WII5MATHS_UNKNOWN,
WII5MATHS_WARN,
WII5MATHS_OFF,
WII5MATHS_ASK,
WII5MATHS_REBOOT,
WII5MATHS_BOOT,
WII5MATHS_BOOT_WAIT,
WII5MATHS_READY,
WII5MATHS_HOLD_ON,
WII5MATHS_FINISH
};
#define MATHS_QUEUE_MAX 2
#define MATHS_QUEUE_BUFFER 150
class WII5Maths : public WII5Power {
public:
WII5Maths() {}
virtual WII5_CONTROLLERS controllerId() {return WII5CONTROLLER_DRIVER;}
virtual WII5_DRIVERS driverId() {return WII5DRIVER_MATHS;}
void setDebug(bool in);
bool getDebug();
void begin();
void loop();
void start(WII5_MATHSMODE mm);
void stop(bool force = false);
void powerOff(bool force = false);
void powerOn(bool force = false);
// Stats on running etc
time_t lastStartTime;
uint32_t lastRunTime;
void setMathsUntil(time_t t);
time_t getMathsUntil();
bool isRunning();
bool isOff();
void gpioClick();
void setHold(uint32_t in, bool allowLower = false);
void woops_on();
void cancelHold();
bool processHold();
void setReturnLine();
void cancelReturnLine();
uint32_t remainingHold();
uint32_t remainingUntil();
bool isMode(WII5_MATHSMODE mm);
void sendTime();
void sendQueue();
bool queueCommand(char *val, uint8_t valSize);
void queueDone();
protected:
uint8_t queueCount;
char queueMessage[MATHS_QUEUE_MAX][MATHS_QUEUE_BUFFER];
elapsedMillis queueLast;
uint8_t queueRetry;
bool running;
bool debug;
WII5_MATHSMODE mathsMode;
uint8_t retry;
bool firstboot; // First booted up - wait, not touching power lines
time_t mathsUntil;
// No longer used
// elapsedMillis elapsedHello;
// elapsedMillis elapsedRequest;
// elapsedMillis elapsedGpio;
elapsedMillis returnlineWait;
elapsedMillis holdWait;
uint32_t holdFor;
WII5MATHS_STEPS step;
WII5MATHS_STEPS stepLast;
elapsedMillis stepWait;
elapsedMillis displayWait;
elapsedMillis timeWait;
};
extern WII5Maths wii5Maths;
#endif
+29
View File
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5Mode.cpp
* @brief Base class for run-time operating modes (Capture, Sleep, Position, etc.).
*/
/*
NOTE: This is a base class for all modes and not really used
*/
#include <Arduino.h>
#include <TimeLib.h>
#include <WII5.h>
void WII5Mode::begin() {
}
void WII5Mode::loop() {
}
//void WII5Mode::metadataPrint(File* fh) {
//}
+41
View File
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5Mode.h
* @brief Base class for run-time operating modes (Capture, Sleep, Position, etc.).
*/
// NOTE: This is a base class for all modes and not really used
#ifndef WII5Mode_h
#define WII5Mode_h
#include <Arduino.h>
#include <TimeLib.h>
#include <WII5.h>
#include <elapsedMillis.h>
/**
* @brief Base class for run-time operating modes.
*
* Each subclass owns a small state machine that is ticked by
* WII5Controller::loop() while that mode is active. Concrete modes
* include WII5ModeCapture, WII5ModeSleep, WII5ModePosition,
* WII5ModeManualTest, WII5ModeSelfTest, and WII5ModeLowBattery.
*/
class WII5Mode {
public:
/** @brief One-time bring-up. */
virtual void begin();
/** @brief State-machine tick while this mode is active. */
virtual void loop();
protected:
elapsedMillis wait;
};
#endif
+564
View File
@@ -0,0 +1,564 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5ModeCapture.cpp
* @brief Capture mode: IMU/wave-motion data capture and SD logging.
*/
/*
MDOECapture - the main event
* Watch for our perfect time to start
* Prepare SD card, Sparton etc for running
* Trigger start / stop sparton
* Store data to disk
*/
#include <Arduino.h>
#include <TimeLib.h>
#include <WII5.h>
void WII5ModeCapture::reset() {
step = WII5CAPTURE_OFF; wait = 0; stepCount = 0;
stepLast = WII5CAPTURE_FINISH;
captureType = WII5CAPTURETYPE_UNKNOWN;
}
void WII5ModeCapture::begin() {
debug = true;
step = WII5CAPTURE_OFF; wait = 0; stepCount = 0;
captureType = WII5CAPTURETYPE_UNKNOWN;
enableAtFile = false;
enableGPS = true;
enableMaths = true;
enableComms = true;
enableLiveSwap = true; // Playing with this being safe for 2024
}
void WII5ModeCapture::start(bool force) {
// If OFF or WAITING for OFF - Skipt
if ( (step == WII5CAPTURE_OFF) || (step == WII5CAPTURE_WAIT5_OFF) || force ) {
console.log(LOG_INFO, F("Capture: MANUAL START - Now"));
captureType = WII5CAPTURETYPE_MANUAL;
captureStart = now();
step = WII5CAPTURE_START; wait = 0; retry = 0; stepCount = 0;
}
else {
// Switch to CANCEL Capture with restart now
console.log(LOG_INFO, F("Capture: MANUAL START - Cancelling existing and restarting"));
captureType = WII5CAPTURETYPE_MANUAL;
captureStart = now();
step = WII5CAPTURE_CANCEL_RESTART; wait = 0; retry = 0; stepCount = 0;
}
}
void WII5ModeCapture::stop(bool force) {
if ((step != WII5CAPTURE_OFF) || force) {
// Switch to CANCEL Capture with OFF (aka Wait)
step = WII5CAPTURE_CANCEL_OFF; wait = 0; retry = 0; stepCount = 0;
}
else {
// Manually just double check everything is off. (done above in the CANCEL_OFF stage)
if (enableGPS)
wii5Gps.off();
wii5Sparton.stop();
}
captureType = WII5CAPTURETYPE_UNKNOWN;
wait = 0; retry = 0; stepCount = 0;
}
elapsedMillis test1;
void WII5ModeCapture::loop() {
// Calculate seconds until next capture has to be started
bool first = (stepLast != step);
stepLast = step;
switch (step) {
case WII5CAPTURE_OFF:
// Total minutes since midnight
minutes = (hour() * 60) + minute();
// Automatically set to a max of 12 hours
period = wii5Config.getCapturePeriod();
remaining = period - (minutes % period);
if (first) {
console.log(LOG_INFO, F("Capture: OFF, waiting"));
sh3dNodeIO.led1Set( LED_SLOW );
}
// else if ((wii5Gps.sinceOnLast > (WII5TIME_1HOUR * 1000)) && (!wii5Gps.isTimeValid()))
// TODO 2024 Changed from 1 hour to 5 minutes - This should change to do a full auto every run
// Note: that & isTimeValid would often be true...
else if (wii5Gps.sinceOnLast > 300000) {
console.log(LOG_INFO, F("Capture: Internal clock / time is not valid, turn on GPS"));
step = WII5CAPTURE_TIME; wait = 0;
}
// NOTE: we have only 60 seconds
else if ( (minutes % period) == 0) {
console.log(LOG_INFO, F("Capture: Starting minutes match period min=%lu, period=%lu"), minutes, wii5Config.getCapturePeriod());
// TODO atData !!!
step = WII5CAPTURE_START; wait = 0; retry = 0; stepCount = 0;
captureType = WII5CAPTURETYPE_TIME;
captureStart = now();
}
// 20 or more mimutes
/*
else if (remaining > 30) {
// Hmmm... lets shut down everything, get the power lower
// TODO check Maths and Communications is off.
if (!wii5Communications.isRunning())
wii5Communications.stop();
if (wii5Controller.getSD() != 0)
wii5Controller.setSDOff();
}
*/
// 3 or more minutes
else if (remaining > 3) {
// Run temperature if more than 5 minutes old
if (wii5Weather_18B20.age > 300000) {
wii5Weather_18B20.temperatureRead();
wii5Battery.start();
}
}
// TODO
// - Iridium: loop can do its own start/stop processing data
// - Weather & Battery already do their own
// -
// TODO
// If WAIT is > X minutes, sleep, or just low power mode?
// GPS off, Iridium Off, Maths off, Sparton off, SD off
// Power Consumption = X
// AVR off as well
// Power Consumption = X
break;
case WII5CAPTURE_START:
// Output Manual vs Time start
switch(captureType) {
case WII5CAPTURETYPE_MANUAL:
wii5Display.statusSend(F("CaptureMode"), F("start,manual"));
wii5Display.atDataSend(
F("CaptureCommand"),
F("START,Manual")
);
break;
case WII5CAPTURETYPE_TIME:
wii5Display.statusSend(F("CaptureMode"), F("start,timeperiod"));
wii5Display.atDataSend(
F("CaptureCommand"),
F("START,Time,period=%lu,minutes=%lu"),
wii5Config.getCapturePeriod(),
minutes
);
break;
};
startTime = now();
step = WII5CAPTURE_PREPARE; wait = 0; retry = 0; stepCount = 0;
break;
case WII5CAPTURE_TIME:
if (first) {
// wii5Gps.autoTime();
wii5Gps.autoAccurate(); // TODO 2024 - Moved to full auto accurate instead
}
else if (wii5Gps.ready()) {
step = WII5CAPTURE_OFF; wait = 0;
}
else if (wait > 300000) {
step = WII5CAPTURE_OFF; wait = 0;
}
break;
case WII5CAPTURE_PREPARE:
// Experiment - start communications DURING a run
wii5Communications.setAutoMode();
// Some emergency off, just in case - also handy as it does a reboot
wii5Sparton.stop();
wii5Sparton.off();
step = WII5CAPTURE_PREPARE_ID; wait = 0; retry = 0; stepCount = 0;
break;
case WII5CAPTURE_PREPARE_ID:
wii5Config.updateRecordCount();
step = WII5CAPTURE_PREPARE_SD; wait = 0; retry = 0; stepCount = 0;
break;
case WII5CAPTURE_PREPARE_SD:
// TODO - consider power cylce and delays, and consequence on Maths
// TODO - Maybe only if card not open?
if (wii5Controller.getSD() == 1) {
wii5Controller.setSD1();
}
else {
wii5Controller.setSD2();
}
// TODO retry X times with power retry every second?
// TODO 2024 ! ZOMG ! safeDelay ? or not care? (was 1500)
delay(1000); // TODO Yes I know !
// TODO - If card not open?
if (sdBlock.cardOpen(wii5Controller.getSD())) {
wii5Display.statusSend(F("CaptureMode"), F("sdcard,current=%d"), wii5Controller.getSD());
wii5Display.atDataSend(
F("CaptureCommand"),
F("SDCard,current,%d"),
wii5Controller.getSD()
);
// open card - else - bugger !
if (sdBlock.dataOpen(wii5Config.getRecordCount())) {
wii5Display.atDataSend(
F("CaptureCommand"),
F("SDCard,current,%d,recordCount=%lu"),
wii5Controller.getSD(), wii5Config.getRecordCount()
);
#ifdef WII5_GPS
if (enableGPS) {
wii5Gps.autoAccurate();
}
#endif
step = WII5CAPTURE_PREPARE_COMMS; wait = 0; retry = 0; stepCount = 0;
}
else {
sh3dNodeIO.led1Set( LED_FAST );
console.log(LOG_FATAL, F("CAPTURE: Failed dataOpen cardNum=%d"), wii5Controller.getSD());
snprintf_P(
wii5Commands.lastCommandMessage, sizeof(wii5Commands.lastCommandMessage),
PSTR("@Err,sd,dataopen,%d"),
wii5Controller.getSD()
);
wii5Commands.lastCommandCmd = 0;
wii5Communications.sendError();
// Flip SD for next time
if (wii5Controller.getSD() == 1)
wii5Controller.setSD2();
else
wii5Controller.setSD1();
console.log(LOG_FATAL, F("CAPTURE: Cards swapped cardNum=%d"), wii5Controller.getSD());
step = WII5CAPTURE_WAIT5_OFF; wait = 0; retry = 0; stepCount = 0;
}
}
else {
sh3dNodeIO.led1Set( LED_FAST );
console.log(LOG_FATAL, F("CAPTURE: Failed cardOpen cardNum=%d"), wii5Controller.getSD());
snprintf_P(
wii5Commands.lastCommandMessage, sizeof(wii5Commands.lastCommandMessage),
PSTR("@Err,sd,cardopen,%d"),
wii5Controller.getSD()
);
wii5Commands.lastCommandCmd = 0;
wii5Communications.sendError();
// Flip SD for next time
if (wii5Controller.getSD() == 1)
wii5Controller.setSD2();
else
wii5Controller.setSD1();
console.log(LOG_FATAL, F("CAPTURE: Cards swapped cardNum=%d"), wii5Controller.getSD());
step = WII5CAPTURE_WAIT5_OFF; wait = 0; retry = 0; stepCount = 0;
}
break;
case WII5CAPTURE_PREPARE_COMMS:
// For now - skipping comms - leaving until the end
step = WII5CAPTURE_PREPARE_SPARTON; wait = 0; retry = 0; stepCount = 0;
break;
// Prepare the sparton to go
case WII5CAPTURE_PREPARE_SPARTON:
#ifdef WII5_IMU_SPARTON
wii5Display.statusSend(F("CaptureMode"), F("sparton,start"));
// TODO Configuration
wii5Sparton.setBinary(1);
wii5Sparton.setHz(8);
wii5Sparton.setRecords(wii5Config.getCaptureRecords());
wii5Sparton.setCapture(1);
// Start
// TODO 2024 - Do we need to wait? - or just start? When is commands sent to Sparton?
wii5Sparton.start();
#endif
step = WII5CAPTURE_INPROGRESS; wait = 0; retry = 0; stepCount = 0;
break;
// Capture is running - report progress? (might be done in IMU)
case WII5CAPTURE_INPROGRESS:
if (first) {
sh3dNodeIO.led1Set( LED_TRIPPLE );
}
else if (wii5Sparton.captureFinished()) {
step = WII5CAPTURE_SHUTDOWN; stepCount = 0; wait = 0;
}
// 30 minutes
else if (wait > 1800000) {
console.log(LOG_FATAL, F("Capture: Failed - timeout after 30 minutes"));
// TODO 2024 - sendError?
sh3dNodeIO.led1Set( LED_FAST );
step = WII5CAPTURE_CANCEL_OFF; wait = 0; retry = 0; stepCount = 0;
}
break;
// Shutdown the capture devices, IMU etc (should be internal IMIU)
case WII5CAPTURE_SHUTDOWN:
if (stepCount == 1) {
// Shutdown sparton?
}
step = WII5CAPTURE_SEND_PREPARE; stepCount = 0; wait = 0;
break;
case WII5CAPTURE_SEND_PREPARE:
step = WII5CAPTURE_PROCESS; stepCount = 0; wait = 0;
break;
case WII5CAPTURE_PROCESS:
// TODO This here should be switching SD Cards if possible
// - Stop SD
// - Switch SD
// - Start Maths
// - This allows code to be processed right away.
// TODO - if Maths is off
// TODO - Switch SD Cards
// TODO - store new one in wii5Controller.getSD() for next time - ie. shoulduse that even if hours away
step = WII5CAPTURE_FINISH; stepCount = 0; wait = 0;
break;
// Three ways to cancel
case WII5CAPTURE_FLIP:
case WII5CAPTURE_CANCEL_RESTART:
case WII5CAPTURE_CANCEL_OFF:
case WII5CAPTURE_MANUAL_OFF:
if (first) {
console.log(LOG_FATAL, F("Capture: Manual Cancel - Turning off the devices."));
#ifdef WII5_GPS
if (enableGPS)
wii5Gps.off();
#endif
wii5Sparton.stop();
if (step == WII5CAPTURE_FLIP) {
console.log(LOG_INFO, F("CaptureMode: FLIP manually requested on SD Cards"));
sdBlock.cardForceClose();
wii5Controller.setSDOff();
delay(500); // TODO Yes I know.... gah. TODO 2024 safeDelay - really 3 seconds TODO 2024 was 3000
if (wii5Controller.getSD() == 1) {
wii5Controller.setSD2();
wii5Display.statusSend(F("CaptureMode"), F("sdcard,swap,2"));
wii5Display.atDataSend(
F("MathsProcess"),
F("SD=2")
);
}
else {
wii5Controller.setSD1();
wii5Display.statusSend(F("CaptureMode"), F("sdcard,swap,1"));
wii5Display.atDataSend(
F("MathsProcess"),
F("SD=1")
);
}
}
}
// Maths ?
if (wait > 2000) {
if (step == WII5CAPTURE_CANCEL_RESTART) {
step = WII5CAPTURE_START; wait = 0; retry = 0; stepCount = 0;
}
else {
step = WII5CAPTURE_WAIT5_OFF; wait = 0; retry = 0; stepCount = 0;
}
}
break;
case WII5CAPTURE_WAIT5_OFF:
if (stepCount == 1)
console.log(LOG_DEBUG, F("ModeCapture: Waiting for 90 seconds after fatal error before retrying"));
if (wait > 90000) {
step = WII5CAPTURE_OFF; stepCount = 0; wait = 0;
}
break;
// Capture is finished - what do we do?
// - Need to Sleep. e.g. more than 1 hour to wait.
// - Twiddlge thumbs waiting for Maths CPU to process and send Iridium
// . Reduce power of all other devices
// . Monitor Maths - don't let it run forever
// . Monitor Iridium - don't let it run forever
case WII5CAPTURE_FINISH:
wii5Display.statusSend(F("CaptureMode"), F("sparton,end"));
// Just sit here
wii5Display.atDataSend(
F("CaptureCommand"),
F("END")
);
// Close DATA
sdBlock.dataClose(false);
// Create empty single entry for results.
sdBlock.dataOpen(wii5Config.getRecordCount());
sdBlock.dataPrepare();
sdBlock.dataWrite();
sdBlock.dataClose(true);
// Trigger temperatureRead - safe place at ene of a read
wii5Weather_18B20.temperatureRead();
#ifdef WII5_GPS
if (wii5Gps.isError()) {
// TODO report errors
console.log(LOG_DEBUG, F("GPS: failure I think"));
}
else if (wii5Gps.ready()) {
console.log(LOG_DEBUG, F("GPS: Success I think"));
}
else {
console.log(LOG_DEBUG, F("GPS: UNKNOWN I think"));
}
wii5Gps.off();
#endif
// TODO Check for errors
sdBlock.metadataPrepare();
if (sizeof(WII5MetaDataObject) <= sizeof(sdBlock.metadata->data)) {
console.log(LOG_DEBUG, F("Writing SDBlock Metadata - WII5 Size=%d, SDBlock Size=%d"),
int(sizeof(WII5MetaDataObject)),
int(sizeof(sdBlock.metadata->data))
);
wii5Display.updateMetadata(sdBlock.metadata->data);
}
else {
console.log(LOG_FATAL, F("SDBlock and MetaData are not compatible sizes - WII5 Size=%d, SDBlock Size=%d"),
int(sizeof(WII5MetaDataObject)),
int(sizeof(sdBlock.metadata->data))
);
}
sdBlock.metadataWrite();
console.log(LOG_DEBUG, F("SDBlock metadata write = %lu"), sdBlock.metadataLast);
// Always close card
sdBlock.cardClose();
if (debug) {
delay(1000);
sdBlock.cardOpen(wii5Controller.getSD());
sdBlock.metadataRead(0);
uint32_t dataBlockStart = sdBlock.metadata->dataBlockStart;
uint32_t dataBlocks = sdBlock.metadata->dataBlocks;
uint32_t resultsBlockStart = sdBlock.metadata->resultsBlockStart;
uint32_t resultsBlocks = sdBlock.metadata->resultsBlocks;
console.printf(F("@Block,metadata,address=%lu,deviceId=%lu,recordId=%lu\r\n"),
sdBlock.metadataLast, sdBlock.metadata->deviceId, sdBlock.metadata->recordId
);
console.printf(F("@Block,metadata,dataBlockStart=%lu,dataBlocks=%lu\r\n"),
dataBlockStart, dataBlocks
);
console.printf(F("@Block,metadata,resultsBlockStart=%lu,resultsBlocks=%lu\r\n"),
resultsBlockStart, resultsBlocks
);
}
// SAFETY
// - Check maths is off, others leave this one live
if (wii5Maths.isOff() || enableLiveSwap) {
// TODO need more delays...
// TODO - consider printing that we did Live Swap
if (wii5Controller.getSD() == 1) {
wii5Controller.setSD2();
wii5Display.statusSend(F("CaptureMode"), F("sdcard,swap,2"));
wii5Display.atDataSend(
F("MathsProcess"),
F("SD=2")
);
}
else {
wii5Controller.setSD1();
wii5Display.statusSend(F("CaptureMode"), F("sdcard,swap,1"));
wii5Display.atDataSend(
F("MathsProcess"),
F("SD=1")
);
}
if (!sdBlock.cardOpen(wii5Controller.getSD())) {
sh3dNodeIO.led1Set( LED_FAST );
console.log(LOG_ERROR, F("Error opening SD Card"));
}
// Signal we have swapped SD
wii5Communications.signalSD();
}
else {
wii5Display.statusSend(F("CaptureMode"), F("sdcard,noswap"));
wii5Display.atDataSend(
F("MathsProcess"),
F("noswap")
);
}
// TODO Calculate waiting time.
// If Maths finished, and Iridium finished.
// And we have to wait longer than X (30 minutes?)
// Deep Sleep for 90% of that time.
if(captureType == WII5CAPTURETYPE_MANUAL) {
console.log(LOG_INFO, F("Manual run executed, switching back to Default Mode"));
wii5Controller.setDefaultMode();
}
if (enableMaths)
wii5Maths.start(WII5MATHSMODE_AUTO);
wii5Communications.start(); // In case we didn't get SD, still tell it to do the job
step = WII5CAPTURE_WAIT_COMMS_MATHS; wait = 0; retry = 0; stepCount = 0;
break;
case WII5CAPTURE_WAIT_COMMS_MATHS:
if (first) {
console.log(LOG_DEBUG, F("Waiting for Maths and Communicatinos to Finish"));
}
// If maths and Comms is off, jumpt to OFF
else if ( !wii5Communications.isRunning() && wii5Maths.isOff()) {
console.log(LOG_INFO, F("CAPTURE: Maths and Comms finsihed"));
step = WII5CAPTURE_OFF; wait = 0; retry = 0; stepCount = 0;
}
// Wait 3 minuts here quietly
else if (wait > 180000) {
console.log(LOG_INFO, F("CAPTURE: 3 minutes and Maths and Communications not finished. Mvoing on but leaving them on."));
step = WII5CAPTURE_OFF; wait = 0; retry = 0; stepCount = 0;
}
break;
default:
console.log(LOG_FATAL, F("CAPTURE: Default step... step=%d"), step);
step = WII5CAPTURE_OFF; stepCount = 0; wait = 0;
break;
}
stepCount++;
stepTotal++;
}
void WII5ModeCapture::flip() {
step = WII5CAPTURE_FLIP; wait = 0; stepCount = 0;
}
WII5ModeCapture wii5ModeCapture;
+102
View File
@@ -0,0 +1,102 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5ModeCapture.h
* @brief Capture mode: IMU/wave-motion data capture and SD logging.
*/
#ifndef WII5ModeCapture_h
#define WII5ModeCapture_h
#include <Arduino.h>
#include <WII5.h>
#include <TimeLib.h>
enum WII5CAPTURE_TYPE {
WII5CAPTURETYPE_UNKNOWN,
WII5CAPTURETYPE_MANUAL,
WII5CAPTURETYPE_TIME,
};
enum WII5CAPTURE_STEPS {
WII5CAPTURE_START,
WII5CAPTURE_OFF,
WII5CAPTURE_TIME,
WII5CAPTURE_PREPARE,
WII5CAPTURE_PREPARE_SD,
WII5CAPTURE_PREPARE_COMMS,
WII5CAPTURE_PREPARE_ID,
WII5CAPTURE_PREPARE_SPARTON,
WII5CAPTURE_WAIT,
WII5CAPTURE_INPROGRESS,
WII5CAPTURE_SHUTDOWN,
WII5CAPTURE_PROCESS,
WII5CAPTURE_SEND_PREPARE,
WII5CAPTURE_SEND_METADATA,
WII5CAPTURE_SEND_RAW,
WII5CAPTURE_CANCEL_RESTART,
WII5CAPTURE_CANCEL_OFF,
WII5CAPTURE_MANUAL_OFF,
WII5CAPTURE_WAIT5_OFF,
WII5CAPTURE_FLIP,
WII5CAPTURE_WAIT_COMMS_MATHS,
WII5CAPTURE_FINISH
};
/**
* @brief Capture mode: time-driven IMU data capture and SD logging.
*
* Drives the WII5CAPTURE_STEPS state machine: prepare SD + comms + IMU,
* wait for the start time, run the capture, write metadata and
* processed-results blocks, optionally flip SD cards, then idle until
* the next capture window.
*/
class WII5ModeCapture : public WII5Mode {
public:
WII5ModeCapture() {}
/** @brief Reset to WII5CAPTURE_START. */
void reset();
/** @brief One-time bring-up. */
void begin();
/** @brief State-machine tick. */
void loop();
/** @brief Begin a manual capture. */
void start(bool force = false);
/** @brief Abort the current capture. */
void stop(bool force = false);
bool enableAtFile;
bool enableGPS;
bool enableMaths;
bool enableComms;
bool enableLiveSwap;
/** @brief Switch the active SD card to the alternate one. */
void flip();
uint32_t startTime;
protected:
elapsedMillis wait;
time_t captureStart;
WII5CAPTURE_TYPE captureType;
WII5CAPTURE_STEPS step;
WII5CAPTURE_STEPS stepLast;
uint32_t stepCount;
uint32_t stepTotal;
uint8_t retry;
uint32_t minutes; // since midnight.
uint32_t period;
uint32_t remaining;
bool debug;
};
extern WII5ModeCapture wii5ModeCapture;
#endif
+142
View File
@@ -0,0 +1,142 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5ModeLowBattery.cpp
* @brief Low-battery mode: degraded operation when supply is depleted.
*/
/*
LowBattery mode - a simple repeatign mode that will capture position data and send it.
*/
#include <Arduino.h>
#include <TimeLib.h>
#include <WII5.h>
void WII5ModeLowBattery::reset() {
step = WII5LOWBATTERY_START;
wait = 0;
}
void WII5ModeLowBattery::begin() {
}
void WII5ModeLowBattery::start() {
step = WII5LOWBATTERY_GPS_START; wait = 0;
}
void WII5ModeLowBattery::stop() {
step = WII5LOWBATTERY_END; wait = 0;
}
void WII5ModeLowBattery::loop() {
bool first = (lastStep != step);
lastStep = step;
switch (step) {
case WII5LOWBATTERY_START:
if (first) {
// TODO This seems bad, perhaps only if next is 10 minutes?
// First time in, turn off devices we don't need.
// Can we turn off Maths.
sh3dNodeIO.led1Set( LED_TRIPPLE_GAP );
wii5Controller.setSDOff();
wii5Gps.off();
wii5Iridium.stop();
wii5Sparton.stop();
wii5Controller.shared5Off();
console.log(LOG_INFO, F("LowBattery: Waiting for %lu minutes"),
wii5Display.minutesUntilNext(40)
);
}
else if (wait > (WII5TIME_1DAY * 1000)) {
console.log(LOG_ERROR, F("LowBattery: Timeout - over 24 hours, moving on to do a location and data send"));
step = WII5LOWBATTERY_GPS_START; wait = 0;
}
else {
// TODO - reduce this complicated task ? Lots of maths, being run at full speed
minutes = (hour() * 60) + minute();
if ( (minutes % 40) == 0) {
console.log(LOG_INFO, F("LowBattery: Starting minutes match period min=%lu, period=%lu"), minutes, 40);
step = WII5LOWBATTERY_GPS_START; wait = 0;
sh3dNodeIO.led1Set( LED_TRIPPLE_GAP );
}
}
break;
case WII5LOWBATTERY_GPS_START:
// Ask the GPS for accurate data
console.log(LOG_DEBUG, F("LowBattery: Starting GPS, Battery, Temperature"));
#ifdef WII5_GPS
wii5Gps.autoAccurate();
#endif
wii5Battery.start();
wii5Weather_18B20.temperatureRead();
step = WII5LOWBATTERY_GPS_WAIT; wait = 0;
break;
case WII5LOWBATTERY_GPS_WAIT:
if (first) {
displayWait = 0;
}
else if (wii5Gps.ready()) {
if (wii5Gps.isError()) {
console.log(LOG_ERROR, F("LowBattery: GPS Finished with ERROR"));
}
wii5Gps.off();
step = WII5LOWBATTERY_IRIDIUM_WAIT; wait = 0;
}
else if (wait > 120000) {
console.log(LOG_ERROR, F("LowBattery: GPS Timed out after 300 seconds"));
step = WII5LOWBATTERY_IRIDIUM_WAIT; wait = 0;
}
break;
case WII5LOWBATTERY_IRIDIUM_WAIT:
if (first) {
console.log(LOG_INFO, F("LowBattery: Communications start"));
wii5Communications.setSimpleMode();
// Increment updateRecordCount for each save to SD OR send to Iridium
wii5Communications.sendBinModeType(1, wii5Config.getRecordCount());
wii5Communications.start();
}
else if (!wii5Communications.isRunning()) {
console.log(LOG_INFO, F("LowBattery: Communications end"));
step = WII5LOWBATTERY_END; wait = 0;
}
// TODO 10 minutes ok
else if (wait > 600000) {
console.log(LOG_FATAL, F("LowBattery: Communications timeout"));
step = WII5LOWBATTERY_END; wait = 0;
}
break;
case WII5LOWBATTERY_END:
if (first) {
console.log(LOG_DEBUG, F("LowBattery: End"));
}
wii5Communications.stop();
wii5Gps.off();
// Stop everything
wii5Controller.setSDOff();
wii5Sparton.stop();
wii5Controller.shared5Off();
step = WII5LOWBATTERY_START; wait = 0;
break;
default:
console.log(LOG_FATAL, F("LOWBATTERY: Default step... step=%d"), step);
step = WII5LOWBATTERY_START;
}
}
WII5ModeLowBattery wii5ModeLowBattery;
+67
View File
@@ -0,0 +1,67 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5ModeLowBattery.h
* @brief Declares WII5ModePosition (position mode).
*
* @note The WII5ModePosition.h / WII5ModeLowBattery.h header pair are
* swapped relative to their filenames in this codebase: this file
* (WII5ModeLowBattery.h) declares class WII5ModePosition, and
* WII5ModePosition.h declares class WII5ModeLowBattery. The corresponding
* .cpp files match their filenames. TODO: untangle and rename.
*/
#ifndef WII5ModePosition_h
#define WII5ModePosition_h
#include <Arduino.h>
#include <WII5.h>
#include <TimeLib.h>
enum WII5POSITION_STEPS {
WII5POSITION_START,
WII5POSITION_STROBE,
WII5POSITION_TIME,
WII5POSITION_GPS_START,
WII5POSITION_GPS_WAIT,
WII5POSITION_IRIDIUM_WAIT,
WII5POSITION_END
};
/**
* @brief Position mode: periodic GPS + Iridium telemetry only.
*
* Lower-power than full Capture mode: turns on GPS, gets a fix, sends
* a position via Iridium SBD, then idles until the next position window.
* No IMU capture, no SD writes.
*/
class WII5ModePosition : public WII5Mode {
public:
WII5ModePosition() {}
/** @brief Reset to WII5POSITION_START. */
void reset();
/** @brief One-time bring-up. */
void begin();
/** @brief State-machine tick. */
void loop();
/** @brief Begin a position acquisition cycle. */
void start();
/** @brief Abort the current cycle. */
void stop();
protected:
bool first;
elapsedMillis displayWait;
elapsedMillis wait;
WII5POSITION_STEPS step;
WII5POSITION_STEPS lastStep;
uint32_t minutes; // since midnight.
};
extern WII5ModePosition wii5ModePosition;
#endif
File diff suppressed because it is too large Load Diff
+56
View File
@@ -0,0 +1,56 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5ModeManualTest.h
* @brief Manual test mode: operator-driven hardware exercise via the console.
*/
#ifndef WII5ModeManualTest_h
#define WII5ModeManualTest_h
#include <Arduino.h>
#include <WII5.h>
#include <TimeLib.h>
/**
* @brief Manual test mode: operator-driven hardware exercise.
*
* Provides a console REPL where the operator types single-letter
* commands ("X;", "G;", etc.) to exercise individual subsystems
* (LEDs, buttons, GPS, Iridium, Sparton, SD cards, etc.). Used in the
* lab and during pre-deployment checkout.
*/
class WII5ModeManualTest : public WII5Mode {
public:
WII5ModeManualTest() {}
/** @brief Reset internal counters. */
void reset();
/** @brief One-time bring-up. */
void begin();
/** @brief Tick: read console, dispatch one-shot test commands. */
void loop();
bool enableMetadata;
bool enableIridium;
bool enableGps;
bool enableSparton;
bool enableBattery;
bool waitGPS;
// virtual void metadataPrint(File* fh);
protected:
elapsedMillis wait;
uint8_t pulsePin;
elapsedMillis pulseWait;
// Lots of improvement...
};
extern WII5ModeManualTest wii5ModeManualTest;
#endif
+172
View File
@@ -0,0 +1,172 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5ModePosition.cpp
* @brief Position mode: periodic GPS + Iridium telemetry only (low power).
*/
/*
Position mode - a simple repeatign mode that will capture position data and send it.
*/
#include <Arduino.h>
#include <TimeLib.h>
#include <WII5.h>
void WII5ModePosition::reset() {
step = WII5POSITION_START;
wait = 0;
}
void WII5ModePosition::begin() {
}
void WII5ModePosition::start() {
step = WII5POSITION_STROBE; wait = 0;
}
void WII5ModePosition::stop() {
step = WII5POSITION_END; wait = 0;
}
void WII5ModePosition::loop() {
bool first = (lastStep != step);
lastStep = step;
switch (step) {
case WII5POSITION_START:
if (first) {
// TODO This seems bad, perhaps only if next is 10 minutes?
// First time in, turn off devices we don't need.
// Can we turn off Maths.
sh3dNodeIO.led1Set( LED_SHORT );
wii5Controller.setSDOff();
wii5Gps.off();
wii5Iridium.stop();
wii5Sparton.stop();
wii5Controller.shared5Off();
console.log(LOG_INFO, F("Position: Waiting for %lu minutes"),
wii5Display.minutesUntilNext(wii5Config.getPositionPeriod())
);
}
else if ((wii5Gps.sinceOnLast > (WII5TIME_1HOUR * 1000)) && (!wii5Gps.isTimeValid())) {
console.log(LOG_ERROR, F("Position: Internal clock / time is not valid, turn on GPS"));
step = WII5POSITION_TIME; wait = 0;
}
else if (wait > (WII5TIME_1DAY * 1000)) {
console.log(LOG_ERROR, F("Position: Timeout - over 24 hours, moving on to do a location and data send"));
step = WII5POSITION_STROBE; wait = 0;
}
else {
// TODO - reduce this complicated task ? Lots of maths, being run at full speed
minutes = (hour() * 60) + minute();
if ( (minutes % wii5Config.getPositionPeriod()) == 0) {
console.log(LOG_INFO, F("Position: Starting minutes match period min=%lu, period=%lu"), minutes, wii5Config.getPositionPeriod());
step = WII5POSITION_STROBE; wait = 0;
}
}
// TODO Sleep ?
break;
case WII5POSITION_STROBE:
// TODO - Manage the strobes
// console.log(LOG_DEBUG, F("Position: strobe -> gps_start (skipping)"));
if (first) {
wii5Config.updateRecordCount();
console.log(LOG_DEBUG, F("Position: New recordcount=%lu"), wii5Config.getRecordCount());
}
step = WII5POSITION_GPS_START; wait = 0;
break;
case WII5POSITION_TIME:
if (first) {
wii5Gps.autoTime();
}
else if (wii5Gps.ready()) {
step = WII5POSITION_START; wait = 0;
}
else if (wait > 300000) {
step = WII5POSITION_START; wait = 0;
}
break;
case WII5POSITION_GPS_START:
// Ask the GPS for accurate data
console.log(LOG_DEBUG, F("Position: Starting GPS, Battery, Temperature"));
#ifdef WII5_GPS
wii5Gps.autoAccurate();
#endif
wii5Battery.start();
wii5Weather_18B20.temperatureRead();
step = WII5POSITION_GPS_WAIT; wait = 0;
break;
case WII5POSITION_GPS_WAIT:
if (first) {
displayWait = 0;
}
else if (wii5Gps.ready()) {
if (wii5Gps.isError()) {
console.log(LOG_ERROR, F("Position: GPS Finished with ERROR"));
}
wii5Gps.off();
step = WII5POSITION_IRIDIUM_WAIT; wait = 0;
}
else if (wait > 300000) {
console.log(LOG_ERROR, F("Position: GPS Timed out after 300 seconds"));
step = WII5POSITION_IRIDIUM_WAIT; wait = 0;
}
else if (displayWait > 10000) {
console.log(LOG_INFO, F("Position: Waiting for GPS Lock - %lu seconds"), (wait / 1000));
displayWait = 0;
}
break;
case WII5POSITION_IRIDIUM_WAIT:
if (first) {
console.log(LOG_INFO, F("Position: Communications start"));
wii5Communications.setSimpleMode();
// Increment updateRecordCount for each save to SD OR send to Iridium
wii5Communications.sendBinModeType(wii5Config.getPositionBinaryType(), wii5Config.getRecordCount());
wii5Communications.start();
}
else if (!wii5Communications.isRunning()) {
console.log(LOG_INFO, F("Position: Communications end"));
step = WII5POSITION_END; wait = 0;
}
// TODO 10 minutes ok
else if (wait > 600000) {
console.log(LOG_FATAL, F("Position: Communications timeout"));
step = WII5POSITION_END; wait = 0;
}
break;
case WII5POSITION_END:
if (first) {
console.log(LOG_DEBUG, F("Position: End"));
}
wii5Communications.stop();
wii5Gps.off();
// Stop everything
wii5Controller.setSDOff();
wii5Sparton.stop();
wii5Controller.shared5Off();
step = WII5POSITION_START; wait = 0;
break;
default:
console.log(LOG_FATAL, F("POSITION: Default step... step=%d"), step);
step = WII5POSITION_START;
}
}
WII5ModePosition wii5ModePosition;
+68
View File
@@ -0,0 +1,68 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5ModePosition.h
* @brief Declares WII5ModeLowBattery (low-battery mode).
*
* @note The WII5ModePosition.h / WII5ModeLowBattery.h header pair are
* swapped relative to their filenames in this codebase: this file
* (WII5ModePosition.h) declares class WII5ModeLowBattery, and
* WII5ModeLowBattery.h declares class WII5ModePosition. The corresponding
* .cpp files match their filenames. TODO: untangle and rename.
*/
#ifndef WII5ModeLowBattery_h
#define WII5ModeLowBattery_h
#include <Arduino.h>
#include <WII5.h>
#include <TimeLib.h>
enum WII5LOWBATTERY_STEPS {
WII5LOWBATTERY_START,
WII5LOWBATTERY_STROBE,
WII5LOWBATTERY_TIME,
WII5LOWBATTERY_GPS_START,
WII5LOWBATTERY_GPS_WAIT,
WII5LOWBATTERY_IRIDIUM_WAIT,
WII5LOWBATTERY_END
};
/**
* @brief Low-battery mode: degraded operation when supply is depleted.
*
* Suppresses Capture mode's heavy operations (IMU, SD writes), keeps
* a minimum-viable position+telemetry cadence, and pesters Iridium
* with a battery-low alert. The buoy returns to its default mode once
* battery voltage recovers (or via an explicit Iridium override).
*/
class WII5ModeLowBattery : public WII5Mode {
public:
WII5ModeLowBattery() {}
/** @brief Reset to WII5LOWBATTERY_START. */
void reset();
/** @brief One-time bring-up. */
void begin();
/** @brief State-machine tick. */
void loop();
/** @brief Begin a low-battery alert cycle. */
void start();
/** @brief Abort the current cycle. */
void stop();
protected:
bool first;
elapsedMillis displayWait;
elapsedMillis wait;
WII5LOWBATTERY_STEPS step;
WII5LOWBATTERY_STEPS lastStep;
uint32_t minutes; // since midnight.
};
extern WII5ModeLowBattery wii5ModeLowBattery;
#endif
+651
View File
@@ -0,0 +1,651 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5ModeSelfTest.cpp
* @brief Self test mode: automated boot-time hardware checks.
*/
/*
WII5 SelfTest
TODO:
* Show values at the end
* Maths loop back test
* Out of range Temperature / Voltage ?
* Not detecting data Iridium even though it is there
* Not detecting data Sparton even though it is there
MODE Default:
* Standard tests (including LED/Buzzer/Button)
* But don't expect user feedback (aka MANUAL for results)
* Turn on Maths, wait for boot and Hello, Shutdown maths
MODE FromMaths:
* Standard tests (including LED/Buzzer/Button)
* No Maths tests at all
MODE NoMaths:
* Existing code, expect user to check voltages etc
*/
#include <Arduino.h>
#include <TimeLib.h>
#include <WII5.h>
void WII5ModeSelfTest::reset() {
step = WII5ST_START; stepWait = 0;
}
void WII5ModeSelfTest::begin() {
step = WII5ST_START;
mode = WII5SM_DEFAULT;
}
WII5SELFTEST_MODE WII5ModeSelfTest::getMode() {
return mode;
}
void WII5ModeSelfTest::setMode(WII5SELFTEST_MODE m) {
mode = m;
}
void WII5ModeSelfTest::dump(bool toConsole, Print* toOther, bool hideNone) {
count_passed = 0;
count_nonenc = 0;
count_failed = 0;
// TODO Temp hack - BOARD should always be success
results[WII5STD_BOARD] = WII5STS_SUCCESS;
for (WII5SELFTEST_DEVICES i = 0; i < WII5STD_DEVICES; i = i + 1) {
switch(results[i]) {
case WII5STS_NONE:
case WII5STS_NC:
count_nonenc++;
break;
case WII5STS_SUCCESS:
case WII5STS_HUMAN:
case WII5STS_SLOW:
count_passed++;
break;
default:
count_failed++;
};
if ( !hideNone && ( (results[i] == WII5STS_NONE) || (results[i] == WII5STS_NC)) ) {
// Hidden this time
}
else {
snprintf_P(
wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT,
PSTR("@Metadata,selftest,%s,"),
wii5Strings.strSelfTestDevice(i)
);
if (toOther) toOther->print(wii5BufferConsolePrint);
if (toConsole) console.print(wii5BufferConsolePrint);
snprintf_P(
wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT,
PSTR("status=%s,"),
wii5Strings.strSelfTestStatus(results[i])
);
if (toOther) toOther->print(wii5BufferConsolePrint);
if (toConsole) console.print(wii5BufferConsolePrint);
snprintf_P(
wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT,
PSTR("description=%s\r\n"),
wii5Strings.strSelfTestStatusLong(results[i])
);
if (toOther) toOther->print(wii5BufferConsolePrint);
if (toConsole) console.print(wii5BufferConsolePrint);
}
}
}
void WII5ModeSelfTest::loop() {
first = (step != stepLast);
stepLast = step;
if (first)
count++;
if (console.available()) {
// Exit Test gracefully - show report
if (console.getCommand() == 'X') {
step = WII5ST_REPORT; stepWait = 0; first = true;
}
// Restart existing test
else if (console.getCommand() == 'S') {
step = WII5ST_START; stepWait = 0; first = true;
}
// Force WDT Test and exit
else if (console.getCommand() == 'W') {
console.printf(F("WARNING - Disabled external WDT reset pin - runCount=%lu\r\n"), sh3dNodeConfig.getRunCount());
wii5Controller.tempDisableWDT = true;
wii5Controller.setDefaultMode(); // Back to default mode now? so no reboot/report
return;
}
}
switch (step) {
case WII5ST_START:
if (first) {
count = 0;
for (WII5SELFTEST_DEVICES i = 0; i < WII5STD_DEVICES; i = i + 1) {
results[i] = WII5STS_NONE;
}
results[WII5STD_BOARD] = WII5STS_SUCCESS;
console.log(LOG_INFO, F("SelfTest %d/%d: Start"), count, WII5ST_MAX);
}
step = WII5ST_SHUTDOWN; stepWait = 0;
break;
case WII5ST_SHUTDOWN: // Shutdown the existing bits (e.g. stopping anything we need to)
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: Shutdown old processes"), count, WII5ST_MAX);
}
step = WII5ST_LED; stepWait = 0;
break;
case WII5ST_LED:
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: LED (check for fast, irregular then back to normal) y; | n; to confirm/reject"), count, WII5ST_MAX);
sh3dNodeIO.led1Set(LED_FAST);
stepCount = 0;
}
if (console.available() && (console.getCommand() == 'y')) {
sh3dNodeIO.led1Set(LED_SLOW);
console.log(LOG_INFO, F("SelfTest: success"));
results[WII5STD_LED] = WII5STS_SUCCESS;
step = WII5ST_BUZZER; stepWait = 0;
}
else if (console.available() && (console.getCommand() == 'n')) {
sh3dNodeIO.led1Set(LED_SLOW);
console.log(LOG_INFO, F("SelfTest: failed"));
results[WII5STD_LED] = WII5STS_UNKNOWN;
step = WII5ST_BUZZER; stepWait = 0;
}
if (stepWait > 20000) {
sh3dNodeIO.led1Set(LED_SLOW);
console.log(LOG_INFO, F("SelfTest: timeout"));
results[WII5STD_LED] = WII5STS_TIMEOUT;
step = WII5ST_BUZZER; stepWait = 0;
}
else if (stepWait > 5000) {
if (stepCount == 1) {
sh3dNodeIO.led1Set(LED_DOUBLE_LONG);
stepCount++;
}
}
else if (stepWait > 2500) {
if (stepCount == 0) {
sh3dNodeIO.led1Set(LED_DOUBLE);
stepCount++;
}
}
break;
case WII5ST_BUZZER:
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: BUZZER (check for fast, irregular then back to normal) y; | n; to confirm/reject"), count, WII5ST_MAX);
sh3dNodeIO.led2Set(LED_FAST);
stepCount = 0;
}
if (console.available() && (console.getCommand() == 'y')) {
sh3dNodeIO.led2Set(LED_OFF);
results[WII5STD_BUZZER] = WII5STS_SUCCESS;
step = WII5ST_BUTTON; stepWait = 0;
}
else if (console.available() && (console.getCommand() == 'n')) {
sh3dNodeIO.led2Set(LED_OFF);
results[WII5STD_BUZZER] = WII5STS_UNKNOWN;
step = WII5ST_BUTTON; stepWait = 0;
}
if (stepWait > 20000) {
sh3dNodeIO.led2Set(LED_OFF);
results[WII5STD_BUZZER] = WII5STS_TIMEOUT;
step = WII5ST_BUTTON; stepWait = 0;
}
else if (stepWait > 5000) {
if (stepCount == 1) {
sh3dNodeIO.led2Set(LED_DOUBLE_LONG);
stepCount++;
}
}
else if (stepWait > 2500) {
if (stepCount == 0) {
sh3dNodeIO.led2Set(LED_DOUBLE);
stepCount++;
}
}
break;
case WII5ST_BUTTON:
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: Button. Toggle button at least twice"), count, WII5ST_MAX);
wii5Commands.disableButtons = 0;
stepCount = 0;
}
else if (sh3dNodeIO.button1->isClicked()) {
stepCount++;
}
else if (stepCount >= 2) {
console.log(LOG_INFO, F("SelfTest: Button detected 2 or more clicks success"));
results[WII5STD_BUTTON] = WII5STS_SUCCESS;
step = WII5ST_INFORMATION; stepWait = 0;
}
else if (console.available() && (console.getCommand() == 'y')) {
console.log(LOG_INFO, F("SelfTest: Button user clicked 'y' manually - human yes"));
results[WII5STD_BUTTON] = WII5STS_HUMAN;
step = WII5ST_INFORMATION; stepWait = 0;
}
else if (console.available() && (console.getCommand() == 'n')) {
console.log(LOG_INFO, F("SelfTest: Button user clicked 'n' manually - failed"));
results[WII5STD_BUTTON] = WII5STS_UNKNOWN;
step = WII5ST_INFORMATION; stepWait = 0;
}
else if (stepWait > 20000) {
console.log(LOG_INFO, F("SelfTest: Button timeout"));
results[WII5STD_BUTTON] = WII5STS_TIMEOUT;
step = WII5ST_INFORMATION; stepWait = 0;
}
break;
case WII5ST_INFORMATION: // What do we know: our ID: our run count etc
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: Information"), count, WII5ST_MAX);
}
step = WII5ST_CONFIG; stepWait = 0;
break;
case WII5ST_CONFIG: // Check we can read and write config
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: Config"), count, WII5ST_MAX);
}
if ( (wii5Config.getDeviceId() < 1) ) {
results[WII5STD_EEPROM] = WII5STS_UNKNOWN;
}
else if (
(wii5Config.getDeviceId() > 1000)
&& (wii5Config.getDeviceId() < 99999)
) {
results[WII5STD_EEPROM] = WII5STS_SUCCESS;
}
else {
results[WII5STD_EEPROM] = WII5STS_OUTOFRANGE;
}
step = WII5ST_GPS_RECEIVE; stepWait = 0;
break;
case WII5ST_GPS_RECEIVE: // Turn on GPS and receive some NMEA
#ifdef WII5_GPS
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: GPS Started, please wait"), count, WII5ST_MAX);
wii5Gps.autoAccurate();
}
if (
(stepWait > 10000)
|| (wii5Gps.isError())
|| (wii5Gps.ready())
) {
console.log(LOG_INFO, F("SelfTest %d/%d: GPS complete"), count, WII5ST_MAX);
step = WII5ST_GPS_OFF; stepWait = 0;
}
#else
step = WII5ST_SD0; stepWait = 0;
results[WII5STD_GPS] = WII5STS_NC;
#endif
break;
case WII5ST_GPS_OFF: // Turn off: and check GPS is ok
#ifdef WII5_GPS
if (first) {
wii5Gps.off(); // TODO inconsistent OFF vs STOP ?
console.log(LOG_INFO, F("SelfTest %d/%d: GPS Off"), count, WII5ST_MAX);
}
if(wii5Gps.isError()) {
results[WII5STD_GPS] = WII5STS_UNKNOWN;
}
else if (wii5Gps.ready()) {
results[WII5STD_GPS] = WII5STS_SUCCESS;
}
else if (wii5Gps.gps->charsProcessed() > 300) {
results[WII5STD_GPS] = WII5STS_SLOW;
}
else {
results[WII5STD_GPS] = WII5STS_TIMEOUT;
}
step = WII5ST_SD0; stepWait = 0;
break;
#else
step = WII5ST_SD0; stepWait = 0;
#endif
// TODO GPS power testing
case WII5ST_SD0: // Make sure SD are both turned off
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: SD0 - powering on but not configured"), count, WII5ST_MAX);
}
if (stepWait > 1000) {
step = WII5ST_SD1; stepWait = 0;
}
break;
case WII5ST_SD1: // Open SD1: write a file with unique id/datetime
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: SD1"), count, WII5ST_MAX);
results[WII5STD_SD1] = WII5STS_SUCCESS;
step = WII5ST_SD2; stepWait = 0;
}
break;
case WII5ST_SD2: // Open SD2 and write a new file
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: SD2"), count, WII5ST_MAX);
// TODO See above
results[WII5STD_SD2] = WII5STS_SUCCESS;
step = WII5ST_SD_OFF; stepWait = 0;
}
break;
// TODO: SD1_B / SD2_B verification — re-enable once data round-trip
// checks are implemented (similar to the SD Card tester).
case WII5ST_SD_OFF: // Shut it down
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: SD_OFF"), count, WII5ST_MAX);
}
step = WII5ST_IRIDIUM; stepWait = 0;
break;
case WII5ST_IRIDIUM: // Turn on Iridium and wait for some responses
#ifdef WII5_COMMS_IRIDIUM
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: Iridium (takes time, Console loggging enabled)"), count, WII5ST_MAX);
wii5Iridium.setPassthrough(false);
wii5Iridium.requestFirmware();
}
// Found IMEI - Yay Iridium !
else if (strlen(wii5Iridium.imei) > 3) {
wii5Iridium.stop();
results[WII5STD_IRIDIUM] = WII5STS_SUCCESS;
step = WII5ST_BATTERY_1; stepWait = 0;
}
else if (stepWait > 10000) {
wii5Iridium.stop();
results[WII5STD_IRIDIUM] = WII5STS_TIMEOUT;
step = WII5ST_BATTERY_1; stepWait = 0;
}
#else
results[WII5STD_IRIDIUM] = WII5STS_NC;
step = WII5ST_BATTERY_1; stepWait = 0;
#endif
break;
case WII5ST_BATTERY_1: // Check battery 1 has volts and doesn't jitter too much
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: Battery 1"), count, WII5ST_MAX);
wii5Battery.start();
}
else if (stepWait > 5000) {
results[WII5STD_BATTERY] = WII5STS_TIMEOUT;
wii5Battery.stop();
step = WII5ST_WEATHER; stepWait = 0;
}
else if (! wii5Battery.isRunning()) {
if (
(wii5Battery.value < 1500)
&& (wii5Battery.value > 700)
) {
results[WII5STD_BATTERY] = WII5STS_SUCCESS;
}
else {
results[WII5STD_BATTERY] = WII5STS_OUTOFRANGE;
}
wii5Battery.stop();
step = WII5ST_WEATHER; stepWait = 0;
}
break;
/*
case WII5ST_BATTERY_2: // If it exists
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: Battery 2"), count, WII5ST_MAX);
}
step = WII5ST_MATHS_START; stepWait = 0;
break;
*/
case WII5ST_WEATHER:
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: Read Temperature"), count, WII5ST_MAX);
wii5Weather_18B20.temperatureRead();
if (
(wii5Weather_18B20.value > 100)
&& (wii5Weather_18B20.value < 5000)
&& (wii5Weather_18B20.value < 2000)
) {
results[WII5STD_18B20] = WII5STS_SUCCESS;
}
else if (
(wii5Weather_18B20.value)
) {
// OR slow ?
results[WII5STD_18B20] = WII5STS_OUTOFRANGE;
}
else {
results[WII5STD_18B20] = WII5STS_UNKNOWN;
}
}
step = WII5ST_5VOLT_START; stepWait = 0;
break;
case WII5ST_5VOLT_START:
if (first) {
if (mode == WII5SM_FROMMATHS) {
console.log(LOG_INFO, F("SelfTest %d/%d: From Maths - skipping 5V and Maths"), count, WII5ST_MAX);
step = WII5ST_RTC; stepWait = 0;
}
else {
console.log(LOG_INFO, F("SelfTest %d/%d: 5VOLT Power On for 30 seconds"), count, WII5ST_MAX);
}
}
else if (console.available() && (console.getCommand() == 'y')) {
step = WII5ST_5VOLT_OFF; stepWait = 0;
}
else if (console.available() && (console.getCommand() == 'n')) {
step = WII5ST_RTC; stepWait = 0;
}
else if (stepWait > 30000) {
step = WII5ST_5VOLT_OFF; stepWait = 0;
}
break;
case WII5ST_5VOLT_OFF:
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: 5VOLT Off for 30 seconds"), count, WII5ST_MAX);
}
else if (console.available() && (console.getCommand() == 'y')) {
results[WII5STD_5VOLT] = WII5STS_SUCCESS;
step = WII5ST_MATHS_START; stepWait = 0;
}
else if (console.available() && (console.getCommand() == 'n')) {
results[WII5STD_5VOLT] = WII5STS_UNKNOWN;
step = WII5ST_RTC; stepWait = 0;
}
else if (stepWait > 30000) {
results[WII5STD_5VOLT] = WII5STS_TIMEOUT;
step = WII5ST_MATHS_START; stepWait = 0;
}
break;
case WII5ST_MATHS_START: // Turn it on ! and make sure we get response
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: Maths Power On for 30 seconds"), count, WII5ST_MAX);
wii5Maths.powerOn();
}
else if (console.available() && (console.getCommand() == 'y')) {
step = WII5ST_MATHS_OFF; stepWait = 0;
}
else if (console.available() && (console.getCommand() == 'n')) {
step = WII5ST_RTC; stepWait = 0;
}
else if (stepWait > 30000) {
step = WII5ST_MATHS_OFF; stepWait = 0;
}
break;
case WII5ST_MATHS_OFF: // Turn it off: and make sure it is off
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: Maths Off for 30 seconds"), count, WII5ST_MAX);
wii5Maths.powerOff();
}
else if (console.available() && (console.getCommand() == 'y')) {
results[WII5STD_MATHS] = WII5STS_SUCCESS;
step = WII5ST_RTC; stepWait = 0;
}
else if (console.available() && (console.getCommand() == 'n')) {
results[WII5STD_MATHS] = WII5STS_UNKNOWN;
step = WII5ST_RTC; stepWait = 0;
}
else if (stepWait > 30000) {
results[WII5STD_MATHS] = WII5STS_TIMEOUT;
step = WII5ST_RTC; stepWait = 0;
}
break;
case WII5ST_RTC: // Do we have one: what version: what details
if (first) {
#ifdef WII5_RTC
console.log(LOG_INFO, F("SelfTest %d/%d: RTC - Checking Temperature"), count, WII5ST_MAX);
int t = int(wii5RTC.getTemperature());
console.log(LOG_INFO, F("SelfTest RTC temperature = %d"), t);
if (t > 1 && t < 50) {
results[WII5STD_RTC] = WII5STS_SUCCESS;
}
else {
results[WII5STD_RTC] = WII5STS_OUTOFRANGE;
}
#else
console.log(LOG_INFO, F("SelfTest %d/%d: RTC - NOT ENABLED"), count, WII5ST_MAX);
results[WII5STD_RTC] = WII5STS_NC;
#endif
}
step = WII5ST_SPARTON; stepWait = 0;
break;
case WII5ST_SPARTON: // Turn it on: get some values: tun it off
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: Sparton"), count, WII5ST_MAX);
wii5Sparton.automatic(64, 180, false);
}
else if (wii5Sparton.captureFinished()) {
wii5Sparton.stop();
if (
1
// (wii5Sparton.lastAccelZ > 900)
// && (wii5Sparton.lastAccelZ < 1100)
) {
results[WII5STD_SPARTON] = WII5STS_SUCCESS;
}
else {
results[WII5STD_SPARTON] = WII5STS_OUTOFRANGE;
}
step = WII5ST_REPORT; stepWait = 0;
}
else if (stepWait > 30000) {
wii5Sparton.stop();
results[WII5STD_SPARTON] = WII5STS_TIMEOUT;
step = WII5ST_REPORT; stepWait = 0;
}
break;
case WII5ST_REPORT: // Report what happened
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: COMPLETE - Running Report (console only)"), count, WII5ST_MAX);
// step = WII5ST_SHUTDOWN;
// wii5Metadata.setPassthrough(true);
// wii5Metadata.setCapture(false);
// wii5Metadata.report();
/*
Human readalbe
* ID
* Number of passes / fails - %
* List of fails
*/
console.printf(F("# SelfTest: Passed=%d, None/NC=%d, Failed=%d\r\n"),
count_passed, count_nonenc, count_failed
);
for (WII5SELFTEST_DEVICES i = 0; i < WII5STD_DEVICES; i = i + 1) {
switch(results[i]) {
case WII5STS_NONE:
case WII5STS_NC:
case WII5STS_SUCCESS:
case WII5STS_HUMAN:
case WII5STS_SLOW:
break;
default:
console.printf(F("# FAILED = %s"),
wii5Strings.strSelfTestDevice(i)
);
console.printf(F(" status=%s"),
wii5Strings.strSelfTestStatus(results[i])
);
console.printf(F(" description=%s\r\n"),
wii5Strings.strSelfTestStatusLong(results[i])
);
break;
};
}
// Manual output numbers we know....
console.printf(F("# VALUES: Temperature=%d\r\n"), wii5Weather_18B20.value);
console.printf(F("# VALUES: Battery=%d\r\n"), wii5Battery.value);
if (mode == WII5SM_FROMMATHS) {
console.printf(F("# 60 seconds until back to default mode\r\n"));
}
else {
console.printf(F("# Commands (or 60 seconds until reboot)\r\n"));
console.printf(F("# .;=back to default, W;=disable WDT, R;=restart.\r\n"));
}
}
else if (stepWait > 60000) {
if (mode == WII5SM_FROMMATHS) {
wii5Controller.setDefaultMode();
}
else {
wii5Controller.setDefaultMode(); // Back to default mode now?
sh3dNodeUtil.reset();
}
}
break;
default:
step = WII5ST_START;
}
}
WII5ModeSelfTest wii5ModeSelfTest;
+101
View File
@@ -0,0 +1,101 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5ModeSelfTest.h
* @brief Self test mode: automated boot-time hardware checks.
*/
#ifndef WII5ModeSelfTest_h
#define WII5ModeSelfTest_h
#include <Arduino.h>
#include <WII5.h>
#include <TimeLib.h>
enum WII5SELFTEST_STEPS {
WII5ST_START,
WII5ST_SHUTDOWN, // Shutdown the existing bits
WII5ST_INFORMATION, // What do we know, our ID, our run count etc
WII5ST_LED,
WII5ST_BUZZER,
WII5ST_BUTTON,
WII5ST_CONFIG, // Check we can read and write config
WII5ST_GPS_RECEIVE, // Turn on GPS and receive some NMEA
WII5ST_GPS_OFF, // Turn off, and check GPS is ok
WII5ST_SD0, // Make sure SD are both turned off
WII5ST_SD1, // Open SD1, write a file with unique id/datetime
WII5ST_SD2, // Open SD2 and write a new file
WII5ST_SD1_B, // Check the SD1 data is there
WII5ST_SD2_B, // Check the SD2 data is there
WII5ST_SD_OFF, // Shut it down
WII5ST_IRIDIUM, // Turn on Iridium and wait for some responses
WII5ST_BATTERY_1, // Check battery 1 has volts and doesn't jitter too much
WII5ST_BATTERY_2, // If it exists
WII5ST_WEATHER, // Do we get some temperatures
WII5ST_MATHS_START, // Turn it on ! and make sure we get response
WII5ST_MATHS_OFF, // Turn it off, and make sure it is off
WII5ST_5VOLT_START, // Turn it on ! and make sure we get response
WII5ST_5VOLT_OFF, // Turn it off, and make sure it is off
WII5ST_RTC, // Do we have one, what version, what details
WII5ST_SPARTON, // Turn it on, get some values, tun it off
WII5ST_REPORT, // Report what happened
};
enum WII5SELFTEST_MODE {
WII5SM_DEFAULT, // Run from AVR CPU (while running), Maths not running, but tested
WII5SM_FROMMATHS, // Test being run by the Maths CPU, therefore can't play maths
WII5SM_NOMATHS, // Simple test, no maths installed, user entered variables
};
#define WII5ST_MAX 25
/**
* @brief Self test mode: automated hardware checkout.
*
* Walks every subsystem once (LED, buzzer, button, config, GPS, SD x2,
* Iridium, batteries, weather, Maths CPU, 5V rail, RTC, Sparton) and
* records pass/fail in `results[]`. Three submodes:
* - WII5SM_DEFAULT: AVR-driven, expects an attached operator
* - WII5SM_FROMMATHS: invoked by the Maths CPU; skips Maths checks
* - WII5SM_NOMATHS: simple check, no Maths CPU present
*/
class WII5ModeSelfTest : public WII5Mode {
public:
WII5ModeSelfTest() {}
/** @brief Reset to WII5ST_START. */
void reset();
/** @brief One-time bring-up. */
void begin();
/** @brief Run the next test step (called repeatedly while active). */
void loop();
/** @brief Print the results table; `toOther` optionally mirrors elsewhere. */
void dump(bool toConsole = true, Print* toOther = NULL, bool hideNone = false);
/** @brief Current self-test submode. */
WII5SELFTEST_MODE getMode();
/** @brief Select self-test submode. */
void setMode(WII5SELFTEST_MODE m);
protected:
WII5SELFTEST_MODE mode;
elapsedMillis wait;
WII5SELFTEST_STEPS step;
WII5SELFTEST_STEPS stepLast;
elapsedMillis stepWait;
uint32_t stepCount; // Use for what ever you like
bool first;
uint16_t count;
uint16_t count_passed;
uint16_t count_failed;
uint16_t count_nonenc;
WII5SELFTEST_STATUS results[WII5STD_DEVICES];
};
extern WII5ModeSelfTest wii5ModeSelfTest;
#endif
+263
View File
@@ -0,0 +1,263 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5ModeSleep.cpp
* @brief Sleep mode: long sleeps between wake-ups; powers down the Maths CPU.
*/
/*
Sleeping.... Not a short one, this is the full deal.
* Start
- Are we holding - jump to hold
- Or calculate next sleep time
* Wait
- Shut down Devices
* Sleep
- Sleep for n seconds
* Update
- Start doing an update
* Wait Gps
* Wait Iridium
* Wait Maths
*/
#include <Arduino.h>
#include <TimeLib.h>
#include <WII5.h>
void WII5ModeSleep::reset() {
step = WII5SLEEP_START; stepWait = 0;
lastStep = WII5SLEEP_SLEEPING;
}
void WII5ModeSleep::begin() {
step = WII5SLEEP_START; stepWait = 0;
lastStep = WII5SLEEP_SLEEPING;
}
void WII5ModeSleep::loop() {
first = (step != lastStep);
lastStep = step;
switch (step) {
case WII5SLEEP_START:
// TODO - Check we have valid time
// TODO - Check this is a timed mode - and change back to default
// - If default, stays here forever
// First time - LED on
if (first) {
console.log(LOG_INFO, F("Sleep: Waiting up to 60 seconds before power down"));
sh3dNodeIO.led1Set( LED_DOUBLE );
}
// Check maths on hold - it always wins
else if (wii5Maths.remainingHold() > 0) {
console.log(LOG_ERROR, F("Sleep: Maths holding, switching to wait"));
step = WII5SLEEP_BUTTONS; stepWait = 0;
}
// TODO 2024 - Removed time at boot start
// Check if GPS is old and been an hour since we checked it, force it now
// else if ((wii5Gps.sinceOnLast > (WII5TIME_1HOUR * 1000)) && (!wii5Gps.isTimeValid())) {
// console.log(LOG_ERROR, F("Sleep: Internal clock / time is not valid, turn on GPS"));
// step = WII5SLEEP_TIME; stepWait = 0;
// }
// Normal mode - calculate next period - Wait 60 seconds first (TODO 2024)
else if (stepWait > 60000) {
// Convert minutes to seconds - note, getSleepPeriod will not return over 24 hours.
sleepNextSeconds = (uint32_t)wii5Config.getSleepPeriod() * (uint32_t)60;
// Check if we have any thing we need to do
// e.g. Check are we allowed to sleep (rules)
// Log our intent
// What about auto switching to Capture - can we do that? Should we
#ifdef DEBUG_SLEEP
console.log(LOG_DEBUG, F("Sleep: step -> wait"));
#endif
step = WII5SLEEP_WAIT; stepWait = 0;
}
break;
case WII5SLEEP_WAIT:
// Has everything finished - e.g. Maths. Give it a bit of time. How long? Need to think about it.
if (first) {
#ifdef WII5_GPS
wii5Gps.off();
#endif
wii5Communications.stop();
wii5Iridium.stop();
wii5Sparton.stop();
// Maths off ?
// wii5Maths.off();
wii5Controller.setSDOff(); // Force off then power down SD
wii5Controller.shared5Off();
}
// TODO 2024 - Wait 20 seconds afterall off before sleeping
else if (stepWait > 20000) {
#ifdef DEBUG_SLEEP
console.log(LOG_DEBUG, F("Sleep: stop devices - step -> sleeping"));
#endif
step = WII5SLEEP_SLEEPING; stepWait = 0;
}
break;
case WII5SLEEP_SLEEPING:
// Something up - should never be here
if (sleepNextSeconds < 10) {
console.log(LOG_ERROR, F("Sleep: Time < 10 seconds, skipping to UPDATE mode"));
step = WII5SLEEP_UPDATE;
}
else {
if (sleepNextSeconds > 21600) {
console.log(LOG_ERROR, F("Sleep: ERROR > 6 hours (21600) seconds, changing to 1 hour"));
sleepNextSeconds = 3600;
}
// Actual sleep
console.log(LOG_INFO, F("Sleep: sleeping %d hours = %lu seconds"), int(sleepNextSeconds / 3600), sleepNextSeconds);
// Sleep, turning off Maths
wii5Setup.sleepBefore();
// TODO 2024 - Consider sleep loop - not actually WDT but rather just tight loop, therefore leaving WDT normal for now
sh3dNodeUtil.sleep(sleepNextSeconds);
wii5Setup.sleepAfter();
// }
console.log(LOG_INFO, F("Sleep: Woke up - requested=%lu, actual=%lu, reasons=%d"), sleepNextSeconds, sh3dNodeUtil.sleepLastSeconds, sh3dNodeUtil.sleepLastReason);
switch(sh3dNodeUtil.sleepLastReason) {
case 0:
console.log(LOG_FATAL, F("Unknwon error inside sleep function"));
// TODO What next? - not sure how to deal with this
// TODO put it in an error counter... if the counter gets too large or rescent
// Then switch back to default mode.
step = WII5SLEEP_START; stepWait = 0;
break;
// Normal exit
case 1:
// TODO 12 hours is up - turn on GPS etc and send/receive data
step = WII5SLEEP_UPDATE;
break;
// TODO Identify that this needs to stay on for some seconds for
// button Processing
// These are like a reboot - coming out of sleep. Beep like one
case 10:
case 11:
wii5Setup.bootbeep();
step = WII5SLEEP_BUTTONS;
break;
default:
console.log(LOG_ERROR, F("Sleep: Unknown mode = %d"), step);
step = WII5SLEEP_UPDATE;
break;
}
}
break;
case WII5SLEEP_BUTTONS:
if (first) {
sh3dNodeIO.led1Set( LED_DOUBLE );
console.log(LOG_ERROR, F("Sleep: BUTTONS process Waiting for Maths and another 10 seconds for buttons"));
wii5Maths.stop();
}
// Maths stopped and 30 seconds
else if (wii5Maths.isOff() && (stepWait > 30000)) {
console.log(LOG_ERROR, F("Sleep: BUTTONS process finished, Maths off, starting sleep process (30 seconds)"));
step = WII5SLEEP_START; stepWait = 0;
}
// 1 hour passed - shut it down buoys
else if (stepWait > ((uint32_t)WII5TIME_1HOUR * 1000)) {
console.log(LOG_FATAL, F("Sleep: Failed timeout of Buttons"));
step = WII5SLEEP_START; stepWait = 0;
}
break;
case WII5SLEEP_TIME:
if (first) {
wii5Gps.autoTime();
displayWait = 0;
}
else if (wii5Gps.ready()) {
step = WII5SLEEP_START; stepWait = 0;
}
else if (stepWait > 300000) {
step = WII5SLEEP_START; stepWait = 0;
}
else if (displayWait > 10000) {
console.log(LOG_INFO, F("Sleep: Waiting for GPS Time - %lu seconds"), (stepWait / 1000));
displayWait = 0;
}
break;
case WII5SLEEP_UPDATE:
if (first) {
sh3dNodeIO.led1Set( LED_DOUBLE );
#ifdef WII5_GPS
wii5Gps.autoAccurate();
#endif
wii5Battery.start();
wii5Weather_18B20.temperatureRead();
displayWait = 0;
wii5Maths.stop();
}
else if (wii5Gps.ready()) {
if (wii5Gps.isError()) {
console.log(LOG_ERROR, F("Sleep: GPS Finished with ERROR"));
}
wii5Gps.off();
step = WII5SLEEP_COMMS; stepWait = 0;
}
else if (stepWait > 300000) {
console.log(LOG_ERROR, F("Sleep: GPS Timed out after 300 seconds"));
step = WII5SLEEP_COMMS; stepWait = 0;
}
else if (displayWait > 10000) {
console.log(LOG_INFO, F("Sleep: Waiting for GPS Lock - %lu seconds"), (stepWait / 1000));
displayWait = 0;
}
break;
case WII5SLEEP_COMMS:
if (first) {
sh3dNodeIO.led1Set( LED_DOUBLE );
console.log(LOG_INFO, F("Sleep: Communications start"));
wii5Communications.setSimpleMode();
wii5Communications.sendBinModeType(wii5Config.getSleepBinaryType(), wii5Config.getRecordCount());
wii5Communications.start();
}
else if (!wii5Communications.isRunning()) {
console.log(LOG_INFO, F("Sleep: Communications end"));
step = WII5SLEEP_START; stepWait = 0;
}
// TODO 10 minutes ok
else if (stepWait > 600000) {
console.log(LOG_FATAL, F("Sleep: Communications timeout"));
step = WII5SLEEP_START; stepWait = 0;
}
break;
default:
console.log(LOG_FATAL, F("SLEEP: Default step... step=%d"), step);
step = WII5SLEEP_START; stepWait = 0;
break;
}
}
WII5ModeSleep wii5ModeSleep;
+60
View File
@@ -0,0 +1,60 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5ModeSleep.h
* @brief Sleep mode: long sleeps between wake-ups; powers down the Maths CPU.
*/
#ifndef WII5ModeSleep_h
#define WII5ModeSleep_h
#include <Arduino.h>
#include <WII5.h>
#include <TimeLib.h>
// TODO - Namespace these - e.g enum class ...
enum WII5SLEEP_STEPS {
WII5SLEEP_START,
WII5SLEEP_WAIT,
WII5SLEEP_SLEEPING,
WII5SLEEP_BUTTONS,
WII5SLEEP_UPDATE,
WII5SLEEP_TIME,
WII5SLEEP_COMMS,
WII5SLEEP_FINISH
};
/**
* @brief Sleep mode: long sleeps between wake-ups.
*
* Computes how long to sleep until the next configured wake-up
* (capture window, position update, etc.), powers down the Maths CPU
* and other peripherals, and uses sh3dNodeUtil.sleep() to put the AVR
* into deep sleep.
*/
class WII5ModeSleep : public WII5Mode {
public:
WII5ModeSleep() {}
/** @brief Reset to WII5SLEEP_START. */
void reset();
/** @brief One-time bring-up. */
void begin();
/** @brief State-machine tick. */
void loop();
protected:
bool first;
WII5SLEEP_STEPS lastStep;
elapsedMillis stepWait;
elapsedMillis displayWait;
WII5SLEEP_STEPS step;
uint32_t sleepNextSeconds; // How long we going to sleep (cleared after sleep)
};
extern WII5ModeSleep wii5ModeSleep;
#endif
+161
View File
@@ -0,0 +1,161 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5Power.cpp
* @brief Base class for power-managed peripherals (powerOn/powerOff state).
*/
/*
WII5Power - Power PIN version of base class - can also be used stand alone
TODO
* (low) pulse - How do we have a loop function or simiar to turn off the pin after a period.
* (medium) two pin - one one on, one off
* (medium) shared pin - sharing a single pin (could this be done better/together)
* (low) deug - approve upon the macro that is here.
*/
#include <Arduino.h>
#include <WII5.h>
#include <TimeLib.h>
/* POWER PIN Debuggig
This can produce an overwhelming amount of serial output
Uses safeLog to prevent output on alternative serial / SD Card.
Add a _PIN versio to allow logging of a single pin
NOTE: This should be generalised across all the modules (different selectors)
*/
// #define POWERDEBUG
// #define POWERDEBUG_PIN POWER_GPS_PIN
#ifdef POWERDEBUG
#if defined(POWERDEBUG_PIN)
#define DODEBUG(...) if(powerData_Pin == POWERDEBUG_PIN) console.safeLog(LOG_DEBUG, __VA_ARGS__)
#else
#define DODEBUG(...) console.safeLog(LOG_DEBUG, __VA_ARGS__)
#endif
#else
#define DODEBUG(...) (void)0
#endif
// powerOn
void WII5Power::powerOn(bool force) {
// FATAL ERROR (can we afford to Serial print here?)
if (powerData_Pin < 2)
return;
DODEBUG(F("Power (ON): ON request %d=%s, status=%d, force=%d"), powerData_Pin, wii5Strings.strPinWII5Name(powerData_Pin), powerData_On, force);
if (powerData_Input || !powerData_On || force) {
powerData_Input = false;
// Shared pin
if (powerData_Shared) {
DODEBUG(F("Power(ON): shared requested"));
powerData_Shared->powerOn();
}
// Pin Modes
DODEBUG(F("Power(ON): pinMode OUTPUT"));
pinMode(powerData_Pin, OUTPUT);
if (powerData_PinOff > 0)
pinMode(powerData_PinOff, OUTPUT);
DODEBUG(F("Power(ON): digitalWrite %d"), powerData_OnIsHigh ? HIGH : LOW);
digitalWrite(powerData_Pin, powerData_OnIsHigh ? HIGH : LOW);
if (powerData_PinOff > 0) {
DODEBUG(F("Power(ON): WARNING - Untested PinOff Code"));
digitalWrite(powerData_PinOff, powerData_OnIsHigh ? LOW : HIGH);
}
// Variables
powerData_Elapsed = 0;
powerData_Last = now();
powerData_On = true;
// Status
// setStatus(WII5STATUS_ON);
}
DODEBUG(F("Power(ON) : Actual=%d OnIs=%d"), digitalRead(powerData_Pin), powerData_OnIsHigh);
}
// powerInput - Turn this pin back into an input - either for safety or sleep
void WII5Power::powerInput() {
powerData_Input = true;
pinMode(powerData_Pin, INPUT);
}
// powerOff
void WII5Power::powerOff(bool force) {
if (powerData_Pin < 2)
return;
DODEBUG(F("Power (OFF): OFF request %d=%s, status=%d, force=%d"), powerData_Pin, wii5Strings.strPinWII5Name(powerData_Pin), powerData_On, force);
if (powerData_Input || powerData_On || force) {
powerData_Input = false;
// Shared pin
if (powerData_Shared)
powerData_Shared->powerOff();
// Local pin
DODEBUG(F("Power(OFF): pinMode OUTPUT"));
pinMode(powerData_Pin, OUTPUT);
if (powerData_PinOff > 0)
pinMode(powerData_PinOff, OUTPUT);
// Output
DODEBUG(F("Power(OFF): digitalWrite %d"), powerData_OnIsHigh ? HIGH : LOW);
digitalWrite(powerData_Pin, powerData_OnIsHigh ? LOW : HIGH);
if (powerData_PinOff > 0)
digitalWrite(powerData_PinOff, powerData_OnIsHigh ? HIGH : LOW);
// Variables (caclulate totals)
powerData_LastTotal = int(powerData_Elapsed / 1000);
powerData_Elapsed = 0;
powerData_Last = now();
powerData_On = false;
// Status
// setStatus(WII5STATUS_OFF);
}
DODEBUG(F("Power(OFF) : Actual=%d OnIs=%d"), digitalRead(powerData_Pin), powerData_OnIsHigh);
}
void WII5Power::powerSetPin(uint8_t p, bool onHigh, uint8_t pOff, uint8_t pulse) {
powerData_Pin = p;
powerData_OnIsHigh = onHigh;
powerData_PinOff = pOff;
powerData_Pulse = pulse;
powerData_Input = true;
powerData_On = false;
powerData_Elapsed = 0;
powerData_Last = 0;
powerData_LastTotal = 0;
powerData_Shared = NULL;
}
void WII5Power::powerSetShared(WII5Power *s) {
powerData_Shared = s;
}
// Status and output
bool WII5Power::powerStatus() {
// NOTE: This is not accurate if the pin as still an INPUT
return powerData_On;
}
uint32_t WII5Power::powerLast() {
return powerData_Last;
}
uint32_t WII5Power::powerElapsed() {
return powerData_Elapsed;
}
uint32_t WII5Power::powerLastTotal() {
return powerData_LastTotal;
}
+59
View File
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5Power.h
* @brief Base class for power-managed peripherals (powerOn/powerOff state).
*/
#ifndef WII5Power_h
#define WII5Power_h
#include <elapsedMillis.h>
class WII5Power : public WII5 {
public:
WII5Power() {
powerData_Pin = 0; powerData_PinOff = 0; powerData_Pulse = 0; powerData_OnIsHigh = false;
powerData_On = false; powerData_Elapsed = 0; powerData_Last = 0; powerData_LastTotal = 0;
powerData_Input = true;
}
virtual WII5_DRIVERS driverId() {return WII5DRIVER_POWER;}
void powerSetPin(uint8_t p, bool onHigh = true, uint8_t pOff = 0, uint8_t pulse = 0);
void powerSetShared(WII5Power *s);
virtual void powerOn(bool force = false);
virtual void powerOff(bool force = false);
virtual void powerInput();
bool powerStatus();
uint32_t powerLast();
uint32_t powerElapsed();
uint32_t powerLastTotal();
protected:
// PIN and which way high
uint8_t powerData_Pin;
uint8_t powerData_PinOff; // If this is set, assume Pin to turn on, PinOff to turn off
uint8_t powerData_Pulse; // Numbers of 10s of milliseconds, from 10 to 2550
bool powerData_OnIsHigh;
// Shared pin - see WII5PowerShared (note it can just be a PIN but Shared counts)
WII5Power *powerData_Shared;
// Current status
bool powerData_On;
bool powerData_Input; // Pin is currently input - e.g. bootup or sleep
// How long since on (millisecond timer, not effected by time changes)
elapsedMillis powerData_Elapsed;
// Actual time truned on (effected by tim echange)
time_t powerData_Last;
// Number of seconds it was on for
uint32_t powerData_LastTotal;
};
#endif
+147
View File
@@ -0,0 +1,147 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5RTC.cpp
* @brief Real-time clock helper.
*/
/*
WII5RTC -
*/
#include <Arduino.h>
#include <WII5RTC.h>
#include <Wire.h>
#include "RTClib.h"
RTC_DS3231 rtc;
uint32_t syncProvider() {
return rtc.now().unixtime();
}
void WII5RTC::begin() {
if (! rtc.begin()) {
SerialConsole.println("RTC: Couldn't find RTC");
}
// TODO Do the lost power thing
// Then connect to GPS
console.printf(F("RTC: startup date time from RTC="));
console.printDateTime(rtc.now().unixtime());
console.printNewLine();
setSyncProvider(syncProvider); // the function to get the time from the RTC
if(timeStatus() != timeSet)
SerialConsole.println("RTC: Unable to sync with the RTC");
else
SerialConsole.println("RTC: has set the system time");
}
/*
What type of device
Whuihc Library
Can they suse SPI or I2c or, writred wrong?
What are the pins
*/
float WII5RTC::getTemperature() {
return rtc.getTemperature();
}
void WII5RTC::setRTC(uint32_t t) {
if (t == 0) {
t = now();
}
rtc.adjust(t);
console.log(LOG_INFO, F("RTC: Adjusted to new time provided"));
console.printf(F("# TimeSet="));
console.printDateTime(t);
console.printf(F(" RTC="));
console.printDateTime(rtc.now().unixtime());
console.printNewLine();
}
void WII5RTC::test() {
// TODO Move to Strings for space
char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
// TODO Move this code into begin !
if (rtc.lostPower()) {
SerialConsole.println(F("RTC lost power, lets set the time!"));
// following line sets the RTC to the date & time this sketch was compiled
// rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
// Fixed date temporary
rtc.adjust(1483268462);
// TODO Log this !
}
elapsedMillis doIt = 0;
while (doIt < 15000) {
DateTime nowrtc = rtc.now();
time_t nowsys = now();
// System vs RTC
SerialConsole.print(F("# UnixTimeStamp System = "));
SerialConsole.print(nowsys);
SerialConsole.print(F(" RTC = "));
SerialConsole.print(nowrtc.unixtime());
SerialConsole.println();
SerialConsole.print(nowrtc.year(), DEC);
SerialConsole.print('/');
SerialConsole.print(nowrtc.month(), DEC);
SerialConsole.print('/');
SerialConsole.print(nowrtc.day(), DEC);
SerialConsole.print(" (");
SerialConsole.print(daysOfTheWeek[nowrtc.dayOfTheWeek()]);
SerialConsole.print(") ");
SerialConsole.print(nowrtc.hour(), DEC);
SerialConsole.print(':');
SerialConsole.print(nowrtc.minute(), DEC);
SerialConsole.print(':');
SerialConsole.print(nowrtc.second(), DEC);
SerialConsole.println();
SerialConsole.print(F(" since midnight 1/1/1970 = "));
SerialConsole.print(nowrtc.unixtime());
SerialConsole.print("s = ");
SerialConsole.print(nowrtc.unixtime() / 86400L);
SerialConsole.println("d");
// calculate a date which is 7 days and 30 seconds into the future
DateTime future (nowrtc + TimeSpan(7,12,30,6));
SerialConsole.print(F(" now + 7d + 30s: "));
SerialConsole.print(future.year(), DEC);
SerialConsole.print('/');
SerialConsole.print(future.month(), DEC);
SerialConsole.print('/');
SerialConsole.print(future.day(), DEC);
SerialConsole.print(' ');
SerialConsole.print(future.hour(), DEC);
SerialConsole.print(':');
SerialConsole.print(future.minute(), DEC);
SerialConsole.print(':');
SerialConsole.print(future.second(), DEC);
SerialConsole.println();
SerialConsole.println();
delay(1000);
}
SerialConsole.println(F("End of test"));
}
WII5RTC wii5RTC;
+33
View File
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5RTC.h
* @brief Real-time clock helper.
*/
#ifndef WII5RTC_h
#define WII5RTC_h
#include <WII5.h>
// Why not inherit our WII5 class?
class WII5RTC {
public:
WII5RTC() {}
virtual WII5_CONTROLLERS controllerId() {return WII5CONTROLLER_DRIVER;}
virtual WII5_DRIVERS driverId() {return WII5DRIVER_RTC;}
void begin();
void test();
void setRTC(uint32_t t = 0);
float getTemperature();
protected:
bool testDone;
};
extern WII5RTC wii5RTC;
#endif
+82
View File
@@ -0,0 +1,82 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5RadioLoRa.cpp
* @brief LoRa radio driver (RFM95) — optional, gated on WII5_RADIO_LORA.
*/
/*
WII5RadioLoRa
*/
#include <Arduino.h>
#include <WII5.h>
#ifdef WII5_RADIO_LORA
#include <WII5RadioLoRa.h>
void WII5RadioLoRa::begin() {
powerSetPin(POWER_RADIO_PIN, POWER_RADIO_ON);
powerOn(true);
running = true;
// sh3dNodeRadio.begin(wii5BufferRadio, WII5_BUFFER_RADIO, &wii5RadioRF95);
// sh3dNodeNetwork.begin();
sh3dNodeBroadcast.begin();
}
// start / stop — currently no-ops. Radio power is managed in begin(); modes
// don't (yet) toggle the radio on demand. Kept as overrides of the WII5Power
// virtuals so callers can still invoke them safely.
void WII5RadioLoRa::start(bool force) {
(void)force;
}
void WII5RadioLoRa::stop(bool force) {
(void)force;
}
void WII5RadioLoRa::loop() {
sh3dNodeRadio.loop();
if (passthrough && sh3dNodeRadio.lastValid) {
console.printf(F("Radio:record - yay. Length = %d. RSSI = %d."),
sh3dNodeRadio.lastLength,
sh3dNodeRadio.lastRadio.rssi,
sh3dNodeRadio.lastRadio.snr
);
console.printf("DATA: ");
console.printf((char*)sh3dNodeRadio.buffer);
console.printf("\r\n");
sh3dNodeRadio.lastClear();
}
sh3dNodeNetwork.loop();
if (sh3dNodeNetwork.lastValid) {
console.printf(F("Radio:packet: type=%d\r\n"),(int(sh3dNodeNetwork.lastPacketType)));
}
}
// No disable ATM
void WII5RadioLoRa::setPassthrough(bool in) {
passthrough = in;
}
bool WII5RadioLoRa::getPassthrough() {
return passthrough;
}
void WII5RadioLoRa::setDebug(bool in) {
debug = in;
}
bool WII5RadioLoRa::getDebug() {
return debug;
}
WII5RadioLoRa wii5RadioLoRa;
#endif
+44
View File
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5RadioLoRa.h
* @brief LoRa radio driver (RFM95) — optional, gated on WII5_RADIO_LORA.
*/
#ifndef WII5RadioLoRa_h
#define WII5RadioLoRa_h
#ifdef WII5_RADIO_LORA
#include <Arduino.h>
#include <WII5.h>
class WII5RadioLoRa : public WII5Power {
public:
WII5RadioLoRa() {}
virtual WII5_CONTROLLERS controllerId() {return WII5CONTROLLER_DRIVER;}
virtual WII5_DRIVERS driverId() {return WII5DRIVER_RADIO;}
void begin();
void loop();
void start(bool force = false);
void stop(bool force = false);
void setPassthrough(bool in);
bool getPassthrough();
void setDebug(bool in);
bool getDebug();
protected:
bool running;
bool passthrough;
bool debug;
uint32_t sendCount;
};
extern WII5RadioLoRa wii5RadioLoRa;
#endif
#endif
+17
View File
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5SerialDebug.cpp
* @brief Optional debug-serial port (SoftwareSerial) — gated on WII5_DEBUG_SERIAL.
*/
#include <WII5.h>
#include <WII5SerialDebug.h>
#ifdef WII5_DEBUG_SERIAL
SoftwareSerial SerialDebug(WII5_DEBUG_SERIAL_RX, WII5_DEBUG_SERIAL_TX);
#endif
+20
View File
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5SerialDebug.h
* @brief Optional debug-serial port (SoftwareSerial) — gated on WII5_DEBUG_SERIAL.
*/
#ifndef WII5SerialDebug_h
#define WII5SerialDebug_h
#ifdef WII5_DEBUG_SERIAL
#include <SoftwareSerial.h>
extern SoftwareSerial SerialDebug;
#endif
#endif
+553
View File
@@ -0,0 +1,553 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5SerialManager.cpp
* @brief Serial-port helper: AT-response parsing, command queue.
*/
/*
WII5SerialManager - Base class for managed serial ports
Sketch uses 106588 bytes (41%) of program storage space. Maximum is 258048 bytes.
Global variables use 5621 bytes (68%) of dynamic memory, leaving 2571 bytes for local variables. Maximum is 8192 bytes.
Global variables use 5621 bytes (68%) of dynamic memory, leaving 2571 bytes for local variables. Maximum is 8192 bytes.
*/
#include <Arduino.h>
#include <WII5SerialManager.h>
#include <WII5.h>
void WII5SerialManager::sendNewLine() {
stream->print(F("\r\n"));
}
void WII5SerialManager::beginSerialManager() {
// console.log(LOG_DEBUG, F("SerialManager: Buffer size=%d"), bufMax);
processTimeout = 15000; // TODO This should be configurable
}
void WII5SerialManager::endSerialManager() {
// closeFile();
}
// No disable ATM
void WII5SerialManager::enableNmea() {
gps = new TinyGPSPlus();
}
void WII5SerialManager::setPassthrough(bool in) {
passthrough = in;
}
bool WII5SerialManager::getPassthrough() {
return passthrough;
}
void WII5SerialManager::setDebug(bool in) {
debug = in;
}
bool WII5SerialManager::getDebug() {
return debug;
}
void WII5SerialManager::setCapture(bool in) {
capture = false;
}
bool WII5SerialManager::getCapture() {
return capture;
}
void WII5SerialManager::setRecords(uint32_t in) {
recordTotal = in;
}
uint32_t WII5SerialManager::getRecords() {
return recordTotal;
}
void WII5SerialManager::setTimeout(uint32_t in) {
processTimeout = in;
}
uint32_t WII5SerialManager::getTimeout() {
return processTimeout;
}
// TODO - Make this reusable - e.g. plug in different list of lines (callback?)
// Before calling:
// - probramCount (where to start), -programTotal (where to end) (not length)
// - sendCount = 0
WII5_SERIALCMDS WII5SerialManager::sendAll(WII5SERIAL_LAST lastUntil) {
// Sent everyting - spin on
if (programCount > programTotal) {
return WII5SERIALCMD_OK;
}
// First count - read line to buffer
if (sendCount == 0) {
programLine(programCount);
}
switch (sendAndWait()) {
// Still waiting.
case WII5SERIALCMD_WAITING:
if (debug && ((sendCount % 10000) == 0))
console.log(LOG_DEBUG, F("SERIAL: waiting, sendCount=%lu"), sendCount);
break;
// Allow these to fall through - we will spin on lines even if error
case WII5SERIALCMD_TIMEOUT:
console.log(LOG_ERROR, F("SERIAL: TIMEOUT sending: %s"), buffer);
programCount++; // NOTE - Just spin on for now
sendCount = 0;
// TODO - consier return WII5SERIALCMD_TIMEOUT;
break;
case WII5SERIALCMD_ERROR:
console.log(LOG_ERROR, F("SERIAL: ERROR sending: %s"), buffer);
programCount++; // NOTE - Just spin on for now
sendCount = 0;
// TODO - consier return WII5SERIALCMD_ERROR;
break;
case WII5SERIALCMD_OK:
// TODO why?
if (debug) {
programLine(programCount);
}
programCount++;
sendCount = 0;
break;
default:
console.log(LOG_ERROR, F("SERIAL: Unknown sendAndWait return"));
programCount++; // NOTE - Just spin on for now
sendCount = 0;
break;
}
return WII5SERIALCMD_WAITING;
}
// TODO return true when done - either timeout or error or success
// - Instead could return:
// CMD_WAITING - Stil waiting
// CMD_OK - OK - success
// CMD_ERROR - Error returned to us (see buffer)
// CMD_TIMEOUT - Timeout
WII5_SERIALCMDS WII5SerialManager::sendAndWait(WII5SERIAL_LAST lastUntil) {
// First time in - send the string
if (sendCount == 0) {
// Send to IMU (this should allow for write, or print and with none, \r, \n or \r\n
// stream->write(buffer, strlen(buffer));
stream->print(buffer);
sendNewLine();
if (passthrough)
consoleBuffer();
// if (debug)
// console.log(LOG_DEBUG, F("SerialManager: send line: %s"), buffer);
// Clear timeout
processWait = 0;
// Clear any incoming data
last = WII5SERIALLAST_NONE;
bufCount = 0; fieldLoc = 0;
buffer[bufCount] = '\0';
processMode = WII5SERIALPARSER_OKHUH;
}
// Future times
else {
// Did we get OK or ERROR? (could this get into other modes - e.g. LINE or RECORD)
if (last == lastUntil) {
sendCount = 0;
return WII5SERIALCMD_OK;
}
else if (last == WII5SERIALLAST_ERROR) {
sendCount = 0;
return WII5SERIALCMD_ERROR;
}
}
if (processWait > processTimeout) {
sendCount = 0;
return WII5SERIALCMD_TIMEOUT;
}
// Increment counter and inform - WAITING
sendCount++;
return WII5SERIALCMD_WAITING;
}
// SerialManager Things:
// - Turn Off
// - Start now
// - Start at X
void WII5SerialManager::loop() {
// Process the Serial - only if it is running - to consider this more...
if (stream) {
while (stream->available()) {
// One line at a time - to allow proessing return data
if (processSerial(stream->read())) {
break;
}
}
}
}
void WII5SerialManager::start(bool force) {}
void WII5SerialManager::stop(bool force) {}
void WII5SerialManager::setBuffer(char* buf, uint8_t sz) {
buffer = buf;
bufMax = sz;
}
void WII5SerialManager::setBaudrate(uint32_t baud) {
console.log(LOG_WARN, F("Baudrate: Not supported by this module"));
}
void WII5SerialManager::repl(uint32_t baud) {
uint8_t lastDot = 0;
start();
if (baud > 0)
setBaudrate(baud);
if (!stream) {
console.log(LOG_FATAL, F("SerialManager: Can not do REPL - no stream after start"));
return;
}
while (1) {
if (SerialConsole.available()) {
char c = SerialConsole.read();
if (c == '.')
lastDot++;
else
lastDot = 0;
if (lastDot >= 3) {
SerialConsole.flush();
stream->flush();
stop();
console.log(LOG_DEBUG, F("SerialManager: Completed REPL. User exit."));
return;
}
stream->write(c);
}
if (stream->available()) {
char c = stream->read() ;
SerialConsole.write(c);
}
}
}
void WII5SerialManager::processBufferLine() {
if (debug)
console.log(LOG_DEBUG, F("SerialManager: received line: %s"), buffer);
last = WII5SERIALLAST_LINE;
}
void WII5SerialManager::processBufferOK() {
if (bufCount < 1)
return;
// If last 2 chars = "ok"
if (
(strncmp_P(buffer,(PGM_P) F("OK"), 2) == 0)
|| (strncmp_P(buffer,(PGM_P) F("ok"), 2) == 0)
) {
if (debug)
console.log(LOG_DEBUG, F("SerialManager: received ok: len=%d, str=%s"), bufCount, buffer);
last = WII5SERIALLAST_OK;
}
// READY
if (
(strncmp_P(buffer,(PGM_P) F("READY"), 5) == 0)
|| (strncmp_P(buffer,(PGM_P) F("ready"), 5) == 0)
) {
if (debug)
console.log(LOG_DEBUG, F("SerialManager: received ready: len=%d, str=%s"), bufCount, buffer);
last = WII5SERIALLAST_READY;
}
if (strncmp_P(buffer,(PGM_P) F("+CSQ:"), 5) == 0) {
lastVal = atol(&buffer[5]);
if (debug)
console.log(LOG_DEBUG, F("SerialManager: received CSQ: val=%lu, buffer=%s"), lastVal, buffer);
last = WII5SERIALLAST_ATCSQ;
}
if (strncmp_P(buffer,(PGM_P) F("+SBDI:"), 6) == 0) {
// +SBDI:<MO status>,<MOMSN>,<MT status>,<MTMSN>,<MT length>,<MT queued>
// MO status
// 0 = No SBD message to send
// 1 = SBD message successfully sent from the 9602 to the GSS
// 2 = An error occurred while attempting to send SBD message from 9602 to GSS
// MOMSN
// Unique number of this message sent, Keep
// MT status
// 0 = No SBD message to receive from the GSS
// 1 = SBD message successfully received from the GSS (how to get?)
// 2 = An error occurred while attempting to perform a mailbox check or receive a message from the GSS.
// MT length
// The MT length is the length in bytes of the mobile terminated SBD message received from the GSS. If no message was received, this eld will be zero
// MT queued
// MT queued is a count of mobile terminated SBD messages waiting at the GSS to be transferred to the 9602
//
// TODO Note - these need to be split on "," and stored in an array
// ?recordVals[]
// lastVal = atol(&buffer[4]);
// Serial:< +SBDI: 1, 8, 0, 0, 0, 0
lastVal = atol(&buffer[5]);
/*
uint8_t lastMOStatus;
uint16_t lastMOMSN;
uint8_t lastMTStatus;
uint16_t lastMTMSN;
uint16_t lastMTLength;
uint16_t lastMTQueued;
*/
if (debug)
console.log(LOG_DEBUG, F("SerialManager: received SBDI: buffer=%s"), buffer);
// +SBDI:<MO status>,<MOMSN>,<MT status>,<MTMSN>,<MT length>,<MT queued>
// Can't seem to ignore the spaces?
// sscanf_P(&buffer[6], PSTR("%d,%d,%d,%d,%d,%d"), lastMSOStatus, lastMOMSN, lastMTStatus, lastMTMSN, lastMTLength, lastMTQueued);
for(fieldCount = 0; fieldCount < WII5_FIELD_MAX; fieldCount++) {
recordVals[fieldCount] = 0;
}
fieldCount = 0;
p = strtok(&buffer[6], ",");
while (p) {
if (fieldCount < WII5_FIELD_MAX) {
recordVals[fieldCount] = atol(p);
p = strtok(NULL, ",");
fieldCount++;
}
else {
p = NULL;
}
}
last = WII5SERIALLAST_ATSBDI;
}
if (strncmp_P(buffer,(PGM_P) F("BOOT Version:"), 13) == 0) {
// TODO Note - these need to be split on "," and stored in an array
// ?recordVals[]
// lastVal = atol(&buffer[4]);
if (debug)
console.log(LOG_DEBUG, F("SerialManager: received BOOT Version: buffer=%s"), buffer);
last = WII5SERIALLAST_BOOTVERSION;
}
// A number, but do we check the whole thing?
if (buffer[0] == '3') {
// lastVal = atol(&buffer[0]);
// TODO Keeping this? Howto
if (debug)
console.log(LOG_DEBUG, F("SerialManager: received a number=%s"), buffer);
last = WII5SERIALLAST_NUMBER;
}
/*
if (debug) {
if (bufCount == 0)
console.log(LOG_DEBUG, F("SerialManager: failed ok: len=%d, str={NA}"), bufCount);
else if (bufCount >= 2)
console.log(LOG_DEBUG, F("SerialManager: failed ok: len-2=%c len-1=%c"), buffer[bufCount - 2], buffer[bufCount - 1]);
else
console.log(LOG_DEBUG, F("SerialManager: failed ok: len=%d, str=%s"), bufCount, buffer);
}
*/
// ??? last = WII5SERIALLAST_OK;
}
// Sample Buffer Field - just assume longs
void WII5SerialManager::processBufferField(char *buf, uint8_t len) {
if (fieldCount < WII5_FIELD_MAX) {
// Check size is valid
if (len >= WII5SERIALMAANGER_FIELD_SIZE) {
console.log(LOG_FATAL, F("SerialManager: processBufferField too large len=%d, available=%d"), int(len), int(WII5SERIALMAANGER_FIELD_SIZE));
return;
}
// Copy the data
// TODO find all strncpy and do this !!!
memset(fieldBuffer, 0, sizeof(fieldBuffer));
strncpy(fieldBuffer, buf, len);
// Convert to Long
recordVals[fieldCount] = atol(buf);
fieldCount++;
}
}
void WII5SerialManager::processRecord() {
if (bufCount == 0) return;
recordCount++;
last = WII5SERIALLAST_RECORD;
}
// Basic line parsers
bool WII5SerialManager::processSerial(char in) {
// processMode
// NONE, LINE, RECORD, OKHUH, AT
if (processMode == WII5SERIALPARSER_NONE) return false;
// TODO Untested binary1 mode - from Iridium....
if (processMode == WII5SERIALPARSER_BINARY1) {
// TODO - maximum buffer size
console.printf(F("# BINARY1 received = %d - %d\r\n"), bufCount, int(in));
binBuffer[bufCount] = in;
bufCount++;
if (binBufferExpect == 0) {
// Look for length
if ((bufCount > 1) && (binBuffer[bufCount - 2] == '\0')) {
binBufferExpect = (uint8_t)binBuffer[bufCount - 1];
console.printf(F("# BINARY1 Expect = %d\r\n"), binBufferExpect);
bufCount = 0;
}
// Hmm... too much guff...
else if (bufCount > 25) {
// Something went very wrong.
processMode = WII5SERIALPARSER_NONE;
console.printf(F("# BINARY1 No length provided\r\n"));
return false;
}
}
else if (bufCount >= (binBufferExpect + 2)) {
processMode = WII5SERIALPARSER_NONE;
return true;
}
return false;
}
// TODO return gps->encode(in);
if (gps)
gps->encode(in);
// NOT a new line
if ( (in != '\n') && (in != '\r') ) {
// Process 1 field at a time (when comma)
if ( (processMode == WII5SERIALPARSER_RECORD) && (in == ',') ) {
processBufferField(&buffer[fieldLoc], bufCount - fieldLoc);
buffer[bufCount] = ',';
bufCount++;
fieldLoc = bufCount; // Next field will start here (could be null)
}
// Keep whole record
else {
if ((bufCount + 1) < bufMax) {
buffer[bufCount] = in;
bufCount++;
buffer[bufCount] = '\0';
}
else {
// TODO Log - just once, FATAL
// last = WII5SPARTONRETURN_ERROR;
}
}
}
// New line - end of record
else {
writePassthrough();
writeCapture();
// CSV Data
if (processMode == WII5SERIALPARSER_RECORD) {
processBufferField(&buffer[fieldLoc], bufCount - fieldLoc);
processRecord();
}
else if (processMode == WII5SERIALPARSER_OKHUH) {
processBufferOK();
}
// An unknown line
else {
processBufferLine();
}
bufCount = 0;
// buffer[0] = '\0';
fieldCount = 0;
fieldLoc = 0;
return true;
}
return false;
}
void WII5SerialManager::writePassthrough() {
if (passthrough && bufCount > 0)
consoleBuffer(true); // Input
}
// Allow override of output of lines to log file (or even multiple)
void WII5SerialManager::writeCapture() {
}
void WII5SerialManager::sendLine(uint16_t l) {
programLine(l);
stream->print(buffer);
sendNewLine();
if (passthrough)
consoleBuffer();
}
// TODO: Move from buffer to wii5StringBuffer
void WII5SerialManager::programLine(uint16_t l) {
if (debug)
console.log(LOG_DEBUG, F("SerialManager: set command=%d buffer=%s"), l, buffer);
}
void WII5SerialManager::consolePrefix(bool input) {
if (input)
console.printf(F("# Serial:< "));
else
console.printf(F("# Serial:> "));
}
void WII5SerialManager::consoleBuffer(bool input, bool useLog) {
// TODO: This will fail if split comma and buffer gets added nulls
if (useLog) {
// TODO prefix?
// TODO Log level?
console.log(LOG_INFO, F("Serial:%s %s"), input ? "<" : ">", buffer);
}
else {
consolePrefix(input);
console.printf(buffer);
console.printNewLine();
}
}
WII5_DRIVERS WII5SerialManager::driverId() {
return WII5DRIVER_UNKNOWN;
}
/*
void WII5SerialManager::displaySTATS() {
// TODO wii5Display.atStatsSend
console.printf(F("STATS:%s:SerialManager:TODO\r\n"), wii5Strings.strDriver(driverId()));
}
*/
// Where? and What? - assumes buffer now has the comma separated data correct
void WII5SerialManager::sendAtDataLine() {
// In other words - assume this serial port, instead of say.... Console
// if (!s) s = stream; - currently assumes console
// NOTE: Steal the console buffer - so don't use it for debugging half way through here.
// You can still use console.print, just not printf, or log
// TODO How to calculate CNC
wii5Display.atDataSend(
F("wii5direct"), // 8 or 64?
// NOTE: Must not add the "@" - it isn't used in CRC calculation
F("%s"), buffer
);
}
+162
View File
@@ -0,0 +1,162 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5SerialManager.h
* @brief Serial-port helper: AT-response parsing, command queue.
*/
#ifndef WII5SerialManager_h
#define WII5SerialManager_h
#include <Arduino.h>
#include <WII5_board.h>
#include <WII5Data.h>
#include <WII5Sh3dConsole.h>
#include <elapsedMillis.h>
#include <TimeLib.h>
#include <TinyGPS++.h>
// TODO Really?
#define WII5SERIALMAANGER_FIELD_SIZE 25
/*
WII5SerialManager - Base class for managed serial ports
Special and Shared Types
WII5SERIAL_LAST
What was the last message we received
WII5SERIAL_PARSERS
Which parser we currently using?
*/
enum WII5SERIAL_PARSERS {
WII5SERIALPARSER_NONE,
WII5SERIALPARSER_LINE,
WII5SERIALPARSER_BINARY1, // likely to have multiple binary fomrats, this one, has the length in the first 2 bytes
// and last 2 bytes are checksum
WII5SERIALPARSER_RECORD,
WII5SERIALPARSER_OKHUH,
WII5SERIALPARSER_AT
};
enum WII5SERIAL_LAST {
WII5SERIALLAST_NONE,
WII5SERIALLAST_LINE,
WII5SERIALLAST_RECORD,
WII5SERIALLAST_OK,
WII5SERIALLAST_READY,
WII5SERIALLAST_ERROR,
WII5SERIALLAST_ATOK, // OK
WII5SERIALLAST_ATERR,
WII5SERIALLAST_ATSBDI, // including +SBDI and +SBDIX
WII5SERIALLAST_BOOTVERSION,
WII5SERIALLAST_NUMBER,
WII5SERIALLAST_ATCSQ // including +CSQ
};
class WII5SerialManager {
public:
WII5SerialManager() {
gps = NULL;
}
virtual WII5_DRIVERS driverId();
virtual void loop();
void beginSerialManager();
void endSerialManager();
void setPassthrough(bool in);
bool getPassthrough();
void setDebug(bool in);
bool getDebug();
void setCapture(bool in);
bool getCapture();
void repl(uint32_t baud = 0);
virtual void sendNewLine();
WII5_SERIALCMDS sendAll(WII5SERIAL_LAST lastUntil = WII5SERIALLAST_OK);
WII5_SERIALCMDS sendAndWait(WII5SERIAL_LAST lastUntil = WII5SERIALLAST_OK);
virtual void setBaudrate(uint32_t baud);
virtual void start(bool force = false);
virtual void stop(bool force = false);
virtual void setRecords(uint32_t in);
virtual uint32_t getRecords();
virtual void writePassthrough();
virtual void writeCapture();
// Public to allow easy access???
TinyGPSPlus *gps;
void enableNmea();
// virtual void displaySTATS();
virtual void sendAtDataLine();
void setTimeout(uint32_t in);
uint32_t getTimeout();
char* buffer;
char* binBuffer;
uint16_t binBufferCount;
uint16_t binBufferExpect;
protected:
// Serial stream
// TODO Stream or HardwareSerial
HardwareSerial *stream;
// Must be implemented in inherited class !
bool capture; // Capture to file - see openFile and closeFile
bool passthrough; // Passthrough to Serial console
bool debug; // Extra debugging to Serial Console
uint32_t sendCount;
virtual bool processSerial(char in);
virtual void processBufferField(char *buf, uint8_t len);
virtual void processBufferOK();
virtual void processBufferLine();
virtual void processRecord();
elapsedMillis processWait;
uint32_t processTimeout;
virtual void programLine(uint16_t l);
virtual void sendLine(uint16_t l);
virtual void consolePrefix(bool input = false);
virtual void consoleBuffer(bool input = false, bool useLog = false);
virtual void setBuffer(char* buf, uint8_t sz);
uint8_t bufMax;
char fieldBuffer[WII5SERIALMAANGER_FIELD_SIZE]; // TODO Configuration etc
uint8_t bufCount;
uint8_t fieldLoc;
uint8_t fieldCount;
WII5SERIAL_PARSERS processMode;
uint8_t programCount;
uint8_t programTotal;
uint32_t recordCount;
uint32_t recordTotal;
WII5SERIAL_LAST last;
uint32_t lastVal;
uint32_t recordVals[WII5_FIELD_MAX];
char *p;
};
#endif
+17
View File
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5SerialMaths.cpp
* @brief Optional Maths-CPU serial port (SoftwareSerial) — gated on WII5_MATHS_SERIAL.
*/
#include <WII5.h>
#include <WII5SerialMaths.h>
#ifdef WII5_MATHS_SERIAL
SoftwareSerial SerialMaths(WII5_MATHS_SERIAL_RX, WII5_MATHS_SERIAL_TX);
#endif
+20
View File
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5SerialMaths.h
* @brief Optional Maths-CPU serial port (SoftwareSerial) — gated on WII5_MATHS_SERIAL.
*/
#ifndef WII5SerialMaths_h
#define WII5SerialMaths_h
#ifdef WII5_MATHS_SERIAL
#include <SoftwareSerial.h>
extern SoftwareSerial SerialMaths;
#endif
#endif
+17
View File
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5SerialStatus.cpp
* @brief Optional status-serial port (SoftwareSerial) — gated on WII5_STATUS_SERIAL.
*/
#include <WII5.h>
#include <WII5SerialStatus.h>
#ifdef WII5_STATUS_SERIAL
SoftwareSerial SerialStatus(WII5_STATUS_SERIAL_RX, WII5_STATUS_SERIAL_TX);
#endif
+20
View File
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5SerialStatus.h
* @brief Optional status-serial port (SoftwareSerial) — gated on WII5_STATUS_SERIAL.
*/
#ifndef WII5SerialStatus_h
#define WII5SerialStatus_h
#ifdef WII5_STATUS_SERIAL
#include <SoftwareSerial.h>
extern SoftwareSerial SerialStatus;
#endif
#endif
+449
View File
@@ -0,0 +1,449 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5Setup.cpp
* @brief Boot-time hardware bring-up: pin defaults, peripherals, and serial ports.
*/
/*
Setup - Turn everything on etc
*/
#include <Arduino.h>
#include <TimeLib.h>
#include <WII5.h>
#include <WII5Setup.h>
#include <avr/wdt.h>
// Debugging etc - move to header file
#define DEBUG_SLEEP
void WII5Setup::beginSafe() {
// WDT
#ifdef WDT_RESET
pinMode(WDT_RESET, OUTPUT);
digitalWrite(WDT_RESET, LOW);
#endif
// Make buzzer OUTPUT and OFF
#ifdef POWER_BUZZER_PIN
pinMode(POWER_BUZZER_PIN, OUTPUT);
digitalWrite(POWER_BUZZER_PIN, LOW);
#endif
// 5V - FORCE On until sure Maths is off
#ifdef SHARED_5VOLT_ON
pinMode(SHARED_5VOLT_PIN, OUTPUT);
digitalWrite(SHARED_5VOLT_PIN, HIGH);
#endif
// Maths - FORCE On until sure Maths is off
#ifdef POWER_MATHS_PIN
pinMode(POWER_MATHS_PIN, OUTPUT);
digitalWrite(POWER_MATHS_PIN, HIGH);
#endif
// SD: Power
#if POWER_STORAGE_1_PIN>0
pinMode(POWER_STORAGE_1_PIN, OUTPUT);
digitalWrite(POWER_STORAGE_1_PIN, HIGH);
#endif
#if POWER_STORAGE_2_PIN>0
pinMode(POWER_STORAGE_2_PIN, OUTPUT);
digitalWrite(POWER_STORAGE_2_PIN, HIGH);
#endif
// SD: Buffers
#ifdef STORAGE_SD1_MATHS_PIN
pinMode(STORAGE_SD1_MATHS_PIN, OUTPUT);
digitalWrite(STORAGE_SD1_MATHS_PIN, HIGH);
pinMode(STORAGE_SD2_MATHS_PIN, OUTPUT);
digitalWrite(STORAGE_SD2_MATHS_PIN, LOW);
#endif
// Sub 1 and 2
#ifdef POWER_SUBBOARD_1_PIN
pinMode(POWER_SUBBOARD_1_PIN, OUTPUT);
digitalWrite(POWER_SUBBOARD_1_PIN, LOW);
#endif
#ifdef POWER_SUBBOARD_2_PIN
pinMode(POWER_SUBBOARD_2_PIN, OUTPUT);
digitalWrite(POWER_SUBBOARD_2_PIN, LOW);
#endif
// GPS
pinMode(POWER_GPS_PIN, OUTPUT);
digitalWrite(POWER_GPS_PIN, LOW);
// Iridium
pinMode(POWER_COMMS_PIN, OUTPUT);
digitalWrite(POWER_COMMS_PIN, LOW);
// Batt2
#ifdef BATTERY_2_PIN
pinMode(BATTERY_2_PIN, OUTPUT);
digitalWrite(BATTERY_2_PIN, LOW);
#endif
// Radio
#ifdef WII5_RADIO_LORA
pinMode(POWER_RADIO_PIN, OUTPUT);
digitalWrite(POWER_RADIO_PIN, LOW);
#endif
// Strobe
pinMode(POWER_STROBE1_ON, OUTPUT);
digitalWrite(POWER_STROBE1_ON, LOW);
#ifdef TIMING_SPARTON_RUN
pinMode(TIMING_SPARTON_RUN, OUTPUT);
#endif
#ifdef TIMING_SPARTON_CHAR
pinMode(TIMING_SPARTON_CHAR, OUTPUT);
#endif
#ifdef TIMING_SPARTON_RECORD
pinMode(TIMING_SPARTON_RECORD, OUTPUT);
#endif
#ifdef TIMING_SD_WRITE
pinMode(TIMING_SD_WRITE, OUTPUT);
#endif
}
void WII5Setup::bootbeep() {
#ifdef POWER_BUZZER_PIN
#ifdef SHARED_5VOLT_ON
digitalWrite(SHARED_5VOLT_PIN, HIGH);
#endif
digitalWrite(POWER_BUZZER_PIN, HIGH);
delay(250);
digitalWrite(POWER_BUZZER_PIN, LOW);
delay(250);
digitalWrite(POWER_BUZZER_PIN, HIGH);
delay(250);
digitalWrite(POWER_BUZZER_PIN, LOW);
#endif
}
void WII5Setup::shutdownbeep() {
#ifdef POWER_BUZZER_PIN
#ifdef SHARED_5VOLT_ON
digitalWrite(SHARED_5VOLT_PIN, HIGH);
#endif
delay(250);
digitalWrite(POWER_BUZZER_PIN, HIGH);
delay(125);
digitalWrite(POWER_BUZZER_PIN, LOW);
delay(125);
digitalWrite(POWER_BUZZER_PIN, HIGH);
delay(125);
digitalWrite(POWER_BUZZER_PIN, LOW);
delay(125);
digitalWrite(POWER_BUZZER_PIN, HIGH);
delay(125);
digitalWrite(POWER_BUZZER_PIN, LOW);
delay(125);
digitalWrite(POWER_BUZZER_PIN, HIGH);
delay(125);
digitalWrite(POWER_BUZZER_PIN, LOW);
delay(125);
digitalWrite(POWER_BUZZER_PIN, HIGH);
delay(125);
digitalWrite(POWER_BUZZER_PIN, LOW);
delay(125);
digitalWrite(POWER_BUZZER_PIN, HIGH);
delay(125);
digitalWrite(POWER_BUZZER_PIN, LOW);
delay(125);
digitalWrite(POWER_BUZZER_PIN, HIGH);
delay(125);
digitalWrite(POWER_BUZZER_PIN, LOW);
delay(125);
digitalWrite(POWER_BUZZER_PIN, HIGH);
delay(125);
digitalWrite(POWER_BUZZER_PIN, LOW);
#endif
}
void WII5Setup::begin() {
// TODO 2024 - Check state of button 2 is on
// if so set special Maths flag - alwayson?
// If button 2 goes off at any point or button1 on, then always off is forever off?
setupConsole();
setupPower();
setupIO();
setupNetwork();
setupWII5();
#ifdef WII5_STORAGE_SDBLOCK
sdBlock.debug = false;
sdBlock.stream = &Serial;
sdBlock.begin(wii5Config.getDeviceId());
#endif
}
#ifdef WII5_DEBUG_SERIAL
void cb(uint8_t l, char *buf) {
switch(l) {
case LOG_INFO:
SerialDebug.print(F("LOG:INFO:"));
break;
case LOG_WARN:
SerialDebug.print(F("LOG:WARN:"));
break;
case LOG_ERROR:
SerialDebug.print(F("LOG:ERROR:"));
break;
case LOG_DEBUG:
SerialDebug.print(F("LOG:DEBUG:"));
break;
case LOG_FATAL:
SerialDebug.print(F("LOG:FATAL:"));
break;
default:
SerialDebug.print(F("LOG:?:"));
break;
};
SerialDebug.print(buf);
SerialDebug.println();
}
#endif
void WII5Setup::setupPower() {
if (_setupPower) return;
_setupPower = true;
}
void WII5Setup::setupConsole() {
if (_setupConsole) return;
_setupConsole = true;
SerialConsole.begin(SerialConsole_Baud);
// Hard coded BAD - Where to put this for reuse in WII5 buoys
// Maybe a macro in the Board file?
console.begin(wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT, wii5BufferConsoleCmd, WII5_BUFFER_CONSOLE_CMD);
// TODO ? console.enableNmea();
// Here we add the main console as an OUTPUT only
#if defined(WII5_DEBUG_SERIAL) && defined(WII5_DEBUG_SERIAL_CONSOLE)
console.add(&SerialConsole, CONSOLE_DIRECTION_OUTPUT);
#else
console.add(&SerialConsole);
#endif
// NOTE: We are using Iridium as it is currently only Binary code we need
#ifdef WII5_GPS
console.setBinaryBuffer(wii5BinaryIridium, WII5_IRIDIUM_BIN_MAX);
#endif
#ifdef WII5_DEBUG_SERIAL
SerialDebug.begin(WII5_DEBUG_SERIAL_BAUD);
#ifdef WII5_DEBUG_SERIAL_CONSOLE
console.add(&SerialDebug, CONSOLE_DIRECTION_BOTH);
#else
console.setCallbackFunction(cb);
console.setCallbackLevel(LOG_FATAL);
#endif
#endif
#ifdef WII5_STATUS_SERIAL
SerialStatus.begin(WII5_STATUS_SERIAL_BAUD);
#endif
#ifdef WII5_MATHS_SERIAL
SerialMaths.begin(WII5_MATHS_SERIAL_BAUD);
console.add(&SerialMaths, CONSOLE_DIRECTION_BOTH);
#endif
console.setLevel(LOG_INFO);
}
void WII5Setup::setupNetwork() {
if (_setupNetwork) return;
_setupNetwork = true;
#ifdef WII5_RADIO_LORA
wii5RadioLoRa.begin();
#endif
}
void WII5Setup::setupIO() {
if (_setupIO) return;
_setupIO = true;
// IO - LED, Buttons et al
sh3dNodeIO.begin(LED_1, LED_2, LED_3, LED_4, BUTTON_1, BUTTON_2, BUTTON_3, BUTTON_4);
// SET LED to Fast Flash
#if LED_1>0
sh3dNodeIO.led1Set( LED_SHORT );
sh3dNodeIO.led1SetDefault(LED_SHORT);
sh3dNodeIO.led1SetTimeout(300000);
#endif
#if LED_2>0
#ifdef POWER_BUZZER_PIN
// Off after 5 seconds
sh3dNodeIO.led2Set( LED_OFF );
sh3dNodeIO.led2SetDefault(LED_OFF);
sh3dNodeIO.led2SetTimeout(10000);
#else
sh3dNodeIO.led2Set( LED_SLOW );
sh3dNodeIO.led2SetDefault(LED_SLOW);
sh3dNodeIO.led2SetTimeout(10000);
#endif
#endif
#if LED_3>0
sh3dNodeIO.led3Set( LED_SLOW );
sh3dNodeIO.led3SetDefault(LED_SLOW);
sh3dNodeIO.led3SetTimeout(10000);
#endif
#if LED_4>0
sh3dNodeIO.led4Set( LED_SLOW );
sh3dNodeIO.led4SetDefault(LED_SLOW);
sh3dNodeIO.led4SetTimeout(10000);
#endif
}
void WII5Setup::setupWII5() {
if (_setupWII5) return;
_setupWII5 = true;
wii5Config.begin();
#ifdef WII5_RTC
wii5RTC.begin();
#endif
wii5Commands.begin();
wii5Battery.begin();
#ifdef WII5_GPS
wii5Gps.begin();
#endif
#ifdef WII5_COMMS_IRIDIUM
wii5Iridium.begin();
#endif
wii5Maths.begin();
// wii5MPU9250.begin();
wii5Weather_18B20.begin();
#ifdef WII5_IMU_SPARTON
wii5Sparton.begin();
#endif
wii5Communications.begin();
}
void WII5Setup::sleepBefore(bool MathsON) {
// Tell devices they are really off.
wii5Sparton.stop(true);
wii5Iridium.stop(true);
wii5Gps.off();
sdBlock.cardForceClose();
#ifdef WII5_WDT_INTERNAL
wdt_disable();
#endif
// Turn off Shared, and Maths CPU
pinMode(SHARED_5VOLT_PIN, OUTPUT);
pinMode(POWER_MATHS_PIN, OUTPUT);
if (MathsON) {
digitalWrite(SHARED_5VOLT_PIN, HIGH);
digitalWrite(POWER_MATHS_PIN, HIGH);
}
else {
digitalWrite(SHARED_5VOLT_PIN, LOW);
digitalWrite(POWER_MATHS_PIN, LOW);
}
// Storage SD Card
#if POWER_STORAGE_1_PIN>0
pinMode(POWER_STORAGE_1_PIN, OUTPUT);
digitalWrite(POWER_STORAGE_1_PIN, LOW);
#endif
#if POWER_STORAGE_2_PIN>0
pinMode(POWER_STORAGE_2_PIN, OUTPUT);
digitalWrite(POWER_STORAGE_2_PIN, LOW);
#endif
// GPS, Radio, MPU and Strobe
pinMode(POWER_GPS_PIN, OUTPUT);
digitalWrite(POWER_GPS_PIN, LOW);
pinMode(POWER_COMMS_PIN, OUTPUT);
digitalWrite(POWER_COMMS_PIN, LOW);
pinMode(POWER_RADIO_PIN, OUTPUT);
digitalWrite(POWER_RADIO_PIN, LOW);
pinMode(POWER_STROBE1_ON, OUTPUT);
digitalWrite(POWER_STROBE1_ON, LOW);
// Misc Controlling lines
pinMode(STORAGE_SD1_MATHS_PIN, INPUT);
pinMode(STORAGE_SD2_MATHS_PIN, INPUT);
pinMode(SPARTON_RESET, INPUT);
pinMode(RADIO_CS, INPUT);
pinMode(STORAGE_CS, INPUT);
// LEDs 2024 Updated to OUTPUT LOW
#if LED_1>0
pinMode(LED_1, OUTPUT);
digitalWrite(LED_1, LOW);
#endif
#if LED_2>0
pinMode(LED_2, OUTPUT);
digitalWrite(LED_2, LOW);
#endif
#if LED_3>0
pinMode(LED_3, OUTPUT);
#endif
// Weather
pinMode(POWER_WEATHER_18B20_PIN, INPUT);
pinMode(POWER_WEATHER_PIN, INPUT);
}
void WII5Setup::sleepAfter() {
// Really make sure still output
pinMode(SHARED_5VOLT_PIN, OUTPUT);
pinMode(POWER_MATHS_PIN, OUTPUT);
// Storage SD Card
#if POWER_STORAGE_1_PIN>0
pinMode(POWER_STORAGE_1_PIN, OUTPUT);
digitalWrite(POWER_STORAGE_1_PIN, HIGH);
#endif
#if POWER_STORAGE_2_PIN>0
pinMode(POWER_STORAGE_2_PIN, OUTPUT);
#endif
// GPS, Radio, MPU and Strobe
pinMode(POWER_GPS_PIN, OUTPUT);
pinMode(POWER_COMMS_PIN, OUTPUT);
pinMode(POWER_RADIO_PIN, OUTPUT);
pinMode(POWER_STROBE1_ON, OUTPUT);
// Misc Controlling lines
pinMode(STORAGE_SD1_MATHS_PIN, OUTPUT);
pinMode(STORAGE_SD2_MATHS_PIN, OUTPUT);
pinMode(SPARTON_RESET, OUTPUT);
pinMode(RADIO_CS, OUTPUT);
pinMode(STORAGE_CS, OUTPUT);
// LEDs
pinMode(LED_1, OUTPUT);
#if LED_2>0
pinMode(LED_2, OUTPUT);
#endif
#if LED_3>0
pinMode(LED_3, OUTPUT);
#endif
// Weather
pinMode(POWER_WEATHER_18B20_PIN, OUTPUT);
pinMode(POWER_WEATHER_PIN, OUTPUT);
#ifdef WII5_WDT_INTERNAL
wdt_enable(WDTO_8S);
#endif
}
WII5Setup wii5Setup;
+42
View File
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5Setup.h
* @brief Boot-time hardware bring-up: pin defaults, peripherals, and serial ports.
*/
#ifndef WII5Setup_h
#define WII5Setup_h
#include <Arduino.h>
#include <WII5.h>
#include <TimeLib.h>
class WII5Setup : public WII5 {
public:
virtual WII5_CONTROLLERS controllerId() {return WII5CONTROLLER_SETUP;}
void begin();
void beginSafe();
void bootbeep();
void shutdownbeep();
void setupPower();
void setupConsole();
void setupNetwork();
void setupIO();
void setupWII5();
void sleepBefore(bool mathsOn = false);
void sleepAfter();
protected:
bool _setupPower;
bool _setupIO;
bool _setupWII5;
bool _setupConsole;
bool _setupNetwork;
};
extern WII5Setup wii5Setup;
#endif
+142
View File
@@ -0,0 +1,142 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5Sh3dConfig.cpp
* @brief WII5 adapter for the Sh3d shared configuration layer.
*/
/*
TODO 2024 - Sh3dConfig vs Config
*/
#include <Arduino.h>
#include "WII5Sh3dConfig.h"
#include "WII5Sh3dConsole.h"
#include <EEPROMex.h>
/* ----------------------------------------------------------------------
*/
// TODO Move this to Start Up
void WII5Sh3dConfig::begin() {
initialized = true;
readConfig();
updateRunCount();
};
void WII5Sh3dConfig::readConfig() {
EEPROM.readBlock(configStart, dataConfig);
if (
(dataConfig.configType == CONFIG_TYPE)
&& (dataConfig.configVersion == CONFIG_VERSION)
) {
return;
}
console.log(LOG_ERROR, F("!EEPROM old=%d,%d. new=%d,%d"),
dataConfig.configType,
dataConfig.configVersion,
CONFIG_TYPE,
CONFIG_VERSION
);
memset(&dataConfig, 0, sizeof(dataConfig));
dataConfig.configType = CONFIG_TYPE;
dataConfig.configVersion = CONFIG_VERSION;
updateConfig();
}
void WII5Sh3dConfig::updateConfig() {
EEPROM.updateBlock(configStart, dataConfig);
}
SH3D_TYPE_UNIQUEID WII5Sh3dConfig::getNodeId() {
if ( (dataConfig.nodeId < 10000) || (dataConfig.nodeId > 32000) )
console.safeLog(LOG_ERROR, F("Invalid nodeId"));
return dataConfig.nodeId;
}
// TODO Ways of getting id etc?
SH3D_TYPE_UNIQUEID WII5Sh3dConfig::setNodeId(SH3D_TYPE_UNIQUEID newId) {
dataConfig.nodeId = newId;
updateConfig();
}
SH3D_TYPE_COUNTER WII5Sh3dConfig::getRunCount() {
return dataConfig.runCount;
}
SH3D_TYPE_COUNTER WII5Sh3dConfig::updateRunCount() {
dataConfig.runCount++;
updateConfig();
return dataConfig.runCount;
}
SH3D_TYPE_COUNTER WII5Sh3dConfig::clearRunCount(SH3D_TYPE_COUNTER setId) {
dataConfig.runCount = setId;
updateConfig();
return dataConfig.runCount;
}
SH3D_TYPE_COUNTER WII5Sh3dConfig::getRecordCount() {
return dataConfig.recordCount;
}
SH3D_TYPE_COUNTER WII5Sh3dConfig::updateRecordCount() {
dataConfig.recordCount++;
updateConfig();
return dataConfig.recordCount;
}
SH3D_TYPE_COUNTER WII5Sh3dConfig::clearRecordCount(SH3D_TYPE_COUNTER setId) {
dataConfig.recordCount = setId;
updateConfig();
return dataConfig.recordCount;
}
SH3D_TYPE_COUNTER WII5Sh3dConfig::getStorageLogLast() {
return dataConfig.storageLogLast;
}
SH3D_TYPE_COUNTER WII5Sh3dConfig::updateStorageLogLast() {
dataConfig.storageLogLast++;
updateConfig();
return dataConfig.storageLogLast;
}
SH3D_TYPE_COUNTER WII5Sh3dConfig::clearStorageLogLast(SH3D_TYPE_COUNTER setId) {
dataConfig.storageLogLast = setId;
updateConfig();
return dataConfig.storageLogLast;
}
SH3D_TYPE_COUNTER WII5Sh3dConfig::getStorageSensorLast() {
return dataConfig.storageSensorLast;
}
SH3D_TYPE_COUNTER WII5Sh3dConfig::updateStorageSensorLast() {
dataConfig.storageSensorLast++;
updateConfig();
return dataConfig.storageSensorLast;
}
SH3D_TYPE_COUNTER WII5Sh3dConfig::clearStorageSensorLast(SH3D_TYPE_COUNTER setId) {
dataConfig.storageSensorLast = setId;
updateConfig();
return dataConfig.storageSensorLast;
}
uint16_t sizeExtra() {
// TODO return extraLen;
return extraLen;
}
void readExtra(char *data, uint16_t len, uint16_t offset) {
// TODO EEPROM.readBlock(data, extraStart + offset, len);
}
void updateExtra(char *data, uint16_t len, uint16_t offset) {
// TODO EEPROM.updateData(data, extraStart + offset, len);
// EEPROM.updateBlock(extraStart, dataConfig);
}
WII5Sh3dConfig sh3dNodeConfig;
+127
View File
@@ -0,0 +1,127 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5Sh3dConfig.h
* @brief WII5 adapter for the Sh3d shared configuration layer.
*/
#ifndef WII5Sh3dConfig_h
#define WII5Sh3dConfig_h
#include <Arduino.h> //assumes Arduino IDE v1.0 or greater
#include <Arduino.h> //assumes Arduino IDE v1.0 or greater
#include <EEPROMex.h>
#include <WII5Data.h>
/*
Features:
* Automatic configuration depending on hardware:
* Size of EEPROM
* TODO
* Automatic Versioning
* Automatic counters
* EEPROM protection - by alternative locations
* Ability to extend
* Passing in one or more STRUCTS for the unused areas
* Helpers, like counters
*/
// ==============================================================================
// Address and Sizes
// ==============================================================================
// TODO Should be variable, this needs lots of work
#define totalLen 512
#define totalStart 0
#define configStart 0
#define configLen 128
#define extraStart 128
#define extraLen 384
// ==============================================================================
// Signature (experimenting)
// ==============================================================================
// TODO HARD CODED FOR NOW
#define CONFIG_TYPE 114
#define CONFIG_VERSION 12
// ==============================================================================
// Main Struct
// ==============================================================================
typedef struct {
// Internal - config data - might need more?
SH3D_TYPE_PACKET_TYPE configType; // Config Type
SH3D_TYPE_PACKET_VERSION configVersion; // Config Versio
// Device ID
SH3D_TYPE_UNIQUEID nodeId;
// Counters - To be moved around
SH3D_TYPE_COUNTER runCount; // Run ID - New ID each time device is booted
SH3D_TYPE_COUNTER recordCount; // Records, likes sesnsor data
// Some common and Basic configurables about this device
// TODO how often - eg. do we want to do it every minute, every hour, daily, etc
// Hard coded for Storage System - needs work
SH3D_TYPE_COUNTER storageLogLast;
SH3D_TYPE_COUNTER storageSensorLast;
} typeConfig;
class WII5Sh3dConfig {
public:
void begin( );
bool initialized = false;
// Do reads
void readConfig();
void updateConfig();
SH3D_TYPE_UNIQUEID getNodeId();
SH3D_TYPE_UNIQUEID setNodeId(SH3D_TYPE_UNIQUEID newId);
SH3D_TYPE_COUNTER getRunCount();
SH3D_TYPE_COUNTER updateRunCount();
SH3D_TYPE_COUNTER clearRunCount(SH3D_TYPE_COUNTER setId = 0);
// Record ID for Sensors
SH3D_TYPE_COUNTER getRecordCount();
SH3D_TYPE_COUNTER updateRecordCount();
SH3D_TYPE_COUNTER clearRecordCount(SH3D_TYPE_COUNTER setId = 0);
// Storage
SH3D_TYPE_COUNTER getStorageLogLast();
SH3D_TYPE_COUNTER updateStorageLogLast();
SH3D_TYPE_COUNTER clearStorageLogLast(SH3D_TYPE_COUNTER setId = 0);
SH3D_TYPE_COUNTER getStorageSensorLast();
SH3D_TYPE_COUNTER updateStorageSensorLast();
SH3D_TYPE_COUNTER clearStorageSensorLast(SH3D_TYPE_COUNTER setId = 0);
// Not sure I like these names, ``
uint16_t sizeExtra();
uint16_t startExtra();
void readExtra(char *data, uint16_t len, uint16_t offset);
void updateExtra(char *data, uint16_t len, uint16_t offset);
// Storage Location etc - as we can't errase a whole device
bool getFlashErased();
bool setFlashErased(bool in);
protected:
typeConfig dataConfig; // Main Config Data
};
extern WII5Sh3dConfig sh3dNodeConfig;
#endif
+658
View File
@@ -0,0 +1,658 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5Sh3dConsole.cpp
* @brief Console abstraction: routes printf/log to one or more serial streams.
*/
/*
Console code
TODO: 2024 Check this is the only copy of console and simplify
Map console to multiple outputs. Use Printf and a buffer. Automatically
support PROGMEM (Flash memory), String object and char pointers.
* TODO This should be callbacks
* NMEA Strings updated - do This
* Etc
* TODO Binary is almost certainly out by one start/end or both
* TODO Write test code that sends streams:
* NMEA - obvious one
* @CSV
* !Binary
Console console();
void setup() {
SerialConsole.begin(115200);
console.begin();
console.add(&SerialConsole);
console.printf(F("Hello world"));
* Rename to something better than console
* Release open source on github Sh3d
* Add print from SD card - ability to get and print data form a SD card out
* Add print from PROGMEM arrays
* Add print from SPIFlash
NOTE on NMEA devices
* NMEA is always processed from incoming data on the consoles
* If a second "TinyGPS++" is required, you can addNmea stream instead
* OR even better, just add a stream as add(&HWSerial, CONSOLE_DIRECTION_INPUT);
Restructure to simpler methods:
* BEFORE
= Sketch size 15378
* AFTER
= Sketch size 15046 (saved 340 bytes)
Restructure to error numbers:
* Sh3dNodeErrors
* console.error(n)
* console.safeError(n)
* uint16_t - 01..65 modules split, 1000 message per module
* Could we add an optional long?
*/
#include "Arduino.h"
#include <WII5Sh3dConsole.h>
#include <TimeLib.h>
#include <MemoryFree.h>
// printf support
// Just a string
void WII5Sh3dConsole::print(char *out) {
for(byte i = 0; i < CONSOLE_STREAMS; i++) {
if (
streams[i]
&& (
(streamsDirection[i] == CONSOLE_DIRECTION_BOTH)
|| (streamsDirection[i] == CONSOLE_DIRECTION_OUTPUT)
)
) {
streams[i]->print(out);
#ifdef CONSOLE_DEBUG_FLUSH
streams[i]->flush();
#endif
}
}
}
// sprintf - STREAM printf
void WII5Sh3dConsole::sprintf(Stream *s, const __FlashStringHelper *out, ... ) {
if (!check()) return;
va_list argptr;
va_start(argptr, out);
VSNPRINTF(printBuffer, bufferMax, (const char *)out, argptr);
va_end(argptr);
s->print(printBuffer);
}
// printf support
void WII5Sh3dConsole::printf(char* out, ...) {
if (!check()) return;
va_list argptr;
va_start(argptr, out);
vsnprintf(printBuffer, bufferMax, out, argptr);
va_end(argptr);
print(printBuffer);
}
// Convert FLASH String to String
void WII5Sh3dConsole::printf(const __FlashStringHelper *out, ... ){
if (!check()) return;
va_list argptr;
va_start(argptr, out);
VSNPRINTF(printBuffer, bufferMax, (const char *)out, argptr);
va_end(argptr);
print(printBuffer);
}
// TODO Move to Sh3dNodeErrors
void WII5Sh3dConsole::printPrefix(uint8_t level) {
switch(level) {
case LOG_INFO:
printf(F(CONSOLE_PRE_INFO));
break;
case LOG_WARN:
printf(F(CONSOLE_PRE_WARN));
break;
case LOG_ERROR:
printf(F(CONSOLE_PRE_ERROR));
break;
case LOG_DEBUG:
printf(F(CONSOLE_PRE_DEBUG));
break;
case LOG_FATAL:
printf(F(CONSOLE_PRE_FATAL));
break;
default:
// Do we need unknown?
break;
}
printDateTime();
printf(F(CONSOLE_POST_TIME));
}
// printf support
void WII5Sh3dConsole::log(uint8_t l, char* out, ...) {
if (level < l) return;
if (!check()) return;
printPrefix(l);
va_list argptr;
va_start(argptr, out);
vsnprintf(printBuffer, bufferMax, out, argptr);
va_end(argptr);
print(printBuffer);
printNewLine();
}
// Convert FLASH String to String
void WII5Sh3dConsole::log(uint8_t l, const __FlashStringHelper *out, ... ){
if (level < l) return;
if (!check()) return;
printPrefix(l);
va_list argptr;
va_start(argptr, out);
VSNPRINTF(printBuffer, bufferMax, (const char *)out, argptr);
va_end(argptr);
print(printBuffer);
printNewLine();
}
// TODO - Compile time option to remove unused entries - e.g We may not support String
void WII5Sh3dConsole::safeLog(uint8_t l, char* out, ...) {
if (!check()) return;
printPrefix(l);
va_list argptr;
va_start(argptr, out);
vsnprintf(printBuffer, bufferMax, out, argptr);
va_end(argptr);
print(printBuffer);
printNewLine();
flush();
}
void WII5Sh3dConsole::safeLog(uint8_t l, const __FlashStringHelper *out, ... ){
if (!check()) return;
printPrefix(l);
va_list argptr;
va_start(argptr, out);
VSNPRINTF(printBuffer, bufferMax, (const char *)out, argptr);
va_end(argptr);
print(printBuffer);
printNewLine();
flush();
}
void WII5Sh3dConsole::printDateTime(uint32_t t) {
if (t == 0) {
if (!displayDateTime) return; // Automatic mode
#ifdef _Time_h
t = now();
#endif
}
// uint16_t - date.year,
// uint8_t - date.month, date.day,
// uint8_t - time.hour, time.minute, time.second
sprintf_P(printBuffer, PSTR("%04d-%02d-%02dT%02d:%02d:%02d"),
year(t),
int(month(t)),
int(day(t)),
int(hour(t)),
int(minute(t)),
int(second(t))
);
print(printBuffer);
}
void WII5Sh3dConsole::loop() {
// Reset cmd
if (cmd) {
cmdCount = 0;
csv_count = 0;
csv_last = 0;
cmdType = 0;
cmd = '\0';
}
// Reset Binary
if (binAvailable) {
binAvailable = false;
binReceiveCount = 0;
}
for(byte i = 0; i < CONSOLE_STREAMS; i++) {
// NOTE - while important, but can be too much - limit?
if (
(streamsDirection[i] == CONSOLE_DIRECTION_BOTH)
|| (streamsDirection[i] == CONSOLE_DIRECTION_INPUT)
) {
while (streams[i] && streams[i]->available())
processCmd(streams[i]->read());
}
}
}
void WII5Sh3dConsole::processCsv() {
// TODO check for csv_count max and cmdCount max Etc
// Point to the start of the last strinb before the comma
csv_str[csv_count] = &cmdBuffer[csv_last];
// Null the ','
cmdBuffer[cmdCount] = '\0';
// Parse to ingeger
csv_uint32[csv_count] = atol(csv_str[csv_count]);
// Next character
csv_last = cmdCount + 1;
// console.log(LOG_DEBUG, F("CSV found comma last=%d, count=%d, str=%s, lu=%lu"), int(csv_last), int(csv_count), csv_str[csv_count], csv_uint32[csv_count]);
csv_count++;
}
// processCmd
void WII5Sh3dConsole::processCmd(char in) {
// DO Timeout
if ( (cmdCount > 0) && (waitInput > CONSOLE_TIMEOUT) ) {
printf(F("# CONSOLE: Timeout for new Serial Command. Max time = %d seconds"), int(CONSOLE_TIMEOUT / 1000));
printNewLine();
cmdCount = 0;
binAvailable = false;
binReceiveCount = 0;
}
// BINARY Data mode - keep here
if (binEnable && (cmdType == CONSOLE_TYPE_BINDATA_ID)) {
if (cmdCount == 0) {
printf(F("# FATAL ERROR - Must not be in BINDATA mode with no count, Invalid mode")); printNewLine();
cmdCount = 0;
cmdType = 0;
}
binBuffer[binReceiveCount] = in;
binReceiveCount++;
if (binReceiveCount >= binReceiveExpect) {
// COMPLETE !
cmdType = 0;
cmdCount = 0;
binAvailable = true;
return;
}
if (binReceiveCount >= binMax) {
// TODO This should NOT be allowed to happen - since we check below
safeLog(LOG_FATAL, F("CONSOLE: Binary incoming data too large"));
cmdCount = 0;
cmd = '\0';
val = 0;
csv_last = 0;
csv_count = 0;
binReceiveCount = 0;
}
// Rest of function is TEXT processing - leave now
return;
}
// TEXT Mode - NOT a new line, keep data
if ( (in != '\n') && (in != '\r') ) {
// First character - see if it is $
if ( (cmdCount == 0) ) {
// Reset timeout
waitInput = 0;
// $ = NMEA = 1
if (in == CONSOLE_TYPE_NMEA_C0) {
cmdType = CONSOLE_TYPE_NMEA_ID;
}
// CSV
else if (in == CONSOLE_TYPE_CSV_C0) {
csv_last = 1; // Skip the "@"
// Start all at Zero
for (csv_count = 0; csv_count < CONSOLE_TYPE_CSV_LEN; csv_count++) {
csv_uint32[csv_count] = 0;
}
csv_count = 0;
cmdType = CONSOLE_TYPE_CSV_ID;
// console.log(LOG_DEBUG, F("CSV START last=%d, count=%d"), int(csv_last), int(csv_count));
}
// BINARY
else if (binEnable && (in == CONSOLE_TYPE_BIN_C0)) {
cmdType = CONSOLE_TYPE_BIN_ID;
binReceiveCount = 0;
binReceiveExpect = 0;
binReceiveCRC = 0; // Optional?
}
// Else assume standard ending in ";"
else {
cmdType = CONSOLE_TYPE_OTHER_ID;
}
}
// Keep cmdBuffer if not NMEA
if (
// Keep Other
cmdType == CONSOLE_TYPE_OTHER_ID // Keep for external use
|| cmdType == CONSOLE_TYPE_CSV_ID // Keep and process on the fly
|| cmdType == CONSOLE_TYPE_NMEA_ID // NMEA
|| cmdType == CONSOLE_TYPE_BIN_ID
) {
// TODO test out by one
if (cmdCount < (consoleMax - 1)) {
cmdBuffer[cmdCount] = in;
// TODO ',' should be #define in case we use : or tab or something
if (cmdType == CONSOLE_TYPE_CSV_ID && in == ',') {
processCsv();
}
cmdCount++;
cmdBuffer[cmdCount] = '\0';
}
}
}
// TEXT MODE - New Line - process - NOTE: This may get called twice (\r then \n)
// - Once leaving this, the only way for a new command to start
// is to set cmdCount = 0
else {
// Do nothing with this
if (cmdType == CONSOLE_TYPE_IGNORE_ID) {
}
// Blank everything
else if (cmdType == CONSOLE_TYPE_DESTROY_ID) {
cmdCount = 0;
cmd = '\0';
val = 0;
csv_last = 0;
csv_count = 0;
}
// Binary
else if (binEnable && (cmdType == CONSOLE_TYPE_BIN_ID)) {
if (cmdCount < 2) {
printf(F("# CONSOLE: Error, no size requests for binary data")); printNewLine();
printf(F("BINARY ERROR: No size")); printNewLine();
cmdCount = 0;
cmdType = 0;
}
else {
val = atol(&cmdBuffer[1]);
if (val >= binMax) {
printf(F("# CONSOLE: Request Binary larger than buffer")); printNewLine();
printf(F("BINARY ERROR: Too large")); printNewLine();
cmd = '\0';
cmdType = 0;
cmdCount = 0;
}
else {
printf(F("# CONSOLE: Request Binary size=%d"), binReceiveExpect); printNewLine();
printf(F("BINARY OK: Ready to send")); printNewLine();
binReceiveExpect = val;
binReceiveCount = 0;
cmdType = CONSOLE_TYPE_BINDATA_ID;
}
}
}
// Add optional ending, or even CRC
else if (cmdType == CONSOLE_TYPE_OTHER_ID) {
if (echo)
log(LOG_DEBUG, F("CONSOLE Receved: %s"), cmdBuffer);
// Check we have a buffe?
if (cmdCount >= (consoleMax - 1)) {
// printf(F("# CONSOLE overflow: cmd size - c=%d of %d\r\n"), cmdCount, consoleMax);
printf(F("# CONSOLE cmd overflow"));
printNewLine();
cmdCount = 0;
cmdType = CONSOLE_TYPE_IGNORE_ID;
}
else if (cmdBuffer[cmdCount - 1] != ';') {
printf(F("# CONSOLE: standard input must end in ; count=%d, last=%d"), cmdCount, (uint8_t)cmdBuffer[cmdCount =- 1]);
printNewLine();
cmdCount = 0;
cmdType = CONSOLE_TYPE_IGNORE_ID;
}
else {
cmd = cmdBuffer[0];
val = atol(&cmdBuffer[1]);
cmdType = CONSOLE_TYPE_IGNORE_ID;
}
}
else if ( cmdType == CONSOLE_TYPE_NMEA_ID ) {
// Ignore the NMEA - already processed
// TODO: Maybe mark the buffer as now a copy of the NMEA for passthrough?
}
else if ( cmdType == CONSOLE_TYPE_CSV_ID ) {
cmdCount++; // Spin on, so this will be like the "," where we add the null
processCsv();
cmd = CONSOLE_TYPE_CSV_C0;
cmdType = CONSOLE_TYPE_IGNORE_ID;
if (echo) {
printPrefix(LOG_DEBUG);
printf(F("CONSOLE Received (@): "));
for (i = 0; i < console.getCsvCount(); i++) {
printf(console.getCsvBuffer(i));
printf(",");
}
printNewLine();
}
}
}
}
bool WII5Sh3dConsole::available(){
return cmd != '\0';
}
// begin - using External Buffers
void WII5Sh3dConsole::begin(char* buf, uint16_t bufLen, char* cmdBuf, uint16_t cmdLen) {
// TODO
echo = true;
printBuffer = buf;
bufferMax = bufLen;
cmdBuffer = cmdBuf;
consoleMax = cmdLen;
cmdType = 0;
setDateTime();
}
// begin - Allocate the buffers and get things going
void WII5Sh3dConsole::begin(uint16_t bufLen, uint16_t cmdLen) {
if ( (bufLen + cmdLen) >= freeMemory() )
internalErrorNotEnoughMemory();
else {
printBuffer = (char*)malloc(bufLen);
if (printBuffer == NULL)
internalErrorNotEnoughMemory();
else
bufferMax = bufLen;
cmdBuffer = (char*)malloc(cmdLen);
if (cmdBuffer == NULL)
internalErrorNotEnoughMemory();
else
consoleMax = cmdLen;
cmdType = 0;
setDateTime();
}
}
void WII5Sh3dConsole::internalErrorNotEnoughMemory() {
SerialConsole.println(F("CONSOLE IntErr: failbuf"));
}
void WII5Sh3dConsole::internalErrorNoBufferStream() {
SerialConsole.println(F("CONSOLE IntErr: buf, stream"));
}
bool WII5Sh3dConsole::check() {
if (streamCount == 0) {
internalErrorNoBufferStream();
return false;
}
if (bufferMax == 0) {
internalErrorNoBufferStream();
return false;
}
return true;
}
void WII5Sh3dConsole::clearCommand() {
cmdCount = 0;
csv_count = 0;
csv_last = 0;
cmdType = 0;
cmd = '\0';
}
void WII5Sh3dConsole::clearCallback() {
callback_default = false;
callback = NULL;
}
void WII5Sh3dConsole::setCallbackFunction(CallbackFunction f) {
callback_default = false;
callback = f;
}
void WII5Sh3dConsole::setCallbackLevel(uint8_t l) {
callback_level = l;
}
uint8_t WII5Sh3dConsole::getCallbackLevel() {
return callback_level;
}
// Add a stream - currently very limited (0 NEITHER, 1 INPUT, 2 OUTPUT, 3 BOTH)
bool WII5Sh3dConsole::add(Stream *s, uint8_t direction) {
for(byte i = 0; i < CONSOLE_STREAMS; i++) {
if (streams[i] == s) {
return true;
}
else if (! streams[i]) {
streams[i] = s;
streamsDirection[i] = direction;
streamCount++;
return true;
}
}
// Internal Error !
return false;
}
bool WII5Sh3dConsole::remove(Stream *s) {
for(byte i = 0; i < CONSOLE_STREAMS; i++) {
if (streams[i] == s) {
streams[i] = NULL;
streamsDirection[i] = 0;
streamCount--;
return true;
}
}
return false;
}
void WII5Sh3dConsole::setLevel(byte l) {
level = l;
}
// Flush sending....no longer doing inputs
void WII5Sh3dConsole::flush() {
for(byte i = 0; i < CONSOLE_STREAMS; i++) {
if (streams[i]) {
streams[i]->flush();
}
}
}
void WII5Sh3dConsole::printNewLine() {
printf(F("\r\n"));
}
bool WII5Sh3dConsole::enableBinary() {
binEnable = true;
}
bool WII5Sh3dConsole::disableBinary() {
binEnable = false;
}
void WII5Sh3dConsole::setBinaryBuffer(char *buf, uint16_t len) {
binBuffer = buf;
binMax = len;
binEnable = true;
}
// Pretty Print Binary
#define PERLINE 10
void WII5Sh3dConsole::printBinary(char* buf, uint16_t len) {
printf(F("LEN = %d\r\n"), len);
uint16_t i = 0;
while (i < len) {
for (uint16_t x = 0; x < PERLINE; x++) {
if ( (i+x) < len) {
printf(F("0x%.2X "), (uint8_t)buf[i+x]);
}
else {
printf(F(" "), (int16_t)buf[i+x]);
}
}
printf(F(" |"));
for (uint16_t x = 0; x < PERLINE; x++) {
if (
((i+x) < len)
&& ((uint8_t)buf[i+x] >= 32)
&& ((uint8_t)buf[i+x] < 127)
) {
printBuffer[x] = buf[i+x];
}
else {
printBuffer[x] = ' ';
}
}
printBuffer[PERLINE] = '\0';
print(printBuffer);
printf(F("|\r\n"));
i = i + PERLINE;
}
printNewLine();
}
void WII5Sh3dConsole::printBits(void* buf, uint16_t len) {
for (uint16_t x = 0; x < len; x++) {
void* n = buf + (x/8);
printf(bitRead(*(uint8_t*)n, x % 8) ? "1" : "0");
}
printNewLine();
SerialConsole.print(F("BIN: "));
SerialConsole.print(*(uint16_t*)buf, BIN);
SerialConsole.println();
}
void WII5Sh3dConsole::setEcho(bool in) {
echo = in;
}
bool WII5Sh3dConsole::getEcho() {
return echo;
}
WII5Sh3dConsole console;
+302
View File
@@ -0,0 +1,302 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5Sh3dConsole.h
* @brief Console abstraction: routes printf/log to one or more serial streams.
*/
#ifndef WII5Sh3dConsole_h
#define WII5Sh3dConsole_h
#include <Arduino.h>
#include <TimeLib.h>
#include <elapsedMillis.h>
#include <WII5Data.h>
// TODO Default serial port?
#define SerialConsole Serial
// Enable debug flush, if you want regular flushing for crashes etc
#define CONSOLE_DEBUG
// #define CONSOLE_DEBUG_FLUSH
#define CONSOLE_STREAMS 2 // TODO - Can we allocate more if needed?
// Enable to allow Strings in the print lines instead of F() and ""
// #define CONSOLE_USE_STRINGS
// Default levels
#define LOG_NONE -1
#define LOG_FATAL 0
#define LOG_ERROR 1
#define LOG_WARN 2
#define LOG_INFO 3
#define LOG_DEBUG 4
#define LOG_ALL 5
#define CONSOLE_DIRECTION_NONE 0
#define CONSOLE_DIRECTION_INPUT 1
#define CONSOLE_DIRECTION_OUTPUT 2
#define CONSOLE_DIRECTION_BOTH 3
// Default sizex - overloaded in begin
#if defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284P__)
#define CONSOLE_BUFFER_SIZE 100
#define CONSOLE_COMMAND_SIZE 25
#define VSNPRINTF vsnprintf_P
#elif defined(CORE_TEENSY)
#define CONSOLE_BUFFER_SIZE 200
#define CONSOLE_COMMAND_SIZE 75
#define VSNPRINTF vsnprintf
#else
#define CONSOLE_BUFFER_SIZE 100
#define CONSOLE_COMMAND_SIZE 25
#define VSNPRINTF vsnprintf_P
#endif
#define CONSOLE_TYPE_NMEA_ID 1
#define CONSOLE_TYPE_NMEA_C0 '$'
#define CONSOLE_TYPE_NMEA_NAME "NMEA"
#define CONSOLE_TYPE_CSV_ID 2
#define CONSOLE_TYPE_CSV_C0 '@'
#define CONSOLE_TYPE_CSV_NAME "CSV"
#define CONSOLE_TYPE_CSV_LEN 10
#define CONSOLE_TYPE_BIN_ID 3
#define CONSOLE_TYPE_BIN_C0 '!'
#define CONSOLE_TYPE_BIN_NAME "Binary"
#define CONSOLE_TYPE_BINDATA_ID 4
#define CONSOLE_TYPE_BINDATA_NAME "BinaryData"
#define CONSOLE_TYPE_IGNORE_ID 97
#define CONSOLE_TYPE_DESTROY_ID 98
#define CONSOLE_TYPE_OTHER_ID 99
#define CONSOLE_TYPE_OTHER_C0 ''
#define CONSOLE_TYPE_OTHER_NAME "Standard"
// NOTE: Make sure all of these are wrapped in F() for prints
#define CONSOLE_PRE_INFO "# INFO: "
#define CONSOLE_PRE_WARN "# WARN: "
#define CONSOLE_PRE_ERROR "# ERROR: "
#define CONSOLE_PRE_DEBUG "# DEBUG: "
// Consider better name
#define CONSOLE_PRE_FATAL "# IMPORTANT/NOTSTORED: "
#define CONSOLE_POST_TIME " - "
typedef void (*CallbackFunction) (uint8_t l, char *buf);
// 10 seconds. Really 1 is enough, but slow type debug @ commands maybe
#define CONSOLE_TIMEOUT 30000
/**
* @brief Console abstraction over multiple Stream objects.
*
* Routes formatted output (printf, log) and parses incoming command frames
* across one or more Stream sources. Recognises three frame syntaxes:
* - NMEA ($-prefixed, CRC-checked)
* - CSV (@-prefixed, comma-separated)
* - Binary (!-prefixed, length+CRC framed)
*
* Most code in this firmware logs through `console` rather than calling
* `Serial.print` directly so that output goes wherever the board is
* configured for.
*/
class WII5Sh3dConsole {
public:
/** @brief One-time bring-up. Allocates internal print + command buffers. */
void begin(uint16_t bufSize = CONSOLE_BUFFER_SIZE, uint16_t cmdSize = CONSOLE_COMMAND_SIZE);
/** @brief One-time bring-up using caller-supplied buffers. */
void begin(char* buf, uint16_t bufSize, char* cmdBuf, uint16_t cmdSize);
/** @brief Add a Stream source to the console (typically a Serial port). */
bool add(Stream *s, uint8_t direction = CONSOLE_DIRECTION_BOTH);
/** @brief Remove a previously-added Stream. */
bool remove (Stream *s);
/** @brief Enable Binary frame parsing (caller must provide a buffer). */
bool enableBinary();
/** @brief Disable Binary frame parsing. */
bool disableBinary();
/** @brief Set the buffer used to receive Binary frames. */
void setBinaryBuffer(char *buf, uint16_t len);
/** @brief Discard any in-flight command. */
void clearCommand();
/** @brief Remove the registered logging callback. */
void clearCallback();
/** @brief Register a callback invoked on log output. */
void setCallbackFunction(CallbackFunction f);
/** @brief Use the default storage-callback (writes to the SD log). */
void setCallbackStorage();
/** @brief Only invoke the callback for messages at this level or above. */
void setCallbackLevel(uint8_t l);
/** @brief Current callback log-level threshold. */
uint8_t getCallbackLevel();
/** @brief Manually fire the callback for a given level. */
void doCallback(uint8_t l);
/** @brief Set the console log-level threshold. */
void setLevel (uint8_t in);
/** @brief Current console log-level threshold. */
int8_t getLevel() { return level; }
/** @brief Tick: read from streams, parse frames, run timeouts. */
void loop();
/** @brief Flush all output streams. */
void flush();
/** @brief True once a complete command frame has been received. */
bool available();
/** @brief First byte of the most recent "X;" standard-style command. */
byte getCommand() {return cmd;}
/** @brief Numeric argument from the most recent "X;" command. */
uint32_t getVal() {return val;}
/** @brief Raw command-buffer pointer. */
char* getBuffer() { return cmdBuffer; }
/** @brief Feed a byte into the standard-style command parser. */
void processCmd(char in);
/** @brief Finalize parsing of a CSV (@-prefixed) command frame. */
void processCsv();
/** @brief Number of CSV fields parsed in the current frame. */
uint8_t getCsvCount() {return csv_count;}
/** @brief CSV field `n` interpreted as uint32. */
uint32_t getCsvVal(uint8_t n) {return csv_uint32[n];}
/** @brief CSV field `n` as the original string. */
char* getCsvBuffer(uint8_t n) { return csv_str[n]; }
/** @brief Enable date-time prefix on log output. */
void setDateTime() {displayDateTime = true;}
/** @brief Disable date-time prefix on log output. */
void clearDateTime() {displayDateTime = false;}
/** @brief Print a formatted date-time prefix (current time if t==0). */
void printDateTime(uint32_t t = 0);
/** @brief Print a string to all output streams. */
void print(char *out);
/** @brief printf to a single stream rather than all of them. */
void sprintf(Stream *s, const __FlashStringHelper *out, ... );
/** @brief printf to all output streams (RAM format string). */
void printf(char* out, ...);
/** @brief printf to all output streams (PROGMEM format string). */
void printf(const __FlashStringHelper *out, ... );
#ifdef CONSOLE_USE_STRINGS
void printf(String& out, ...);
#endif
/** @brief Emit the level-prefix string ("# INFO: " etc) for a level. */
void printPrefix(uint8_t level);
/** @brief Log a message at level `l` (RAM format string). */
void log(uint8_t l, char* out, ...);
/** @brief Log a message at level `l` (PROGMEM format string). */
void log(uint8_t l, const __FlashStringHelper *out, ... );
#ifdef CONSOLE_USE_STRINGS
void log(uint8_t l, String& out, ...);
#endif
// Special handlers
// void logReduceNumStr(uint8_t l, uint16 num, const __FlashStringHelper *out, ... );
// void logReduceNum(uint8_t l, uint16 num);
void safeLog(uint8_t l, char* out, ...);
void safeLog(uint8_t l, const __FlashStringHelper *out, ... );
#ifdef CONSOLE_USE_STRINGS
void safeLog(uint8_t l, String& out, ...);
#endif
void nmeaSend(const __FlashStringHelper *out, ... );
void nmeaCRCSend();
void printNewLine();
char *printBuffer; // [CONSOLE_BUFFER_SIZE];
uint16_t bufferMax;
bool availableBin() { return binAvailable; }
char* getBinBuffer() { return binBuffer; }
uint16_t getBinLength() { return binReceiveCount; }
void printBinary(char* buf, uint16_t len);
void printBits(void* buf, uint16_t len);
WII5Sh3dConsole() {
level = LOG_ALL;
callback_level = LOG_NONE;
}
void setEcho(bool in);
bool getEcho();
protected:
bool echo;
void internalErrorNotEnoughMemory();
void internalErrorNoBufferStream();
bool check();
// Timeouts for new lines for safety
elapsedMillis waitInput;
CallbackFunction callback;
int8_t callback_level;
bool callback_default;
// Maximum number of streams?
Stream* streams[CONSOLE_STREAMS];
uint8_t streamsDirection[CONSOLE_STREAMS];
uint8_t streamCount = 0;
// Level of debugging ?
int8_t level;
bool displayDateTime;
// TODO Storage?
// func callback for storage ?
// Command data
uint8_t cmdType;
uint8_t cmdCount;
byte cmd;
uint32_t val;
// Big buffers - only created at start
// Noite - moved public temporary - char *printBuffer; // [CONSOLE_BUFFER_SIZE];
char *cmdBuffer; // [CONSOLE_COMMAND_SIZE];
char *csv_str[CONSOLE_TYPE_CSV_LEN];
uint32_t csv_uint32[CONSOLE_TYPE_CSV_LEN];
uint8_t csv_count; // How many CSV fields do we have so far
uint8_t csv_last; // What location in the buffer is it
// Binary Data external buffers, counts and status
bool binAvailable;
char *binBuffer;
uint16_t binMax;
bool binEnable;
uint16_t binReceiveCount;
uint16_t binReceiveExpect;
uint16_t binReceiveCRC;
// Actual allocated memory
// Note - moved public temporary - uint16_t bufferMax;
uint16_t consoleMax;
uint16_t i;
};
// TODO Better name?
extern WII5Sh3dConsole console;
#endif
+327
View File
@@ -0,0 +1,327 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5Sh3dIO.cpp
* @brief I/O abstraction: buttons, LEDs, and the IO loop tick.
*/
/*
TODO 2024 - Check this is only valid IO for Buttons and LEDs
*/
#include <Arduino.h> //assumes Arduino IDE v1.0 or greater
#include "WII5Sh3dIO.h"
#include "WII5Sh3dConsole.h"
#include <PushButton.h> // Include the PushButton library
#include <elapsedMillis.h>
/* ----------------------------------------------------------------------
*/
// TODO Can we use NUM_PINS
#define LED_MAX 100
#define BUTTON_MAX 100
void WII5Sh3dIO::begin(uint8_t led1, uint8_t led2, uint8_t btn1, uint8_t btn2) {
begin(led1, led2, 0, 0, btn1, btn2, 0, 0);
}
// Quick method to save buttons before begin
void WII5Sh3dIO::preButton(PushButton* btn1, PushButton* btn2, PushButton* btn3, PushButton* btn4) {
button1 = btn1;
button2 = btn2;
button3 = btn3;
button4 = btn4;
}
void WII5Sh3dIO::begin(uint8_t led1, uint8_t led2, uint8_t led3, uint8_t led4, uint8_t btn1, uint8_t btn2, uint8_t btn3, uint8_t btn4) {
console.log(LOG_DEBUG, F("LEDS: %d,%d,%d,%d BUTTONS: %d,%d,%d,%d"), int(led1), int(led2), int(led3), int(led4), int(btn1), int(btn2), int(btn3), int(btn4));
if (_begin)
return;
_begin = true;
if (led1 > 0 && led1 < LED_MAX) {
led1_pin = led1;
pinMode(led1_pin, OUTPUT);
}
if (led2 > 0 && led2 < LED_MAX) {
led2_pin = led2;
pinMode(led2_pin, OUTPUT);
}
if (led3 > 0 && led3 < LED_MAX) {
led3_pin = led2;
pinMode(led3_pin, OUTPUT);
}
if (led4 > 0 && led4 < LED_MAX) {
led4_pin = led2;
pinMode(led4_pin, OUTPUT);
}
if (btn1 > 0 && btn1 < BUTTON_MAX) {
pinMode(btn1, INPUT_PULLUP);
if (!button1)
button1 = new PushButton(btn1);
button1_pin = btn1;
// TODO
button1_activelow = true;
button1->setActiveLogic(LOW);
}
if (btn2 > 0 && btn2 < BUTTON_MAX) {
pinMode(btn2, INPUT_PULLUP);
if (!button2)
button2 = new PushButton(btn2);
button2->setActiveLogic(LOW);
button2_pin = btn2;
button2_activelow = true;
}
if (btn3 > 0 && btn3 < BUTTON_MAX) {
pinMode(btn3, INPUT_PULLUP);
if (!button3)
button3 = new PushButton(btn3);
button3_pin = btn3;
button3_activelow = true;
button3->setActiveLogic(LOW);
}
if (btn4 > 0 && btn4 < BUTTON_MAX) {
pinMode(btn4, INPUT_PULLUP);
if (!button4)
button4 = new PushButton(btn4);
button4_pin = btn4;
button4_activelow = true;
button4->setActiveLogic(LOW);
}
return;
}
void WII5Sh3dIO::loop() {
if (lastLedLoop > 100) {
if (led1_pin > 0) {
// Max time and not default mode
if ((led1_max > 0) && (led1_mode != led1_default) && (led1_timeon > led1_max))
led1Set(led1_default);
if (!updateLED(led1_pin, led1_mode, led1_state)) {
led1Set(led1_default);
}
led1_state++;
}
if (led2_pin > 0) {
if ((led2_max > 0) && (led2_mode != led2_default) && (led2_timeon > led2_max))
led2Set(led2_default);
if (!updateLED(led2_pin, led2_mode, led2_state)) {
led2Set(led2_default);
}
led2_state++;
}
if (led3_pin > 0) {
if ((led3_max > 0) && (led3_mode != led3_default) && (led3_timeon > led3_max))
led3Set(led3_default);
if (!updateLED(led3_pin, led3_mode, led3_state)) {
led3Set(led3_default);
}
led3_state++;
}
if (led4_pin > 0) {
if ((led4_max > 0) && (led4_mode != led4_default) && (led4_timeon > led4_max))
led4Set(led4_default);
if (!updateLED(led4_pin, led4_mode, led4_state)) {
led4Set(led4_default);
}
led4_state++;
}
lastLedLoop = 0;
}
if (button1)
button1->update();
if (button2)
button2->update();
if (button3)
button3->update();
if (button4)
button4->update();
}
// Return 0 - nothing, or button number, 1,2,3
// TODO - Improve the performance of this to be tiny, so it does not have to be run every singel 8 seconds
uint8_t WII5Sh3dIO::buttonSleepCheck() {
if (button1) {
// pinMode(button1_pin, button1_activelow ? INPUT_PULLUP : INPUT);
if (digitalRead(button1_pin) == (button1_activelow ? LOW : HIGH)) {
return 1;
}
}
if (button2) {
// pinMode(button2_pin, button2_activelow ? INPUT_PULLUP : INPUT);
if (digitalRead(button2_pin) == (button2_activelow ? LOW : HIGH)) {
return 2;
}
}
return 0;
}
// TODO - Consider adding automatic getting Temperature at timed internval (except for blocking issue)
// TODO - Consider adding ability to read ALL sensors, not just one
void WII5Sh3dIO::sleep() {
// TODO - Toun off 18B20
// TODO - Turn off GPS
// TODO - Turn off LED (which may mean make them float/pull high)
// TODO - Make sure the LEDs are actually off
// digitalWrite(LED1, HIGH);
// digitalWrite(LED2, HIGH);
}
void WII5Sh3dIO::wake() {
}
#define inverted false
// Called once per 100ms
bool WII5Sh3dIO::updateLED(uint8_t pin, uint8_t mode, uint8_t state) {
// console.log(LOG_INFO, F("Updating LED %d, %d, %d"), pin, mode, state);
switch (mode) {
case LED_OFF: // Off
digitalWrite(pin, inverted ? HIGH : LOW);
break;
case LED_ON: // On
digitalWrite(pin, inverted ? LOW : HIGH);
break;
case LED_SLOW: // Slow
if ((state % 4) == 0)
digitalWrite(pin, !digitalRead(pin));
break;
case LED_FAST: // Fast
if ((state % 2) == 0)
digitalWrite(pin, !digitalRead(pin));
break;
case LED_SHORT: // Short - on for a short period
if ((state % 6) == 0)
digitalWrite(pin, inverted ? LOW : HIGH);
else
digitalWrite(pin, inverted ? HIGH : LOW);
break;
case LED_DOUBLE: // Double - 2 short, long off
if ( ((state % 8) == 0) || ((state % 8) == 2) )
digitalWrite(pin, inverted ? LOW : HIGH);
else
digitalWrite(pin, inverted ? HIGH : LOW);
break;
case LED_TRIPPLE: // Trpple - 3 short, long off
if ( ((state % 12) == 0) || ((state % 12) == 2) || ((state % 12) == 4) )
digitalWrite(pin, inverted ? LOW : HIGH);
else
digitalWrite(pin, inverted ? HIGH : LOW);
break;
case LED_SHORT_LONG: // Long - very slow long on and off
if ((state % 12) == 0)
digitalWrite(pin, inverted ? LOW : HIGH);
else
digitalWrite(pin, inverted ? HIGH : LOW);
break;
case LED_DOUBLE_LONG:
if ( ((state % 16) == 0) || ((state % 16) == 2) )
digitalWrite(pin, inverted ? LOW : HIGH);
else
digitalWrite(pin, inverted ? HIGH : LOW);
break;
case LED_TRIPPLE_LONG:
if ( ((state % 20) == 0) || ((state % 20) == 2) || ((state % 20) == 4) )
digitalWrite(pin, inverted ? LOW : HIGH);
else
digitalWrite(pin, inverted ? HIGH : LOW);
break;
case LED_SHORT_GAP: // Long - very slow long on and off
if ((state % 12) == 0)
digitalWrite(pin, inverted ? LOW : HIGH);
else
digitalWrite(pin, inverted ? HIGH : LOW);
break;
case LED_DOUBLE_GAP:
if ( ((state % 48) == 0) || ((state % 48) == 2) )
digitalWrite(pin, inverted ? LOW : HIGH);
else
digitalWrite(pin, inverted ? HIGH : LOW);
break;
case LED_TRIPPLE_GAP:
if ( ((state % 50) == 0) || ((state % 50) == 2) || ((state % 50) == 4) )
digitalWrite(pin, inverted ? LOW : HIGH);
else
digitalWrite(pin, inverted ? HIGH : LOW);
break;
case LED_ONE:
if ((state % 12) == 0)
digitalWrite(pin, inverted ? LOW : HIGH);
else
digitalWrite(pin, inverted ? HIGH : LOW);
break;
case LED_TWO:
if ( ((state % 16) == 0) || ((state % 16) == 2) )
digitalWrite(pin, inverted ? LOW : HIGH);
else
digitalWrite(pin, inverted ? HIGH : LOW);
break;
case LED_THREE:
if ( ((state % 20) == 0) || ((state % 20) == 2) || ((state % 20) == 4) )
digitalWrite(pin, inverted ? LOW : HIGH);
else
digitalWrite(pin, inverted ? HIGH : LOW);
break;
case LED_FOUR:
// TODO Simplify
break;
case LED_FIVE:
break;
case LED_SIX:
break;
case LED_SEVEN:
break;
case LED_EIGHT:
break;
case LED_NINE:
break;
case LED_TEN:
break;
case LED_ONCE:
if (state < 10)
digitalWrite(pin, inverted ? LOW : HIGH);
else if (state < 20)
digitalWrite(pin, inverted ? HIGH : LOW);
else {
return false;
}
break;
// TODO seemed to be just one long beep
case LED_TWICE:
if (state < 10)
digitalWrite(pin, inverted ? LOW : HIGH);
else if (state < 20)
digitalWrite(pin, inverted ? HIGH : LOW);
else if (state < 30)
digitalWrite(pin, inverted ? LOW : HIGH);
else if (state < 40)
digitalWrite(pin, inverted ? HIGH : LOW);
else {
return false;
}
break;
}
return true;
}
WII5Sh3dIO sh3dNodeIO;
+225
View File
@@ -0,0 +1,225 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5Sh3dIO.h
* @brief I/O abstraction: buttons, LEDs, and the IO loop tick.
*/
#ifndef WII5Sh3dIO_h
#define WII5Sh3dIO_h
#include <Arduino.h> //assumes Arduino IDE v1.0 or greater
#include <WII5Data.h>
// Standard Hardware Locations
#ifdef ISMEGA
#ifndef SH3D_NODE_LED_1
#define SH3D_NODE_LED_1 15
#endif
#ifndef SH3D_NODE_LED_2
#define SH3D_NODE_LED_2 14
#endif
// does not work
// #elifdef SH3D_DEVICE_HANDHELD_1
// #define SH3D_NODE_LED_1 17
// #define SH3D_NODE_LED_2 16
#else
// #define LED1 9
// #define LED2 6
// TODO Talk2
#ifndef SH3D_NODE_LED_1
#define SH3D_NODE_LED_1 6
// T2_WPN_LED_1
#endif
#ifndef SH3D_NODE_LED_2
#define SH3D_NODE_LED_2 9
// T2_WPN_LED_2
#endif
#endif
// Moteino Mega = 16, 15
// Moteino Standard = X, Y
// Talk2 = T2_WPN_BTN_1, T2_WPN_BTN_2
#define SH3D_NODE_BUTTON_1 4
// T2_WPN_BTN_1
#define SH3D_NODE_BUTTON_2 5
// T2_WPN_BTN_2
// Temperature Sensor
// TODO Temp remove for Simon #define SH3D_NODE_18B20
//#ifdef ISMEGA
// #define SH3D_NODE_ONE_WIRE_BUS 24
//#else
// #define SH3D_NODE_ONE_WIRE_BUS 14
//#endif
#define SH3D_NODE_ONE_WIRE_BUS 16
#ifdef SH3D_NODE_18B20
#include <OneWire.h>
#include <DallasTemperature.h>
#endif
#include <elapsedMillis.h>
/*
Noticed: T2Led - looks like it might have better features.
*/
#include <PushButton.h>
// ENUM !!!
enum {
LED_OFF,
LED_ON,
LED_SLOW,
LED_FAST,
LED_SHORT,
LED_DOUBLE,
LED_TRIPPLE,
LED_SHORT_LONG,
LED_DOUBLE_LONG,
LED_TRIPPLE_LONG,
LED_SHORT_GAP,
LED_DOUBLE_GAP,
LED_TRIPPLE_GAP,
LED_ONE,
LED_TWO,
LED_THREE,
LED_FOUR,
LED_FIVE,
LED_SIX,
LED_SEVEN,
LED_EIGHT,
LED_NINE,
LED_TEN,
LED_ONCE,
LED_TWICE,
};
class WII5Sh3dIO {
public:
void preButton(PushButton* btn1 = NULL, PushButton* btn2 = NULL, PushButton* btn3 = NULL, PushButton* btn4 = NULL);
void begin(
uint8_t led1 = SH3D_NODE_LED_1,
uint8_t led2 = SH3D_NODE_LED_2,
uint8_t btn1 = SH3D_NODE_BUTTON_1,
uint8_t btn2 = SH3D_NODE_BUTTON_2
);
void begin(
uint8_t led1 = SH3D_NODE_LED_1,
uint8_t led2 = SH3D_NODE_LED_2,
uint8_t led3 = 0,
uint8_t led4 = 0,
uint8_t btn1 = SH3D_NODE_BUTTON_1,
uint8_t btn2 = SH3D_NODE_BUTTON_2,
uint8_t btn3 = 0,
uint8_t btn4 = 0
);
void loop();
void led1Set(uint8_t m) { led1_mode = m; led1_state = 0; led1_timeon = 0; }
void led1SetTimeout(uint32_t m) { led1_max = m; led1_timeon = 0; }
void led1SetDefault(uint8_t m) { led1_default = m; }
uint8_t led1Get() { return led1_mode; }
void led2Set(uint8_t m) { led2_mode = m; led2_state = 0; led2_timeon = 0; }
void led2SetTimeout(uint32_t m) { led2_max = m; led2_timeon = 0; }
void led2SetDefault(uint8_t m) { led2_default = m; }
uint8_t led2Get() { return led2_mode; }
void led3Set(uint8_t m) { led3_mode = m; led3_state = 0; led3_timeon = 0; }
void led3SetTimeout(uint32_t m) { led3_max = m; led3_timeon = 0; }
void led3SetDefault(uint8_t m) { led3_default = m; }
uint8_t led3Get() { return led3_mode; }
void led4Set(uint8_t m) { led4_mode = m; led4_state = 0; led4_timeon = 0; }
void led4SetTimeout(uint32_t m) { led4_max = m; led4_timeon = 0; }
void led4SetDefault(uint8_t m) { led4_default = m; }
uint8_t led4Get() { return led4_mode; }
// Temporary HACK version with individual - see array branch for single function for unlimited LED and Buttons
PushButton *button1;
PushButton *button2;
PushButton *button3;
PushButton *button4;
uint8_t buttonSleepCheck();
void sleep();
void wake();
protected:
elapsedMillis lastLedLoop;
// Validity etc of most recent data
uint8_t validPacketType;
bool _begin;
// Led udpate
bool updateLED(uint8_t pin, uint8_t mode, uint8_t state);
uint16_t battery_voltage;
// LED1
uint8_t led1_pin;
bool led1_activelow;
uint8_t led1_mode;
uint8_t led1_default;
uint8_t led1_state;
elapsedMillis led1_timeon;
uint32_t led1_max;
// LED2
uint8_t led2_pin;
bool led2_activelow;
uint8_t led2_mode;
uint8_t led2_default;
uint8_t led2_state;
elapsedMillis led2_timeon;
uint32_t led2_max;
// LED3
uint8_t led3_pin;
uint8_t led3_mode;
uint8_t led3_default;
uint8_t led3_state;
elapsedMillis led3_timeon;
uint32_t led3_max;
// LED4
uint8_t led4_pin;
uint8_t led4_mode;
uint8_t led4_default;
uint8_t led4_state;
elapsedMillis led4_timeon;
uint32_t led4_max;
// Button1
uint8_t button1_pin;
bool button1_activelow;
// Button2
uint8_t button2_pin;
bool button2_activelow;
// Button3
uint8_t button3_pin;
bool button3_activelow;
// Button4
uint8_t button4_pin;
bool button4_activelow;
};
extern WII5Sh3dIO sh3dNodeIO;
#endif
+139
View File
@@ -0,0 +1,139 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5Sh3dUtil.cpp
* @brief Sh3d utility helpers: sleep, reset, and platform glue.
*/
/*
Sh3dNode - Utils / Utilities:
TODO 2024 - Check this is the main code for sleep
* Date Time - Sync and format
* Sleep - Management
* Software reset
* WatchDog Timer (internal and external)
*/
#include <Arduino.h>
#include <WII5.h>
#include "WII5Sh3dUtil.h"
#include "WII5Sh3dIO.h"
#include <avr/wdt.h>
#ifdef KINETISK
// TODO See Snooze - https://github.com/duff2013/Snooze
#else
#ifdef WII5_LOWPOWERSLEEP
#include <LowPower.h>
#endif
#endif
#include <TimeLib.h>
#include <MemoryFree.h>
// Just in case
void WII5Sh3dUtil::begin() {
}
/*
sleep - Put the whole device to sleep for "n" seconds.
TODO: Wake on Radio option
*/
uint8_t WII5Sh3dUtil::sleep(uint32_t seconds) {
// TODO - Make sure the LEDs are actually off
// digitalWrite(LED1, HIGH);
// digitalWrite(LED2, HIGH);
// Deep sleep mode ! (NOTE: NMEA out?)
console.printf(F("# SLEEP %lu sec\r\n"), seconds);
console.flush();
// TODO these should be registered? and not used if not required
// sh3dNodeRadio.sleep();
// sh3dNodeIO.sleep();
// sh3dNodeStorage.sleep();
sleepLastTime = now();
volatile uint32_t sleepCount = 0;
pinMode(LED_1, OUTPUT);
while ( (sleepCount * 8) < seconds) {
digitalWrite(LED_1, LOW);
#ifdef WDT_RESET
pinMode(WDT_RESET, OUTPUT);
digitalWrite(WDT_RESET, (sleepCount % 2) == 0 ? HIGH : LOW);
#endif
// Compare LowPower with work on Talk2
#ifdef KINETISK
// TODO look up snooze
#endif
// List boards supported
#ifdef WII5_LOWPOWERSLEEP
LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
#endif
sleepCount++;
uint8_t btn = sh3dNodeIO.buttonSleepCheck();
if (btn > 0) {
console.flush();
console.printf(F("# !SLEEP Button %d %lu sec, %lu c\r\n"), btn, seconds, sleepCount);
sleepLastSeconds = sleepCount * 8;
sleepLastReason = 10 + btn;
return 10 + btn;
}
digitalWrite(LED_1,HIGH);
}
// TODO Fix time
// cli();
// timer0_millis += 64000; //TODO should use multiplier
// sei()
// TODO these should be registered?
// sh3dNodeRadio.wake();
// sh3dNodeIO.wake();
// sh3dNodeStorage.wake();
// Flsuh devices again
console.flush();
console.printf(F("# SLEEP END %lu sec, %lu c\r\n"), seconds, sleepCount);
sleepLastSeconds = sleepCount * 8;
sleepLastReason = 1;
return 1;
}
void WII5Sh3dUtil::reset() {
wdt_enable(WDTO_15MS);
// TODO 2024 - This may need - WDTCSR |= (1 << WDE); // Watchdog System Reset Enable
// TODO or maybe not
while(1);
/*
#ifdef WATCHDOG
wdt_enable(WDTO_15MS);
#else
// Teensy 3
#ifndef SCB_AIRCR_SYSRESETREQ_MASK
#define SCB_AIRCR_SYSRESETREQ_MASK ((unsigned int) 0x00000004)
#endif
cli();
delay(100);
SCB_AIRCR = 0x05FA0000 | SCB_AIRCR_SYSRESETREQ_MASK;
while(1);
#endif
delay(1000);
*/
}
WII5Sh3dUtil sh3dNodeUtil;
+44
View File
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5Sh3dUtil.h
* @brief Sh3d utility helpers: sleep, reset, and platform glue.
*/
#ifndef WII5Sh3dUtil_h
#define WII5Sh3dUtil_h
#include <Arduino.h> //assumes Arduino IDE v1.0 or greater
#include <TimeLib.h>
#include <WII5Data.h>
#include <WII5Sh3dIO.h> // Codependence GAH ! TODO
#include <WII5Sh3dConsole.h>
#include <elapsedMillis.h>
class WII5Sh3dUtil {
public:
void begin();
void loop();
// Reset - and other low level hacks
void reset();
// Sleep Stuff
uint8_t sleep(uint32_t seconds);
uint32_t sleepLastSeconds; // How long did we last sleep for - replaced after sleep
uint8_t sleepLastReason; // Why did we leave sleep (button, time up etc)
time_t sleepLastTime; // When did the sleep occur
protected:
};
extern WII5Sh3dUtil sh3dNodeUtil;
#endif
+1076
View File
File diff suppressed because it is too large Load Diff
+286
View File
@@ -0,0 +1,286 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5Sparton.h
* @brief Sparton AHRS-M1/M2 IMU driver: capture timing, NorthTek configuration.
*/
#ifndef WII5Sparton_h
#define WII5Sparton_h
#include <Arduino.h>
#include <WII5.h>
#include <elapsedMillis.h>
#include <TimeLib.h>
#include <AvgStd.h>
#define SPARTON_RECORDS 30
/*
Sparton format
// $CUS0,
// POSE * 1000
// 5.007294e-01,-3.079974,294.518433,
// MAG * 1000
// 70.337479,174.480072,-450.354431,
// ACCEL * 10,000
// 3.233985,-53.680248,1007.986023,
// GYRO * 1000
// 2.710441e-02,-2.003428e-02,7.615486e-02,
// 75988968,*
float pose_x;
float pose_y;
float pose_z;
float mag_x;
float mag_y;
float mag_z;
float accel_x;
float accel_y;
float accel_z;
float gyro_x;
float gyro_y;
float gyro_z;
uint32_t stamp;
chan0Enable.
<item name="pitch">
<vid>8</vid>
<offset>0</offset>
<type>Float32</type>
</item>
<item name="roll">
<vid>9</vid>
<offset>4</offset>
<type>Float32</type>
</item>
<item name="yaw">
<vid>10</vid>
<offset>8</offset>
<type>Float32</type>
</item>
<item name="magp">
<vid>23</vid>
<offset>12</offset>
<type>Float32[3]</type>
</item>
<item name="accelp">
<vid>25</vid>
<offset>24</offset>
<type>Float32[3]</type>
</item>
<item name="gyrop">
<vid>27</vid>
<offset>36</offset>
<type>Float32[3]</type>
</item>
<item name="cputime">
<vid>249</vid>
<offset>48</offset>
<type>Int32</type>
</item>
* 52 = Bytes of record size
* But records appear to be about 70+ Bytes
New Record Types:
*/
enum WII5SPARTON_STEPS {
WII5SPARTON_OFF,
WII5SPARTON_BOOT,
WII5SPARTON_BOOT_BAUD,
WII5SPARTON_BOOT_STOP,
WII5SPARTON_BOOT_WAIT,
WII5SPARTON_READY,
WII5SPARTON_PROGRAM_BASE,
WII5SPARTON_PROGRAM_START,
WII5SPARTON_CAPTURE,
WII5SPARTON_PROGRAM_STOP,
WII5SPARTON_INFO,
WII5SPARTON_INFO_WAIT,
WII5SPARTON_CANCEL,
WII5SPARTON_FINISH
};
/**
* @brief Driver for the Sparton AHRS-M1 / AHRS-M2 IMU.
*
* Programs the AHRS via NorthTek-style commands over a UART, captures
* binary records (pose / mag / accel / gyro / cputime) at a configured
* sample rate, and writes them either straight to SD or via the console
* passthrough. State machine in WII5SPARTON_STEPS; record layout in
* WII5_DATA_SpartonBinary.
*/
class WII5Sparton : public WII5Power {
public:
WII5Sparton() {}
virtual WII5_CONTROLLERS controllerId() {return WII5CONTROLLER_DRIVER;}
virtual WII5_DRIVERS driverId() {return WII5DRIVER_SPARTON;}
/** @brief One-time bring-up. */
void begin();
/** @brief State-machine tick: parses incoming records, advances steps. */
void loop();
/** @brief Begin a programming + capture cycle. */
virtual void start();
/** @brief Query the AHRS for firmware/version info. */
void info();
/** @brief Abort the current cycle. */
void stop(bool force = false);
/** @brief Run an automated capture for `records` samples at `hz` Hz. */
void automatic(uint8_t hz, uint32_t records, bool capture);
/** @brief Reconfigure the IMU UART baud rate. */
virtual void setBaudrate(uint32_t baud);
/** @brief Apply power to the Sparton. */
void on(bool forced = false);
/** @brief Cut power to the Sparton. */
void off(bool forced = false);
/** @brief Is a programming/capture cycle in flight? */
bool isRunning();
/** @brief Begin a capture (after programming completes). */
void captureRun();
/** @brief Are we currently capturing samples? */
bool captureRunning();
/** @brief Are we waiting for a capture trigger? */
bool captureWaiting();
/** @brief Has the most recent capture completed? */
bool captureFinished();
bool capture;
time_t lastStartTime;
uint32_t lastRunTime;
/** @brief Send a single NorthTek programming line to the IMU. */
void sendLine(uint16_t l);
/** @brief Enable/disable raw-record passthrough to the capture file. */
void setRawCapture(bool in);
/** @brief Current raw-capture flag. */
bool getRawCapture();
/** @brief Enable/disable console-direct output (used when no SD card is present). */
void setConsoleDirect(bool in);
/** @brief Current console-direct flag. */
bool getConsoleDirect();
/** @brief Print capture statistics to the console. */
virtual void displaySTATS();
/** @brief Current sample rate in Hz. */
uint16_t getHz();
/** @brief Current sample period in milliseconds. */
uint16_t getMs();
/** @brief Reconfigure the sample rate (Hz). */
void setHz(uint16_t h);
/** @brief Enable/disable binary record format. */
void setBinary(bool in);
/** @brief Emit a single NorthTek programming line by index. */
virtual void programLine(uint16_t l);
/** @brief Enable/disable raw serial passthrough to the console. */
void setPassthrough(bool in);
/** @brief Current passthrough flag. */
bool getPassthrough();
/** @brief Enable/disable verbose debug output. */
void setDebug(bool in);
/** @brief Current debug flag. */
bool getDebug();
/** @brief Enable/disable capture-on-start. */
void setCapture(bool in);
/** @brief Current capture flag. */
bool getCapture();
/** @brief Set the number of records to capture. */
void setRecords(uint32_t in);
/** @brief Current record count target. */
uint32_t getRecords();
/** @brief Drop into a NorthTek REPL for interactive configuration. */
void repl();
/** @brief Reset internal capture buffers and counters. */
void resetData();
// TODO int ? float, long ?
/*
int lastAccelX;
int lastAccelY;
int lastAccelZ;
int lastPoseX;
int lastPoseY;
int lastPoseZ;
*/
// Last capture errors etc
uint32_t captureWriteMax;
uint32_t captureWriteMin;
uint32_t captureWriteOver;
uint32_t statsTimeError;
uint32_t serialSizeError;
protected:
bool running;
bool cancel;
bool rawCapture; // Do we want to write raw data to capture file
bool consoleDirect; // Direct access
uint16_t HZ;
bool binMode;
bool debug;
uint32_t recordTotal;
elapsedMillis elapsedRequest;
// Inclusive
uint32_t sendCount; // Where are we up to in program data
uint16_t programStart;
uint16_t programEnd;
// Internal steps
WII5SPARTON_STEPS step;
WII5SPARTON_STEPS stepLast;
elapsedMillis stepWait;
void WII5Sparton::handleRxChar( uint8_t c );
uint32_t recordCount;
uint8_t lastValidRecord;
// Binary Records
WII5_DATA_SpartonBinary binRecords[SPARTON_RECORDS];
uint8_t currentRecord;
uint8_t currentCount;
bool binEscape;
uint8_t processMode;
elapsedMillis startCapture;
elapsedMillis startWhen;
elapsedMillis lastWrite;
// elapsedMillis endWhen = 0;
uint32_t blockNext;
elapsedMillis captureWriteTime;
time_t timeStart;
time_t timeEnd;
};
extern WII5Sparton wii5Sparton;
#endif
+788
View File
@@ -0,0 +1,788 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5Strings.cpp
* @brief String-formatting helpers: strDriver, strStatus, strError, etc.
*/
/*
WII5Strings - Parsing and Generating larger text strings
*/
#include <Arduino.h>
#include <WII5Strings.h>
// TODO This is hard coded to console
/*
Parse Console commands
TODO:
* Lower case - but only as compared
* NOW = lower case only
*
* Move literals ("hellow") etc into #DEFINE or similar
* Consider shorter strings - e.g allow "he" instead of hello
*/
// TODO 3 inputs - not using Console !!!
// TODO Returns command (could have consumed 1..3 entries)
// TODO Returns params (could have converted strings to numbers, but numbers returned)
// - Consisiting of the rest of the params consumed
// TODO how?
WII5_COMMANDS WII5Strings::parseCommand(char* in1, char* in2, char* in3) {
// 1 - One level entries
if (strcmp_P(in1,(PGM_P) F("hello")) == 0) {
return WII5COMMAND_HELLO;
}
else if (strcmp_P(in1,(PGM_P) F("hello!")) == 0) {
return WII5COMMAND_HELLOACK;
}
else if (strcmp_P(in1,(PGM_P) F("dump")) == 0) {
return WII5COMMAND_DUMP;
}
else if (strcmp_P(in1,(PGM_P) F("status")) == 0) {
return WII5COMMAND_STATUS;
}
else if (strcmp_P(in1,(PGM_P) F("waitx")) == 0) {
return WII5COMMAND_WAITX;
}
else if (strcmp_P(in1,(PGM_P) F("waity")) == 0) {
return WII5COMMAND_WAITY;
}
else if (strcmp_P(in1,(PGM_P) F("waitz")) == 0) {
return WII5COMMAND_WAITZ;
}
else if (strcmp_P(in1,(PGM_P) F("waitq")) == 0) {
return WII5COMMAND_WAITQ;
}
else if (strcmp_P(in1,(PGM_P) F("waits")) == 0) {
return WII5COMMAND_WAITS;
}
else if (strcmp_P(in1,(PGM_P) F("help")) == 0) {
return WII5COMMAND_HELP;
}
else if (strcmp_P(in1,(PGM_P) F("reset")) == 0) {
return WII5COMMAND_RESET;
}
else if (strcmp_P(in1,(PGM_P) F("echo")) == 0) {
return WII5COMMAND_ECHO;
}
else if (strcmp_P(in1,(PGM_P) F("people")) == 0) {
return WII5COMMAND_PEOPLE;
}
else if (strcmp_P(in1,(PGM_P) F("set")) == 0) {
return WII5COMMAND_SET;
}
else if (strcmp_P(in1,(PGM_P) F("send")) == 0) {
return WII5COMMAND_SEND;
}
// GPS
else if (strcmp_P(in1,(PGM_P) F("gps")) == 0) {
if (strcmp_P(in2,(PGM_P) F("dump")) == 0)
return WII5COMMAND_GPS_DUMP;
else if (strcmp_P(in2,(PGM_P) F("passthrough")) == 0)
return WII5COMMAND_GPS_PASSTHROUGH;
else if (strcmp_P(in2,(PGM_P) F("debug")) == 0)
return WII5COMMAND_GPS_DEBUG;
else if (strcmp_P(in2,(PGM_P) F("start")) == 0)
return WII5COMMAND_GPS_START;
else if (strcmp_P(in2,(PGM_P) F("stop")) == 0)
return WII5COMMAND_GPS_STOP;
else if (strcmp_P(in2,(PGM_P) F("time")) == 0)
return WII5COMMAND_GPS_TIME;
else if (strcmp_P(in2,(PGM_P) F("pos")) == 0)
return WII5COMMAND_GPS_POS;
else if (strcmp_P(in2,(PGM_P) F("accurate")) == 0)
return WII5COMMAND_GPS_ACCURATE;
}
// SPARTON
else if (strcmp_P(in1,(PGM_P) F("sparton")) == 0) {
if (strcmp_P(in2,(PGM_P) F("mode")) == 0)
return WII5COMMAND_SPARTON_MODE;
else if (strcmp_P(in2,(PGM_P) F("dump")) == 0)
return WII5COMMAND_SPARTON_DUMP;
else if (strcmp_P(in2,(PGM_P) F("passthrough")) == 0)
return WII5COMMAND_SPARTON_PASSTHROUGH;
else if (strcmp_P(in2,(PGM_P) F("debug")) == 0)
return WII5COMMAND_SPARTON_DEBUG;
else if (strcmp_P(in2,(PGM_P) F("info")) == 0)
return WII5COMMAND_SPARTON_INFO;
}
// MATHS
else if (strcmp_P(in1,(PGM_P) F("maths")) == 0) {
if (strcmp_P(in2,(PGM_P) F("start")) == 0)
return WII5COMMAND_MATHS_START;
else if (strcmp_P(in2,(PGM_P) F("stop")) == 0)
return WII5COMMAND_MATHS_STOP;
else if (strcmp_P(in2,(PGM_P) F("beep")) == 0)
return WII5COMMAND_MATHS_BEEP;
else if (strcmp_P(in2,(PGM_P) F("restart")) == 0)
return WII5COMMAND_MATHS_RESTART;
else if (strcmp_P(in2,(PGM_P) F("flip")) == 0)
return WII5COMMAND_MATHS_FLIP;
else if (strcmp_P(in2,(PGM_P) F("hold")) == 0)
return WII5COMMAND_MATHS_HOLD;
else if (strcmp_P(in2,(PGM_P) F("until")) == 0)
return WII5COMMAND_MATHS_UNTIL;
else if (strcmp_P(in2,(PGM_P) F("info")) == 0)
return WII5COMMAND_MATHS_INFO;
else if (strcmp_P(in2,(PGM_P) F("halt")) == 0)
return WII5COMMAND_MATHS_HALT;
else if (strcmp_P(in2,(PGM_P) F("do")) == 0)
return WII5COMMAND_MATHS_DO;
else if (strcmp_P(in2,(PGM_P) F("make")) == 0)
return WII5COMMAND_MATHS_MAKE;
else if (strcmp_P(in2,(PGM_P) F("script")) == 0)
return WII5COMMAND_MATHS_SCRIPT;
else if (strcmp_P(in2,(PGM_P) F("booted")) == 0)
return WII5COMMAND_MATHS_BOOTED;
else if (strcmp_P(in2,(PGM_P) F("return")) == 0)
return WII5COMMAND_MATHS_RETURN;
}
// LOG
// @WII5,log,debug|deafult|fatal
else if (strcmp_P(in1,(PGM_P) F("log")) == 0) {
if (strcmp_P(in2,(PGM_P) F("debug")) == 0)
return WII5COMMAND_LOG_DEBUG;
else if (strcmp_P(in2,(PGM_P) F("default")) == 0)
return WII5COMMAND_LOG_DEFAULT;
else if (strcmp_P(in2,(PGM_P) F("error")) == 0)
return WII5COMMAND_LOG_ERROR;
else if (strcmp_P(in2,(PGM_P) F("fatal")) == 0)
return WII5COMMAND_LOG_FATAL;
else if (strcmp_P(in2,(PGM_P) F("test")) == 0)
return WII5COMMAND_LOG_TEST;
}
// SETTINGS
// @WII5,setting,deviceid,10045
else if (strcmp_P(in1,(PGM_P) F("setting")) == 0) {
if (strcmp_P(in2,(PGM_P) F("list")) == 0)
return WII5COMMAND_SETTING_LIST;
else if (strcmp_P(in2,(PGM_P) F("deviceid")) == 0)
return WII5COMMAND_SETTING_DEVICEID;
else if (strcmp_P(in2,(PGM_P) F("defaults")) == 0)
return WII5COMMAND_SETTING_DEFAULTS;
else if (strcmp_P(in2,(PGM_P) F("gpstimeout")) == 0)
return WII5COMMAND_SETTING_GPSTIMEOUT;
else if (strcmp_P(in2,(PGM_P) F("mode")) == 0)
return WII5COMMAND_SETTING_MODE;
else if (strcmp_P(in2,(PGM_P) F("time")) == 0)
return WII5COMMAND_SETTING_TIME;
else if (strcmp_P(in2,(PGM_P) F("disablelowbattery")) == 0)
return WII5COMMAND_SETTING_DISABLELOWBATTERY;
// FLAGS - all,uint32_t or X,0|1
else if (strcmp_P(in2,(PGM_P) F("flags")) == 0)
return WII5COMMAND_SETTING_FLAGS;
// OPTIONS - pass in {PERIOD} and {BINARYTYPE} - 0=ignore
else if (strcmp_P(in2,(PGM_P) F("captureoptions")) == 0)
return WII5COMMAND_SETTING_CAPTUREOPTIONS;
else if (strcmp_P(in2,(PGM_P) F("positionoptions")) == 0)
return WII5COMMAND_SETTING_POSITIONOPTIONS;
else if (strcmp_P(in2,(PGM_P) F("sleepoptions")) == 0)
return WII5COMMAND_SETTING_SLEEPOPTIONS;
else if (strcmp_P(in2,(PGM_P) F("batteryoptions")) == 0)
return WII5COMMAND_SETTING_BATTERYOPTIONS;
}
// NETWORK
else if (strcmp_P(in1,(PGM_P) F("network")) == 0) {
if (strcmp_P(in2,(PGM_P) F("echo")) == 0)
return WII5COMMAND_NETWORK_ECHO;
else if (strcmp_P(in2,(PGM_P) F("echoack")) == 0)
return WII5COMMAND_NETWORK_ECHOACK;
else if (strcmp_P(in2,(PGM_P) F("fakeecho")) == 0)
return WII5COMMAND_NETWORK_FAKEECHO;
else if (strcmp_P(in2,(PGM_P) F("fakeechoack")) == 0)
return WII5COMMAND_NETWORK_FAKEECHOACK;
}
// STORAGE
// Storage
else if (strcmp_P(in1,(PGM_P) F("storage")) == 0) {
if (strcmp_P(in2,(PGM_P) F("format")) == 0)
return WII5COMMAND_STORAGE_FORMAT;
else if (strcmp_P(in2,(PGM_P) F("testfile")) == 0)
return WII5COMMAND_STORAGE_TESTFILE;
else if (strcmp_P(in2,(PGM_P) F("setstatus")) == 0)
return WII5COMMAND_STORAGE_SETSTATUS;
else if (strcmp_P(in2,(PGM_P) F("off")) == 0)
return WII5COMMAND_STORAGE_OFF;
else if (strcmp_P(in2,(PGM_P) F("sd1")) == 0)
return WII5COMMAND_STORAGE_SD1;
else if (strcmp_P(in2,(PGM_P) F("sd2")) == 0)
return WII5COMMAND_STORAGE_SD2;
else if (strcmp_P(in2,(PGM_P) F("list")) == 0)
return WII5COMMAND_STORAGE_LIST;
else if (strcmp_P(in2,(PGM_P) F("view")) == 0)
return WII5COMMAND_STORAGE_VIEW;
else if (strcmp_P(in2,(PGM_P) F("metadata")) == 0)
return WII5COMMAND_STORAGE_METADATA;
else if (strcmp_P(in2,(PGM_P) F("results")) == 0)
return WII5COMMAND_STORAGE_RESULTS;
else if (strcmp_P(in2,(PGM_P) F("raw")) == 0)
return WII5COMMAND_STORAGE_RAW;
else if (strcmp_P(in2,(PGM_P) F("debug")) == 0)
return WII5COMMAND_STORAGE_DEBUG;
else if (strcmp_P(in2,(PGM_P) F("status")) == 0)
return WII5COMMAND_STORAGE_STATUS;
}
// Battery
else if (strcmp_P(in1,(PGM_P) F("battery")) == 0) {
if (strcmp_P(in2,(PGM_P) F("start")) == 0)
return WII5COMMAND_BATTERY_START;
}
// Weather
else if (strcmp_P(in1,(PGM_P) F("weather")) == 0) {
if (strcmp_P(in2,(PGM_P) F("read")) == 0)
return WII5COMMAND_WEATHER_READ;
else if (strcmp_P(in2,(PGM_P) F("test")) == 0)
return WII5COMMAND_WEATHER_TEST;
}
// CAPTURE
else if (strcmp_P(in1,(PGM_P) F("capture")) == 0) {
if (strcmp_P(in2,(PGM_P) F("start")) == 0)
return WII5COMMAND_CAPTURE_START;
else if (strcmp_P(in2,(PGM_P) F("stop")) == 0)
return WII5COMMAND_CAPTURE_STOP;
}
// POSITION
else if (strcmp_P(in1,(PGM_P) F("position")) == 0) {
if (strcmp_P(in2,(PGM_P) F("start")) == 0)
return WII5COMMAND_POSITION_START;
else if (strcmp_P(in2,(PGM_P) F("stop")) == 0)
return WII5COMMAND_POSITION_STOP;
else if (strcmp_P(in2,(PGM_P) F("passthrough")) == 0)
return WII5COMMAND_POSITION_PASSTHROUGH;
}
// Mode
else if (strcmp_P(in1,(PGM_P) F("mode")) == 0) {
if (strcmp_P(in2,(PGM_P) F("position")) == 0)
return WII5COMMAND_MODE_POSITION;
else if (strcmp_P(in2,(PGM_P) F("manualtest")) == 0)
return WII5COMMAND_MODE_MANUALTEST;
else if (strcmp_P(in2,(PGM_P) F("selftest")) == 0)
return WII5COMMAND_MODE_SELFTEST;
else if (strcmp_P(in2,(PGM_P) F("capture")) == 0)
return WII5COMMAND_MODE_CAPTURE;
else if (strcmp_P(in2,(PGM_P) F("sleep")) == 0)
return WII5COMMAND_MODE_SLEEP;
else if (strcmp_P(in2,(PGM_P) F("manualtest")) == 0)
return WII5COMMAND_MODE_MANUALTEST;
else if (strcmp_P(in2,(PGM_P) F("default")) == 0)
return WII5COMMAND_MODE_DEFAULT;
}
// Iridium
else if (strcmp_P(in1,(PGM_P) F("iridium")) == 0) {
if (strcmp_P(in2,(PGM_P) F("send")) == 0)
return WII5COMMAND_IRIDIUM_SEND;
}
// Communication
else if (strcmp_P(in1,(PGM_P) F("communications")) == 0) {
if (strcmp_P(in2,(PGM_P) F("disabled")) == 0)
return WII5COMMAND_COMMUNICATIONS_DISABLED;
else if (strcmp_P(in2,(PGM_P) F("test")) == 0)
return WII5COMMAND_COMMUNICATIONS_TEST;
else if (strcmp_P(in2,(PGM_P) F("signal")) == 0)
return WII5COMMAND_COMMUNICATIONS_SIGNAL;
else if (strcmp_P(in2,(PGM_P) F("start")) == 0)
return WII5COMMAND_COMMUNICATIONS_START;
else if (strcmp_P(in2,(PGM_P) F("stop")) == 0)
return WII5COMMAND_COMMUNICATIONS_STOP;
}
// Bindata
else if (strcmp_P(in1,(PGM_P) F("bindata")) == 0) {
if (strcmp_P(in2,(PGM_P) F("list")) == 0)
return WII5COMMAND_BINDATA_LIST;
else if (strcmp_P(in2,(PGM_P) F("split")) == 0)
return WII5COMMAND_BINDATA_SPLIT;
}
return WII5COMMAND_NONE;
}
WII5_MODES WII5Strings::parseMode(char *in) {
if (strcmp_P(in,(PGM_P) F("boot")) == 0) return WII5MODE_BOOT;
else if (strcmp_P(in,(PGM_P) F("start")) == 0) return WII5MODE_START;
else if (strcmp_P(in,(PGM_P) F("demo")) == 0) return WII5MODE_DEMO;
else if (strcmp_P(in,(PGM_P) F("lowbattery")) == 0) return WII5MODE_LOWBATTERY;
else if (strcmp_P(in,(PGM_P) F("position")) == 0) return WII5MODE_POSITION;
else if (strcmp_P(in,(PGM_P) F("capture")) == 0) return WII5MODE_CAPTURE;
else if (strcmp_P(in,(PGM_P) F("sleep")) == 0) return WII5MODE_SLEEP;
else if (strcmp_P(in,(PGM_P) F("manualtest")) == 0) return WII5MODE_MANUALTEST;
else if (strcmp_P(in,(PGM_P) F("selftest")) == 0) return WII5MODE_SELFTEST;
return WII5MODE_NONE;
}
WII5_SWITCH WII5Strings::parseSwitch(char *in) {
if (
(strcmp_P(in,(PGM_P) F("on")) == 0)
|| (strcmp_P(in,(PGM_P) F("yes")) == 0)
|| (strcmp_P(in,(PGM_P) F("true")) == 0)
) {
return WII5SWITCH_ON;
}
else if (
(strcmp_P(in,(PGM_P) F("off")) == 0)
|| (strcmp_P(in,(PGM_P) F("no")) == 0)
|| (strcmp_P(in,(PGM_P) F("false")) == 0)
) {
return WII5SWITCH_OFF;
}
else if (
(strcmp_P(in,(PGM_P) F("toggle")) == 0)
) {
return WII5SWITCH_TOGGLE;
}
return WII5SWITCH_NONE;
}
// TODO Deprecate?
WII5GPS_MODES WII5Strings::parseGPSMode(char *in) {
if (
(strcmp_P(in,(PGM_P) F("off")) == 0)
)
return WII5GPS_OFF;
else if (
(strcmp_P(in,(PGM_P) F("on")) == 0)
)
return WII5GPS_ON;
else if (
(strcmp_P(in,(PGM_P) F("time")) == 0)
)
return WII5GPS_TIME;
else if (
(strcmp_P(in,(PGM_P) F("pos")) == 0)
)
return WII5GPS_POS;
else if (
(strcmp_P(in,(PGM_P) F("accurate")) == 0)
)
return WII5GPS_ACCURATE;
else if (
(strcmp_P(in,(PGM_P) F("repeat")) == 0)
)
return WII5GPS_REPEAT;
return WII5GPS_UNKNOWN;
}
WII5_DEVICES WII5Strings::parseDevice(char *in) {
if (strcmp_P(in,(PGM_P) F("wavebuoy")) == 0) return WII5DEVICE_WAVEBUOY;
else if (strcmp_P(in,(PGM_P) F("maths")) == 0) return WII5DEVICE_MATHS;
else if (strcmp_P(in,(PGM_P) F("controller")) == 0) return WII5DEVICE_CONTROLLER;
else if (strcmp_P(in,(PGM_P) F("cli")) == 0) return WII5DEVICE_CLI;
else if (strcmp_P(in,(PGM_P) F("server")) == 0) return WII5DEVICE_SERVER;
return WII5DEVICE_UNKNOWN;
}
WII5_DRIVERS WII5Strings::parseDriver(char *in) {
// TODO
return WII5DRIVER_GPS;
}
WII5_PORTS WII5Strings::parsePort(char *in) {
return WII5PORT_CONSOLE;
}
void WII5Strings::setBufferUnknown() {
strcpy_P(wii5BufferString ,(PGM_P) F("unknown"));
}
char* WII5Strings::strCommand(WII5_COMMANDS c) {
/*
switch(d) {
case WII5DEVICE_WAVEBUOY: strcpy_P(wii5BufferString ,(PGM_P) F("wavebuoy")); break;
case WII5DEVICE_MATHS: strcpy_P(wii5BufferString ,(PGM_P) F("maths")); break;
case WII5DEVICE_CONTROLLER: strcpy_P(wii5BufferString ,(PGM_P) F("controller")); break;
case WII5DEVICE_CLI: strcpy_P(wii5BufferString ,(PGM_P) F("cli")); break;
case WII5DEVICE_SERVER: strcpy_P(wii5BufferString ,(PGM_P) F("server")); break;
default: setBufferUnknown(); break;
};
*/
setBufferUnknown();
return wii5BufferString;
}
char* WII5Strings::strDevice(WII5_DEVICES d) {
switch(d) {
case WII5DEVICE_WAVEBUOY: strcpy_P(wii5BufferString ,(PGM_P) F("wavebuoy")); break;
case WII5DEVICE_MATHS: strcpy_P(wii5BufferString ,(PGM_P) F("maths")); break;
case WII5DEVICE_CONTROLLER: strcpy_P(wii5BufferString ,(PGM_P) F("controller")); break;
case WII5DEVICE_CLI: strcpy_P(wii5BufferString ,(PGM_P) F("cli")); break;
case WII5DEVICE_SERVER: strcpy_P(wii5BufferString ,(PGM_P) F("server")); break;
default: setBufferUnknown(); break;
};
return wii5BufferString;
}
char* WII5Strings::strPort(WII5_PORTS p) {
setBufferUnknown();
return wii5BufferString;
}
char* WII5Strings::strStatus(WII5_STATUS s) {
switch(s) {
case WII5STATUS_OFF: strcpy_P(wii5BufferString ,(PGM_P) F("off")); break;
case WII5STATUS_ON: strcpy_P(wii5BufferString ,(PGM_P) F("on")); break;
case WII5STATUS_ON_VALID: strcpy_P(wii5BufferString ,(PGM_P) F("valid")); break;
case WII5STATUS_BUSY: strcpy_P(wii5BufferString ,(PGM_P) F("busy")); break;
case WII5STATUS_WAITING: strcpy_P(wii5BufferString ,(PGM_P) F("waiting")); break;
case WII5STATUS_ERROR: strcpy_P(wii5BufferString ,(PGM_P) F("error")); break;
default: setBufferUnknown(); break;
}
return wii5BufferString;
}
char* WII5Strings::strError(WII5_ERRORS e) {
switch(e) {
case WII5ERROR_RETRY: strcpy_P(wii5BufferString ,(PGM_P) F("RETRY")); break;
case WII5ERROR_INIT: strcpy_P(wii5BufferString ,(PGM_P) F("INIT")); break;
case WII5ERROR_MEMORY: strcpy_P(wii5BufferString ,(PGM_P) F("MEMORY")); break;
case WII5ERROR_TIMEOUT: strcpy_P(wii5BufferString ,(PGM_P) F("TIMEOUT")); break;
case WII5ERROR_FATAL: strcpy_P(wii5BufferString ,(PGM_P) F("FATAL")); break;
default: setBufferUnknown(); break;
}
return wii5BufferString;
}
char* WII5Strings::strSwitch(WII5_SWITCH s) {
strcpy_P(wii5BufferString ,(PGM_P) F("UnknownSwitch"));
return wii5BufferString;
}
// TODO instead of strDriver - and getting
// the wii5BufferString set. Return the PROGMEM string
// and then use a helper function toBuffer or similar to change.
// As PROGMEM returns have use.
char* WII5Strings::strDriver(WII5_DRIVERS d) {
switch(d) {
case WII5DRIVER_GPS: strcpy_P(wii5BufferString ,(PGM_P) F("GPS")); break;
case WII5DRIVER_COMMS: strcpy_P(wii5BufferString ,(PGM_P) F("Comms")); break;
case WII5DRIVER_COMMS_IRIDIUM: strcpy_P(wii5BufferString ,(PGM_P) F("CommsIridium")); break;
case WII5DRIVER_MATHS: strcpy_P(wii5BufferString ,(PGM_P) F("Maths")); break;
case WII5DRIVER_POWER: strcpy_P(wii5BufferString ,(PGM_P) F("Power")); break;
case WII5DRIVER_MPU9250: strcpy_P(wii5BufferString ,(PGM_P) F("MPU9250")); break;
case WII5DRIVER_SPARTON: strcpy_P(wii5BufferString ,(PGM_P) F("Sparton")); break;
case WII5DRIVER_STORAGE: strcpy_P(wii5BufferString ,(PGM_P) F("Storage")); break;
case WII5DRIVER_WEATHER_18B20: strcpy_P(wii5BufferString ,(PGM_P) F("Weather_18B20")); break;
case WII5DRIVER_BATTERY: strcpy_P(wii5BufferString ,(PGM_P) F("Battery")); break;
default: setBufferUnknown(); break;
}
return wii5BufferString;
}
// TODO DEPRECATE
char* WII5Strings::strGPSMode(WII5GPS_MODES g) {
switch(g) {
case WII5GPS_OFF: strcpy_P(wii5BufferString ,(PGM_P) F("Off")); break;
case WII5GPS_ON: strcpy_P(wii5BufferString ,(PGM_P) F("On")); break;
case WII5GPS_TIME: strcpy_P(wii5BufferString ,(PGM_P) F("Time")); break;
case WII5GPS_POS: strcpy_P(wii5BufferString ,(PGM_P) F("Pos")); break;
case WII5GPS_ACCURATE: strcpy_P(wii5BufferString ,(PGM_P) F("Accurate")); break;
case WII5GPS_REPEAT: strcpy_P(wii5BufferString ,(PGM_P) F("Repeat")); break;
default: setBufferUnknown(); break;
}
return wii5BufferString;
}
char* WII5Strings::strMode(WII5_MODES g) {
switch(g) {
case WII5MODE_BOOT: strcpy_P(wii5BufferString, (PGM_P) F("boot")); break;
case WII5MODE_START: strcpy_P(wii5BufferString, (PGM_P) F("start")); break;
case WII5MODE_DEMO: strcpy_P(wii5BufferString, (PGM_P) F("demo")); break;
case WII5MODE_SLEEP: strcpy_P(wii5BufferString, (PGM_P) F("sleep")); break;
case WII5MODE_LOWBATTERY: strcpy_P(wii5BufferString, (PGM_P) F("lowbattery")); break;
case WII5MODE_POSITION: strcpy_P(wii5BufferString, (PGM_P) F("position")); break;
case WII5MODE_CAPTURE: strcpy_P(wii5BufferString, (PGM_P) F("capture")); break;
case WII5MODE_MANUALTEST: strcpy_P(wii5BufferString, (PGM_P) F("manualtest")); break;
case WII5MODE_SELFTEST: strcpy_P(wii5BufferString, (PGM_P) F("selftest")); break;
default: setBufferUnknown(); break;
}
return wii5BufferString;
}
char* WII5Strings::strPinMode(uint8_t p) {
if (p == INPUT) strcpy_P(wii5BufferString, (PGM_P) F("INPUT "));
else if (p == INPUT_PULLUP) strcpy_P(wii5BufferString, (PGM_P) F("INPUT_PULLUP"));
else if (p == OUTPUT) strcpy_P(wii5BufferString, (PGM_P) F("OUTPUT "));
else strcpy_P(wii5BufferString ,(PGM_P) F("Unknown "));
return wii5BufferString;
}
char* WII5Strings::strState(uint8_t p) {
if (p == HIGH) strcpy_P(wii5BufferString, (PGM_P) F("HIGH"));
else if (p == LOW) strcpy_P(wii5BufferString, (PGM_P) F("low "));
else strcpy_P(wii5BufferString ,(PGM_P) F("????"));
return wii5BufferString;
}
char* WII5Strings::strPinArduinoName(uint8_t p) {
// Analopg Pin Number
strcpy_P(wii5BufferString ,(PGM_P) F(" "));
if ((p >= A0) && (p < (A0 + NUM_ANALOG_INPUTS))) {
uint8_t a = p - A0;
if (a < 10) {
wii5BufferString[0] = 'A';
wii5BufferString[1] = '0';
wii5BufferString[2] = '0' + a;
}
else {
wii5BufferString[0] = 'A';
wii5BufferString[1] = '1';
wii5BufferString[2] = '0' + (a - 10);
}
return wii5BufferString;
}
switch(p) {
// TODO Needs config - not hard coded
case 0:
strcpy_P(wii5BufferString ,(PGM_P) F("RX0 "));
break;
case 1:
strcpy_P(wii5BufferString ,(PGM_P) F("TX0 "));
break;
case 14:
strcpy_P(wii5BufferString ,(PGM_P) F("TX3 "));
break;
case 15:
strcpy_P(wii5BufferString ,(PGM_P) F("RX3 "));
break;
/*
case 16:
strcpy_P(wii5BufferString ,(PGM_P) F("TX2 "));
break;
case 17:
strcpy_P(wii5BufferString ,(PGM_P) F("RX2 "));
break;
*/
case 18:
strcpy_P(wii5BufferString ,(PGM_P) F("TX1 "));
break;
case 19:
strcpy_P(wii5BufferString ,(PGM_P) F("RX1 "));
break;
case SS:
strcpy_P(wii5BufferString ,(PGM_P) F("SS "));
break;
case MOSI:
strcpy_P(wii5BufferString ,(PGM_P) F("MOSI "));
break;
case MISO:
strcpy_P(wii5BufferString ,(PGM_P) F("MISO "));
break;
case SCK:
strcpy_P(wii5BufferString ,(PGM_P) F("SCK "));
break;
case SDA:
strcpy_P(wii5BufferString ,(PGM_P) F("SDA "));
break;
case SCL:
strcpy_P(wii5BufferString ,(PGM_P) F("SCL "));
break;
}
return wii5BufferString;
}
char* WII5Strings::strPinWII5Name(uint8_t p) {
strcpy_P(wii5BufferString ,(PGM_P) F(" "));
switch(p) {
#if LED_1>0
case LED_1:
strcpy_P(wii5BufferString ,(PGM_P) F("LED1 "));
break;
#endif
#if LED_2>0
case LED_2:
strcpy_P(wii5BufferString ,(PGM_P) F("LED2/Buzz "));
break;
#endif
#if LED_3>0
case LED_3:
strcpy_P(wii5BufferString ,(PGM_P) F("LED3 "));
break;
#endif
#if LED_4>0
case LED_4:
strcpy_P(wii5BufferString ,(PGM_P) F("LED4 "));
break;
#endif
#if BUTTON_1>0
case BUTTON_1:
strcpy_P(wii5BufferString ,(PGM_P) F("BTN1 "));
break;
#endif
#if BUTTON_2>0
case BUTTON_2:
strcpy_P(wii5BufferString ,(PGM_P) F("BTN2 "));
break;
#endif
#if BUTTON_3>0
case BUTTON_3:
strcpy_P(wii5BufferString ,(PGM_P) F("BTN3 "));
break;
#endif
#if BUTTON_4>0
case BUTTON_4:
strcpy_P(wii5BufferString ,(PGM_P) F("BTN4 "));
break;
#endif
#ifdef POWER_MATHS_PIN
case POWER_MATHS_PIN:
strcpy_P(wii5BufferString, (PGM_P) F("Pwr Maths "));
break;
#endif
case POWER_RADIO_PIN:
strcpy_P(wii5BufferString, (PGM_P) F("Pwr Radio "));
break;
case POWER_COMMS_PIN:
strcpy_P(wii5BufferString, (PGM_P) F("Pwr Comms "));
break;
case POWER_GPS_PIN:
strcpy_P(wii5BufferString, (PGM_P) F("Pwr GPS "));
break;
#ifdef POWER_STROBE1_PIN
case POWER_STROBE1_PIN:
strcpy_P(wii5BufferString, (PGM_P) F("Pwr Strob1"));
break;
#endif
#ifdef POWER_STROBE1_PIN
case POWER_STROBE2_PIN:
strcpy_P(wii5BufferString, (PGM_P) F("Pwr Strob2"));
break;
#endif
#ifdef POWER_IMU_MPU9250_PIN
case POWER_IMU_MPU9250_PIN:
strcpy_P(wii5BufferString, (PGM_P) F("Pwr 9250 "));
break;
#endif
#ifdef POWER_IMU_SERIAL_PIN
case POWER_IMU_SERIAL_PIN:
strcpy_P(wii5BufferString, (PGM_P) F("Pwr Spartn"));
break;
#endif
case WEATHER_18B20_DATA:
strcpy_P(wii5BufferString, (PGM_P) F("Data 18B20"));
break;
case POWER_WEATHER_18B20_PIN:
strcpy_P(wii5BufferString, (PGM_P) F("Pwr 18B20 "));
break;
case POWER_WEATHER_PIN:
strcpy_P(wii5BufferString, (PGM_P) F("Pwr Weathr"));
break;
case BATTERY_1_VOLTS_PIN:
strcpy_P(wii5BufferString, (PGM_P) F("Bat1 Meas "));
break;
#if POWER_SUBBOARD_1_PIN>0
case POWER_SUBBOARD_1_PIN:
strcpy_P(wii5BufferString, (PGM_P) F("Pwr Sub1 "));
break;
#endif
#if POWER_SUBBOARD_1_PIN>0
case POWER_SUBBOARD_2_PIN:
strcpy_P(wii5BufferString, (PGM_P) F("Pwr Sub2 "));
break;
#endif
#if POWER_STORAGE_1_PIN>0
case POWER_STORAGE_1_PIN:
strcpy_P(wii5BufferString, (PGM_P) F("Pwr SD 1 "));
break;
#endif
#if POWER_STORAGE_2_PIN>0
case POWER_STORAGE_2_PIN:
strcpy_P(wii5BufferString, (PGM_P) F("Pwr SD 2 "));
break;
#endif
#ifdef STORAGE_SD1_MATHS_PIN
case STORAGE_SD1_MATHS_PIN:
strcpy_P(wii5BufferString, (PGM_P) F("SEL SD 1 "));
break;
#endif
#ifdef STORAGE_SD2_MATHS_PIN 57
case STORAGE_SD2_MATHS_PIN:
strcpy_P(wii5BufferString, (PGM_P) F("SEL SD 2 "));
break;
#endif
#ifdef SHARED_5VOLT_PIN
case SHARED_5VOLT_PIN:
strcpy_P(wii5BufferString, (PGM_P) F("Shrd 5Volt"));
break;
#endif
}
return wii5BufferString;
}
char* WII5Strings::strSelfTestDevice(WII5SELFTEST_DEVICES in) {
switch(in) {
case WII5STD_BOARD: strcpy_P(wii5BufferString, (PGM_P) F("Board")); break;
case WII5STD_GPS: strcpy_P(wii5BufferString, (PGM_P) F("GPS")); break;
case WII5STD_EEPROM: strcpy_P(wii5BufferString, (PGM_P) F("EEPROM")); break;
case WII5STD_SD1: strcpy_P(wii5BufferString, (PGM_P) F("SD1")); break;
case WII5STD_SD2: strcpy_P(wii5BufferString, (PGM_P) F("SD2")); break;
case WII5STD_IRIDIUM: strcpy_P(wii5BufferString, (PGM_P) F("Iridium")); break;
case WII5STD_BATTERY: strcpy_P(wii5BufferString, (PGM_P) F("Battery")); break;
case WII5STD_MATHS: strcpy_P(wii5BufferString, (PGM_P) F("Maths")); break;
case WII5STD_RTC: strcpy_P(wii5BufferString, (PGM_P) F("RTC")); break;
case WII5STD_SPARTON: strcpy_P(wii5BufferString, (PGM_P) F("Sparton")); break;
case WII5STD_18B20: strcpy_P(wii5BufferString, (PGM_P) F("18B20")); break;
case WII5STD_BUTTON: strcpy_P(wii5BufferString, (PGM_P) F("Button")); break;
case WII5STD_LED: strcpy_P(wii5BufferString, (PGM_P) F("LED")); break;
case WII5STD_BUZZER: strcpy_P(wii5BufferString, (PGM_P) F("Buzzer")); break;
case WII5STD_5VOLT: strcpy_P(wii5BufferString, (PGM_P) F("5Volts")); break;
// TODO LED, Button, Buzzer, TEmperature sensor
default: setBufferUnknown(); break;
}
return wii5BufferString;
};
char* WII5Strings::strSelfTestStatus(WII5SELFTEST_STATUS in) {
switch(in) {
case WII5STS_NONE: strcpy_P(wii5BufferString, (PGM_P) F("NONE")); break; // , // Never run
case WII5STS_NC: strcpy_P(wii5BufferString, (PGM_P) F("NC")); break; // , // Not connected, not enabled, or not applicable
case WII5STS_SUCCESS: strcpy_P(wii5BufferString, (PGM_P) F("Success")); break; // , // SUCCCESS - literally nothing to report
case WII5STS_HUMAN: strcpy_P(wii5BufferString, (PGM_P) F("Human")); break; // , // HUMAN - we assume success, but need a human to confirm
case WII5STS_SLOW: strcpy_P(wii5BufferString, (PGM_P) F("Slow")); break; // , // SLOW - SUCCESS in all ways, but slower than expected. Good for SD, Maths boot, GPS lock etc
case WII5STS_POWER: strcpy_P(wii5BufferString, (PGM_P) F("Power")); break; // , // Works, but power management may have failed (e.g. serial data still coming in when power off)
case WII5STS_TIMEOUT: strcpy_P(wii5BufferString, (PGM_P) F("Timeout")); break; // , // Literally timed out. No failure we can actually understand.
case WII5STS_OUTOFRANGE: strcpy_P(wii5BufferString, (PGM_P) F("OutOfRange")); break; // , // Out of range - voltage, or GPS or something out of range
case WII5STS_UNKNOWN: strcpy_P(wii5BufferString, (PGM_P) F("Unknown")); break; // , // Some sort of bad, unknown error
default: setBufferUnknown(); break;
}
return wii5BufferString;
};
char* WII5Strings::strSelfTestStatusLong(WII5SELFTEST_STATUS in) {
switch(in) {
case WII5STS_NONE: strcpy_P(wii5BufferString, (PGM_P) F("Never been run")); break; // , // Never run
case WII5STS_NC: strcpy_P(wii5BufferString, (PGM_P) F("Not connected or enabled")); break; // , // Not connected, not enabled, or not applicable
case WII5STS_SUCCESS: strcpy_P(wii5BufferString, (PGM_P) F("Nothing wrong. Fantastic!")); break; // , // SUCCCESS - literally nothing to report
case WII5STS_HUMAN: strcpy_P(wii5BufferString, (PGM_P) F("Human needed to see results - e.g. did the LED or buzzer go off")); break; // , // HUMAN - we assume success, but need a human to confirm
case WII5STS_SLOW: strcpy_P(wii5BufferString, (PGM_P) F("Success but slow - e.g. long time to load SD or slow gps lock")); break; // , // SLOW - SUCCESS in all ways, but slower than expected. Good for SD, Maths boot, GPS lock etc
case WII5STS_POWER: strcpy_P(wii5BufferString, (PGM_P) F("Power management issue. E.g. serial data still coming in after power pin off")); break; // , // Works, but power management may have failed (e.g. serial data still coming in when power off)
case WII5STS_TIMEOUT: strcpy_P(wii5BufferString, (PGM_P) F("Timeout no idea why...")); break; // , // Literally timed out. No failure we can actually understand.
case WII5STS_OUTOFRANGE: strcpy_P(wii5BufferString, (PGM_P) F("Out of range, e.g. volts or temperature")); break; // , // Out of range - voltage, or GPS or something out of range
case WII5STS_UNKNOWN: strcpy_P(wii5BufferString, (PGM_P) F("Unknown error")); break; // , // Bad, unknown error
default: setBufferUnknown(); break;
}
return wii5BufferString;
};
WII5Strings wii5Strings;
+56
View File
@@ -0,0 +1,56 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5Strings.h
* @brief String-formatting helpers: strDriver, strStatus, strError, etc.
*/
#ifndef WII5Strings_h
#define WII5Strings_h
#include <WII5.h>
#include <WII5Strings.h>
class WII5Strings {
public:
WII5Strings() {}
virtual WII5_CONTROLLERS controllerId() {return WII5CONTROLLER_STRINGS;}
WII5_COMMANDS parseCommand(char *in1, char *in2 = NULL, char *in3 = NULL);
WII5_DEVICES parseDevice(char *in);
WII5_DRIVERS parseDriver(char *in);
WII5_PORTS parsePort(char *in);
WII5GPS_MODES parseGPSMode(char *in);
WII5_SWITCH parseSwitch(char *in);
WII5_MODES parseMode(char *in);
// char* strDevice();
char* strCommand(WII5_COMMANDS c);
char* strDevice(WII5_DEVICES d);
char* strDriver(WII5_DRIVERS d);
char* strPort(WII5_PORTS p);
char* strStatus(WII5_STATUS s);
char* strError(WII5_ERRORS e);
char* strSwitch(WII5_SWITCH s);
char* strGPSMode(WII5GPS_MODES m);
char* strPinMode(uint8_t p);
char* strState(uint8_t p);
char* strMode(WII5_MODES p);
char* strPinArduinoName(uint8_t p);
char* strPinWII5Name(uint8_t p);
char* strSelfTestStatus(WII5SELFTEST_STATUS in);
char* strSelfTestStatusLong(WII5SELFTEST_STATUS in);
char* strSelfTestDevice(WII5SELFTEST_DEVICES in);
protected:
// TODO - remove anything hard coded
// TODO - use protected memory - strncpy et al
void setBufferUnknown();
};
extern WII5Strings wii5Strings;
#endif
+191
View File
@@ -0,0 +1,191 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5Weather_18B20.cpp
* @brief Dallas DS18B20 temperature sensor driver (OneWire).
*/
/*
WII5Weather_18B20
TODO 2024 - Check when this is running as it blocks all other use
TODO:
Should be Weather / Temperature then drivers.
WII5Weather
WII5Weather::Temperature
age()
value();
value({n}) - which sensor?
WII5Weather::Pressure
WII5Weather::Humidity
...
*/
#include <Arduino.h>
#include <WII5Weather_18B20.h>
OneWire ds(WEATHER_18B20_DATA);
void WII5Weather_18B20::begin() {
powerSetPin(POWER_WEATHER_18B20_PIN, POWER_WEATHER_18B20_ON);
powerOff(true); // Force power off at boot
stepWait = 300001;
age = WII5TIME_1WEEK * 1000;
}
void WII5Weather_18B20::loop() {
// WII5 - removing this loop for now, temperature has to be an active decision.
/*
// If at lest been 5 minutes (maybe make this bigger later)
if (stepWait > 300000) {
if ( (minute() > 0) && ((minute() % 7) == 0) ) {
if (wii5Sparton.captureRunning()) {
console.log(LOG_DEBUG, F("Weather: Skipped - capture running. Min=%d"), minute());
}
else {
// REMEMBER - This blocks a bit.
if (temperatureRead()) {
console.log(LOG_DEBUG, F("Weather: Temperature Read. Min=%d"), minute());
}
else {
console.log(LOG_DEBUG, F("Weather: Failed to read temperature (unknown). Min=%d"), minute());
}
}
stepWait = 0;
}
}
*/
}
// TODO - This is a blocking libary. Fix it.
bool WII5Weather_18B20::temperatureRead() {
// (check we have nothing else on) return false;
powerOn();
// Give power time
delay(250);
currentTemperature = 2000;
console.log(LOG_DEBUG, F("currentTemperature=%d"), int(currentTemperature));
ds.reset_search();
// TODO Rework code - make it so it will seaerch over 10 minutes or so
// But then just queries those, no more searching, or maybe research and
// try and find more devices once every hour.
// What about lost devices?
/*
inside OneWire seems to be only small delayMicroseconds.
With the exception of one 400 - (half 1 millisecond)
This means that we could easily write a state machine here with a 1ms loop time
Is that fast enough for other things - then this can be non blocking, even for
large number of temperature sensors.
*/
for(byte count = 0; count < 3; count++) {
if ( ds.search(addr)) {
ds.reset();
ds.select(addr);
ds.write(0x44, 1); // start conversion, with parasite power on at the end
// TODO Gah... this should be background !
// wii5Controller.safeDelay(1000); // maybe 750ms is enough, maybe not
delay(1000);
ds.reset();
ds.select(addr);
ds.write(0xBE); // Read Scratchpad
for ( byte i = 0; i < 9; i++) // we need 9 bytes
data[i] = ds.read();
int16_t raw = (data[1] << 8) | data[0];
byte cfg = (data[4] & 0x60);
if (cfg == 0x00) raw = raw & ~7; // 9 bit resolution, 93.75 ms
else if (cfg == 0x20) raw = raw & ~3; // 10 bit res, 187.5 ms
else if (cfg == 0x40) raw = raw & ~1; // 11 bit res, 375 ms
currentTemperature = (float)raw / 16.0;
// FIND and STORE them
/*
bool found = false;
for (byte i = 0; i < countTemperature; i++) {
if (ByteArrayCompare(addr, lastTemperature[i].addr, 8)) {
found = true;
lastTemperature[i].age = 0;
// Convert to Decidegrees
lastTemperature[i].celsius = currentTemperature;
}
}
*/
/*
if (!found) {
if (countTemperature < maxTemperature) {
lastTemperature[countTemperature].age = 0;
// Convert to Decidegrees
lastTemperature[countTemperature].celsius = currentTemperature;
memcpy( lastTemperature[countTemperature].addr, addr, 8 ); // TODO length?
countTemperature++;
}
else {
console.printf(F("ERROR: Maximum temperature probes found\r\n"));
// TODO Too many errors this requires a complete reset
// TODO Make it so if we get this 10 times, we reset the
// whole list and start again. e.g. change countTemperature = 0
// Reset for next time...
countTemperature = 0;
return true;
}
}
*/
// Temporary hard coded to one sensor
if (count == 0) {
console.log(LOG_DEBUG, F("Temperature: %02x%02x%02x%02x%02x%02x%02x%02x count=%d currentTemperature=%d.%02d*"),
addr[0],
addr[1],
addr[2],
addr[3],
addr[4],
addr[5],
addr[6],
addr[7],
count,
int(currentTemperature),
abs(int(currentTemperature * 100) % 100)
);
}
}
}
// copy to rest of places
powerOff();
console.log(LOG_DEBUG, F("currentTemperature=%d"), int(currentTemperature));
// TODO value is int32_t, but int16_t should be heaps?
value = int(currentTemperature * 100);
age = 0;
wii5Display.atCommandSend(F("status"), F("temperature=%d"),
int(value)
);
wii5Display.atCommandSend(F("status"), F("update"));
// TODO ?Move
return true;
}
WII5Weather_18B20 wii5Weather_18B20;
+42
View File
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5Weather_18B20.h
* @brief Dallas DS18B20 temperature sensor driver (OneWire).
*/
#ifndef WII5Weather_18B20_h
#define WII5Weather_18B20_h
#include <Arduino.h>
#include <WII5.h>
#include <OneWire.h>
class WII5Weather_18B20 : public WII5Power {
public:
WII5Weather_18B20() {}
virtual WII5_CONTROLLERS controllerId() {return WII5CONTROLLER_DRIVER;}
virtual WII5_DRIVERS driverId() {return WII5DRIVER_WEATHER_18B20;}
void begin();
void loop();
bool temperatureRead();
elapsedMillis age;
int32_t value;
protected:
byte data[12];
byte addr[8];
float currentTemperature;
elapsedMillis stepWait;
};
extern OneWire ds;
extern WII5Weather_18B20 wii5Weather_18B20;
#endif
+49
View File
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5_board.h
* @brief Board-selection shim: forwards to the active board's pin/peripheral header.
*/
/*
WII5 Board File !
This is included in all other files. In here we can change which board
file we load. As much as possible this should be automatic, to allow switching
between boards easily.
*/
// LOCAL - What is this?
#define WII5_DEVICE_TYPE "WII5WaveBuoy"
#define WII5_DEVICE_SUBTYPE "PiperC"
#define WII5_SOFTWARE_NAME "WII5_Buoy"
// Defaults - change these at build
#ifndef WII5_SOFTWARE_VERSION
#define WII5_SOFTWARE_VERSION "WII5Buoy_5.5.12"
#endif
#ifndef WII5_SOFTWARE_COMMIT
#define WII5_SOFTWARE_COMMIT "Unknown"
#endif
#ifndef WII5_SOFTWARE_INTVER
// a * 65536 + b * 256 + c
// 5 * 65536 + 5 * 256 + 4 = 327680 + 1280 + 4 = 328964
#define WII5_SOFTWARE_INTVER 328972
#endif
// 2019-05 - First board build for WII5
// Currently only the WII5 v2 board is supported. Earlier prototypes (v1,
// Arduino Mega devboard, Moteino Mega, M5Stick C) lived in this tree but
// have been retired; see git history for those headers.
#if defined (__WII5_V02__)
#include <WII5_board_v2.h>
#else
#error "WII5 BOARD SELECTION = No board definition (expected __WII5_V02__)"
#endif
+439
View File
@@ -0,0 +1,439 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file WII5_board_v2.h
* @brief Pin and peripheral configuration for the WII5 v2 hardware revision.
*/
/*
WII5 Board = WII5v2.0..v5.0 - This is current configuration used in V5.5 board in 2024
* TODO:
* Update all PINS
* Remove unused PINS
* Test
* Test V1 board is still working
Define PINS and pin Modes, Serial ports etc
Board Versions
v1 - Hardware / Layout, just increment each time
v1.1 - Second number is changes locall - e.g. v1.1 might use a Software Serial for Matrix input
mega_dev - Development board using a Sparkfun 3.3V 8Mhz Arduino Mega
File Structure
* Names for all ports etc used on this board.
NOTE: Do we need WII5 prefix?, e.g. WII5_PIN_D0_NAME
Initial Build test using 595eb5a
Sketch uses 115740 bytes (44%) of program storage space. Maximum is 258048 bytes.
Global variables use 6667 bytes (81%) of dynamic memory, leaving 1525 bytes for local variables. Maximum is 8192 bytes.
Sketch uses 115740 bytes (44%) of program storage space. Maximum is 258048 bytes.
Global variables use 6667 bytes (81%) of dynamic memory, leaving 1525 bytes for local variables. Maximum is 8192 bytes.
Improved strings
Sketch uses 115738 bytes (44%) of program storage space. Maximum is 258048 bytes.
Global variables use 6661 bytes (81%) of dynamic memory, leaving 1531 bytes for local variables. Maximum is 8192 bytes.
Sketch uses 115914 bytes (44%) of program storage space. Maximum is 258048 bytes.
Global variables use 6647 bytes (81%) of dynamic memory, leaving 1545 bytes for local variables. Maximum is 8192 bytes.
Free Memory
213/8192 really of 213 of 1545 - so we still have 1200 bytes dynamic created ! Find and destory
SUB 1
left
right
SUB 3
left
2 - D3
5 - D27
right
3 - D9
4 - D12
5 - D62
Shared 5 Volts:
* Maths - now has its own supply, not shared
* Needs 5 Volts:
* Sparton
* Iridium
* Beeper
*/
// Enable extended EEPROM code
#define WII5_EEPROMex
#define WII5_BOARD_ID 102
#define WII5_BOARD_NAME "WII5_V5.0"
#define WII5_BOARD_CPU "AVR25560"
#define WII5_BOARD_MEMORY 8192
#define WII5_BOARD_EEPROM 4096
#ifdef F_CPU
#define WII5_BOARD_CLOCK F_CPU
#else
#define WII5_BOARD_CLOCK 8000000L
#endif
// ----------------------------------------------------------------------
// Sh3d stuff - how to pass in.
// auto_node_id = 'manual'
// ----------------------------------------------------------------------
// Primary Hardware Components
// ----------------------------------------------------------------------
// Small packet radio
// #define WII5_RADIO
// #define WII5_RADIO_LORA
// #define WII5_RADIO_69
// RTC Configuration
// #define WII5_RTC
// TODO 2024 review
// (Removed 2024, board 5.5.5) #define WDT_RESET 59
// #define WII5_WDT_INTERNAL
#define WII5_WDT_EXTERNAL
// WDT External
#define WDT_RESET 59
#define WII5_LOWPOWERSLEEP
// Test and Self Test etc (TODO 2024 - consider to save memory etc)
#define WII5_ENABLE_MANUALTEST
#define WII5_ENABLE_SELFTEST
// GPS Configuration
#define WII5_GPS
// #define WII5_GPS_COMPASS // Compass specific version - some differences
// COMMS - aka GSM Modem or Iridium Satellite
#define WII5_COMMS
#define WII5_COMMS_IRIDIUM // Iridium version of COMMS Driver
// #define WII5_COMMS_GSMSMS // GSM SMS version
// IMU
#define WII5_IMU
#define WII5_IMU_SPARTON // SPARTON aka Serial IMU
// #define WII5_IMU_MPU9250 // 9250 aka I2C 9250
// Weather - Temperature, pressure, humidity and much more
#define WII5_WEATHER_18B20 // Multiple Weather, incluing standard 18B20
// #define WII5_WEATHER_ANEMOMETER
#define WII5_STORAGE_SDBLOCK
#define WII5_BUTTON_HOLD_TIME 10000 // milliseconds
// ----------------------------------------------------------------------
// BUFFER SIZES
// (see Iridium instead) #define WII5_BUFFER_CONSOLE_BINARY 20
#define WII5_BUFFER_CONSOLE_PRINT 220
#define WII5_BUFFER_CONSOLE_CMD 50
#define WII5_BUFFER_RADIO 150
#define WII5_BUFFER_STRING 50
#ifdef WII5_COMMS_IRIDIUM
// NOTE: Max receive = 270, Max send = 340
#define WII5_BUFFER_IRIDIUM 80
#define WII5_IRIDIUM_BIN_MAX 340
#endif
#ifdef WII5_GPS
#define WII5_BUFFER_GPS 80
#endif
#define WII5_FIELD_MAX 15
#define WII5_TIMERLAPS 5
// ----------------------------------------------------------------------
// Convert Float to Integers etc
#define GPS_POS_MULT 100000
#define GPS_ALT_MULT 100
// See BATTERY
// ----------------------------------------------------------------------
// SERIAL
#define SerialConsole Serial
#ifdef F_CPU_11
// #define SerialConsole_Baud 115200
#define SerialConsole_Baud 230400
#else
#define SerialConsole_Baud 57600
#endif
#define SerialConsole_RX 0
#define SerialConsole_TX 1
#define SerialGPS Serial2
#define SerialGPS_Baud 9600
// Comms = Iridium here
#define SerialComms Serial3
#define SerialComms_Baud 19200
// IMU = Sparton
#define SerialIMU Serial1
#ifdef F_CPU_11
#define SerialIMU_Baud 115200
#else
#define SerialIMU_Baud 57600
#endif
// NOTE - Test these and all pins
// Software Serial Devices
// Not all pins on the Mega and Mega 2560 support change interrupts,
// so only the following can be used for RX:
// 10, 11, 12, 13, 14, 15, 50, 51, 52, 53,
// A8 (62), A9 (63), A10 (64), A11 (65), A12 (66), A13 (67), A14 (68), A15 (69).
// STATUS SERIAL - a low level interface
// #define WII5_STATUS_SERIAL
// #define WII5_STATUS_SERIAL_RX 11
// #define WII5_STATUS_SERIAL_TX 8
// #define WII5_STATUS_SERIAL_BAUD 9600
// DEBUG SERIAL Port - to SUB BOARD 1
// #define WII5_DEBUG_SERIAL
// #define WII5_DEBUG_SERIAL_CONSOLE // Add to Console, and mark main as OUTPUT
// #define WII5_DEBUG_SERIAL_RX SoftwareSerial5_RX
// #define WII5_DEBUG_SERIAL_TX SoftwareSerial5_TX
// #define WII5_DEBUG_SERIAL_RX 37
// #define WII5_DEBUG_SERIAL_TX 36
// #define WII5_DEBUG_SERIAL_BAUD 9600
// MOVE Maths to SoftwareSerial 6
// #define WII5_MATHS_SERIAL
/*
Testing
35, 34 - no
34, 35 - Yes (I could have it backwards on the begin?)
D64, D65
*/
// #define WII5_MATHS_SERIAL_RX 64
// #define WII5_MATHS_SERIAL_TX 65
// #define WII5_MATHS_SERIAL_BAUD 57600
// ----------------------------------------------------------------------
// Buttons and LEDjson2csv');
#define LED_1 13
#define LED_1_ON HIGH
#define LED_2 41
#define LED_2_ON HIGH
#define LED_3 0
#define LED_3_ON LOW
#define LED_4 0
#define LED_4_ON LOW
#define BUTTON_1 67
#define BUTTON_1_ON LOW
#define BUTTON_2 45
#define BUTTON_2_ON LOW
#define BUTTON_3 0 // Connects to GPIO 18 on the Pi
#define BUTTON_3_ON LOW
#define BUTTON_4 0
#define BUTTON_4_ON LOW
// ----------------------------------------------------------------------
// Display / Debug - these pins can be used with LED & Buttons
// - OR as a buffer / output for single wire LED grids
#define LCD_1 69
#define LCD_2 68
#define LCD_3 67
#define LCD_4 66
#define LCD_5 65
#define LCD_6 64
// ----------------------------------------------------------------------
// Power Management
#define POWER_MATHS_PIN 29
#define POWER_MATHS_ON HIGH
#define POWER_RADIO_PIN 42
#define POWER_RADIO_ON HIGH
#define POWER_COMMS_PIN 30
#define POWER_COMMS_ON HIGH
#define POWER_GPS_PIN 56
#define POWER_GPS_ON HIGH
#define POWER_STROBE1_PIN 33
#define POWER_STROBE1_ON HIGH
#define POWER_STROBE2_PIN 32
#define POWER_STROBE2_ON HIGH
#define POWER_IMU_SPARTON_PIN 25
#define POWER_IMU_SPARTON_ON HIGH
#define POWER_WEATHER_18B20_PIN 6
#define POWER_WEATHER_18B20_ON HIGH
#define POWER_WEATHER_PIN 39 // NC
#define POWER_WEATHER_ON HIGH
#define POWER_STORAGE_1_PIN 54
#define POWER_STORAGE_1_ON HIGH
#define POWER_STORAGE_2_PIN 0
#define POWER_STORAGE_2_ON HIGH
#define POWER_BUZZER_PIN 41
#define POWER_BUZZER_ON HIGH
// Shared 5 volt piY_
#define SHARED_5VOLT_PIN 28
#define SHARED_5VOLT_ON HIGH
// ----------------------------------------------------------------------
// Battery Management
#define BATTERY_1_VOLTS_PIN 35
#define BATTERY_1_VOLTS_ON HIGH
#define BATTERY_1_VOLTS_ANALOG A14
//#define BATTERY_2_VOLTS_PIN 36
//#define BATTERY_2_VOLTS_ON HIGH
//#define BATTERY_2_VOLTS_ANALOG A15
// 3.027 / 3.3
// #define BATTERY_1_VOLTS_MULT 1320
#define BATTERY_1_VOLTS_MULT 1300
//#define BATTERY_2_VOLTS_MULT 1300
/*
1024 * 3027 / 3300
980
*/
// What do we need to do to turn it into DeciVolts (100th of a volt) as integer
// TODO Hard coded vs Config calibration of Voltage
/*
#define BATTERY_2_PIN 58
#define BATTERY_2_ON HIGH
#define BATTERY_2_VOLTS_PIN 56
#define BATTERY_2_VOLTS_ON HIGH
#define BATTERY_2_VOLTS_ANALOG A0
*/
// TODO Hard coded vs Config calibration of Voltage
// ----------------------------------------------------------------------
// Other analog sensors (e.g. Water)
// ----------------------------------------------------------------------
// SPI Storage
// - SPIMEMORY
// - SD - Single SD Card
// - SD2 - Two SD Cards - switched between boards
// - FRAM
#define STORAGE_PATH_TOP "data"
#define STORAGE_PATH_EXT ".txt"
#define STORAGE_PATH_META "meta"
#define STORAGE_PATH_CONSOLE "console"
#define STORAGE_PATH_DATA "data"
#define STORAGE_TYPE_SD2
#define STORAGE_SPI SPI
#define STORAGE_CS 53
#define STORAGE_SCK 52
#define STORAGE_MOSI 51
#define STORAGE_MISO 50
#define STORAGE_SD1_MATHS_PIN 58
#define STORAGE_SD1_MATHS_ON LOW
#define STORAGE_SD2_MATHS_PIN 57
#define STORAGE_SD2_MATHS_ON LOW
// ----------------------------------------------------------------------
// IMU - Serial (Sparton)
// See also POWER_IMU_SERIAL*
#define SPARTON_RESET 26
// #define SPARTON_INT 30
#define SPARTON_HZ 8 // 8 or 64 only !
#define SPARTON_MS (1000 / SPARTON_HZ) // Milliseconds, approx
// ----------------------------------------------------------------------
// IMU - 9250
// See also POWER_IMU_9250*
// + I2C
// ----------------------------------------------------------------------
// Weather and Other Special Hardware
#define WEATHER_18B20_DATA 5
// ----------------------------------------------------------------------
// Radio (aka LoRa - not used in WII5 2024)
// See also POWER_RADIO_*
#define RADIO_TYPE_RF95
#define RADIO_INT 2
#define RADIO_SPI SPI
#define RADIO_CS 43
#define RADIO_SCK 52
#define RADIO_MOSI 51
#define RADIO_MISO 50
// ----------------------------------------------------------------------
// Communications / Iridium et al
// See also POWER_COMMS* + Serial
#define COMMS_NETWORK 31
// ----------------------------------------------------------------------
// Maths
#define MATHS_RETURNLINE 4 // Connects to GPIO 18 on the Pi, PIN 12
#define MATHS_RETURNHOLD 3600 // Return HOLD for max 1 hour
#define MATHS_MAXHOLD 43200 // aka 12 hours
// ----------------------------------------------------------------------
// RTC
// See also POWER_RTC
#define RTC_ALARM 24
#define META_NC 20000
#define META_VOLTS_12 10012
#define META_VOLTS_5 10005
#define META_VOLTS_3 10003
#define META_VOLTS_SWITCHED 10001
#define META_GROUND 10000
#define META_I2C_SDA 20001
#define META_I2C_SCL 20002
#define META_SPI_MOSI 20003
#define META_SPI_MISO 20004
#define META_SPI_SCK 20005
// SubBoard 1
#define SUBBOARD_1_LEFT_1 META_VOLTS_12
#define SUBBOARD_1_LEFT_2 META_SPI_SCK
#define SUBBOARD_1_LEFT_3 META_SPI_MOSI
#define SUBBOARD_1_LEFT_4 META_SPI_MISO
#define SUBBOARD_1_LEFT_5 33
#define SUBBOARD_1_LEFT_6 META_GROUND
#define SUBBOARD_1_RIGHT_1 META_VOLTS_SWITCHED // controlled by 32
#define SUBBOARD_1_RIGHT_2 META_VOLTS_3 // Always on
#define SUBBOARD_1_RIGHT_3 7 // TX4
#define SUBBOARD_1_RIGHT_4 10 // RX4
#define SUBBOARD_1_RIGHT_5 66
#define SUBBOARD_1_RIGHT_6 META_GROUND
// SubBoard 2
#define SUBBOARD_2_LEFT_1 META_VOLTS_12
#define SUBBOARD_2_LEFT_2 META_NC
#define SUBBOARD_2_LEFT_3 META_NC
#define SUBBOARD_2_LEFT_4 META_NC
#define SUBBOARD_2_LEFT_5 23
#define SUBBOARD_2_LEFT_6 META_GROUND
#define SUBBOARD_2_RIGHT_1 META_VOLTS_SWITCHED // controlled by 32
#define SUBBOARD_2_RIGHT_2 META_VOLTS_3 // Always on
#define SUBBOARD_2_RIGHT_3 8 // TX5
#define SUBBOARD_2_RIGHT_4 11 // RX5
#define SUBBOARD_2_RIGHT_5 65
#define SUBBOARD_2_RIGHT_6 META_GROUND
// SubBoard 3
#define SUBBOARD_3_LEFT_1 META_VOLTS_12
#define SUBBOARD_3_LEFT_2 3
#define SUBBOARD_3_LEFT_3 META_I2C_SDA
#define SUBBOARD_3_LEFT_4 META_I2C_SCL
#define SUBBOARD_3_LEFT_5 27
#define SUBBOARD_3_LEFT_6 META_GROUND
#define SUBBOARD_3_RIGHT_1 META_VOLTS_3 // Controlled by D61 (POWER_SUBBOARD_3_PIN)
#define SUBBOARD_3_RIGHT_2 META_VOLTS_3 // Always on
#define SUBBOARD_3_RIGHT_3 9
#define SUBBOARD_3_RIGHT_4 12
#define SUBBOARD_3_RIGHT_5 62
#define SUBBOARD_3_RIGHT_6 META_GROUND
// Timing testing
// #define TIMING_SPARTON_RUN SUBBOARD_3_LEFT_2
// #define TIMING_SPARTON_CHAR SUBBOARD_3_RIGHT_3
// #define TIMING_SPARTON_RECORD SUBBOARD_3_RIGHT_4
// #define TIMING_SD_WRITE SUBBOARD_3_LEFT_5
+40
View File
@@ -0,0 +1,40 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file wii5_buoy.ino
* @brief Arduino sketch entry point: setup() and loop() for the WII5 buoy firmware.
*/
#include "WII5.h"
#include "WII5Controller.h"
void setup () {
// Put everthing we can into safe PIN mode (including driving high/low)
wii5Setup.beginSafe();
// Move to Setup - e.g. beginSafe(); (including safe pins below with proper names and #ifdef)
SerialConsole.begin(SerialConsole_Baud);
#ifdef WAITBOOT_HALFSECOND
SerialConsole.println(F("# WII5: Waiting for 1/2 seconds in boot mode, to allow ICSP"));
SerialConsole.flush();
delay(500);
#endif
// Blocking call - 3 seconds
wii5Setup.bootbeep();
// Do first always...
wii5Setup.begin();
// Normal WII startup
wii5Controller.begin();
}
void loop() {
wii5Controller.loop();
}
+45
View File
@@ -0,0 +1,45 @@
# External References
Vendor documentation that the WII5 buoy firmware was developed against.
These documents are not redistributed in this repository (their licensing is
unclear); refer to the vendor sites for current copies.
## Iridium 9602 / 9603 SBD modems
Used for satellite telemetry. Key reference is the **Iridium 9602 / 9603
SBD Transceiver Developer's Guide** (AT-command set, SBD message lifecycle,
binary message framing for `+SBDWB` / `+SBDI`).
- Iridium developer portal: https://www.iridium.com/services/iridium-sbd/
- Search the Iridium partner network for the current "9602/9603 Developer
Guide" PDF.
Relevant code: `WII5Iridium.cpp`, `WII5SerialManager.cpp` (`+SBDWB`,
`+SBDI`, `+CSQ` parsing), `WII5BinData.*` (340-byte message format).
## Sparton AHRS-M1 / AHRS-M2
Used as the primary IMU / wave-motion sensor. Key references:
- AHRS-M1 / AHRS-M2 Software Interface Control Document
- AHRS-M1 / AHRS-M2 Hardware ICD
- NorthTek Programming Manual (Sparton's Forth-like configuration language;
the `programLine()` helpers in `WII5SerialManager.cpp` emit NorthTek
commands).
- Sparton (now Bel Power Solutions / part of Bel Fuse) documentation:
https://www.belfuse.com/
Relevant code: `WII5Sparton.cpp`, `WII5Setup.cpp` (compass/AHRS init).
## Other components
- **Dallas DS18B20** — temperature sensor:
https://www.analog.com/en/products/ds18b20.html
(Used in `WII5Weather_18B20.cpp` via the OneWire / DallasTemperature
Arduino libraries.)
- **TinyGPS++** — NMEA parser library:
https://github.com/mikalhart/TinyGPSPlus
- **u-blox NEO-6M / NEO-7M** — GPS module datasheets (NMEA reference):
https://www.u-blox.com/
+218
View File
@@ -0,0 +1,218 @@
Final Modes for WII5 Tasmania Release
* Incidental ones - used to transition the modes in the state machine
* BOOT
* START
* Demo / Testing modes - these must not be used in prod, but will have to have just timeouts
(these modes do not have Iridium and are unreliable, they must exit)
* DEMO
* MANUALTEST
* SELFTEST
* Fully functional modes - each of these has full control, metadata, and Iridium
* SLEEP - as deep as possible, basically off. But will come on every 12 hours
* LOWBATTERY - Automatically selected, no matter what the mode
* POSITION - Send the position over and over
* CAPTURE - Full controlled and capture mode
Original Notes:
Top Level Modes
Most of the buoy uses a lot of memory. But can be rebooted to other states.
And most of the memory is using MALLOC
Boot up stages
WII5MODE_BOOT - Boot up only once
. Loading configuration from EEPROM etc only needs to be done once
. Long processes, like reading the blocks from the SD Cards
SET - NA
AUTO - Boot up
WII5MODE_START - Probably jump here each time waking from sleep etc
. GPS, RTC, and other updates that need to be regularly checked can be done here
SET - NA
AUTO - Start ?
WII5MODE_OFF, - OFF - Disable everything and go into deep sleep, don't wake, back to sleep
. Really just a mode that allows us to stay here - (e.g. switch is off)
SET - NA
AUTO - Switch position
WII5MODE_SLEEP, - Same as OFF, but will wake with button, alarm, and every 12 hours
. Sleep - wake can be from button, network, time is up, alarm hit (RTC), every 12 hours and more
. Allowing more complicated decisions, e.g. long press to move to MANUAL TEST mode or Force Capture test
-> REPORT - If 12 hoursly updates
-> TemporaryMode - If still not expired
-> mode would be - POSITION or CAPTURE or SLEEP
-> DefaultMode - Otherwise
-> mode would be - POSITION or CAPTURE or SLEEP
SET - expires
val1 = ReportPeriod - How many minutes between reports. Default is 720 (12 hours)
NOTE: No way to go back to temporary mode right now. Consider that?
AUTO - NA
WII5MODE_LOWBATTERY, - Automatically set mode based on Battery Management
. A command mode change can force to MANUALTEST, or others but only for a maximum time period
SET - NA
AUTO - From Battery Module - not saved, recalculated all the time
WII5MODE_POSITION, - Position mode - light weight, no Maths, regular updates
SET - expires
val1 = ReportPeriod - How many minutes between sending position (default 15 minutes)
val2 = StrobeModes - e.g. Turn on strobe never, 5 minutes per hour, 1 minute at every send, etc
NOTE: No way to go back to temporary mode right now. Consider that?
AUTO - NA
WII5MODE_REPORT, - Report - this is like position, but a single report, and back to sleep
MINI Self Test or Requested Self Test
SET - NA
AUTO - Done from sleep, returns back to SLEEP - which will test etc
WII5MODE_CAPTURE, - Normal full capture mode.
SET - expires
val1 - RunPeriod - Minutes between - 15 (every 15 mins), 60 (once an hour), 120 (every 2 hours)
val2 - Records - How many records do we want (needs to be what is needed by maths)
val3 - Predefined sets of data to send back for Iridium - this is going to be a
bit mask. So we can have 32 sets of data we can add. Some might be contray to
each other. e.g. 1 might be low resolution temperature, but 2 high resolution,
so no point adding both.
New simpler Binary module - since now we only need to know this 32 bit number,
to know what is in the rest of the packet - and new types of data get added to the
end..... maybe.
AUTO - NA
WII5MODE_MANUALTEST - Manual test - each power button, module etc, can be toggled.
. Leaving this mode - we should consider a software reboot as the modes and hardware will be in an unknown state
SET - (restrict to set from Serial and not Iridium)
AUTO - NA
WII5MODE_SELFTEST - Self Test mode allows running through all the self tests automatically and rebooting
SET - (restrict to set from Serial and not Iridium)
val1 - ?
AUTO - NA
Changing Modes:
* Code:
- wii5Controler.setMode(WII5MODE_MANUALTEST); // Forever - write to config
- wii5Controler.setTemporaryMode(WII5MODE_MANUALTEST); // Until reboot (ie, not to config)
- wii5Controler.setTemporaryMode(WII5MODE_MANUALTEST, 300); // 5 minutes
* Iridium
- mode:ModeName:Seconds(0 forever):option1,option2,option3
(NOTE: Could we support all serial commands here?)
(NOTE: Could we make it a specific one line reply is always made)
@WII5!,mode,... e.g. it is WII5! for ACK and then the first string, e.g. mode, rest is optional
@WII5,hello,From,Here would be therefore @WII5!,hello,My,Reply
* Serial
- @WII5,mode,ModeName,Seconds,option1,option2,option3
* Modules Logic
- WII5Battery - force to LOWBATTERY mode (never saved), switch back to Capture when it can (or what ever default)
Boot Mode: (how do we know where to start)
(first one to match)
* Check the OFF Switch - if it is active, WII5MODE_OFF
. We don't actually need to do this one, if OFF switch active in loop, it will set it no matter what the mode.
* Check Battery
. Special option in config to ignore the battery for a period of time.
. If battery too low, switch to low power mode (and not ignored)
* Sleeping
. Read the sleeping configuration from memory or EEPROM
. Check the date / time for it has not yet expired (check time is synced)
. Back to sleep
* TemporaryMode
. Read Temporary mode - from memory or EEPROM
. Check the date / time for it has not yet expired (check time is synced)
. Use this mode
* DefaultMode
. Back to default mode
Configuration Options Per mode:
* WII5MODE_BOOT
. NA
* WII5MODE_START
. NA
* WII5MODE_OFF
. NA
* WII5MODE_SLEEP
. When to wake up (time in future, or number of seconds from now)
* WII5MODE_WAKE
. NA
* WII5MODE_REPORT
. NA
* WII5MODE_LOWBATTERY
. NA
* WII5MODE_POSITION
. Frequency (how many minutes apart)
. Strobe Rules - instead of just on/off - strobe can have rules in here
* WII5MODE_CAPTURE
. Number of seconds/minutes/records to caputre
. Process flags - Pass in some optional extra flags on processing and what you want returned via Iridium
. When to start (minute in hour or some way of multiple per hour, half hour, quarter hour)
. Hours between too
. Examples:
. Start 5 minutes past the hour, ever 3 hours (ie. 00, 03, 06, 09 ...)
. Run every 15 minutes for one hour, then sleep for an hour (00:00, 00:15, 00:30, 00:45, sleep... 02:00, 02:15, ...)
. etc.
* WII5MODE_MANUALTEST
. NA
* WII5MODE_SELFTEST
. NA - might have some subsets in the future
Main Modes:
* OFF
* A rare mode where the device is completely off.
* Exit = ? Physical butto?
* Time?
* Sleep
* Deep down sleep, don't take up much power
* WakeUp
* Check why we got woken up?
* User input button
* Back to old mode after sleep (period hit)
* Iridium input
* Period wake up - send status, che
* Exit - always does end of this, back to sleep
* LowPower
* Special mode, keep everything really short
* Send Data
* Sleep
* Position
* Capture
* Capture Start will define what is checked, e.g. It might not allow iridium
* In that case, Iridium NMEA commands should return correct error to wait
* NOTE: This is not capture itself
* ManualTest
* Stay in this mode (max tme, maybe 4 hours) to test
* Allow manual start/stop of processes
Exclusive vs Together:
* Classic example...
* Start capture
* In the background the Maths processor is still dealing with the old data.
* And it sends Iridium commands
* And capture needs metadata such as GPS.
* So Sparton serial in, SD writes, Iridium sending data, GPS on. All not kind of related.
Sequence:
Boot
Load up configuration from EEPROM, start devices etc
SelfTest
A built in self test that takes under 1 minutes. It will just set a Simple
error we can use to update the LED
Run
Sleep
Run Modes
+448
View File
@@ -0,0 +1,448 @@
New reordered array
process1.part1float
0..3 hz_max
4..7 hcm_max
8..11 htm_max
12..15 tz_mean
16 tp
17 hmod
18..72 psd
process2.parse2float
0..7 moment
8..16 ...
// 4
for (i=0;i<2*k;i++) fprintf(ido_o,"%LF\n",return_data->hz_max[i]);
// 4
for (i=0;i<2*k;i++) fprintf(ido_o,"%LF\n",return_data->hcm_max[i]);
// 4
for (i=0;i<2*k;i++) fprintf(ido_o,"%LF\n",return_data->htm_max[i]);
// 4 16
for (i=0;i<2*k;i++) fprintf(ido_o,"%LF\n",return_data->tz_mean[i]);
// 1
fprintf(ido_o,"%LF\n",return_data->tp);
// 1 18
fprintf(ido_o,"%LF\n",return_data->hmod);
// n_psd = 55 73
for (i=0;i<n_psd;i++) fprintf(ido_o,"%LF\n",return_data->psd[i]);
// n_mom = 7
for (i=0;i<n_mom;i++) fprintf(ido_o,"%LF\n",return_data->moment[i]);
// 8
fprintf(ido_o,"%d\n",return_data->qflg_open_water);
fprintf(ido_o,"%d\n",return_data->qflg_kist);
fprintf(ido_o,"%d\n",return_data->qflg_imu);
fprintf(ido_o,"%d\n",return_data->qflg_pkist);
fprintf(ido_o,"%d\n",return_data->qflg_accel);
fprintf(ido_o,"%d\n",return_data->qflg_gyro);
fprintf(ido_o,"%d\n",return_data->qflg_magno);
fprintf(ido_o,"%d\n",return_data->qflg_head);
// 54 62
155 lines
On average these are all floats - 4 Bytes
620 bytes - which does not fit into one block
OR:
101 * 4
54 * 2
= 512 - exactly the block size!
psd 55 = 220
Normal output from WII3/4
* All 4 byte float unless specified here
* direction - 54 * Int (2 byte) - total 108
* psd - 55 * float (4 byte) - total 220
hz_max: [1, 4], // Maximum wave height (trough - crest) in a 10 min segment
Double
4 * 4
= 16
hcm_max: [5, 8], // Maximum height of a crest above mean water level in a 10 min segment
Double
4 * 4
= 16
htm_max: [9, 12], // Maximum depth of a trough below mean water level in a 10 min segment
Double
4 * 4
= 16
tz_max: [13, 16], // Mean wave period in a 10 min segment
Double
4 * 4
= 16
tp: 17, // Peak period from the full (non averaged) spectra
Double
4
= 4
hmo: 18, // 4*standard deviation of elevation
Double
4
= 4
psd: [19, 73], // Using 1/f_avg = frequency
Double
55 * 4
= 220
moments: [74, 80], // Spectral moments
Double
7 * 4
= 28
theta: 81, // Mean wave direction
Double
4
= 4
dp: 82, // Peak wave direction
Double
4
= 4
s: 83, // Wave spread
Double
4
= 4
r: 84, // Ratio term to evaluate quality of wave direction approximation
Double
4
= 4
hs_dir:85, // Hs calculated using direction analysis
Double
4
= 4
a: 86, // Noise slope
Double
4
= 4
b: 87, // Noise intercept
Double
4
= 4
nstd: 88, // Noise standard deviation
Double
4
= 4
f2: 89, // Frequency cut-off
Double
4
= 4
qf_mvar: 90, // Quality flag: Mean vertical acceleration removed
Double
4
= 4
qf_kist: 91, // Quality flat: Kistler passed (0), Kistler failed (1)
Int
2
= 2
qf_imu: 92, // Quality flat: IMU passed (0), IMU failed (1)
Int
2
= 2
qf_p_kist: 93, // Quality flag: Percentage of flagged Kistler samples
Int
2
= 2
qf_p_accel: 94, // Quality flag: Percentage of flagged acceleration samples (averaged across 3 axis)
Int
2
= 2
qf_p_gyro: 95, // Quality flag: Percentage of flagged gyro samples (averaged across 3 axis)
Int
2
= 2
qf_p_mag: 96, // Quality flag: Percentage of flagged magno samples (averaged across 3 axis)
Int
2
= 2
qf_head: 97, // Quality flag: GPS heading passed (0), GPS heading failed (1)
Int
2
= 2
power_diff: 98, // Difference between power in the time domain and frequency domain
Double
4
= 4
yaw_std: 99, // Yaw standard deviation
Double
4
= 4
open_water: 100, // Open Water flag
Int
2
= 2
direction: [101, 154], // direction data
Int
54 * 2
= 108
492 bytes
11.787015
9.582090
12.528253
14.153369
7.735189
5.657926
4.293090
5.485378
-7.106659
-5.864575
-8.348605
-8.667991
29.714286
28.750000
28.055556
30.400000
32.000000
10.224887
11.823206
46.393283
50.172329
45.776413
96.298192
69.859577
37.341004
29.351810
17.013856
24.406505
10.531730
6.074609
4.671587
3.752939
6.758200
5.808995
5.183176
4.280694
1.921353
2.080787
1.444816
1.249582
1.329185
0.709623
1.501197
1.624132
0.683771
1.638818
0.252953
0.567005
0.321616
0.659857
0.243726
0.682648
0.268455
0.297780
0.281510
0.218103
0.302124
0.219463
0.273396
0.091317
0.122532
0.145382
0.038717
0.057687
0.044266
0.035344
0.020552
0.013517
0.007770
0.006380
0.004311
0.002205
0.001864
5740.408809
186.498311
6.548624
0.253406
0.011854
0.000831
0.000111
55.514691
14.862172
75.469394
32.156745
3.584171
-4.368659
-10.127936
410.341584
0.031250
10.716096
1
0
99
1
99
0
2
4.583661
11165.160686
1
5.154564
214
149
245
815
687
659
204
455
253
786
371
692
550
430
490
358
201
440
431
388
644
676
389
521
855
550
728
583
863
635
818
863
795
567
674
482
884
429
787
299
619
787
328
742
572
712
798
130
650
780
336
680
547
434
for (i=0;i<2*k;i++) fprintf(ido_o,"%LF\n",return_data->hz_max[i]);
4 Float
for (i=0;i<2*k;i++) fprintf(ido_o,"%LF\n",return_data->hcm_max[i]);
4 Float
for (i=0;i<2*k;i++) fprintf(ido_o,"%LF\n",return_data->htm_max[i]);
4 Float
for (i=0;i<2*k;i++) fprintf(ido_o,"%LF\n",return_data->tz_mean[i]);
4 Float
fprintf(ido_o,"%LF\n",return_data->tp);
1 Float
fprintf(ido_o,"%LF\n",return_data->hmod);
1 Float
for (i=0;i<n_psd;i++) fprintf(ido_o,"%LF\n",return_data->psd[i]);
55 Float
for (i=0;i<n_mom;i++) fprintf(ido_o,"%LF\n",return_data->moment[i]);
7 Float
fprintf(ido_o,"%LF\n",return_data->direction*180.0/pi);
1 Float
fprintf(ido_o,"%LF\n",return_data->peak_direction*180.0/pi);
1 Float
fprintf(ido_o,"%LF\n",return_data->spread*180.0/pi);
1 Float
fprintf(ido_o,"%LF\n",return_data->ratio);
1 Float
fprintf(ido_o,"%LF\n",return_data->hs_dir);
1 Float
fprintf(ido_o,"%LF\n",return_data->a);
1 Float
fprintf(ido_o,"%LF\n",return_data->b);
1 Float
fprintf(ido_o,"%LF\n",return_data->nstd);
1 Float
fprintf(ido_o,"%LF\n",return_data->f2);
1 Float
fprintf(ido_o,"%LF\n",return_data->qflg_mean_removed_imu);
1 Float
fprintf(ido_o,"%d\n",return_data->qflg_kist);
1 Int
fprintf(ido_o,"%d\n",return_data->qflg_imu);
1 Int
fprintf(ido_o,"%d\n",return_data->qflg_pkist);
1 Int
fprintf(ido_o,"%d\n",return_data->qflg_accel);
1 Int
fprintf(ido_o,"%d\n",return_data->qflg_gyro);
1 Int
fprintf(ido_o,"%d\n",return_data->qflg_magno);
1 Int
fprintf(ido_o,"%d\n",return_data->qflg_head);
1 Int
fprintf(ido_o,"%LF\n",return_data->qflg_tot_pow_imu);
1 Float
fprintf(ido_o,"%LF\n",return_data->std_yaw*180.0/pi);
1 Float
fprintf(ido_o,"%d\n",return_data->qflg_open_water);
1 Int
fprintf(ido_o,"%LF\n",return_data->hz_mean[0]);
1 Float
direction
54 Int
4 Float
4 Float
4 Float
4 Float
1 Float
1 Float
55 Float
73
= 292 Bytes
7 Float
1 Float
1 Float
1 Float
1 Float
1 Float
1 Float
1 Float
1 Float
1 Float
1 Float
17
= 68 bytes
1 Int
1 Int
1 Int
1 Int
1 Int
1 Int
1 Int
7
= 14 bytes
1 Float
1 Float
= 8 bytes
1 Int
= 2 bytes
1 Float
= 4 bytres
54 Int
= 108
= 204 total
+39
View File
@@ -0,0 +1,39 @@
Sparton Programming
Standard:
'chan0TriggerDivisor 0 set drop',
'accelrange 2 set drop',
'chan0Enables array[ 0 15 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ]array set drop',
'chan0Format 3 set drop',
'chan0EnableBit cputime dvid@ set drop',
'chan0EnableBit magp dvid@ set drop',
'chan0EnableBit accelp dvid@ set drop',
'chan0EnableBit roll dvid@ set drop',
'chan0EnableBit pitch dvid@ set drop',
'chan0EnableBit yaw dvid@ set drop',
'chan0EnableBit gyrop dvid@ set drop',
'chan0Trigger 6 set drop',
'chanTimerX100us 156 set drop',
// Trigger on delay?
'chan0TriggerDivisor 1 set drop',
Change from 64Hz to 8Hz
Problem with X and Y Data
centripetalCompensation 0 set drop
magAiding 1 set drop
Log some sample Data
name di.
serialnumber di.
VERSION di.
VERSION_M4 di.
gyroSampleRate di.
save_mask d.on
db.print
+84
View File
@@ -0,0 +1,84 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file wii5_bindata.ino
* @brief Test sketch: BinData layout/split exercise.
*/
/*
* BinData Test
*/
#include <WII5Sh3dConsole.h>
#include <WII5_board.h>
#include <WII5Setup.h>
#include <WII5.h>
#include <WII5Data.h>
void setup() {
wii5Setup.setupConsole();
wii5Setup.setupIO();
console.printf(F("waiting 1 seconds\r\n"));
delay(1000);
console.printf(F("WII5: Test BinData\r\n"));
}
elapsedMillis wdtWait = 0;
void loop() {
if (wdtWait > 5100) {
digitalWrite(WDT_RESET, LOW);
wdtWait = 0;
}
else if (wdtWait > 5000) {
digitalWrite(WDT_RESET, HIGH);
}
sh3dNodeIO.loop();
console.loop();
if (console.available()) {
switch(console.getCommand()) {
case '0':
break;
}
}
console.printf(F("BinData: showSizes\n"));
wii5BinData.showSizes();
console.printf(F("BinData: Map Bits test\n"));
for (uint8_t x = 0; x < 16; x++) {
uint32_t t = 0;
SetBit(t, x);
console.printf(F("X = %d, T = %lu\r\n"), x, t);
for (uint8_t y = 0; y < 16; y++) {
if (BitVal(t, y)) {
console.printf(F("BitVal matched on %d\r\n"), y);
}
}
}
console.printf(F("BinData: Trying first 32 types\r\n"));
uint32_t binDataType = 0;
while(1) {
binDataType = (binDataType << 1) + 1;
console.printf(F("BinData: type=%lu, size=%d\r\n"), binDataType, wii5BinData.getSize(binDataType));
console.flush();
delay(500);
console.printf(F("BinData: Creating\r\n"));
wii5BinData.setData(binDataType, wii5BinaryIridium, 340); // TODO Hard coded
console.flush();
delay(500);
console.printf(F("BinData: type=%lu, size=%d\r\n"), binDataType, wii5BinData.getSize(binDataType));
wii5BinData.dumpData(binDataType, wii5BinaryIridium, 340); // TODO Hard coded
console.flush();
delay(500);
}
delay(5000);
}
+22
View File
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file wii5_commands.ino
* @brief Test sketch: command-protocol exercise.
*/
#include "WII5.h"
#include "WII5Commands.h"
void setup () {
Serial.begin(115200);
wii5Setup.begin();
}
void loop() {
wii5Commands.loop();
}
+102
View File
@@ -0,0 +1,102 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file wii5_gps.ino
* @brief Test sketch: GPS NMEA passthrough.
*/
#include <WII5Sh3dConsole.h>
#include <WII5.h>
#include <WII5GPS.h>
#define SerialGPS Serial1
void setup() {
// Serial
Serial.begin(57600);
console.begin();
console.add(&Serial);
console.printf(F("WII5: Test GPS"));
// GPS
wii5Gps.begin();
// wii5Gps.begin(&SerialGPS);
// wii5Gps.setMode(WII5GPS_PASSTHROUGH);
wii5Gps.start();
// wii5Gps.setMode(WII5GPS_PASSTHROUGH);
}
void loop() {
if (SerialGPS.available()) {
char c = SerialGPS.read();
Serial.write(c);
}
return;
console.loop();
wii5Gps.loop();
if (console.available()) {
console.printf(F("CONSOLE: Got command %c=%d \r\n"),
console.getCommand(),
console.getVal()
);
switch(console.getCommand()) {
case '0':
console.printf(F("setMode WII5GPS_OFF\n"));
wii5Gps.setMode(WII5GPS_OFF);
break;
case '1':
console.printf(F("setMode WII5GPS_QUIET\n"));
wii5Gps.setMode(WII5GPS_QUIET);
break;
case '2':
console.printf(F("setMode WII5GPS_PASSTHROUGH\n"));
wii5Gps.setMode(WII5GPS_PASSTHROUGH);
break;
case '3':
console.printf(F("setMode WII5GPS_TIMEONCE\n"));
wii5Gps.setMode(WII5GPS_TIMEONCE);
break;
case '4':
console.printf(F("setMode WII5GPS_POSONCE\n"));
wii5Gps.setMode(WII5GPS_POSONCE);
break;
case '5':
console.printf(F("setMode WII5GPS_POSACCURATE\n"));
wii5Gps.setMode(WII5GPS_POSACCURATE);
break;
case '6':
console.printf(F("setMode WII5GPS_POSREPEAT\n"));
wii5Gps.setMode(WII5GPS_POSREPEAT);
break;
case 'D':
// TODO dump;
wii5Gps.dump();
break;
case 'S':
// Status
// - Number of bytes received
// - Lock status etc
break;
default:
console.printf(F("GPS Help:\r\n"));
console.printf(F(" 0 - mode off\r\n"));
console.printf(F(" 1 - mode Quiet\r\n"));
console.printf(F(" 2 - mode Passthrough\r\n"));
console.printf(F(" 3 - mode Time Once\r\n"));
console.printf(F(" 4 - mode Position Once\r\n"));
console.printf(F(" 5 - mode Position Accurate\r\n"));
console.printf(F(" 6 - mode Position Repeat\r\n"));
console.printf(F(" D - Dump all data\r\n"));
console.printf(F(" S - Show status\r\n"));
};
}
}
+55
View File
@@ -0,0 +1,55 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file wii5_iridium.ino
* @brief Test sketch: Iridium AT-command exercise.
*/
#include <WII5Sh3dConsole.h>
#include <WII5.h>
#include <WII5Iridium.h>
void setup() {
wii5Setup.begin();
}
void loop() {
wii5Iridium.loop();
console.loop();
if (console.available()) {
switch(console.getCommand()) {
case 'p':
console.log(LOG_DEBUG, F("IRIDIUM: 'p' passthrough toggle"));
wii5Iridium.setPassthrough(!wii5Iridium.getPassthrough());
console.log(LOG_DEBUG, F("IRIDIUM: passthrough=%d"), int(wii5Iridium.getPassthrough()));
break;
case 'd':
console.log(LOG_DEBUG, F("IRIDIUM: 'd' debug toggle"));
wii5Iridium.setDebug(!wii5Iridium.getDebug());
console.log(LOG_DEBUG, F("IRIDIUM: debug=%d"), int(wii5Iridium.getDebug()));
break;
case 's':
console.log(LOG_DEBUG, F("IRIDIUM: 's' start"));
wii5Iridium.start();
break;
case 'S':
console.log(LOG_DEBUG, F("IRIDIUM: 'S' stop"));
wii5Iridium.stop();
break;
default:
break;
};
console.log(LOG_DEBUG, F("IRIDIUM: Status = %d"), wii5Iridium.getPassthrough());
wii5Iridium.displayStatus();
wii5Iridium.displayError();
}
}
+179
View File
@@ -0,0 +1,179 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file wii5_pins.ino
* @brief Test sketch: multi-port serial passthrough for pin diagnostics.
*/
/*
WII5 Pins
*/
#include <elapsedMillis.h>
#include <WII5Sh3dConsole.h>
#include <Sh3dNodeUtil.h>
// TODO This needs serious normalisation for each serial port ! (A class perhaps)
// Large Line buffers for passthrough
#define BUFFER_SIZE 250
#define DEVICE_MAX 6
Stream *streams[DEVICE_MAX];
char bufferSerial[DEVICE_MAX][BUFFER_SIZE + 1];
uint8_t bufferPos[DEVICE_MAX];
uint32_t count = 0;
elapsedMillis passthroughWait = 0;
bool passthroughSerial[DEVICE_MAX];
void setup() {
// Short delay for debug / errors
Serial.begin(57600);
console.begin();
console.add(&Serial);
sh3dNodeUtil.begin();
// Start each port at the right speed
Serial1.begin(9600);
streams[1] = &Serial1;
Serial2.begin(9600);
streams[2] = &Serial2;
Serial3.begin(9600);
streams[3] = &Serial3;
// Serial4.begin(9600);
// streams[4] = &Serial4;
// Serial5.begin(9600);
// streams[5] = &Serial5;
}
void processSerial(uint8_t id) {
if (!streams[id]) {
return;
}
while (streams[id]->available()) {
char c = streams[id]->read();
// Ignore start/stop character of Wind Sonic
if (c == 2)
c = 'W';
if (c == 3)
c = '*';
if ( (c != '\n') && (c != '\r') ) {
if (bufferPos[id] < BUFFER_SIZE) {
bufferSerial[id][bufferPos[id]] = c;
bufferPos[id]++;
bufferSerial[id][bufferPos[id]] = '\0';
}
}
else {
if (passthroughSerial[id]) {
console.printf(F("S%d: (%lu) %s\r\n"), id, millis(), bufferSerial[id]);
}
}
}
}
elapsedMillis wait = 0;
void loop() {
// TODO Should not be hard coded 60 seconds
/*
if (passthroughWait > 60000) {
// Skips 0
for (uint8_t id = 1; id < DEVICE_MAX; id++) {
passthroughSerial[id] = false;
}
console.log(LOG_INFO, F("Passthrough time expired"));
}
*/
console.loop();
if (console.available()) {
console.log(LOG_INFO, F("WII5 Console command received - %c=%d"),
console.getCommand(),
console.getVal()
);
switch(console.getCommand()) {
case '1':
case '2':
case '3':
case '4':
case '5':
// Toggle
passthroughSerial[(uint8_t)console.getCommand() - '0']
= !passthroughSerial[(uint8_t)console.getCommand() - '0'];
// Reset timer - all off when hits time
passthroughWait = 0;
console.log(LOG_INFO,
F("Passthrough enabled for $d for %d seconds"),
(uint16_t)console.getCommand() - '0',
60
);
break;
case 'A':
for (uint8_t id = 1; id < DEVICE_MAX; id++) {
passthroughSerial[id] = true;
}
passthroughWait = 0;
break;
// Output
case 'o':
console.printf(F("OUTPU pin=%d\r\n"), int(console.getVal()));
pinMode(int(console.getVal()), OUTPUT);
break;
// Output
case 'i':
console.printf(F("INPUT pin=%d\r\n"), int(console.getVal()));
pinMode(int(console.getVal()), INPUT);
break;
// Low
case 'x':
console.printf(F("LOW pin=%d\r\n"), int(console.getVal()));
digitalWrite(int(console.getVal()), LOW);
console.printf(F("pin=%d\r\n"), int(digitalRead(console.getVal())));
break;
// HIGH
case 'X':
console.printf(F("HIGH pin=%d\r\n"), int(console.getVal()));
digitalWrite(int(console.getVal()), HIGH);
console.printf(F("pin=%d\r\n"), int(digitalRead(console.getVal())));
break;
// Low
case 's':
console.printf(F("pin=%d, state=%d\r\n"), int(console.getVal()), int(digitalRead(console.getVal())));
break;
case 'd':
// dumpPins();
break;
case 'h':
case 'H':
console.log(LOG_INFO, F(" WII5: Pins"));
console.log(LOG_INFO, F(" 'L' - Stop logging"));
break;
}
}
if (wait > 500) {
console.printf(F("pin=%d, state=%d\r\n"), int(console.getVal()), int(digitalRead(console.getVal())));
wait = 0;
}
// Process all incoming Serial
for (uint8_t id = 1; id < DEVICE_MAX; id++) {
processSerial(id);
}
}
+64
View File
@@ -0,0 +1,64 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file wii5_sparton.ino
* @brief Test sketch: Sparton AHRS exercise.
*/
#include <WII5Sh3dConsole.h>
#include <WII5.h>
#include <WII5Sparton.h>
void setup() {
Serial.begin(115200);
console.begin(WII5_BUFFER_CONSOLE_PRINT, WII5_BUFFER_CONSOLE_CMD);
console.add(&Serial);
console.printf(F("WII5: Test Sparton"));
wii5Sparton.begin();
wii5Sparton.setDebug(true);
wii5Sparton.setRecords(5000);
wii5Sparton.start();
}
void loop() {
wii5Sparton.loop();
console.loop();
if (console.available()) {
uint32_t val = console.getVal();
console.printf(F("CONSOLE: Got command %c=%d \r\n"),
console.getCommand(),
val
);
switch(console.getCommand()) {
// Toggle Sparton passthrough
case 'p':
console.log(LOG_DEBUG, F("SPARTON: 'p' passthrough toggle"));
wii5Sparton.setPassthrough(!wii5Sparton.getPassthrough());
break;
case 's':
if (val < 100) val = 100;
console.log(LOG_DEBUG, F("SPARTON: 's' start with %lu records"), val);
wii5Sparton.setRecords(val);
wii5Sparton.start();
break;
case 'S':
console.log(LOG_DEBUG, F("SPARTON: 'S' stop"));
wii5Sparton.stop();
break;
default:
break;
};
console.log(LOG_DEBUG, F("SPARTON: Status = %d"), wii5Sparton.getPassthrough());
wii5Sparton.displayStatus();
wii5Sparton.displayError();
}
}
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
//
// This file is part of WII5 Buoy firmware.
// See LICENSE for full terms.
/**
* @file wii5_weather_18b20.ino
* @brief Test sketch: DS18B20 temperature exercise.
*/
#include "WII5.h"
#include "WII5Weather_18B20.h"
void setup () {
Serial.begin(115200);
console.begin();
console.add(&Serial);
wii5Weather_18B20.begin();
}
void loop() {
wii5Weather_18B20.temperatureRead();
delay(5000);
}
+13
View File
@@ -0,0 +1,13 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
#
# This file is part of WII5 Buoy firmware.
# See LICENSE for full terms.
# Reference snippet (not directly executable): how the 2022 build burned the
# optiboot bootloader on an ATmega2560 via ICSP. Use tools/icsp.sh for the
# parameterized version.
#
# avrdude -C <avrdude.conf> -v -patmega2560 -cstk500v2 -Pusb \
# -Uflash:w:<path to optiboot_flash_atmega2560_UART0_230400_11059200L_BIGBOOT.hex>:i \
# -Ulock:w:0x0F:m
+26
View File
@@ -0,0 +1,26 @@
#!/bin/bash
# SPDX-License-Identifier: Apache-2.0
# Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
#
# This file is part of WII5 Buoy firmware.
# See LICENSE for full terms.
# Burn LED-flash test sketch + bootloader for the 11.0592 MHz board variant.
# See http://eleccelerator.com/fusecalc/fusecalc.php?chip=atmega2560
# lfuse 0xff - external oscillator
# hfuse 0xd8
# efuse 0xfd - BOD Level 1 = 2.7V
: "${WII5_AVRDUDE:=avrdude}"
CONFIGFILE=./avrdude.conf
BOOT_HEX=./optiboot_flash_atmega2560_UART0_230400_11059200L_BIGBOOT.hex
"$WII5_AVRDUDE" \
-C"$CONFIGFILE" -v \
-v \
-patmega2560 \
-cstk500v2 \
-Pusb \
-Uflash:w:ledflash_11mhz.ino.hex:i \
-Uboot:w:"$BOOT_HEX":i \
-Ulock:w:0

Some files were not shown because too many files have changed in this diff Show More