diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..62f0eed --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,5 @@ +# See more here https://help.github.com/en/articles/about-code-owners + +* @bakercp + +src/Encoding/SLIP.h @avilleret \ No newline at end of file diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..b7f421e --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +github: bakercp +custom: https://paypal.me/bakercp diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..344b7fb --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,35 @@ +# New Issue Guidelines + +_Your issue may already be reported! Please search on the [Issue Tracker](../) before creating one._ + +## Expected Behavior + + + +## Current Behavior + + + +## Possible Solution + + + +## Steps to Reproduce (for bugs) + + + +1. Step 1. +2. Step 2. +3. Step 3. +4. etc. + +## Context + + + +## Your Environment + +* Version used: +* IDE Name and version: +* Operating System and version (desktop or mobile): +* Link to your project: diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..8812b5a --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,24 @@ +# New Pull Request Guidelines + +_A similar PR may already be submitted! Please search among the [Pull Requests](../) before creating one._ + +## This Pull-Request Fixes the Following Issue(s) + +- Fixes # _the issue number here_ +- Fixes # _etc_ + +## Proposed Changes + +- _Change 1._ +- _Change 2._ +- _Change 3._ +- _etc._ + +## Testing Procedure + +_How do we test it?_ + +- _Test 1._ +- _Test 2._ +- _Test 3._ +- _etc._ diff --git a/.github/REPOSITORY_NOTES.md b/.github/REPOSITORY_NOTES.md new file mode 100644 index 0000000..39ad47f --- /dev/null +++ b/.github/REPOSITORY_NOTES.md @@ -0,0 +1,15 @@ + +# Github Documentation Help + +- [README.md](https://help.github.com/en/articles/about-readmes) +- [CODE_OF_CONDUCT.md](https://help.github.com/en/articles/adding-a-code-of-conduct-to-your-project) +- [CODEONWERS](https://help.github.com/en/articles/about-code-owners) +- [CONTRIBUTING.md](https://help.github.com/en/articles/setting-guidelines-for-repository-contributors) +- [ISSUE_TEMPLATE.md](https://help.github.com/en/articles/about-issue-and-pull-request-templates) +- [PULL_REQUEST_TEMPLATE.md](https://help.github.com/en/articles/about-issue-and-pull-request-templates) +- [FUNDING.yml](https://help.github.com/en/articles/displaying-a-sponsor-button-in-your-repository) + - Enable in settings. +- [SECURITY.md](https://help.github.com/en/articles/adding-a-security-policy-to-your-repository) +- [SUPPORT.md](https://help.github.com/en/articles/adding-support-resources-to-your-project) +- [LICENSE.md](https://help.github.com/en/articles/adding-a-license-to-a-repository) + \ No newline at end of file diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 0000000..87f6977 --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,3 @@ +# Security + +If an exploitable security issue is discovered, please contact the repository owner at [info@christopherbaker.net](mailto:info@christopherbaker.net) before creating an issue. diff --git a/.gitignore b/.gitignore index c3b258f..5aacce5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ *.mode1v3 +docs/html +docs/tagfile.xml + # xcode *.pbxuser *.perspectivev3 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..b4b3c22 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,30 @@ +language: c +sudo: false +cache: + directories: + - ~/arduino_ide + - ~/.arduino15/packages/ +before_install: + - source <(curl -SLs https://raw.githubusercontent.com/adafruit/travis-ci-arduino/master/install.sh) +install: + - arduino --install-library "ArduinoUnit" +script: + - build_main_platforms +notifications: + email: + on_success: change + on_failure: change + +jobs: + include: + - stage: Deploy Documentation + script: + - source <(curl -SLs https://raw.githubusercontent.com/bakercp/ci/master/deploy_documentation.sh) + deploy: + provider: pages + skip_cleanup: true + github_token: $GITHUB_TOKEN # Set in the settings page of your repository, as a secure variable + keep_history: false + local_dir: docs/html + on: + branch: master diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..8ecd1de --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Christopher Baker + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 1c3cbe9..f9ea068 100644 --- a/README.md +++ b/README.md @@ -1,189 +1,52 @@ -PacketSerial -============ +# PacketSerial -_PacketSerial_ is an small, efficient, library that allows [Arduinos](http://www.arduino.cc/) to send and receive serial data packets that include bytes with any value (0-255). A _packet_ is simply an array of bytes. +[![Build Status](https://travis-ci.org/bakercp/PacketSerial.svg?branch=master)](https://travis-ci.org/bakercp/PacketSerial) -_"Why do I need this?"_ you may ask. The truth is that you may not need it if you are converting your values to ASCII strings and separating them with a known character (like a new line `\n`) before sending them. This is what happens if you call `Serial.print()` or `Serial.println()`. For instance, if you just want to send a byte with the value of 255 and follow it with a new line character (i.e. `Serial.println(255)`) the Arduino automatically converts the number to the equivalent printable ASCII characters, sending 4 bytes total. As a result the receiver won't just receive a byte for the number and a byte for the new line character. Instead it will receive a stream of 4 bytes: +An Arduino Library that facilitates packet-based serial communication using COBS or SLIP encoding. -``` -50 // ASCII 2 -53 // ASCII 5 -53 // ASCII 5 -10 // Serial.println() appends a new line \n character. -``` +## Features -The receiver must then collect the 3 ASCII characters (2, 5, 5), combine them and convert them back into a single byte with a value of 255. This process can get complicated when the user wants to send large quantities of structured data between the Arduino and a receiver. +_PacketSerial_ is an small, efficient, library that allows [Arduinos](http://www.arduino.cc/) to send and receive serial data packets (with COBS, SLIP or a user-defined encoding) that include bytes of any value (0 - 255). +A _packet_ is simply an array of bytes. -One way to send a _packet_ of data without this library is to send each byte separated by a comma or space and terminate the sequence with a new line character. Thus, to send the value 255 and the value 10, one might call: +## Documentation -``` -Serial.print(255); -Serial.print(','); -Serial.print(10); -Serial.print('\n'); -``` +If you're asking _Why do I need this?_, read the [background introduction](docs/BACKGROUND.md) page. If you're ready to get started, check out the [getting started](docs/GETTING_STARTED.md) page. You can also learn a lot by reading through the [examples](./examples/). Finally, if you're interested in learning more about how the code works, read the comments in the [source code](./src/) and check out the [API documentation](https://bakercp.github.io/PacketSerial/). -The receiver will actually see a stream of 8 bytes: +## Support -``` -50 // ASCII 2 -53 // ASCII 5 -53 // ASCII 5 -44 // ASCII , -49 // ASCII 1 -48 // ASCII 0 -10 // ASCII \n -``` +If you're looking for help, read the [support](docs/SUPPORT.md) page. -In this case, the receiver must then collect the ASCII characters, combine them, skip the delimiter (the comma in this case) and then process the packet when a new line is encountered. While effective, this method doesn't scale well. Bytes with values larger than 9 require are encoded as 2 bytes and bytes with values larger than 99 are encoded as 3 bytes. If the user would like to send the number 4,294,967,295 (the maximum value of a 4 byte `unsigned long`), it would be encoded as 10 bytes. This means that there is an overhead of 6 extra bytes to transmit a 4 byte `unsigned long`. +## Compatibility -An alternative to ASCII encoding is to write the bytes directly to using the `Serial.write()` methods. These methods do not convert the byte values to ASCII. So if the user wants to send a single byte with the value of 255 and follow it with a new line character (i.e. `Serial.write(255); Serial.write('\n');`), the receiver will see a stream of 2 bytes: +### Requrements -``` -255 // The value transmitted. -10 // The new line character (\n). -``` +- [Arduino IDE](https://www.arduino.cc/en/main/software) (version 1.5+) -This is much more compact but can create problems when the user wants to send a _packet_ of data. If the user wants to send a packet consisting of two values such as 255 and 10, we run into problems if we also use the new line ('\n' ASCII 10) character as a packet boundary. This essentially means that the receiver will incorrectly think that a new packet is beginning when it receives the _value_ of 10. Thus, to use this more compact form of sending bytes while reserving one value for a packet boundary marker. Several unambiguous packet boundary marking encodings exist, but one with a small predictable overhead is called [Consistent Overhead Byte Stuffing](http://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing). For a raw packet of length `SIZE`, the maximum encoded buffer size will only be `SIZE + SIZE / 254 + 1`. This is significantly less than ASCII encoding and the encoding / decoding algorithm is simple and fast. In its default mode, the COBS encoding process simply removes all _zeros_ from the packet, allowing the sender and receiver to use the value of _zero_ as a packet boundary marker. +### Other Platforms -## PacketSerial +This project has been used successfully with [openFrameworks](https://openframeworks.cc/) using the [ofxSerial](https://github.com/bakercp/ofxSerial) addon. In particular, see the `ofx::IO::PacketSerial` object. Additionally this project has been used with Python using the [PySerial](https://pythonhosted.org/pyserial/index.html) package. In particular, check out the [COBS](https://pythonhosted.org/cobs/) (see [this discussion](https://github.com/bakercp/PacketSerial/issues/10)) and [SLIP](https://pypi.python.org/pypi/sliplib/0.0.1) packages. -The `PacketSerial` class wraps the standard Arduino `Serial` class to automatically encode and decode byte packets automatically. Thus users can still call methods on the `Serial` object (e.g. `Serial.write()`, the built in `serialEvent()` callback etc), but it is not recommended. Users are advised to let PacketSerial manage all Serial communication via the packet handler callback for incoming packets and the `send(const uint8_t* buffer, size_t size)` method for outgoing packets. +Ultimately, any library that correctly implements a COBS or SLIP encoding scheme should be compatible with this project. +### Continuous Integration -## Setup +Continuous integration tests are carried out on a variety of common Arduino platforms. See [this script](https://raw.githubusercontent.com/adafruit/travis-ci-arduino/master/install.sh) for a list. -For Arduino boards with more than one serial port, `PacketSerial` the desired serial port can be specified with the `begin` method, i.e. +## Licensing -```c++ - void begin(unsigned long baud, size_t port = 0) -``` +This project is licensed under the [MIT License](LICENSE.md). -Where: +## Project Management -| _Arduino_ Serial Port | _PacketSerial_ Port Number | -| ------------- |:-------------:| -| `Serial` | 0 | -| `Serial1` | 1 | -| `Serial2` | 2 | -| `Serial3` | 3 | +### Repository -To receive decoded packets automatically, the user should register a packet callback. The packet callback should be placed in your main Arduino Sketch and should have a method that looks like this signatur that looks like: +[https://github.com/bakercp/PacketSerial](https://github.com/bakercp/PacketSerial) -```c++ -void onPacket(const uint8_t* buffer, size_t size) -{ - /// Process your incoming packet here. -} -``` - -Your callback can have any name and should be registered in the `setup()` method like this: - -```c++ -void setPacketHandler(PacketHandlerFunction onPacketFunction) -``` - -## Main Loop - -In order to processing incoming serial packets, the user must call the `update()` method at the end of the `loop()` method. - -```c++ -void loop() -{ - // Your program here. - - serial.update(); -} - -``` - -## Sending Packets - -To send packets call the `send()` method. The send method will take a packet (an array of bytes, encode it, transmit the array and transmit the packet boundary marker (`0`). To send the values 255 and 10, one might do the following: - -```c++ - -// Make an array. -uint8_t myPacket[] { 255, 10 }; - -serial.send(myPacket, 2); -``` - -# Example - -In this "reverse echo" example, we listen for incoming packets. When a new packet arrives in the `onPacket` method, we reverse the contents and send it back to the sender. - -```c++ -#include - - -// The PacketSerial object. -// It cleverly wraps one of the Serial objects. -// While it is still possible to use the Serial object -// directly, it is recommended that the user let the -// PacketSerial object manage all serial communication. -// Thus the user should not call Serial.write(), etc. -// Additionally the user should not use the serialEvent() -// callbacks. -PacketSerial serial; - - -void setup() -{ - // We must specify a packet handler method so that - serial.setPacketHandler(&onPacket); - serial.begin(115200); -} - - -void loop() -{ - // Do other things here. - - // The update() method attempts to read in - // any incoming serial data and emits packets via - // the user's onPacket(const uint8_t* buffer, size_t size) - // method registered with the setPacketHandler() method. - // - // The update() method should be called at the end of the loop(). - serial.update(); -} - -// This is our packet callback. -// The buffer is delivered already decoded. -void onPacket(const uint8_t* buffer, size_t size) -{ - // Make a temporary buffer. - uint8_t tmp[size]; - - // Copy the packet into our temporary buffer. - memcpy(tmp, buffer, size); - - // Reverse our buffer. - reverse(tmp, size); - - // Send the reversed buffer back. - // The send() method will encode the buffer - // as a packet, set packet markers, etc. - serial.send(tmp, size); -} - -/// \brief A simple array reversal method. -void reverse(uint8_t* buffer, size_t size) -{ - uint8_t tmp; - - for (int i=0; i < size / 2; i++) - { - tmp = buffer[i]; - buffer[i] = buffer[size-i-1]; - buffer[size-i-1] = tmp; - } -} -``` - -## Compatible libraries - -- openFrameworks - - https://github.com/bakercp/ofxSerial - - See the `ofx::IO::PacketSerial` object which is directly compatible with this library. \ No newline at end of file +### Contributing + +If you'd like to contribute to this project, please check out the [Code of Conduct](docs/CODE_OF_CONDUCT.md) and the [contributing](docs/CONTRIBUTING.md) guide. + +### Versioning + +This project uses [Semantic Versioning](http://semver.org/spec/v2.0.0.html). You can check out recent changes in the [changelog](CHANGELOG.md). diff --git a/docs/BACKGROUND.md b/docs/BACKGROUND.md new file mode 100644 index 0000000..a1e4afd --- /dev/null +++ b/docs/BACKGROUND.md @@ -0,0 +1,54 @@ +# Background + +_Why do I need this?_ you may ask. The truth is that you may not need it if you are converting your values to ASCII strings and separating them with a known character (like a carriage return `\r` and a line feed `\n`) before sending them. This is what happens if you call and `Serial.println();`. For instance, if you just want to send a byte with the value of 255 and follow it with a new line character (i.e. `Serial.println(255);`) the Arduino automatically converts the number to the equivalent printable ASCII characters, sending 5 bytes total. As a result the receiver won't just receive a byte for the number and two bytes for the carriage return and new line character. Instead it will receive a stream of 5 bytes: + +```console +50 // ASCII '2' +53 // ASCII '5' +53 // ASCII '5' +13 // ASCII '\r' +10 // ASCII '\n' +``` + +The receiver must then collect the 3 ASCII characters { '2', '5', '5' }, combine them and convert them back into a single byte with a value of `255`. This process can get complicated when the user wants to send large quantities of structured data between the Arduino and a receiver. + +One way to send a _packet_ of data without this library is to send each byte separated by a comma or space and terminate the sequence with a new line character. Thus, to send the value `255` and the value `10`, one might call: + +```cpp +Serial.print(255); +Serial.print(','); +Serial.print(10); +Serial.print('\n'); +``` + +The receiver will actually see a stream of 7 bytes: + +```console +50 // ASCII '2' +53 // ASCII '5' +53 // ASCII '5' +44 // ASCII ',' +49 // ASCII '1' +48 // ASCII '0' +10 // ASCII '\n' +``` + +In this case, the receiver must then collect the ASCII characters, combine them, skip the delimiter (the comma in this case) and then process the packet when a new line is encountered. While effective, this method doesn't scale particularly well. Bytes with values larger than 9 are encoded as 2 bytes and bytes with values larger than 99 are encoded as 3 bytes, etc. If the user would like to send the number 4,294,967,295 (the maximum value of a 4 byte `unsigned long`), it would be encoded as 10 bytes. This means that there is an overhead of 6 extra bytes to transmit a 4 byte `unsigned long`. + +An alternative to ASCII encoding is to write the bytes directly to using the `Serial.write()` methods. These methods do not convert the byte values to ASCII. So if the user wants to send a single byte with the value of `255` and follow it with a new line character: + +```cpp +Serial.write(255); +Serial.write('\n'); +``` + +the receiver will see a stream of 2 bytes: + +```console +255 // The value transmitted. +10 // The new line character (\n). +``` + +This is much more compact but can create problems when the user wants to send a _packet_ of data. If the user wants to send a packet consisting of two values such as 255 and 10, we run into problems if we also use the new line (`\n` ASCII 10) character as a packet boundary. This essentially means that the receiver will incorrectly think that a new packet is beginning when it receives the _value_ of 10. Thus, to use this more compact form of sending bytes while reserving one value for a packet boundary marker. Several unambiguous packet boundary marking encodings exist, but one with a small predictable overhead is called [Consistent Overhead Byte Stuffing](http://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing). For a raw packet of length `SIZE`, the maximum encoded buffer size will only be `SIZE + SIZE / 254 + 1`. This is significantly less than ASCII encoding and the encoding / decoding algorithm is simple and fast. In its default mode, the COBS encoding process simply removes all _zeros_ from the packet, allowing the sender and receiver to use the value of _zero_ as a packet boundary marker. + +Another encoding available in `PacketSerial` is [Serial Line Internet Protocol](https://en.wikipedia.org/wiki/Serial_Line_Internet_Protocol) which is often used to send OSC over serial or TCP connections. To use SLIP encoding instead of COBS, use `SLIPPacketSerial` instead of `PacketSerial`. You can find an openFrameworks example of sending OSC data over serial in the [ofxSerial](https://github.com/bakercp/ofxSerial) repository. diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md new file mode 100644 index 0000000..029798a --- /dev/null +++ b/docs/CHANGELOG.md @@ -0,0 +1,102 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +- None + +### Changed + +- None + +### Removed + +- None + +### Fixed + +- None + +### Security + +- None + +## [1.4.0] 2019-07-26 + +### Added + +- Added `const Stream* getStream() const` and `Stream* getStream()` methods. Thanks @orgicus. + +### Changed + +- Updated README. + +## [1.3.0] 2019-07-25 + +### Removed + +- Remove the `void begin(unsigned long speed, size_t port)` function. +- Remove the `void begin(Stream* stream)` function. +- `while(!SerialX)` line when `CORE_TEENSY` is defined. This was leading to unexpected behavior where programs would not start until a serial connection was opened. + +### Added + +- Lambda function packetHandler examples and documentation. +- Add `bool overflow() const` to check for a receive buffer overflow. +- Added API documentation @ [http://bakercp.github.io/PacketSerial/](http://bakercp.github.io/PacketSerial/) + +### Changed + +- Updated README.md, fixed spelling error, added links, docs. +- Rewrote SLIP enum in terms of decimal notation for consistency. +- Updated documentation folder, .github structure. +- Updated CI testing. + +## [1.2.0] 2017-11-09 + +### Added + +- An additional PacketHandler pointer type that includes the sender's pointer e.g. `void onPacketReceived(const void* sender, const uint8_t* buffer, size_t size)`. Either functions can be set. Calling `setPacketHandler()` will always remove all previous function pointers. + +### Removed + +- Deprecated all but one basic `void begin(Stream* stream)` function. Use `void setStream(Stream* stream)` instead. +- Reverted void `PacketSerial_::begin(unsigned long speed, uint8_t config, size_t port)`. + +### Changed + +- Updated README.md, fixed errors, spelling and updated examples. + +## [1.1.0] 2017-11-09 + +### Added + +- Additional inline documentation. +- Added doxygen configuration file. +- Added CHANGELOG.md file. +- Added the `void PacketSerial_::begin(unsigned long speed, uint8_t config, size_t port)` method to avoid confusion with the standard `Serial.begin(unsigned long speed, uint8_t config)`. + +### Changed + +- Updated README.md, fixed errors, spelling, byte counts, etc. +- Updated documentation / comments in documentation for clarity. + +### Removed + +- Deprecated the `void begin(unsigned long speed, size_t port)` method because it could be confused with the standard `Serial.begin(unsigned long speed, uint8_t config)` method. + +### Fixed + +- Fixed Duplicated SLIP END Packet #[11](https://github.com/bakercp/PacketSerial/issues/11) +- Fix types to remove warnings in examples. +- Add `const` qualifier to the `send()` method. + +### Security + +- None diff --git a/docs/CODE_OF_CONDUCT.md b/docs/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..06e0fe1 --- /dev/null +++ b/docs/CODE_OF_CONDUCT.md @@ -0,0 +1,73 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies within all project spaces, and it also applies when +an individual is representing the project or its community in public spaces. +Examples of representing a project or community include using an official +project e-mail address, posting via an official social media account, or acting +as an appointed representative at an online or offline event. Representation of +a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at [info@christopherbaker.net](mailto:info@christopherbaker.net?subject=[GitHub]%20Code%20Of%20Conduct). All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][[homepage](https://www.contributor-covenant.org)], version 1.4, +available at [https://www.contributor-covenant.org/version/1/4/code-of-conduct.html](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html). + +For answers to common questions about this code of conduct, see [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq). diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md new file mode 100644 index 0000000..1d16a63 --- /dev/null +++ b/docs/CONTRIBUTING.md @@ -0,0 +1,25 @@ +# Contributing + +## Code of Conduct + +First, please see the [Code of Conduct](CODE_OF_CONDUCT.md). + +## How to Help + +Check out the [Todo](TODO.md) list to see if there are any planned items. + +Check out the [Help Wanted](../../../issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) tag in the issues section for specific ideas or propose your own new ideas. + +## Making Pull Requests + +Pull Requests are always welcome, so if you make any improvements please feel free to float them back upstream :) + +1. Fork this repository. +2. Create your feature branch (`git checkout -b my-new-feature`). +3. Commit your changes (`git commit -am 'Add some feature'`). +4. Push to the branch (`git push origin my-new-feature`). +5. Create new Pull Request. + +New features should include tests (if possible) to confirm functionality. + +All code should be documented inline using [Doxygen](http://www.doxygen.nl/manual/docblocks.html) documentation. See the project's source for examples of the preferred style. diff --git a/docs/CONTRIBUTORS.md b/docs/CONTRIBUTORS.md new file mode 100644 index 0000000..9d4d42a --- /dev/null +++ b/docs/CONTRIBUTORS.md @@ -0,0 +1,9 @@ +# Contributors + +- Christopher Baker [@bakercp](https://github.com/bakercp) - Primary author. +- Antoine Villeret [@avilleret](https://github.com/avilleret) - SLIP Encoding. +- Anton Viktorov [@latonita](https://github.com/latonita) - Bugfixes. +- [@per1234](https://github.com/per1234) - Metadata. +- George Profenza [@orgicus](https://github.com/orgicus) - API Suggestions. + +_For and up-to-date list of contributors, see [contributors](../graphs/contributors)._ diff --git a/docs/Doxyfile b/docs/Doxyfile new file mode 100644 index 0000000..a4824b1 --- /dev/null +++ b/docs/Doxyfile @@ -0,0 +1,18 @@ +PROJECT_NAME = PacketSerial +PROJECT_BRIEF = "An Arduino Library that facilitates packet-based serial communication using COBS or SLIP encoding." +INPUT = ../src \ + . +FILE_PATTERNS = *.c \ + *.cpp \ + *.h \ + *.hpp \ + *.md \ + *.ino \ + *.pde +EXCLUDE = ./html +EXCLUDE_PATTERNS = *.bak +USE_MDFILE_AS_MAINPAGE = README.md +HTML_OUTPUT = html +GENERATE_TREEVIEW = YES +GENERATE_TAGFILE = PacketSerial.xml +GENERATE_LATEX = NO diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md new file mode 100644 index 0000000..9a7fb6d --- /dev/null +++ b/docs/GETTING_STARTED.md @@ -0,0 +1,268 @@ +# Getting Started + +First, it might be worth it to read the [background introduction](BACKGROUND.md). + +## Installation + +This project can be installed using the Arduino IDE. Navigate to the _Tools > Manage Libraries ..._ menu item and search for `PacketSerial`. + +Alternatively, this library can be downloaded or cloned from github and to your Arduino `libraries` folder. + +## Use + +`PacketSerial` class wraps the Arduino `Stream` class to automatically encode and decode byte packets that are sent and received. Typically serial communication uses the default `Serial` object, which implements the `Stream` class. In most cases, `PacketSerial` should be given exclusive access to the serial `Stream` (e.g. for a default setup using `Serial`, users should avoid calling functions like `Serial.print()`, `Serial.write()`, etc directly). Data should be sent via the `send(const uint8_t* buffer, size_t size) const` method and received in a `PacketSerial` callback function (see below). + +## Setup + +### Basic + +To use the default `Serial` object and the default communication settings (usually `SERIAL_8N1`), set up `PacketSerial` like this: + +```cpp +PacketSerial myPacketSerial; + +void setup() +{ + myPacketSerial.begin(9600); + myPacketSerial.setPacketHandler(&onPacketReceived); +} +``` + +### Advanced + +For a non-default Serial connection, a class implementing the `Stream` interface should be configured and then set for the `PacketSerial` instance. + +#### Using A Non-Standard Serial Configuration + +```cpp +PacketSerial myPacketSerial; + +void setup() +{ + Serial.begin(300, SERIAL_7N1); + myPacketSerial.setStream(&Serial); + myPacketSerial.setPacketHandler(&onPacketReceived); +} +``` + +#### Using Secondary Serial Ports (e.g. Serial1, Serial2, etc) + +```cpp +PacketSerial myPacketSerial; + +void setup() +{ + Serial1.begin(9600); + myPacketSerial.setStream(&Serial1); + myPacketSerial.setPacketHandler(&onPacketReceived); +} +``` + +#### Using SoftwareSerial + +```cpp +PacketSerial myPacketSerial; +SoftwareSerial mySoftwareSerial(10, 11); + +void setup() +{ + mySoftwareSerial.begin(38400); + myPacketSerial.setStream(&mySoftwareSerial); + myPacketSerial.setPacketHandler(&onPacketReceived); +} +``` + +#### Other Streams + +Any class that correctly implements the `Stream` interface should work, which includes some network communication objects. + +### Loop + +In order to processing incoming serial packets, the user must call the `update()` method at the end of the `loop()` method. + +```cpp +void loop() +{ + // Your program here. + + + // Call update to receive, decode and process incoming packets. + myPacketSerial.update(); +} + +``` + +### Receiving Packets + +All packets are received via handler functions. A typical handler function would be registered in the `void setup()` function like: + +```cpp +PacketSerial myPacketSerial; + +void setup() +{ + myPacketSerial.begin(9600); + myPacketSerial.setPacketHandler(&onPacketReceived); +} +``` + +The `onPacketReceived` function can take two forms. The simplest looks like this: + +```cpp +void onPacketReceived(const uint8_t* buffer, size_t size) +{ + // Process your decoded incoming packet here. +} +``` + +For more advanced programs with multiple PacketSerial instances and a shared handler, it may be useful to know which PacketSerial instance received the packet. In this case you could define a callback like this: + +```cpp +void onPacketReceived(const void* sender, const uint8_t* buffer, size_t size) +{ + if (sender == &myPacketSerial) + { + // Do something with the packet from myPacketSerial. + } + else if (sender == &myOtherPacketSerial) + { + // Do something with the packet from myOtherPacketSerial. + } +} +``` + +Finally, it is also possible to set arbitrary packet handlers that point to member functions of a given class instance using lambda functions. For example: + +```cpp +// Instances of this class can receive data packets when registered. +class MyClass +{ +public: + void processPacketFromSender(const PacketSerial& sender, const uint8_t* buffer, size_t size) + { + // Just send the buffer back to the sender. + sender.send(buffer, size); + } +}; + + +MyClass myClassInstance; +PacketSerial myPacketSerial; + +void setup() +{ + myPacketSerial.begin(115200); + myPacketSerial.setPacketHandler([](const uint8_t* buffer, size_t size) { + myClassInstance.processPacketFromSender(myPacketSerial, buffer, size); + }); +} +``` + +### Sending Packets + +To send packets call the `send()` method. The send method will take a packet (an array of bytes), encode it, transmit it and send the packet boundary marker. To send the values `255` and `10`, one might do the following: + +```cpp + +// Make an array. +uint8_t myPacket[2] = { 255, 10 }; + +// Send the array. +myPacketSerial.send(myPacket, 2); +``` + +### Multiple Streams + +On boards with multiple serial ports, this strategy can also be used to set up two Serial streams, one for packets and one for debug ASCII (see [this discussion](https://github.com/bakercp/PacketSerial/issues/10) for more). + +### Checking for Receive Buffer Overflows + +In some cases the receive buffer may not be large enough for an incoming encoded packet. + +To check for overflows, call the `receiveBufferOverflowed()` method after calling `update()`. + +For example: + +```cpp +void loop() +{ + // Other program code. + myPacketSerial.update(); + + // Check for a receive buffer overflow. + if (myPacketSerial.overflow()) + { + // Send an alert via a pin (e.g. make an overflow LED) or return a + // user-defined packet to the sender. + // + // Ultimately you may need to just increase your recieve buffer via the + // template parameters. + } +} +``` + +The state of the overflow flag is reset every time a new packet marker is detected, NOT when the `overflow()` method is called. + +### Customizing the PacketSerial Class + +The `PacketSerial_` class is a templated class that allows us to statically set the encoder type, packet marker and buffer size at compile time. + +The the template parameters are as follows: + +```cpp +template +class PacketSerial_ + +(...) +``` + +The `PacketMarker` has a default of `0` while the `BufferSize` has a default of `256` bytes. + +Thus, if you define your class as: + +```cpp +PacketSerial_ myPacketSerial; +``` + +You will use the `COBS` encoder type and a default PacketMarker of `0` and buffer size of `256`. + +Currently there are three default `PacketSerial_` types defined via `typedef` for convenience: + +```cpp +/// \brief A typedef for the default COBS PacketSerial class. +typedef PacketSerial_ PacketSerial; + +/// \brief A typedef for a PacketSerial type with COBS encoding. +typedef PacketSerial_ COBSPacketSerial; + +/// \brief A typedef for a PacketSerial type with SLIP encoding. +typedef PacketSerial_ SLIPPacketSerial; +``` + +### Changing the EncoderType Type + +To use a custom encoding type, the `EncoderType` class must implement the following functions: + +```cpp + static size_t encode(const uint8_t* buffer, size_t size, uint8_t* encodedBuffer); + static size_t decode(const uint8_t* encodedBuffer, size_t size, uint8_t* decodedBuffer); + static size_t getEncodedBufferSize(size_t unencodedBufferSize); +``` + +See the `Encoding/COBS.h` and `Encoding/SLIP.h` for examples and further documentation. + +### Changing the Packet Marker Byte and Receive Buffer Size + +For example, to increase the buffer size for a standard `COBS` encoder to 512, one can defined the templated class like this: + +```cpp +PacketSerial_ myPacketSerial; +``` + +This uses the COBS encoder type, a PacketMarker of 0 and a buffer size of 512. + +Likewise, a custom `SLIP` encoder with a buffer size of 512 bytes would be defined like this: + +```cpp +PacketSerial_ myPacketSerial; +``` \ No newline at end of file diff --git a/docs/SUPPORT.md b/docs/SUPPORT.md new file mode 100644 index 0000000..98b7f4b --- /dev/null +++ b/docs/SUPPORT.md @@ -0,0 +1,5 @@ +# Support + +First, read the [Getting Started](GETTING_STARTED.md) guide and see the [Troubleshooting](TROUBLESHOOTING.md) guide. You might also check and see if there are any [existing issues](../../../issues?utf8=✓&q=is%3Aissue) that are helpful to you. If those don't help and you, create a new [issue](../../../issues). + +Please do not email the author directly with questions. diff --git a/docs/TODO.md b/docs/TODO.md new file mode 100644 index 0000000..8d5b46b --- /dev/null +++ b/docs/TODO.md @@ -0,0 +1,7 @@ +# TODO + +First check out the [Help Wanted](../../../issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) tag in the issues section for specific ideas. + +## Future Plans + +Right now the encoder / decoder works by encoding or decoding a full buffer. SLIP (and COBS?) encoding, for instance, can be encoded / decoded on the fly, allowing for a smaller buffer allocation. https://github.com/CNMAT/OSC uses an "on-the-fly" approach, rather than a large buffer approach. It would be interesting to investigate this. diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md new file mode 100644 index 0000000..c64ad25 --- /dev/null +++ b/docs/TROUBLESHOOTING.md @@ -0,0 +1,3 @@ +# Troubleshooting + +There are no additional _Troubleshooting_ notes at steps time. diff --git a/examples/PacketSerialReverseEcho/PacketSerialReverseEcho.ino b/examples/PacketSerialReverseEcho/PacketSerialReverseEcho.ino index 0c6cec0..1bc6fd1 100644 --- a/examples/PacketSerialReverseEcho/PacketSerialReverseEcho.ino +++ b/examples/PacketSerialReverseEcho/PacketSerialReverseEcho.ino @@ -1,92 +1,97 @@ -// ============================================================================= // -// Copyright (c) 2012-2014 Christopher Baker +// Copyright (c) 2012 Christopher Baker // -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: +// SPDX-License-Identifier: MIT // -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// -// ============================================================================= #include -// The PacketSerial object. -// It cleverly wraps one of the Serial objects. -// While it is still possible to use the Serial object -// directly, it is recommended that the user let the -// PacketSerial object manage all serial communication. -// Thus the user should not call Serial.write(), etc. -// Additionally the user should not use the serialEvent() -// callbacks. -PacketSerial serial; +// By default, PacketSerial automatically wraps the built-in `Serial` object. +// While it is still possible to use the Serial object directly, it is +// recommended that the user let the PacketSerial object manage all serial +// communication. Thus the user should not call Serial.write(), Serial.print(), +// etc. Additionally the user should not use the serialEvent() framework. +// +// By default, PacketSerial uses COBS encoding and has a 256 byte receive +// buffer. This can be adjusted by the user by replacing `PacketSerial` with +// a variation of the `PacketSerial_` template found in +// PacketSerial.h. +PacketSerial myPacketSerial; void setup() { - // We must specify a packet handler method so that - serial.setPacketHandler(&onPacket); - serial.begin(115200); + // We begin communication with our PacketSerial object by setting the + // communication speed in bits / second (baud). + myPacketSerial.begin(115200); + + // If we want to receive packets, we must specify a packet handler function. + // The packet handler is a custom function with a signature like the + // onPacketReceived function below. + myPacketSerial.setPacketHandler(&onPacketReceived); } void loop() { - // Do other things here. - - // The update() method attempts to read in - // any incoming serial data and emits packets via - // the user's onPacket(const uint8_t* buffer, size_t size) - // method registered with the setPacketHandler() method. + // Do your program-specific loop() work here as usual. + + // The PacketSerial::update() method attempts to read in any incoming serial + // data and emits received and decoded packets via the packet handler + // function specified by the user in the void setup() function. // - // The update() method should be called at the end of the loop(). - serial.update(); + // The PacketSerial::update() method should be called once per loop(). Failure + // to call the PacketSerial::update() frequently enough may result in buffer + // serial overflows. + myPacketSerial.update(); + + // Check for a receive buffer overflow (optional). + if (myPacketSerial.overflow()) + { + // Send an alert via a pin (e.g. make an overflow LED) or return a + // user-defined packet to the sender. + // + // Ultimately you may need to just increase your recieve buffer via the + // template parameters (see the README.md). + } } -// This is our packet callback. -// The buffer is delivered already decoded. -void onPacket(const uint8_t* buffer, size_t size) +// This is our handler callback function. +// When an encoded packet is received and decoded, it will be delivered here. +// The `buffer` is a pointer to the decoded byte array. `size` is the number of +// bytes in the `buffer`. +void onPacketReceived(const uint8_t* buffer, size_t size) { + // In this example, we will simply reverse the contents of the array and send + // it back to the sender. + // Make a temporary buffer. - uint8_t tmp[size]; - + uint8_t tempBuffer[size]; + // Copy the packet into our temporary buffer. - memcpy(tmp, buffer, size); - + memcpy(tempBuffer, buffer, size); + // Reverse our temporaray buffer. - reverse(tmp, size); - - // Send the reversed buffer back. - // The send() method will encode the buffer - // as a packet, set packet markers, etc. - serial.send(tmp, size); + reverse(tempBuffer, size); + + // Send the reversed buffer back to the sender. The send() method will encode + // the whole buffer as as single packet, set packet markers, etc. + // The `tempBuffer` is a pointer to the `tempBuffer` array and `size` is the + // number of bytes to send in the `tempBuffer`. + myPacketSerial.send(tempBuffer, size); } -/// \brief A simple array reversal method. +// This function takes a byte buffer and reverses it. void reverse(uint8_t* buffer, size_t size) { uint8_t tmp; - - for (int i=0; i < size / 2; i++) + + for (size_t i = 0; i < size / 2; i++) { tmp = buffer[i]; - buffer[i] = buffer[size-i-1]; - buffer[size-i-1] = tmp; + buffer[i] = buffer[size - i - 1]; + buffer[size - i - 1] = tmp; } } - diff --git a/examples/PacketSerialReverseEchoAdvanced/.due.test.skip b/examples/PacketSerialReverseEchoAdvanced/.due.test.skip new file mode 100644 index 0000000..e69de29 diff --git a/examples/PacketSerialReverseEchoAdvanced/.esp32.test.skip b/examples/PacketSerialReverseEchoAdvanced/.esp32.test.skip new file mode 100644 index 0000000..e69de29 diff --git a/examples/PacketSerialReverseEchoAdvanced/.m4.test.skip b/examples/PacketSerialReverseEchoAdvanced/.m4.test.skip new file mode 100644 index 0000000..e69de29 diff --git a/examples/PacketSerialReverseEchoAdvanced/.zero.test.skip b/examples/PacketSerialReverseEchoAdvanced/.zero.test.skip new file mode 100644 index 0000000..e69de29 diff --git a/examples/PacketSerialReverseEchoAdvanced/PacketSerialReverseEchoAdvanced.ino b/examples/PacketSerialReverseEchoAdvanced/PacketSerialReverseEchoAdvanced.ino new file mode 100644 index 0000000..99f68e3 --- /dev/null +++ b/examples/PacketSerialReverseEchoAdvanced/PacketSerialReverseEchoAdvanced.ino @@ -0,0 +1,139 @@ +// +// Copyright (c) 2012 Christopher Baker +// +// SPDX-License-Identifier: MIT +// + + +#include +#include + + +// Instances of this class can recieve data packets when registered. +class MyClass +{ +public: + void processPacketFromSender(const PacketSerial& sender, const uint8_t* buffer, size_t size) + { + // Just send the buffer back to the sender. + sender.send(buffer, size); + } +}; + +// By default, PacketSerial automatically wraps the built-in `Serial` object. +// While it is still possible to use the Serial object directly, it is +// recommended that the user let the PacketSerial object manage all serial +// communication. Thus the user should not call Serial.write(), Serial.print(), +// etc. Additionally the user should not use the serialEvent() framework. +// +// By default, PacketSerial uses COBS encoding and has a 256 byte receive +// buffer. This can be adjusted by the user by replacing `PacketSerial` with +// a variation of the `PacketSerial_` template found in +// PacketSerial.h. +PacketSerial myPacketSerial; + + +// Note that SoftwareSerial is not compatible with SAMD_ZERO + +// An additional PacketSerial instance. +SoftwareSerial mySoftwareSerial(10, 11); +PacketSerial myOtherPacketSerial; + +// An instance of our custom class. +MyClass myClassInstance; + +void setup() +{ + // We begin communication with our PacketSerial object by setting the + // communication speed in bits / second (baud). + myPacketSerial.begin(115200); + + // If we want to receive packets, we must specify a packet handler function. + // The packet handler is a custom function with a signature like the + // onPacketReceived function below. + myPacketSerial.setPacketHandler(&onPacketReceived); + + // Set up a scond custom Serial connection on Serial1. + mySoftwareSerial.begin(9600); + myOtherPacketSerial.setStream(&mySoftwareSerial); + + // Here we set the packet handler to be a member of the given instance of + // MyClass using a lambda function. Static variables (e.g. myClassInstance) + // don't need to be captured. Additionally the member function of MyClass + // that processes the packet isn't required to match the packet handler + // function signature. + myOtherPacketSerial.setPacketHandler([](const uint8_t* buffer, size_t size) { + myClassInstance.processPacketFromSender(myOtherPacketSerial, buffer, size); + }); +} + + +void loop() +{ + // Do your program-specific loop() work here as usual. + + // The PacketSerial::update() method attempts to read in any incoming serial + // data and emits received and decoded packets via the packet handler + // function specified by the user in the void setup() function. + // + // The PacketSerial::update() method should be called once per loop(). Failure + // to call the PacketSerial::update() frequently enough may result in buffer + // serial overflows. + myPacketSerial.update(); + + // Check for a receive buffer overflow (optional). + if (myPacketSerial.overflow()) + { + // Send an alert via a pin (e.g. make an overflow LED) or return a + // user-defined packet to the sender. + // + // Ultimately you may need to just increase your recieve buffer via the + // template parameters (see the README.md). + } +} + +// This is our handler callback function. +// When an encoded packet is received and decoded, it will be delivered here. +// The sender is a pointer to the sending PacketSerial instance. The `buffer` is +// a pointer to the decoded byte array. `size` is the number of bytes in the +// `buffer`. +void onPacketReceived(const void* sender, const uint8_t* buffer, size_t size) +{ + if (sender == &myPacketSerial) + { + // In this example, we will simply reverse the contents of the array and send + // it back to the sender. + // Make a temporary buffer. + uint8_t tempBuffer[size]; + + // Copy the packet into our temporary buffer. + memcpy(tempBuffer, buffer, size); + + // Reverse our temporaray buffer. + reverse(tempBuffer, size); + + // Send the reversed buffer back to the sender. The send() method will encode + // the whole buffer as as single packet, set packet markers, etc. + // The `tempBuffer` is a pointer to the `tempBuffer` array and `size` is the + // number of bytes to send in the `tempBuffer`. + myPacketSerial.send(tempBuffer, size); + } + else if (sender == &myOtherPacketSerial) + { + // Just send it back without reversing it. + myOtherPacketSerial.send(buffer, size); + } +} + +// This function takes a byte buffer and reverses it. +void reverse(uint8_t* buffer, size_t size) +{ + uint8_t tmp; + + for (size_t i = 0; i < size / 2; i++) + { + tmp = buffer[i]; + buffer[i] = buffer[size - i - 1]; + buffer[size - i - 1] = tmp; + } +} diff --git a/examples/PacketSerialReverseEchoAdvanced/README.md b/examples/PacketSerialReverseEchoAdvanced/README.md new file mode 100644 index 0000000..be267dc --- /dev/null +++ b/examples/PacketSerialReverseEchoAdvanced/README.md @@ -0,0 +1,5 @@ +# Note + +This example will not compile on boards that don't support `SoftwareSerial`. This includes DUE, Zero, etc. + +That said, the concept of setting a custom `Stream` output will apply to any `Stream` supported by those boards. diff --git a/examples/PacketSerialReverseEchoClass/PacketSerialReverseEchoClass.ino b/examples/PacketSerialReverseEchoClass/PacketSerialReverseEchoClass.ino new file mode 100644 index 0000000..59744f3 --- /dev/null +++ b/examples/PacketSerialReverseEchoClass/PacketSerialReverseEchoClass.ino @@ -0,0 +1,115 @@ +// +// Copyright (c) 2012 Christopher Baker +// +// SPDX-License-Identifier: MIT +// + +// This example is PacketSerialReverseEcho modified to use PacketSerial from within a class + +#include + +// This function takes a byte buffer and reverses it. +void reverse(uint8_t* buffer, size_t size) +{ + uint8_t tmp; + + for (size_t i = 0; i < size / 2; i++) + { + tmp = buffer[i]; + buffer[i] = buffer[size - i - 1]; + buffer[size - i - 1] = tmp; + } +} + +class EchoClass { + public: + void begin(unsigned long speed) { + // If we want to receive packets, we must specify a packet handler function. + // The packet handler is a custom function with a signature like the + // onPacketReceived function below. + myPacketSerial.setPacketHandler(&onPacketReceived, this); + + myPacketSerial.begin(speed); + } + + void loop() { + // The PacketSerial::update() method attempts to read in any incoming serial + // data and emits received and decoded packets via the packet handler + // function specified by the user in the void setup() function. + // + // The PacketSerial::update() method should be called once per loop(). Failure + // to call the PacketSerial::update() frequently enough may result in buffer + // serial overflows. + myPacketSerial.update(); + + // Check for a receive buffer overflow (optional). + if (myPacketSerial.overflow()) + { + // Send an alert via a pin (e.g. make an overflow LED) or return a + // user-defined packet to the sender. + // + // Ultimately you may need to just increase your recieve buffer via the + // template parameters (see the README.md). + } + } + + private: + // C-style callbacks can't use non-static methods, so we use a static method that receives "this" as the sender argument: https://wiki.c2.com/?VirtualStaticIdiom + static void onPacketReceived(const void* sender, const uint8_t* buffer, size_t size) { + ((EchoClass*)sender)->onPacketReceived(buffer, size); + } + + // This is our handler callback function. + // When an encoded packet is received and decoded, it will be delivered here. + // The `buffer` is a pointer to the decoded byte array. `size` is the number of + // bytes in the `buffer`. + void onPacketReceived(const uint8_t* buffer, size_t size) { + // In this example, we will simply reverse the contents of the array and send + // it back to the sender. + + // Make a temporary buffer. + uint8_t tempBuffer[size]; + + // Copy the packet into our temporary buffer. + memcpy(tempBuffer, buffer, size); + + // Reverse our temporaray buffer. + reverse(tempBuffer, size); + + // Send the reversed buffer back to the sender. The send() method will encode + // the whole buffer as as single packet, set packet markers, etc. + // The `tempBuffer` is a pointer to the `tempBuffer` array and `size` is the + // number of bytes to send in the `tempBuffer`. + myPacketSerial.send(tempBuffer, size); + } + + PacketSerial myPacketSerial; +}; + +// By default, PacketSerial automatically wraps the built-in `Serial` object. +// While it is still possible to use the Serial object directly, it is +// recommended that the user let the PacketSerial object manage all serial +// communication. Thus the user should not call Serial.write(), Serial.print(), +// etc. Additionally the user should not use the serialEvent() framework. +// +// By default, PacketSerial uses COBS encoding and has a 256 byte receive +// buffer. This can be adjusted by the user by replacing `PacketSerial` with +// a variation of the `PacketSerial_` template found in +// PacketSerial.h. + +EchoClass myEchoClass; + +void setup() +{ + // We begin communication with our PacketSerial object by setting the + // communication speed in bits / second (baud). + myEchoClass.begin(115200); +} + + +void loop() +{ + // Do your program-specific loop() work here as usual. + + myEchoClass.loop(); +} diff --git a/examples/PacketSerialReverseEchoSLIP/PacketSerialReverseEchoSLIP.ino b/examples/PacketSerialReverseEchoSLIP/PacketSerialReverseEchoSLIP.ino new file mode 100644 index 0000000..15cfb8a --- /dev/null +++ b/examples/PacketSerialReverseEchoSLIP/PacketSerialReverseEchoSLIP.ino @@ -0,0 +1,97 @@ +// +// Copyright (c) 2012 Christopher Baker +// +// SPDX-License-Identifier: MIT +// + + +#include + + +// By default, PacketSerial automatically wraps the built-in `Serial` object. +// While it is still possible to use the Serial object directly, it is +// recommended that the user let the PacketSerial object manage all serial +// communication. Thus the user should not call Serial.write(), Serial.print(), +// etc. Additionally the user should not use the serialEvent() framework. +// +// By default, SLIPPacketSerial uses SLIP encoding and has a 256 byte receive +// buffer. This can be adjusted by the user by replacing `SLIPPacketSerial` +// with a variation of the `PacketSerial_` template +// found in PacketSerial.h. +SLIPPacketSerial myPacketSerial; + + +void setup() +{ + // We begin communication with our PacketSerial object by setting the + // communication speed in bits / second (baud). + myPacketSerial.begin(115200); + + // If we want to receive packets, we must specify a packet handler function. + // The packet handler is a custom function with a signature like the + // onPacketReceived function below. + myPacketSerial.setPacketHandler(&onPacketReceived); +} + + +void loop() +{ + // Do your program-specific loop() work here as usual. + + // The PacketSerial::update() method attempts to read in any incoming serial + // data and emits received and decoded packets via the packet handler + // function specified by the user in the void setup() function. + // + // The PacketSerial::update() method should be called once per loop(). Failure + // to call the PacketSerial::update() frequently enough may result in buffer + // serial overflows. + myPacketSerial.update(); + + // Check for a receive buffer overflow (optional). + if (myPacketSerial.overflow()) + { + // Send an alert via a pin (e.g. make an overflow LED) or return a + // user-defined packet to the sender. + // + // Ultimately you may need to just increase your recieve buffer via the + // template parameters (see the README.md). + } +} + +// This is our handler callback function. +// When an encoded packet is received and decoded, it will be delivered here. +// The `buffer` is a pointer to the decoded byte array. `size` is the number of +// bytes in the `buffer`. +void onPacketReceived(const uint8_t* buffer, size_t size) +{ + // In this example, we will simply reverse the contents of the array and send + // it back to the sender. + + // Make a temporary buffer. + uint8_t tempBuffer[size]; + + // Copy the packet into our temporary buffer. + memcpy(tempBuffer, buffer, size); + + // Reverse our temporaray buffer. + reverse(tempBuffer, size); + + // Send the reversed buffer back to the sender. The send() method will encode + // the whole buffer as as single packet, set packet markers, etc. + // The `tempBuffer` is a pointer to the `tempBuffer` array and `size` is the + // number of bytes to send in the `tempBuffer`. + myPacketSerial.send(tempBuffer, size); +} + +// This function takes a byte buffer and reverses it. +void reverse(uint8_t* buffer, size_t size) +{ + uint8_t tmp; + + for (size_t i = 0; i < size / 2; i++) + { + tmp = buffer[i]; + buffer[i] = buffer[size - i - 1]; + buffer[size - i - 1] = tmp; + } +} diff --git a/keywords.txt b/keywords.txt index a32cbf9..8065a22 100644 --- a/keywords.txt +++ b/keywords.txt @@ -7,6 +7,11 @@ ####################################### PacketSerial_ KEYWORD1 PacketSerial KEYWORD1 +COBSPacketSerial KEYWORD1 +SLIPPacketSerial KEYWORD1 + +SLIP KEYWORD1 +COBS KEYWORD1 ####################################### # Methods and Functions (KEYWORD2) @@ -14,14 +19,19 @@ PacketSerial KEYWORD1 encode KEYWORD2 decode KEYWORD2 -getEncodedBufferSize KEYWORD2 -send KEYWORD2 -setPacketHandler KEYWORD2 -update KEYWORD2 -begin KEYWORD2 -onPacket KEYWORD2 +getEncodedBufferSize KEYWORD2 +send KEYWORD2 +setPacketHandler KEYWORD2 +update KEYWORD2 +begin KEYWORD2 +setStream KEYWORD2 +onPacketReceived KEYWORD2 ####################################### # Constants (LITERAL1) ####################################### +END LITERAL1 +ESC LITERAL1 +ESC_END LITERAL1 +ESC_ESC LITERAL1 diff --git a/library.properties b/library.properties index 0a141ec..6110603 100644 --- a/library.properties +++ b/library.properties @@ -1,8 +1,9 @@ name=PacketSerial -version=1.0 +version=1.4.0 author=Christopher Baker maintainer=Christopher Baker -sentence=A small, efficient library for sending serial data packets. -paragraph=PacketSerial is an small, efficient, library that allows Arduinos to send and receive serial data packets that include bytes with any value (0-255). A packet is simply an array of bytes. +sentence=An Arduino Library that facilitates packet-based serial communication using COBS or SLIP encoding. +paragraph=PacketSerial is an small, efficient, library that allows Arduinos to send and receive serial data packets (with COBS, SLIP or a user-defined encoding) that include bytes of any value (0 - 255). A packet is simply an array of bytes. +category=Communication url=https://github.com/bakercp/PacketSerial -architectures=avr +architectures=* diff --git a/src/Encoding/COBS.h b/src/Encoding/COBS.h index 822f1c9..5723e43 100644 --- a/src/Encoding/COBS.h +++ b/src/Encoding/COBS.h @@ -1,30 +1,9 @@ -// ============================================================================= // -// Copyright (c) 2010-2014 Christopher Baker +// Copyright (c) 2011 Christopher Baker +// Copyright (c) 2011 Jacques Fortier // -// Portions: -// Copyright (c) 2011, Jacques Fortier. All rights reserved. -// https://github.com/jacquesf/COBS-Consistent-Overhead-Byte-Stuffing +// SPDX-License-Identifier: MIT // -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// -// ============================================================================= #pragma once @@ -44,102 +23,107 @@ /// additional byte per 254 bytes of data). For messages smaller than 254 bytes, /// the overhead is constant. /// -/// (via http://www.jacquesf.com/2011/03/consistent-overhead-byte-stuffing/) -/// /// \sa http://conferences.sigcomm.org/sigcomm/1997/papers/p062.pdf /// \sa http://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing /// \sa https://github.com/jacquesf/COBS-Consistent-Overhead-Byte-Stuffing -/// \sa http://www.jacquesf.com/2011/03/consistent-overhead-byte-stuffing/ -class COBS +/// \sa http://www.jacquesf.com/2011/03/consistent-overhead-byte-stuffing +class COBS { public: /// \brief Encode a byte buffer with the COBS encoder. - /// \param source The buffer to encode. - /// \param size The size of the buffer to encode. - /// \param destination The target buffer for the encoded bytes. - /// \returns The number of bytes in the encoded buffer. - /// \warning destination must have a minimum capacity of - /// (size + size / 254 + 1). - static size_t encode(const uint8_t* source, size_t size, uint8_t* destination) + /// \param buffer A pointer to the unencoded buffer to encode. + /// \param size The number of bytes in the \p buffer. + /// \param encodedBuffer The buffer for the encoded bytes. + /// \returns The number of bytes written to the \p encodedBuffer. + /// \warning The encodedBuffer must have at least getEncodedBufferSize() + /// allocated. + static size_t encode(const uint8_t* buffer, + size_t size, + uint8_t* encodedBuffer) { size_t read_index = 0; size_t write_index = 1; size_t code_index = 0; uint8_t code = 1; - while(read_index < size) + while (read_index < size) { - if(source[read_index] == 0) + if (buffer[read_index] == 0) { - destination[code_index] = code; + encodedBuffer[code_index] = code; code = 1; code_index = write_index++; read_index++; } else { - destination[write_index++] = source[read_index++]; + encodedBuffer[write_index++] = buffer[read_index++]; code++; - if(code == 0xFF) + if (code == 0xFF) { - destination[code_index] = code; + encodedBuffer[code_index] = code; code = 1; code_index = write_index++; } } } - destination[code_index] = code; + encodedBuffer[code_index] = code; return write_index; } + /// \brief Decode a COBS-encoded buffer. - /// \param source The COBS-encoded buffer to decode. - /// \param size The size of the COBS-encoded buffer. - /// \param destination The target buffer for the decoded bytes. - /// \returns The number of bytes in the decoded buffer. - /// \warning destination must have a minimum capacity of - /// size - static size_t decode(const uint8_t* source, size_t size, uint8_t* destination) + /// \param encodedBuffer A pointer to the \p encodedBuffer to decode. + /// \param size The number of bytes in the \p encodedBuffer. + /// \param decodedBuffer The target buffer for the decoded bytes. + /// \returns The number of bytes written to the \p decodedBuffer. + /// \warning decodedBuffer must have a minimum capacity of size. + static size_t decode(const uint8_t* encodedBuffer, + size_t size, + uint8_t* decodedBuffer) { + if (size == 0) + return 0; + size_t read_index = 0; size_t write_index = 0; - uint8_t code; - uint8_t i; + uint8_t code = 0; + uint8_t i = 0; - while(read_index < size) + while (read_index < size) { - code = source[read_index]; + code = encodedBuffer[read_index]; - if(read_index + code > size && code != 1) + if (read_index + code > size && code != 1) { return 0; } read_index++; - for(i = 1; i < code; i++) + for (i = 1; i < code; i++) { - destination[write_index++] = source[read_index++]; + decodedBuffer[write_index++] = encodedBuffer[read_index++]; } - if(code != 0xFF && read_index != size) + if (code != 0xFF && read_index != size) { - destination[write_index++] = '\0'; + decodedBuffer[write_index++] = '\0'; } } - + return write_index; } - /// \brief Get the maximum encoded buffer size needed for a given source size. - /// \param sourceSize The size of the buffer to be encoded. + /// \brief Get the maximum encoded buffer size for an unencoded buffer size. + /// \param unencodedBufferSize The size of the buffer to be encoded. /// \returns the maximum size of the required encoded buffer. - static size_t getEncodedBufferSize(size_t sourceSize) + static size_t getEncodedBufferSize(size_t unencodedBufferSize) { - return sourceSize + sourceSize / 254 + 1; + return unencodedBufferSize + unencodedBufferSize / 254 + 1; } }; diff --git a/src/Encoding/SLIP.h b/src/Encoding/SLIP.h new file mode 100644 index 0000000..bbd1c70 --- /dev/null +++ b/src/Encoding/SLIP.h @@ -0,0 +1,158 @@ +// +// Copyright (c) 2010 Christopher Baker +// Copyright (c) 2016 Antoine Villeret +// +// SPDX-License-Identifier: MIT +// + + +#pragma once + + +#include "Arduino.h" + + +/// \brief A Serial Line Internet Protocol (SLIP) Encoder. +/// +/// Serial Line Internet Protocol (SLIP) is a packet framing protocol: SLIP +/// defines a sequence of characters that frame IP packets on a serial line and +/// nothing more. It provides no addressing, packet type identification, error +/// detection, correction or compression mechanisms. Because the protocol does +/// so little its implementation is trivial and fast. +/// +/// \sa http://tools.ietf.org/html/rfc1055 +class SLIP +{ +public: + /// \brief Encode a byte buffer with the SLIP encoder. + /// \param buffer A pointer to the unencoded buffer to encode. + /// \param size The number of bytes in the \p buffer. + /// \param encodedBuffer The buffer for the encoded bytes. + /// \returns The number of bytes written to the \p encodedBuffer. + /// \warning The encodedBuffer must have at least getEncodedBufferSize() + /// allocated. + static size_t encode(const uint8_t* buffer, + size_t size, + uint8_t* encodedBuffer) + { + if (size == 0) + return 0; + + size_t read_index = 0; + size_t write_index = 0; + + // Double-ENDed, flush any data that may have accumulated due to line + // noise. + encodedBuffer[write_index++] = END; + + while (read_index < size) + { + if(buffer[read_index] == END) + { + encodedBuffer[write_index++] = ESC; + encodedBuffer[write_index++] = ESC_END; + read_index++; + } + else if(buffer[read_index] == ESC) + { + encodedBuffer[write_index++] = ESC; + encodedBuffer[write_index++] = ESC_ESC; + read_index++; + } + else + { + encodedBuffer[write_index++] = buffer[read_index++]; + } + } + + return write_index; + } + + /// \brief Decode a SLIP-encoded buffer. + /// \param encodedBuffer A pointer to the \p encodedBuffer to decode. + /// \param size The number of bytes in the \p encodedBuffer. + /// \param decodedBuffer The target buffer for the decoded bytes. + /// \returns The number of bytes written to the \p decodedBuffer. + /// \warning decodedBuffer must have a minimum capacity of size. + static size_t decode(const uint8_t* encodedBuffer, + size_t size, + uint8_t* decodedBuffer) + { + if (size == 0) + return 0; + + size_t read_index = 0; + size_t write_index = 0; + + while (read_index < size) + { + if (encodedBuffer[read_index] == END) + { + // flush or done + read_index++; + } + else if (encodedBuffer[read_index] == ESC) + { + if (encodedBuffer[read_index+1] == ESC_END) + { + decodedBuffer[write_index++] = END; + read_index += 2; + } + else if (encodedBuffer[read_index+1] == ESC_ESC) + { + decodedBuffer[write_index++] = ESC; + read_index += 2; + } + else + { + // This case is considered a protocol violation. + } + } + else + { + decodedBuffer[write_index++] = encodedBuffer[read_index++]; + } + } + + return write_index; + } + + /// \brief Get the maximum encoded buffer size for an unencoded buffer size. + /// + /// SLIP has a start and end markers (192 and 219). Marker value is + /// replaced by 2 bytes in the encoded buffer. So in the worst case of + /// sending a buffer with only '192' or '219', the encoded buffer length + /// will be 2 * buffer.size() + 2. + /// + /// \param unencodedBufferSize The size of the buffer to be encoded. + /// \returns the maximum size of the required encoded buffer. + static size_t getEncodedBufferSize(size_t unencodedBufferSize) + { + return unencodedBufferSize * 2 + 2; + } + + /// \brief Key constants used in the SLIP protocol. + enum + { + /// \brief The decimal END character (octal 0300). + /// + /// Indicates the end of a packet. + END = 192, + + /// \brief The decimal ESC character (octal 0333). + /// + /// Indicates byte stuffing. + ESC = 219, + + /// \brief The decimal ESC_END character (octal 0334). + /// + /// ESC ESC_END means END data byte. + ESC_END = 220, + + /// \brief The decimal ESC_ESC character (ocatal 0335). + /// + /// ESC ESC_ESC means ESC data byte. + ESC_ESC = 221 + }; + +}; diff --git a/src/PacketSerial.h b/src/PacketSerial.h index a4b1333..67de570 100644 --- a/src/PacketSerial.h +++ b/src/PacketSerial.h @@ -1,25 +1,8 @@ -// ============================================================================= -// Copyright (c) 2013 Christopher Baker // -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: +// Copyright (c) 2013 Christopher Baker // -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. +// SPDX-License-Identifier: MIT // -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// -// ============================================================================= #pragma once @@ -27,122 +10,425 @@ #include #include "Encoding/COBS.h" +#include "Encoding/SLIP.h" -template +/// \brief A template class enabling packet-based Serial communication. +/// +/// Typically one of the typedefined versions are used, for example, +/// `COBSPacketSerial` or `SLIPPacketSerial`. +/// +/// The template parameters allow the user to define their own packet encoder / +/// decoder, custom packet marker and receive buffer size. +/// +/// \tparam EncoderType The static packet encoder class name. +/// \tparam PacketMarker The byte value used to mark the packet boundary. +/// \tparam BufferSize The number of bytes allocated for the receive buffer. +template class PacketSerial_ { public: + /// \brief A typedef describing the packet handler method. + /// + /// The packet handler method usually has the form: + /// + /// void onPacketReceived(const uint8_t* buffer, size_t size); + /// + /// where buffer is a pointer to the incoming buffer array, and size is the + /// number of bytes in the incoming buffer. typedef void (*PacketHandlerFunction)(const uint8_t* buffer, size_t size); + /// \brief A typedef describing the packet handler method. + /// + /// The packet handler method usually has the form: + /// + /// void onPacketReceived(void* sender, const uint8_t* buffer, size_t size); + /// + /// where sender is a pointer to the PacketSerial_ instance that recieved + /// the buffer, buffer is a pointer to the incoming buffer array, and size + /// is the number of bytes in the incoming buffer. + typedef void (*PacketHandlerFunctionWithSender)(const void* sender, const uint8_t* buffer, size_t size); + + /// \brief Construct a default PacketSerial_ device. PacketSerial_(): - _recieveBufferIndex(0), - _serial(0), - _onPacketFunction(0) + _receiveBufferIndex(0), + _stream(nullptr), + _onPacketFunction(nullptr), + _onPacketFunctionWithSender(nullptr), + _senderPtr(nullptr) { } + /// \brief Destroy the PacketSerial_ device. ~PacketSerial_() { } - void begin(unsigned long baud, size_t port = 0) + /// \brief Begin a default serial connection with the given speed. + /// + /// The default Serial port `Serial` and default config `SERIAL_8N1` will be + /// used. For example: + /// + /// PacketSerial myPacketSerial; + /// + /// void setup() + /// { + /// myPacketSerial.begin(9600); + /// } + /// + /// This is a convenience method. For more complex Serial port + /// configurations, use the `setStream()` function to set an arbitrary + /// Arduino Stream. + /// + /// \param speed The serial data transmission speed in bits / second (baud). + /// \sa https://www.arduino.cc/en/Serial/Begin + void begin(unsigned long speed) + { + Serial.begin(speed); + #if ARDUINO >= 100 && !defined(CORE_TEENSY) + while (!Serial) {;} + #endif + setStream(&Serial); + } + + /// \brief Deprecated. Use setStream() to configure a non-default port. + /// \param speed The serial data transmission speed in bits / second (baud). + /// \param port The Serial port number (e.g. 0 is Serial, 1 is Serial1). + /// \deprecated Use setStream() to configure a non-default port. + void begin(unsigned long speed, size_t port) __attribute__ ((deprecated)) { - switch(port) + switch(port) { #if defined(UBRR1H) case 1: - Serial1.begin(baud); - _serial = &Serial1; + Serial1.begin(speed); + #if ARDUINO >= 100 && !defined(CORE_TEENSY) + while (!Serial1) {;} + #endif + setStream(&Serial1); break; #endif #if defined(UBRR2H) case 2: - Serial2.begin(baud); - _serial = &Serial2; + Serial2.begin(speed); + #if ARDUINO >= 100 && !defined(CORE_TEENSY) + while (!Serial1) {;} + #endif + setStream(&Serial2); break; #endif #if defined(UBRR3H) case 3: - Serial3.begin(baud); - _serial = &Serial3; + Serial3.begin(speed); + #if ARDUINO >= 100 && !defined(CORE_TEENSY) + while (!Serial3) {;} + #endif + setStream(&Serial3); break; #endif default: - Serial.begin(baud); - _serial = &Serial; + begin(speed); } } + /// \brief Deprecated. Use setStream() to configure a non-default port. + /// \param stream A pointer to an Arduino `Stream`. + /// \deprecated Use setStream() to configure a non-default port. + void begin(Stream* stream) __attribute__ ((deprecated)) + { + _stream = stream; + } + + /// \brief Attach PacketSerial to an existing Arduino `Stream`. + /// + /// This `Stream` could be a standard `Serial` `Stream` with a non-default + /// configuration such as: + /// + /// PacketSerial myPacketSerial; + /// + /// void setup() + /// { + /// Serial.begin(300, SERIAL_7N1); + /// myPacketSerial.setStream(&Serial); + /// } + /// + /// Or it might be a `SoftwareSerial` `Stream` such as: + /// + /// PacketSerial myPacketSerial; + /// SoftwareSerial mySoftwareSerial(10, 11); + /// + /// void setup() + /// { + /// mySoftwareSerial.begin(38400); + /// myPacketSerial.setStream(&mySoftwareSerial); + /// } + /// + /// Any class that implements the `Stream` interface should work, which + /// includes some network objects. + /// + /// \param stream A pointer to an Arduino `Stream`. + void setStream(Stream* stream) + { + _stream = stream; + } + + /// \brief Get a pointer to the current stream. + /// \warning Reading from or writing to the stream managed by PacketSerial_ + /// may break the packet-serial protocol if not done so with care. + /// Access to the stream is allowed because PacketSerial_ never + /// takes ownership of the stream and thus does not have exclusive + /// access to the stream anyway. + /// \returns a non-const pointer to the stream, or nullptr if unset. + Stream* getStream() + { + return _stream; + } + + /// \brief Get a pointer to the current stream. + /// \warning Reading from or writing to the stream managed by PacketSerial_ + /// may break the packet-serial protocol if not done so with care. + /// Access to the stream is allowed because PacketSerial_ never + /// takes ownership of the stream and thus does not have exclusive + /// access to the stream anyway. + /// \returns a const pointer to the stream, or nullptr if unset. + const Stream* getStream() const + { + return _stream; + } + + /// \brief The update function services the serial connection. + /// + /// This must be called often, ideally once per `loop()`, e.g.: + /// + /// void loop() + /// { + /// // Other program code. + /// + /// myPacketSerial.update(); + /// } + /// void update() { - if (_serial == 0) return; + if (_stream == nullptr) return; - while (_serial->available() > 0) + while (_stream->available() > 0) { - uint8_t data = _serial->read(); + uint8_t data = _stream->read(); if (data == PacketMarker) { - if (_onPacketFunction) + if (_onPacketFunction || _onPacketFunctionWithSender) { - uint8_t _decodeBuffer[_recieveBufferIndex]; + uint8_t _decodeBuffer[_receiveBufferIndex]; - size_t numDecoded = EncoderType::decode(_recieveBuffer, - _recieveBufferIndex, + size_t numDecoded = EncoderType::decode(_receiveBuffer, + _receiveBufferIndex, _decodeBuffer); - _onPacketFunction(_decodeBuffer, numDecoded); - } + // clear the index here so that the callback function can call update() if needed and receive more data + _receiveBufferIndex = 0; + _recieveBufferOverflow = false; + + if (_onPacketFunction) + { + _onPacketFunction(_decodeBuffer, numDecoded); + } + else if (_onPacketFunctionWithSender) + { + _onPacketFunctionWithSender(_senderPtr, _decodeBuffer, numDecoded); + } - _recieveBufferIndex = 0; + } else { + _receiveBufferIndex = 0; + _recieveBufferOverflow = false; + } } else { - if ((_recieveBufferIndex + 1) < BufferSize) + if ((_receiveBufferIndex + 1) < ReceiveBufferSize) { - _recieveBuffer[_recieveBufferIndex++] = data; + _receiveBuffer[_receiveBufferIndex++] = data; } else { - // Error, buffer overflow if we write. + // The buffer will be in an overflowed state if we write + // so set a buffer overflowed flag. + _recieveBufferOverflow = true; } } } } - void send(const uint8_t* buffer, size_t size) + /// \brief Set a packet of data. + /// + /// This function will encode and send an arbitrary packet of data. After + /// sending, it will send the specified `PacketMarker` defined in the + /// template parameters. + /// + /// // Make an array. + /// uint8_t myPacket[2] = { 255, 10 }; + /// + /// // Send the array. + /// myPacketSerial.send(myPacket, 2); + /// + /// \param buffer A pointer to a data buffer. + /// \param size The number of bytes in the data buffer. + void send(const uint8_t* buffer, size_t size) const { - if(_serial == 0 || buffer == 0 || size == 0) return; + if(_stream == nullptr || buffer == nullptr || size == 0) return; - uint8_t _encodeBuffer[EncoderType::getEncodedBufferSize(size)]; + uint8_t _encodeBuffer[EncoderType::getEncodedBufferSize(size)]; - size_t numEncoded = EncoderType::encode(buffer, - size, - _encodeBuffer); + size_t numEncoded = EncoderType::encode(buffer, + size, + _encodeBuffer); - _serial->write(_encodeBuffer, numEncoded); - _serial->write(PacketMarker); + _stream->write(_encodeBuffer, numEncoded); + _stream->write(PacketMarker); } + /// \brief Set the function that will receive decoded packets. + /// + /// This function will be called when data is read from the serial stream + /// connection and a packet is decoded. The decoded packet will be passed + /// to the packet handler. The packet handler must have the form: + /// + /// The packet handler method usually has the form: + /// + /// void onPacketReceived(const uint8_t* buffer, size_t size); + /// + /// The packet handler would then be registered like this: + /// + /// myPacketSerial.setPacketHandler(&onPacketReceived); + /// + /// Setting a packet handler will remove all other packet handlers. + /// + /// \param onPacketFunction A pointer to the packet handler function. void setPacketHandler(PacketHandlerFunction onPacketFunction) { _onPacketFunction = onPacketFunction; + _onPacketFunctionWithSender = nullptr; + _senderPtr = nullptr; + } + + /// \brief Set the function that will receive decoded packets. + /// + /// This function will be called when data is read from the serial stream + /// connection and a packet is decoded. The decoded packet will be passed + /// to the packet handler. The packet handler must have the form: + /// + /// The packet handler method usually has the form: + /// + /// void onPacketReceived(const void* sender, const uint8_t* buffer, size_t size); + /// + /// To determine the sender, compare the pointer to the known possible + /// PacketSerial senders. + /// + /// void onPacketReceived(void* sender, const uint8_t* buffer, size_t size) + /// { + /// if (sender == &myPacketSerial) + /// { + /// // Do something with the packet from myPacketSerial. + /// } + /// else if (sender == &myOtherPacketSerial) + /// { + /// // Do something with the packet from myOtherPacketSerial. + /// } + /// } + /// + /// The packet handler would then be registered like this: + /// + /// myPacketSerial.setPacketHandler(&onPacketReceived); + /// + /// You can also register an arbitrary void* pointer to be passed to your packet handler method. + /// This is most useful when PacketSerial is used inside a class, to pass a pointer to + /// the containing class: + /// + /// class EchoClass { + /// public: + /// void begin(unsigned long speed) { + /// myPacketSerial.setPacketHandler(&onPacketReceived, this); + /// myPacketSerial.begin(speed); + /// } + /// + /// // C-style callbacks can't use non-static methods, + /// // so we use a static method that receives "this" as the sender argument: + /// // https://wiki.c2.com/?VirtualStaticIdiom + /// static void onPacketReceived(const void* sender, const uint8_t* buffer, size_t size) { + /// ((EchoClass*)sender)->onPacketReceived(buffer, size); + /// } + /// + /// void onPacketReceived(const uint8_t* buffer, size_t size) { + /// // we can now use myPacketSerial as needed here + /// } + /// + /// PacketSerial myPacketSerial; + /// }; + /// + /// Setting a packet handler will remove all other packet handlers. + /// + /// \param onPacketFunctionWithSender A pointer to the packet handler function. + /// \param senderPtr Optional pointer to a void* pointer, default argument will pass a pointer to the sending PacketSerial instance to the callback + void setPacketHandler(PacketHandlerFunctionWithSender onPacketFunctionWithSender, void * senderPtr = nullptr) + { + _onPacketFunction = nullptr; + _onPacketFunctionWithSender = onPacketFunctionWithSender; + _senderPtr = senderPtr; + // for backwards compatibility, the default _senderPtr is "this", but you can't use "this" as a default argument + if(!senderPtr) _senderPtr = this; + } + + /// \brief Check to see if the receive buffer overflowed. + /// + /// This must be called often, directly after the `update()` function. + /// + /// void loop() + /// { + /// // Other program code. + /// myPacketSerial.update(); + /// + /// // Check for a receive buffer overflow. + /// if (myPacketSerial.overflow()) + /// { + /// // Send an alert via a pin (e.g. make an overflow LED) or return a + /// // user-defined packet to the sender. + /// // + /// // Ultimately you may need to just increase your recieve buffer via the + /// // template parameters. + /// } + /// } + /// + /// The state is reset every time a new packet marker is received NOT when + /// overflow() method is called. + /// + /// \returns true if the receive buffer overflowed. + bool overflow() const + { + return _recieveBufferOverflow; } - private: PacketSerial_(const PacketSerial_&); PacketSerial_& operator = (const PacketSerial_&); - uint8_t _recieveBuffer[BufferSize]; - size_t _recieveBufferIndex; - - Stream* _serial; - - PacketHandlerFunction _onPacketFunction; + bool _recieveBufferOverflow = false; + + uint8_t _receiveBuffer[ReceiveBufferSize]; + size_t _receiveBufferIndex = 0; + Stream* _stream = nullptr; + + PacketHandlerFunction _onPacketFunction = nullptr; + PacketHandlerFunctionWithSender _onPacketFunctionWithSender = nullptr; + void* _senderPtr = nullptr; }; +/// \brief A typedef for the default COBS PacketSerial class. typedef PacketSerial_ PacketSerial; + +/// \brief A typedef for a PacketSerial type with COBS encoding. +typedef PacketSerial_ COBSPacketSerial; + +/// \brief A typedef for a PacketSerial type with SLIP encoding. +typedef PacketSerial_ SLIPPacketSerial;