update Code
This commit is contained in:
parent
3c47103b39
commit
a641fad56c
|
@ -1,9 +1,101 @@
|
|||
#include <FlowMeter.h>
|
||||
#include <Wire.h>
|
||||
#include <Adafruit_MCP23017.h>
|
||||
Adafruit_MCP23017 mcp;
|
||||
byte ledPin=13;
|
||||
byte InteruptPinA=1;
|
||||
byte InteruptPinB=3;
|
||||
byte arduinoInterrupt=1;
|
||||
FlowMeter sensor[12]=
|
||||
volatile boolean awakenByInterrupt = false;
|
||||
|
||||
// Two pins at the MCP (Ports A/B where some buttons have been setup.)
|
||||
// Buttons connect the pin to grond, and pins are pulled up.
|
||||
byte mcpPinA=7;
|
||||
byte mcpPinB=15;
|
||||
|
||||
void setup(){
|
||||
// put your setup code here, to run once:
|
||||
pinMode(1, FUNCTION_3);
|
||||
pinMode(3, FUNCTION_3);
|
||||
|
||||
pinMode(arduinoIntPin,INPUT);
|
||||
mcp.begin(); // use default address 0
|
||||
mcp.setupInterrupts(true,false,LOW);
|
||||
// configuration for a button on port A
|
||||
mcp.pinMode(mcpPinA, INPUT);
|
||||
// mcp.pullUp(mcpPinA, HIGH); // turn on a 100K pullup internally
|
||||
mcp.setupInterruptPin(mcpPinA,RISING);
|
||||
mcp.pinMode(mcpPinB, INPUT);
|
||||
// mcp.pullUp(mcpPinB, HIGH); // turn on a 100K pullup internall
|
||||
mcp.setupInterruptPin(mcpPinB,RISING);
|
||||
|
||||
// We will setup a pin for flashing from the int routine
|
||||
pinMode(ledPin, OUTPUT);
|
||||
}
|
||||
|
||||
// The int handler will just signal that the int has happen
|
||||
// we will do the work from the main loop.
|
||||
void intCallBack(){
|
||||
awakenByInterrupt=true;
|
||||
}
|
||||
|
||||
void handleInterrupt(){
|
||||
|
||||
// Get more information from the MCP from the INT
|
||||
uint8_t pin=mcp.getLastInterruptPin();
|
||||
uint8_t val=mcp.getLastInterruptPinValue();
|
||||
|
||||
// We will flash the led 1 or 2 times depending on the PIN that triggered the Interrupt
|
||||
// 3 and 4 flases are supposed to be impossible conditions... just for debugging.
|
||||
uint8_t flashes=4;
|
||||
if(pin==mcpPinA) flashes=1;
|
||||
if(pin==mcpPinB) flashes=2;
|
||||
if(val!=LOW) flashes=3;
|
||||
|
||||
// simulate some output associated to this
|
||||
for(int i=0;i<flashes;i++){
|
||||
delay(100);
|
||||
digitalWrite(ledPin,HIGH);
|
||||
delay(100);
|
||||
digitalWrite(ledPin,LOW);
|
||||
}
|
||||
|
||||
// we have to wait for the interrupt condition to finish
|
||||
// otherwise we might go to sleep with an ongoing condition and never wake up again.
|
||||
// as, an action is required to clear the INT flag, and allow it to trigger again.
|
||||
// see datasheet for datails.
|
||||
while( ! (mcp.digitalRead(mcpPinB) && mcp.digitalRead(mcpPinA) ));
|
||||
// and clean queued INT signal
|
||||
cleanInterrupts();
|
||||
}
|
||||
|
||||
// handy for interrupts triggered by buttons
|
||||
// normally signal a few due to bouncing issues
|
||||
void cleanInterrupts(){
|
||||
EIFR=0x01;
|
||||
awakenByInterrupt=false;
|
||||
}
|
||||
|
||||
/**
|
||||
* main routine: sleep the arduino, and wake up on Interrups.
|
||||
* the LowPower library, or similar is required for sleeping, but sleep is simulated here.
|
||||
* It is actually posible to get the MCP to draw only 1uA while in standby as the datasheet claims,
|
||||
* however there is no stadndby mode. Its all down to seting up each pin in a way that current does not flow.
|
||||
* and you can wait for interrupts while waiting.
|
||||
*/
|
||||
void loop(){
|
||||
// put your main code here, to run repeatedly:
|
||||
|
||||
// enable interrupts before going to sleep/wait
|
||||
// And we setup a callback for the arduino INT handler.
|
||||
attachInterrupt(arduinoInterrupt,intCallBack,FALLING);
|
||||
|
||||
// Simulate a deep sleep
|
||||
while(!awakenByInterrupt);
|
||||
// Or sleep the arduino, this lib is great, if you have it.
|
||||
//LowPower.powerDown(SLEEP_1S, ADC_OFF, BOD_OFF);
|
||||
|
||||
// disable interrupts while handling them.
|
||||
detachInterrupt(arduinoInterrupt);
|
||||
|
||||
if(awakenByInterrupt) handleInterrupt();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
## Contributing to the FlowMeter library
|
||||
|
||||
All pull requests are welcome.
|
||||
Please follow the pull request template.
|
||||
|
||||
If you have a question or suggestion, feel free to open an issue on Github.
|
||||
Please follow the issue template.
|
|
@ -0,0 +1,9 @@
|
|||
## Expected behavior
|
||||
|
||||
## Observed behavior.
|
||||
|
||||
## Steps to reproduce the problem.
|
||||
|
||||
## Arduino (or other IDE) version info
|
||||
|
||||
## Arduino (or compatible) hardware info
|
|
@ -0,0 +1,37 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
|
||||
---
|
||||
|
||||
**Description**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Reproduction**
|
||||
Steps to reproduce the behavior:
|
||||
1. Connect '...'
|
||||
2. Measure '...'
|
||||
3. Error '...'
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Hardware (please complete the following information):**
|
||||
- Board type
|
||||
- Sensor type
|
||||
- Pins used
|
||||
|
||||
**Software (please complete the following information):**
|
||||
- OS
|
||||
- IDE
|
||||
- Other libraries
|
||||
|
||||
**Minimal example to reproduce (please complete the following information):**
|
||||
- Code to reproduce
|
||||
- Wiring to reproduce
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
|
@ -0,0 +1,11 @@
|
|||
## Tackles issue #
|
||||
|
||||
## changes being applied by this pull request
|
||||
|
||||
-
|
||||
-
|
||||
-
|
||||
|
||||
## people involved
|
||||
|
||||
@user/repository
|
|
@ -0,0 +1,22 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 sekdiy
|
||||
|
||||
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.
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
# FlowMeter Library [![Version](https://img.shields.io/badge/FlowMeter-1.1.0-blue.svg 'still in beta')](https://github.com/sekdiy/FlowMeter) [![Build Status](https://travis-ci.org/sekdiy/FlowMeter.svg?branch=master)](https://travis-ci.org/sekdiy/FlowMeter) [![analytics](http://www.google-analytics.com/collect?v=1&t=pageview&dl=https%3A%2F%2Fgithub.com%2Fsekdiy%2FFlowMeter&cid=2238D739-76DE-4205-9768-2F3277FA2561&tid=UA-65656434-2&aip=1&dt=README)]()
|
||||
|
||||
**FlowMeter** is an Arduino library that provides calibrated flow and volume measurement with flow sensors.
|
||||
|
||||
You can use it to count flow and volume of liquids and gases (although the documentation focuses on applications using liquids) and can support multiple flow sensors at the same time.
|
||||
|
||||
It also works as a totalizer, accumulating total volume and average flow rate over the run time of your project.
|
||||
|
||||
A provision for calibration helps you to get the most out of your sensor. You can even estimate the recent and overall error margin.
|
||||
|
||||
The library is intended for use with flow sensors that provide a frequency output signal proportional to flow, although other types of sensors could be made to work.
|
||||
|
||||
## A Simple Example
|
||||
|
||||
Getting started with **FlowMeter** is easy. Take a look at this simple example:
|
||||
|
||||
```c++
|
||||
void setup() {
|
||||
// prepare serial communication
|
||||
Serial.begin(9600);
|
||||
|
||||
// enable a call to a helper function on every rising edge
|
||||
attachInterrupt(INT0, MeterISR, RISING);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// wait between output updates
|
||||
delay(period);
|
||||
|
||||
// process the (possibly) counted ticks
|
||||
Meter.tick(period);
|
||||
|
||||
// output some measurement result
|
||||
Serial.print("Currently ");
|
||||
Serial.print(Meter.getCurrentFlowrate());
|
||||
Serial.print(" l/min, ");
|
||||
Serial.print(Meter.getTotalVolume());
|
||||
Serial.println(" l total.");
|
||||
|
||||
//
|
||||
// any other code can go here
|
||||
//
|
||||
}
|
||||
```
|
||||
|
||||
In the above example, a flow sensor is assumed to be connected to the `INT0` pin. The corresponding object `Meter` is updated every `period` (in milliseconds, e.g. 1000ms).
|
||||
|
||||
So after every measurement `period`, the current *flow rate* and the total *volume* are printed out.
|
||||
|
||||
Please read on in the [examples](https://github.com/sekdiy/FlowMeter/wiki/Examples) section of the library's [documentation pages](https://github.com/sekdiy/FlowMeter/wiki).
|
||||
|
||||
## Installing the library
|
||||
|
||||
Just check out the [**FlowMeter**](https://github.com/sekdiy/FlowMeter) Repository on GitHub (or download the ZIP archive) and copy it to your `libraries/` folder (usually within your Arduino sketchbook).
|
||||
|
||||
See the [installation](https://github.com/sekdiy/FlowMeter/wiki/Installation) section in the [documentation pages](https://github.com/sekdiy/FlowMeter/wiki) for more.
|
||||
|
||||
## Unit of measure
|
||||
|
||||
The **FlowMeter** library expresses flow in the unit **l/min**.
|
||||
Most units of measure can be derived by simple conversion (just try not to measure in [Sverdrups](https://en.wikipedia.org/wiki/Sverdrup)).
|
||||
|
||||
As an example, conversion between **l/min** and US **gal/min** can be done with a factor of *3.78541178*, conversion from **min** to **s** with a factor of *60*.
|
||||
|
||||
```math
|
||||
3.78541178 l/min ≈ 1 gal/min ≈ 0.0167 gal/s.
|
||||
```
|
||||
|
||||
Please make sure you consult the [documentation](https://github.com/sekdiy/FlowMeter/wiki/Properties) in order to further understand how the library works.
|
||||
|
||||
## Should you calibrate your own sensor?
|
||||
|
||||
The **FlowMeter** library can be used with many different flow sensors right away. Some [sensor examples](https://github.com/sekdiy/FlowMeter/wiki/Sensors) are listed in the documentation.
|
||||
|
||||
For many projects you don't need to worry about calibration. But it still makes sense to be aware of the limitations that come with an uncalibrated sensor in a metering application.
|
||||
|
||||
It's easy to calibrate yourself. Preferrably you'd do this after installing the sensor into your project. The flow meter then benefits from increased precision within the viscosity and flow range of your application.
|
||||
|
||||
There's a [complete how-to](https://github.com/sekdiy/FlowMeter/wiki/Calibration) in the documentation.
|
||||
|
||||
![Calibration Example: Irrigation with FS400A](https://github.com/sekdiy/FlowMeter/wiki/images/FS400A-calibration.jpg)
|
||||
|
||||
## Documentation
|
||||
|
||||
For further details please take a look at the **FlowMeter** [documentation pages](https://github.com/sekdiy/FlowMeter/wiki).
|
||||
|
||||
Also, the library source code (in the folder [`src/`](src/)) and the examples (in the folder [`examples/`](examples/)) are fully documented.
|
|
@ -0,0 +1,48 @@
|
|||
#include <FlowMeter.h> // https://github.com/sekdiy/FlowMeter
|
||||
|
||||
// enter your own sensor properties here, including calibration points
|
||||
FlowSensorProperties MySensor = {60.0f, 5.5f, {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}};
|
||||
FlowMeter Meter = FlowMeter(2, MySensor);
|
||||
|
||||
// timekeeping variables
|
||||
long period = 1000; // one second (in milliseconds)
|
||||
long lastTime = 0;
|
||||
|
||||
// define an 'interrupt service routine' (ISR)
|
||||
void MeterISR() {
|
||||
// let our flow meter count the pulses
|
||||
Meter.count();
|
||||
}
|
||||
|
||||
void setup() {
|
||||
// prepare serial communication
|
||||
Serial.begin(9600);
|
||||
|
||||
// enable a call to the 'interrupt service handler' (ISR) on every rising edge at the interrupt pin
|
||||
attachInterrupt(INT0, MeterISR, RISING);
|
||||
|
||||
// sometimes initializing the gear generates some pulses that we should ignore
|
||||
Meter.reset();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// do some timekeeping
|
||||
long currentTime = millis();
|
||||
long duration = currentTime - lastTime;
|
||||
|
||||
// wait between display updates
|
||||
if (duration >= period) {
|
||||
|
||||
// process the counted ticks
|
||||
Meter.tick(duration);
|
||||
|
||||
// output some measurement result
|
||||
Serial.println("FlowMeter - current flow rate: " + String(Meter.getCurrentFlowrate()) + " l/min, " +
|
||||
"nominal volume: " + String(Meter.getTotalVolume()) + " l, " +
|
||||
"compensated error: " + String(Meter.getCurrentError()) + " %, " +
|
||||
"duration: " + String(Meter.getTotalDuration() / 1000) + " s.");
|
||||
|
||||
// prepare for next cycle
|
||||
lastTime = currentTime;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
#include <FlowMeter.h> // https://github.com/sekdiy/FlowMeter
|
||||
|
||||
// connect a flow meter to an interrupt pin (see notes on your Arduino model for pin numbers)
|
||||
FlowMeter Meter1 = FlowMeter(2);
|
||||
FlowMeter Meter2 = FlowMeter(3);
|
||||
|
||||
// set the measurement update period to 1s (1000 ms)
|
||||
const unsigned long period = 1000;
|
||||
|
||||
// define an 'interrupt service handler' (ISR) for every interrupt pin you use
|
||||
void Meter1ISR() {
|
||||
// let our flow meter count the pulses
|
||||
Meter1.count();
|
||||
}
|
||||
|
||||
// define an 'interrupt service handler' (ISR) for every interrupt pin you use
|
||||
void Meter2ISR() {
|
||||
// let our flow meter count the pulses
|
||||
Meter2.count();
|
||||
}
|
||||
|
||||
void setup() {
|
||||
// prepare serial communication
|
||||
Serial.begin(9600);
|
||||
|
||||
// enable a call to the 'interrupt service handler' (ISR) on every rising edge at the interrupt pin
|
||||
// do this setup step for every ISR you have defined, depending on how many interrupts you use
|
||||
attachInterrupt(INT0, Meter1ISR, RISING);
|
||||
attachInterrupt(INT1, Meter2ISR, RISING);
|
||||
|
||||
// sometimes initializing the gear generates some pulses that we should ignore
|
||||
Meter1.reset();
|
||||
Meter2.reset();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// wait between output updates
|
||||
delay(period);
|
||||
|
||||
// process the (possibly) counted ticks
|
||||
Meter1.tick(period);
|
||||
Meter2.tick(period);
|
||||
|
||||
// output some measurement result
|
||||
Serial.println("Meter 1 currently " + String(Meter1.getCurrentFlowrate()) + " l/min, " + String(Meter1.getTotalVolume())+ " l total.");
|
||||
Serial.println("Meter 2 currently " + String(Meter2.getCurrentFlowrate()) + " l/min, " + String(Meter2.getTotalVolume())+ " l total.");
|
||||
|
||||
//
|
||||
// any other code can go here
|
||||
//
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
#include <FlowMeter.h> // https://github.com/sekdiy/FlowMeter
|
||||
|
||||
// connect a flow meter to an interrupt pin (see notes on your Arduino model for pin numbers)
|
||||
FlowMeter Meter = FlowMeter(2);
|
||||
|
||||
// set the measurement update period to 1s (1000 ms)
|
||||
const unsigned long period = 1000;
|
||||
|
||||
// define an 'interrupt service handler' (ISR) for every interrupt pin you use
|
||||
void MeterISR() {
|
||||
// let our flow meter count the pulses
|
||||
Meter.count();
|
||||
}
|
||||
|
||||
void setup() {
|
||||
// prepare serial communication
|
||||
Serial.begin(9600);
|
||||
|
||||
// enable a call to the 'interrupt service handler' (ISR) on every rising edge at the interrupt pin
|
||||
// do this setup step for every ISR you have defined, depending on how many interrupts you use
|
||||
attachInterrupt(INT0, MeterISR, RISING);
|
||||
|
||||
// sometimes initializing the gear generates some pulses that we should ignore
|
||||
Meter.reset();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// wait between output updates
|
||||
delay(period);
|
||||
|
||||
// process the (possibly) counted ticks
|
||||
Meter.tick(period);
|
||||
|
||||
// output some measurement result
|
||||
Serial.println("Currently " + String(Meter.getCurrentFlowrate()) + " l/min, " + String(Meter.getTotalVolume())+ " l total.");
|
||||
|
||||
//
|
||||
// any other code can go here
|
||||
//
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
#include <FlowMeter.h> // https://github.com/sekdiy/FlowMeter
|
||||
|
||||
// let's provide our own sensor properties, including calibration points for error correction
|
||||
FlowSensorProperties MySensor = {60.0f, 4.5f, {1.2, 1.1, 1.05, 1, 1, 1, 1, 0.95, 0.9, 0.8}};
|
||||
|
||||
// let's pretend there's a flow sensor connected to pin 3
|
||||
FlowMeter Meter = FlowMeter(3, MySensor);
|
||||
|
||||
void setup() {
|
||||
// prepare serial communication
|
||||
Serial.begin(9600);
|
||||
|
||||
// sometimes initializing the gear generates some pulses that we should ignore
|
||||
Meter.reset();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// randomly change simulation period and pulse rate
|
||||
long frequency = random(MySensor.capacity * MySensor.kFactor); // Hz
|
||||
long period = random(3 * frequency, 5000); // ms
|
||||
|
||||
// simulate random flow meter pulses within a random period
|
||||
for (long i = 0; i < (long) ((float) period * (float) frequency / 1000.0f); i++) {
|
||||
Meter.count();
|
||||
}
|
||||
|
||||
// wait that random period
|
||||
delay(period);
|
||||
|
||||
// process the counted ticks
|
||||
Meter.tick(period);
|
||||
|
||||
// output some measurement result
|
||||
Serial.println("FlowMeter - period: " + String(Meter.getCurrentDuration() / 1000.0f, 3) + "s, " +
|
||||
"frequency: " + String(Meter.getCurrentFrequency(), 0) + "Hz, " +
|
||||
"volume: " + Meter.getCurrentVolume() + "l, " +
|
||||
"flow rate: " + Meter.getCurrentFlowrate() + "l/min, " +
|
||||
"error: " + Meter.getCurrentError() + "%, " +
|
||||
"total duration: " + Meter.getTotalDuration() / 1000.0f + "s, " +
|
||||
"total volume: " + Meter.getTotalVolume() + "l, " +
|
||||
"average flow rate: " + Meter.getTotalFlowrate() + "l/min, " +
|
||||
"average error: " + Meter.getTotalError() + "%.");
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
#
|
||||
# Project Configuration File
|
||||
#
|
||||
# A detailed documentation with the EXAMPLES is located here:
|
||||
# http://docs.platformio.org/en/latest/projectconf.html
|
||||
#
|
||||
|
||||
[env:2009]
|
||||
platform = atmelavr
|
||||
framework = arduino
|
||||
board = diecimilaatmega328
|
||||
|
||||
[env:pro8]
|
||||
platform = atmelavr
|
||||
framework = arduino
|
||||
board = pro8MHzatmega328
|
||||
|
||||
# Automatic targets - enable auto-uploading
|
||||
targets = upload
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* Flow Meter
|
||||
*/
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "FlowMeter.h" // https://github.com/sekdiy/FlowMeter
|
||||
#include <math.h>
|
||||
|
||||
FlowMeter::FlowMeter(unsigned int pin, FlowSensorProperties prop) :
|
||||
_pin(pin), //!< store pin number
|
||||
_properties(prop) //!< store sensor properties
|
||||
{
|
||||
pinMode(pin, INPUT_PULLUP); //!< initialize interrupt pin as input with pullup
|
||||
}
|
||||
|
||||
double FlowMeter::getCurrentFlowrate() {
|
||||
return this->_currentFlowrate; //!< in l/min
|
||||
}
|
||||
|
||||
double FlowMeter::getCurrentVolume() {
|
||||
return this->_currentVolume; //!< in l
|
||||
}
|
||||
|
||||
double FlowMeter::getTotalFlowrate() {
|
||||
return this->_totalVolume / (this->_totalDuration / 1000.0f) * 60.0f; //!< in l/min
|
||||
}
|
||||
|
||||
double FlowMeter::getTotalVolume() {
|
||||
return this->_totalVolume; //!< in l
|
||||
}
|
||||
|
||||
void FlowMeter::tick(unsigned long duration) {
|
||||
/* sampling and normalisation */
|
||||
double seconds = duration / 1000.0f; //!< normalised duration (in s, i.e. per 1000ms)
|
||||
cli(); //!< going to change interrupt variable(s)
|
||||
double frequency = this->_currentPulses / seconds; //!< normalised frequency (in 1/s)
|
||||
this->_currentPulses = 0; //!< reset pulse counter after successfull sampling
|
||||
sei(); //!< done changing interrupt variable(s)
|
||||
|
||||
/* determine current correction factor (from sensor properties) */
|
||||
unsigned int decile = floor(10.0f * frequency / (this->_properties.capacity * this->_properties.kFactor)); //!< decile of current flow relative to sensor capacity
|
||||
unsigned int ceiling = 9; //!< highest possible decile index
|
||||
this->_currentCorrection = this->_properties.kFactor / this->_properties.mFactor[min(decile, ceiling)]; //!< combine constant k-factor and m-factor for decile
|
||||
|
||||
/* update current calculations: */
|
||||
this->_currentFlowrate = frequency / this->_currentCorrection; //!< get flow rate (in l/min) from normalised frequency and combined correction factor
|
||||
this->_currentVolume = this->_currentFlowrate / (60.0f/seconds); //!< get volume (in l) from normalised flow rate and normalised time
|
||||
|
||||
/* update statistics: */
|
||||
this->_currentDuration = duration; //!< store current tick duration (convenience, in ms)
|
||||
this->_currentFrequency = frequency; //!< store current pulses per second (convenience, in 1/s)
|
||||
this->_totalDuration += duration; //!< accumulate total duration (in ms)
|
||||
this->_totalVolume += this->_currentVolume; //!< accumulate total volume (in l)
|
||||
this->_totalCorrection += this->_currentCorrection * duration; //!< accumulate corrections over time
|
||||
}
|
||||
|
||||
void FlowMeter::count() {
|
||||
this->_currentPulses++; //!< this should be called from an interrupt service routine
|
||||
}
|
||||
|
||||
void FlowMeter::reset() {
|
||||
cli(); //!< going to change interrupt variable(s)
|
||||
this->_currentPulses = 0; //!< reset pulse counter
|
||||
sei(); //!< done changing interrupt variable(s)
|
||||
|
||||
this->_currentFrequency = 0.0f;
|
||||
this->_currentDuration = 0.0f;
|
||||
this->_currentFlowrate = 0.0f;
|
||||
this->_currentVolume = 0.0f;
|
||||
this->_currentCorrection = 0.0f;
|
||||
}
|
||||
|
||||
unsigned int FlowMeter::getPin() {
|
||||
return this->_pin;
|
||||
}
|
||||
|
||||
unsigned long FlowMeter::getCurrentDuration() {
|
||||
return this->_currentDuration; //!< in ms
|
||||
}
|
||||
|
||||
double FlowMeter::getCurrentFrequency() {
|
||||
return this->_currentFrequency; //!< in 1/s
|
||||
}
|
||||
|
||||
double FlowMeter::getCurrentError() {
|
||||
/// error (in %) = error * 100
|
||||
/// error = correction rate - 1
|
||||
/// correction rate = k-factor / correction
|
||||
return (this->_properties.kFactor / this->_currentCorrection - 1) * 100; //!< in %
|
||||
}
|
||||
|
||||
unsigned long FlowMeter::getTotalDuration() {
|
||||
return this->_totalDuration; //!< in ms
|
||||
}
|
||||
|
||||
double FlowMeter::getTotalError() {
|
||||
/// average error (in %) = average error * 100
|
||||
/// average error = average correction rate - 1
|
||||
/// average correction rate = k-factor / corrections over time * total time
|
||||
return (this->_properties.kFactor / this->_totalCorrection * this->_totalDuration - 1) * 100;
|
||||
}
|
||||
|
||||
FlowMeter* FlowMeter::setTotalDuration(unsigned long totalDuration) {
|
||||
this->_totalDuration = totalDuration;
|
||||
return this;
|
||||
}
|
||||
|
||||
FlowMeter* FlowMeter::setTotalVolume(double totalVolume) {
|
||||
this->_totalVolume = totalVolume;
|
||||
return this;
|
||||
}
|
||||
|
||||
FlowMeter* FlowMeter::setTotalCorrection(double totalCorrection) {
|
||||
this->_totalCorrection = totalCorrection;
|
||||
return this;
|
||||
}
|
||||
|
||||
FlowSensorProperties UncalibratedSensor = {60.0f, 5.0f, {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}};
|
||||
FlowSensorProperties FS300A = {60.0f, 5.5f, {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}};
|
||||
FlowSensorProperties FS400A = {60.0f, 4.8f, {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}};
|
|
@ -0,0 +1,171 @@
|
|||
/**
|
||||
* Flow Meter
|
||||
*
|
||||
* An Arduino flow meter library that provides calibrated liquid flow and volume measurement with flow sensors.
|
||||
*
|
||||
* @author sekdiy (https://github.com/sekdiy/FlowMeter)
|
||||
* @date 14.07.2015 Initial release.
|
||||
* @version See git comments for changes.
|
||||
*/
|
||||
|
||||
#ifndef FLOWMETER_H
|
||||
#define FLOWMETER_H
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
/**
|
||||
* FlowSensorProperties
|
||||
*
|
||||
* Structure that holds essential information about a flow sensor.
|
||||
* Stores general sensor properties and calibration points.
|
||||
*
|
||||
* See file G34_Flow_rate_to_frequency.jpg for reference.
|
||||
*/
|
||||
typedef struct {
|
||||
double capacity; //!< capacity, upper limit of flow rate (in l/min)
|
||||
double kFactor; //!< "k-factor" (in (pulses/s) / (l/min)), e.g.: 1 pulse/s = kFactor * l/min
|
||||
double mFactor[10]; //!< multiplicative correction factor near unity, "meter factor" (per decile of flow)
|
||||
} FlowSensorProperties;
|
||||
|
||||
extern FlowSensorProperties UncalibratedSensor; //!< default sensor
|
||||
extern FlowSensorProperties FS300A; //!< see documentation about FS300A/SEN02141B
|
||||
extern FlowSensorProperties FS400A; //!< see documentation about FS400A/USN-HS10TA
|
||||
|
||||
/**
|
||||
* FlowMeter
|
||||
*/
|
||||
class FlowMeter {
|
||||
public:
|
||||
/**
|
||||
* Initializes a new flow meter object.
|
||||
*
|
||||
* @param pin The pin that the flow sensor is connected to (has to be interrupt capable, default: INT0).
|
||||
* @param prop The properties of the actual flow sensor being used (default: UncalibratedSensor).
|
||||
*/
|
||||
FlowMeter(unsigned int pin = 2, FlowSensorProperties prop = UncalibratedSensor);
|
||||
|
||||
double getCurrentFlowrate(); //!< Returns the current flow rate since last reset (in l/min).
|
||||
double getCurrentVolume(); //!< Returns the current volume since last reset (in l).
|
||||
|
||||
double getTotalFlowrate(); //!< Returns the (linear) average flow rate in this flow meter instance (in l/min).
|
||||
double getTotalVolume(); //!< Returns the total volume flown trough this flow meter instance (in l).
|
||||
|
||||
/**
|
||||
* The tick method updates all internal calculations at the end of a measurement period.
|
||||
*
|
||||
* We're calculating flow and volume data over time.
|
||||
* The actual pulses have to be sampled using the count method (i.e. via an interrupt service routine).
|
||||
*
|
||||
* Flow sensor formulae:
|
||||
*
|
||||
* Let K: pulses per second per unit of measure (i.e. (1/s)/(l/min)),
|
||||
* f: pulse frequency (1/s),
|
||||
* Q: flow rate (l/min),
|
||||
* p: sensor pulses (no dimension/unit),
|
||||
* t: time since last measurements (s).
|
||||
*
|
||||
* K = f / Q | units: (1/s) / (l/min) = (1/s) / (l/min)
|
||||
* <=> | Substitute p / t for f in order to allow for different measurement intervals
|
||||
* K = (p / t) / Q | units: ((1/s)/(l/min)) = (1/s) / (l/min)
|
||||
* <=> | Solve for Q:
|
||||
* Q = (p / t) / K | untis: l/min = 1/s / (1/s / (l/min))
|
||||
* <=> | Volume in l:
|
||||
* V = Q / 60 | units: l = (l/min) / (min)
|
||||
*
|
||||
* The property K is sometimes stated in pulses per liter or pulses per gallon.
|
||||
* In these cases the unit of measure has to be converted accordingly (e.g. from gal/s to l/min).
|
||||
* See file G34_Flow_rate_to_frequency.jpg for reference.
|
||||
*
|
||||
* @param duration The tick duration (in ms).
|
||||
*/
|
||||
void tick(unsigned long duration = 1000);
|
||||
void count(); //!< Increments the internal pulse counter. Serves as an interrupt callback routine.
|
||||
void reset(); //!< Prepares the flow meter for a fresh measurement. Resets all current values, but not the totals.
|
||||
|
||||
/*
|
||||
* setters enabling continued metering across power cycles
|
||||
*/
|
||||
FlowMeter* setTotalDuration(unsigned long totalDuration); //!< Sets the total (overall) duration (i.e. after power up).
|
||||
FlowMeter* setTotalVolume(double totalVolume); //!< Sets the total (overall) volume (i.e. after power up).
|
||||
FlowMeter* setTotalCorrection(double totalCorrection); //!< Sets the total (overall) correction factor (i.e. after power up).
|
||||
|
||||
/*
|
||||
* convenience methods and calibration helpers
|
||||
*/
|
||||
unsigned int getPin(); //!< Returns the Arduino pin number that the flow sensor is connected to.
|
||||
|
||||
unsigned long getCurrentDuration(); //!< Returns the duration of the current tick (in ms).
|
||||
double getCurrentFrequency(); //!< Returns the pulse rate in the current tick (in 1/s).
|
||||
double getCurrentError(); //!< Returns the error resulting from the current measurement (in %).
|
||||
|
||||
unsigned long getTotalDuration(); //!< Returns the total run time of this flow meter instance (in ms).
|
||||
double getTotalError(); //!< Returns the (linear) average error of this flow meter instance (in %).
|
||||
|
||||
protected:
|
||||
unsigned int _pin; //!< connection pin (has to be interrupt capable!)
|
||||
FlowSensorProperties _properties; //!< sensor properties (including calibration data)
|
||||
|
||||
unsigned long _currentDuration; //!< current tick duration (convenience, in ms)
|
||||
double _currentFrequency; //!< current pulses per second (convenience, in 1/s)
|
||||
double _currentFlowrate = 0.0f; //!< current flow rate (in l/tick), e.g.: 1 l / min = 1 pulse / s / (pulses / s / l / min)
|
||||
double _currentVolume = 0.0f; //!< current volume (in l), e.g.: 1 l = 1 (l / min) / (60 * s)
|
||||
double _currentCorrection; //!< currently applied correction factor
|
||||
|
||||
unsigned long _totalDuration = 0.0f; //!< total measured duration since begin of measurement (in ms)
|
||||
double _totalVolume = 0.0f; //!< total volume since begin of measurement (in l)
|
||||
double _totalCorrection = 0.0f; //!< accumulated correction factors
|
||||
|
||||
volatile unsigned long _currentPulses = 0; //!< pulses within current sample period
|
||||
};
|
||||
|
||||
/**
|
||||
* FlowSensorCalibration
|
||||
*
|
||||
* Convenience class for manipulating sensor properties.
|
||||
*/
|
||||
class FlowSensorCalibration {
|
||||
public:
|
||||
FlowSensorCalibration() {};
|
||||
FlowSensorCalibration(FlowSensorProperties properties): _properties(properties) {};
|
||||
|
||||
FlowSensorCalibration* setProperties(FlowSensorProperties properties) {
|
||||
this->_properties = properties;
|
||||
return this;
|
||||
};
|
||||
|
||||
FlowSensorCalibration* setCapacity(double capacity) {
|
||||
this->_properties.capacity = capacity;
|
||||
return this;
|
||||
}
|
||||
|
||||
FlowSensorCalibration* setKFactor(double kFactor) {
|
||||
this->_properties.kFactor = kFactor;
|
||||
return this;
|
||||
}
|
||||
|
||||
FlowSensorCalibration* setMeterFactorPerDecile(unsigned int decile, unsigned int mFactor) {
|
||||
this->_properties.mFactor[decile] = mFactor;
|
||||
return this;
|
||||
}
|
||||
|
||||
FlowSensorProperties getProperties() {
|
||||
return this->_properties;
|
||||
}
|
||||
|
||||
double getCapacity() {
|
||||
return this->_properties.capacity;
|
||||
}
|
||||
|
||||
double getKFactor() {
|
||||
return this->_properties.kFactor;
|
||||
}
|
||||
|
||||
unsigned int getMeterFactorPerDecile(unsigned int decile) {
|
||||
return this->_properties.mFactor[decile];
|
||||
}
|
||||
|
||||
protected:
|
||||
FlowSensorProperties _properties;
|
||||
};
|
||||
|
||||
#endif // FLOWMETER_H
|
|
@ -0,0 +1,409 @@
|
|||
/*
|
||||
* Copyright (C) 2018 Andreas Motzek andreas-motzek@t-online.de
|
||||
*
|
||||
* This file is part of the MQTT Client package.
|
||||
*
|
||||
* You can use, redistribute and/or modify this file under the terms of the Modified Artistic License.
|
||||
* See http://simplysomethings.de/open+source/modified+artistic+license.html for details.
|
||||
*
|
||||
* This file is distributed in the hope that it will be useful, but without any warranty; without even
|
||||
* the implied warranty of merchantability or fitness for a particular purpose.
|
||||
*/
|
||||
|
||||
#include "MQTTClient.h"
|
||||
|
||||
MQTTClient* MQTTClient::current = NULL;
|
||||
|
||||
MQTTClient::MQTTClient(CooperativeMultitasking* _tasks, Client* _client, const char* _host, uint16_t _port, const char* _clientid, const char* _username, const char* _password, uint16_t _keepalive) {
|
||||
tasks = _tasks;
|
||||
client = _client;
|
||||
host = strdup(_host);
|
||||
port = _port;
|
||||
clientid = strdup(_clientid);
|
||||
username = strdupOrNull(_username);
|
||||
password = strdupOrNull(_password);
|
||||
keepalive = _keepalive;
|
||||
isconnected = false;
|
||||
head = NULL;
|
||||
tail = NULL;
|
||||
}
|
||||
|
||||
MQTTClient::~MQTTClient() {
|
||||
free(host);
|
||||
free(clientid);
|
||||
free(username);
|
||||
free(password);
|
||||
host = NULL;
|
||||
clientid = NULL;
|
||||
username = NULL;
|
||||
password = NULL;
|
||||
//
|
||||
while (head) {
|
||||
PublishPacket* next = head->next;
|
||||
free(head->payload);
|
||||
delete head;
|
||||
head = next;
|
||||
}
|
||||
//
|
||||
tail = NULL;
|
||||
//
|
||||
if (current == this) current = NULL;
|
||||
}
|
||||
|
||||
bool MQTTClient::connect() {
|
||||
if (!current) {
|
||||
if (client->connect(host, port)) {
|
||||
if (sendConnectPacket()) {
|
||||
current = this;
|
||||
auto task1 = tasks->ifThen([] () -> bool { return current ? current->available() >= 4 : true; },
|
||||
[] () -> void { if (current) current->receiveConnectAcknowledgementPacket(); });
|
||||
auto task2 = tasks->after(10000, [] () -> void { if (current) current->stop(); });
|
||||
tasks->onlyOneOf(task1, task2);
|
||||
//
|
||||
return true;
|
||||
}
|
||||
//
|
||||
Serial.println("cannot send connect packet");
|
||||
//
|
||||
stop();
|
||||
} else {
|
||||
Serial.print("cannot connect to ");
|
||||
Serial.print(host);
|
||||
Serial.print(":");
|
||||
Serial.println(port);
|
||||
}
|
||||
} else {
|
||||
Serial.println("another mqtt client is connected");
|
||||
}
|
||||
//
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MQTTClient::connected() {
|
||||
return isconnected;
|
||||
}
|
||||
|
||||
bool MQTTClient::publishAcknowledged() {
|
||||
return !head;
|
||||
}
|
||||
|
||||
bool MQTTClient::publish(bool retain, const char* topicname, const char* payload) {
|
||||
PublishPacket* packet = new PublishPacket(); // std::nothrow is default
|
||||
//
|
||||
if (packet) {
|
||||
packet->retain = retain;
|
||||
packet->topicname = topicname;
|
||||
packet->payload = strdup(payload);
|
||||
enqueuePublishPacket(packet);
|
||||
transmitPublishPacketsAfter(0);
|
||||
//
|
||||
return true;
|
||||
}
|
||||
//
|
||||
Serial.println("cannot enqueue publish packet");
|
||||
//
|
||||
return false;
|
||||
}
|
||||
|
||||
void MQTTClient::disconnect() {
|
||||
if (isconnected) sendDisconnectPacket();
|
||||
//
|
||||
stop();
|
||||
}
|
||||
|
||||
void MQTTClient::enqueuePublishPacket(PublishPacket* packet) {
|
||||
uint16_t packetid = 1; // 2.3.1 non-zero 16-bit packetid
|
||||
//
|
||||
if (head) {
|
||||
PublishPacket* packet = head;
|
||||
//
|
||||
while (packet) {
|
||||
if (packet->packetid > packetid) packetid = packet->packetid;
|
||||
//
|
||||
packet = packet->next;
|
||||
}
|
||||
//
|
||||
packetid++; // biggest packetid plus 1
|
||||
}
|
||||
//
|
||||
packet->packetid = packetid;
|
||||
packet->trycount = 0;
|
||||
packet->next = head;
|
||||
head = packet;
|
||||
//
|
||||
if (!tail) tail = head;
|
||||
}
|
||||
|
||||
void MQTTClient::transmitPublishPacketsAfter(unsigned long duration) {
|
||||
tasks->after(duration, [] () -> void { if (current) current->transmitPublishPackets(); });
|
||||
}
|
||||
|
||||
void MQTTClient::transmitPublishPackets() {
|
||||
if (isconnected && current == this && head) {
|
||||
if (available() >= 4) {
|
||||
receivePublishAcknowledgementPacket();
|
||||
transmitPublishPacketsAfter(100);
|
||||
//
|
||||
return;
|
||||
}
|
||||
//
|
||||
if (head->trycount >= 20) {
|
||||
Serial.println("discarding packet");
|
||||
//
|
||||
removePublishPacket(head->packetid);
|
||||
transmitPublishPacketsAfter(100);
|
||||
//
|
||||
return;
|
||||
}
|
||||
//
|
||||
if (sendHeadPublishPacket()) {
|
||||
head->trycount++;
|
||||
rotatePublishPackets();
|
||||
transmitPublishPacketsAfter(10000);
|
||||
//
|
||||
return;
|
||||
}
|
||||
//
|
||||
Serial.println("cannot send publish packet");
|
||||
//
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
void MQTTClient::removePublishPacket(uint16_t packetid) {
|
||||
PublishPacket* last = NULL;
|
||||
PublishPacket* packet = head;
|
||||
//
|
||||
while (packet) {
|
||||
if (packet->packetid == packetid) {
|
||||
if (last) last->next = packet->next;
|
||||
//
|
||||
if (packet == head) head = packet->next;
|
||||
//
|
||||
if (packet == tail) tail = last;
|
||||
//
|
||||
free(packet->payload);
|
||||
delete packet;
|
||||
//
|
||||
return;
|
||||
}
|
||||
//
|
||||
last = packet;
|
||||
packet = packet->next;
|
||||
}
|
||||
}
|
||||
|
||||
void MQTTClient::rotatePublishPackets() {
|
||||
if (!head || head == tail) return; // less than 2 packets
|
||||
//
|
||||
tail->next = head;
|
||||
tail = head;
|
||||
head = head->next;
|
||||
tail->next = NULL;
|
||||
}
|
||||
|
||||
bool MQTTClient::sendConnectPacket() {
|
||||
int packetlength = 2 + 4 + 1 + 1 + 2 + 2 + strlen(clientid);
|
||||
//
|
||||
if (username != NULL) packetlength += (2 + strlen(username));
|
||||
//
|
||||
if (password != NULL) packetlength += (2 + strlen(password));
|
||||
//
|
||||
uint8_t connectflags = 2; // clean session
|
||||
//
|
||||
if (username != NULL) connectflags |= 128;
|
||||
//
|
||||
if (password != NULL) connectflags |= 64;
|
||||
//
|
||||
// Type, Flags, Packet Length
|
||||
writeTypeFlags(1, 0); // connect, 0
|
||||
writePacketLength(packetlength);
|
||||
//
|
||||
// Header
|
||||
writeLengthString("MQTT"); // protocol name
|
||||
writeByte(4); // protocol level
|
||||
writeByte(connectflags);
|
||||
writeShort(keepalive);
|
||||
//
|
||||
// Payload
|
||||
writeLengthString(clientid);
|
||||
//
|
||||
if (username != NULL) writeLengthString(username);
|
||||
//
|
||||
if (password != NULL) writeLengthString(password);
|
||||
//
|
||||
flush();
|
||||
//
|
||||
return !getWriteError();
|
||||
}
|
||||
|
||||
void MQTTClient::receiveConnectAcknowledgementPacket() {
|
||||
uint8_t typeflags = readByte();
|
||||
uint8_t packetlength = readByte();
|
||||
uint8_t sessionpresent = readByte();
|
||||
uint8_t returncode = readByte();
|
||||
//
|
||||
if (typeflags == (2 << 4) && packetlength == 2) {
|
||||
switch (returncode) {
|
||||
case 0: Serial.println("connection accepted"); isconnected = true; transmitPublishPacketsAfter(0); return;
|
||||
case 1: Serial.println("unacceptable protocol version"); break;
|
||||
case 2: Serial.println("identifier rejected"); break;
|
||||
case 3: Serial.println("server unavailable"); break;
|
||||
case 4: Serial.println("bad user name or password"); break;
|
||||
case 5: Serial.println("not authorized"); break;
|
||||
default: Serial.println(returncode); break;
|
||||
}
|
||||
} else {
|
||||
Serial.println("not a connect acknowledgement");
|
||||
}
|
||||
//
|
||||
stop();
|
||||
}
|
||||
|
||||
bool MQTTClient::sendHeadPublishPacket() {
|
||||
int packetlength = 2 + strlen(head->topicname) + 2 + strlen(head->payload);
|
||||
uint8_t flags = 2; // QoS 1
|
||||
//
|
||||
if (head->trycount > 0) flags |= 8; // duplicate
|
||||
//
|
||||
if (head->retain) flags |= 1;
|
||||
//
|
||||
// Type, Flags, Packet Length
|
||||
writeTypeFlags(3, flags); // publish, flags
|
||||
writePacketLength(packetlength);
|
||||
//
|
||||
// Header
|
||||
writeLengthString(head->topicname);
|
||||
writeShort(head->packetid);
|
||||
//
|
||||
// Payload
|
||||
writeString(head->payload, strlen(head->payload));
|
||||
//
|
||||
flush();
|
||||
//
|
||||
return !getWriteError();
|
||||
}
|
||||
|
||||
void MQTTClient::receivePublishAcknowledgementPacket() {
|
||||
uint8_t typeflags = readByte();
|
||||
uint8_t packetlength = readByte();
|
||||
uint16_t packetid = readShort();
|
||||
//
|
||||
if (typeflags == (4 << 4) && packetlength == 2) {
|
||||
removePublishPacket(packetid);
|
||||
//
|
||||
Serial.println("publish acknowledged");
|
||||
//
|
||||
return;
|
||||
}
|
||||
//
|
||||
Serial.println("not a publish acknowledgement");
|
||||
disconnect();
|
||||
}
|
||||
|
||||
void MQTTClient::sendDisconnectPacket() {
|
||||
// Type, Flags, Packet Length
|
||||
writeTypeFlags(14, 0); // disconnect, 0
|
||||
writePacketLength(0);
|
||||
//
|
||||
flush();
|
||||
}
|
||||
|
||||
void MQTTClient::writeTypeFlags(uint8_t type, uint8_t flags) {
|
||||
writeByte(type << 4 | flags);
|
||||
}
|
||||
|
||||
void MQTTClient::writePacketLength(int value) {
|
||||
while (true) {
|
||||
int digit = value & 127;
|
||||
value >>= 7;
|
||||
//
|
||||
if (value > 0) {
|
||||
writeByte(digit | 128);
|
||||
} else {
|
||||
writeByte(digit);
|
||||
//
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MQTTClient::writeLengthString(const char* value) {
|
||||
size_t len = strlen(value);
|
||||
//
|
||||
if (len > 65535) return;
|
||||
//
|
||||
writeShort(len);
|
||||
writeString(value, len);
|
||||
}
|
||||
|
||||
void MQTTClient::writeString(const char* value, size_t len) {
|
||||
client->write((uint8_t*) value, len);
|
||||
}
|
||||
|
||||
void MQTTClient::writeShort(uint16_t value) {
|
||||
writeByte(value >> 8);
|
||||
writeByte(value & 255);
|
||||
}
|
||||
|
||||
void MQTTClient::writeByte(uint8_t value) {
|
||||
client->write(value);
|
||||
}
|
||||
|
||||
void MQTTClient::flush() {
|
||||
client->flush();
|
||||
}
|
||||
|
||||
int MQTTClient::getWriteError() {
|
||||
return client->getWriteError();
|
||||
}
|
||||
|
||||
int MQTTClient::available() {
|
||||
return client->available();
|
||||
}
|
||||
|
||||
uint8_t MQTTClient::readByte() {
|
||||
return client->read();
|
||||
}
|
||||
|
||||
uint16_t MQTTClient::readShort() {
|
||||
uint16_t value = client->read();
|
||||
value <<= 8;
|
||||
value += client->read();
|
||||
//
|
||||
return value;
|
||||
}
|
||||
|
||||
void MQTTClient::stop() {
|
||||
if (client->connected()) {
|
||||
client->stop();
|
||||
client->clearWriteError();
|
||||
}
|
||||
//
|
||||
isconnected = false;
|
||||
current = NULL;
|
||||
}
|
||||
|
||||
char* MQTTClient::strdupOrNull(const char* string) {
|
||||
if (string == NULL) return NULL;
|
||||
//
|
||||
return strdup(string);
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
|
||||
MQTTTopic::MQTTTopic(MQTTClient* _client, const char* _topicname) {
|
||||
client = _client;
|
||||
topicname = strdup(_topicname);
|
||||
}
|
||||
|
||||
MQTTTopic::~MQTTTopic() {
|
||||
free(topicname);
|
||||
topicname = NULL;
|
||||
}
|
||||
|
||||
bool MQTTTopic::publish(const char* payload, bool retain) {
|
||||
return client->publish(retain, topicname, payload);
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Copyright (C) 2018 Andreas Motzek andreas-motzek@t-online.de
|
||||
*
|
||||
* This file is part of the MQTT Client package.
|
||||
*
|
||||
* You can use, redistribute and/or modify this file under the terms of the Modified Artistic License.
|
||||
* See http://simplysomethings.de/open+source/modified+artistic+license.html for details.
|
||||
*
|
||||
* This file is distributed in the hope that it will be useful, but without any warranty; without even
|
||||
* the implied warranty of merchantability or fitness for a particular purpose.
|
||||
*/
|
||||
|
||||
#ifndef MQTTClient_h
|
||||
#define MQTTClient_h
|
||||
|
||||
#include "Client.h"
|
||||
#include "CooperativeMultitasking.h"
|
||||
|
||||
class MQTTClient {
|
||||
friend class MQTTTopic;
|
||||
|
||||
private:
|
||||
struct PublishPacket {
|
||||
bool retain;
|
||||
const char* topicname;
|
||||
char* payload;
|
||||
uint16_t packetid;
|
||||
uint16_t trycount;
|
||||
PublishPacket* next;
|
||||
};
|
||||
|
||||
static MQTTClient* current;
|
||||
|
||||
CooperativeMultitasking* tasks;
|
||||
Client* client;
|
||||
char* host;
|
||||
uint16_t port;
|
||||
char* clientid;
|
||||
char* username;
|
||||
char* password;
|
||||
uint16_t keepalive;
|
||||
bool isconnected;
|
||||
PublishPacket* head;
|
||||
PublishPacket* tail;
|
||||
|
||||
void enqueuePublishPacket(PublishPacket* packet);
|
||||
void transmitPublishPacketsAfter(unsigned long duration);
|
||||
void transmitPublishPackets();
|
||||
void removePublishPacket(uint16_t packetid);
|
||||
void rotatePublishPackets();
|
||||
|
||||
bool sendConnectPacket();
|
||||
void receiveConnectAcknowledgementPacket();
|
||||
bool sendHeadPublishPacket();
|
||||
void receivePublishAcknowledgementPacket();
|
||||
void sendDisconnectPacket();
|
||||
|
||||
void writeTypeFlags(uint8_t type, uint8_t flags);
|
||||
void writePacketLength(int value);
|
||||
void writeLengthString(const char* value);
|
||||
void writeString(const char* value, size_t len);
|
||||
void writeShort(uint16_t value);
|
||||
void writeByte(uint8_t value);
|
||||
uint8_t readByte();
|
||||
uint16_t readShort();
|
||||
|
||||
void flush();
|
||||
int getWriteError();
|
||||
int available();
|
||||
void stop();
|
||||
|
||||
static char* strdupOrNull(const char* string);
|
||||
|
||||
protected:
|
||||
bool publish(bool retain, const char* topicname, const char* payload);
|
||||
|
||||
public:
|
||||
MQTTClient(CooperativeMultitasking* tasks, Client* client, const char* host, uint16_t port, const char* clientid, const char* username, const char* password, uint16_t keepalive = 300);
|
||||
virtual ~MQTTClient();
|
||||
bool connect();
|
||||
bool connected();
|
||||
bool publishAcknowledged();
|
||||
void disconnect();
|
||||
};
|
||||
|
||||
class MQTTTopic {
|
||||
private:
|
||||
MQTTClient* client;
|
||||
char* topicname;
|
||||
bool retain;
|
||||
|
||||
public:
|
||||
MQTTTopic(MQTTClient* client, const char* topicname);
|
||||
virtual ~MQTTTopic();
|
||||
bool publish(const char* payload, bool retain = true);
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,91 @@
|
|||
## MQTT Client
|
||||
|
||||
MQTT Client is a library that can publish strings to a topic of a MQTT broker.
|
||||
For using it you have to include the library
|
||||
[Cooperative Multitasking](https://bitbucket.org/amotzek/cooperative-multitasking) too.
|
||||
|
||||
#### Example
|
||||
|
||||
The following example publishes the string `"Hello"` to the topic `"amotzek/hello"`
|
||||
of the broker broker.hivemq.com repeatedly. The message can be observed in HiveMQs
|
||||
[Websockets client](http://www.hivemq.com/demos/websocket-client/).
|
||||
|
||||
The example assumes that you connect to the internet via `Wifi101`, so you need the
|
||||
SSID and the password of your WLAN. You also need an Arduino board that is compatible
|
||||
with `Wifi101`, e.g. the MKR1000. If your board connects to the internet in a
|
||||
different way, you have to pass a different client (for example an `EthernetClient`)
|
||||
to the constructor of `MQTTClient`.
|
||||
|
||||
#include <Client.h>
|
||||
#include <WiFi101.h>
|
||||
#include <CooperativeMultitasking.h>
|
||||
#include <MQTTClient.h>
|
||||
|
||||
char ssid[] = "...";
|
||||
char pass[] = "...";
|
||||
char host[] = "broker.hivemq.com";
|
||||
char clientid[] = "...";
|
||||
char username[] = "...";
|
||||
char password[] = "...";
|
||||
char topicname[] = "amotzek/hello";
|
||||
|
||||
CooperativeMultitasking tasks;
|
||||
WiFiClient wificlient;
|
||||
MQTTClient mqttclient(&tasks, &wificlient, host, 1883, clientid, username, password);
|
||||
MQTTTopic topic(&mqttclient, topicname);
|
||||
|
||||
void setup() {
|
||||
Serial.begin(9600);
|
||||
//
|
||||
while (!Serial) {
|
||||
delay(1000);
|
||||
}
|
||||
//
|
||||
WiFi.begin(ssid, pass);
|
||||
delay(10000); // wait until WiFi connection is established
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (mqttclient.connect()) {
|
||||
topic.publish("Hello");
|
||||
//
|
||||
while (tasks.available()) {
|
||||
tasks.run(); // receive connect acknowledgement, send publish, receive publish acknowledgement
|
||||
}
|
||||
//
|
||||
mqttclient.disconnect();
|
||||
}
|
||||
//
|
||||
switch (WiFi.status()) {
|
||||
case WL_CONNECT_FAILED:
|
||||
case WL_CONNECTION_LOST:
|
||||
case WL_DISCONNECTED: WiFi.begin(ssid, pass); // reconnect WiFi if necessary
|
||||
}
|
||||
//
|
||||
delay(30000);
|
||||
}
|
||||
|
||||
#### Details
|
||||
|
||||
To create an instance of class `MQTTClient` you need a reference to an instance of class
|
||||
`CooperativeMultitasking`, a reference to an instance of a network connection (e.g. from
|
||||
`WiFi101` or `Ethernet`), the host name of the MQTT broker, its port number (typically
|
||||
1883) and a client id. If the broker requires that, you also have to pass a user name and
|
||||
a password.
|
||||
|
||||
To create an instance of class `MQTTTopic` you have to pass a reference to an instance of
|
||||
`MQTTClient` and the topic name.
|
||||
|
||||
The method `connect()` of `MQTTClient` creates a network connection to the broker.
|
||||
|
||||
You can publish a string by calling the method `publish("...")` of `MQTTTopic`. The
|
||||
method can be called independently of an established connection to the MQTT broker. It
|
||||
will delay publishing your message until a connection becomes available.
|
||||
|
||||
The message is published with QoS 1. That means that `MQTTClient` waits for a publish
|
||||
acknowledgement from the MQTT broker. If the acknowledgement does not arrive in time,
|
||||
`MQTTClient` retries publishing the message up to 15 times until it gives up.
|
||||
|
||||
`connect` and `publish` work asynchronously. They create tasks that are managed by the
|
||||
instance of `CooperativeMultitasking`. That's why you have to call it's method `run()`
|
||||
until all tasks are finished.
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Publishes "Hello" at regular intervals. The message can be subscribed and observed in HiveMQs
|
||||
* Websockets client http://www.hivemq.com/demos/websocket-client/
|
||||
*
|
||||
* Copyright (C) 2018 Andreas Motzek andreas-motzek@t-online.de
|
||||
*
|
||||
* You can use, redistribute and/or modify this file under the terms of the Modified Artistic License.
|
||||
* See http://simplysomethings.de/open+source/modified+artistic+license.html for details.
|
||||
*
|
||||
* This file is distributed in the hope that it will be useful, but without any warranty; without even
|
||||
* the implied warranty of merchantability or fitness for a particular purpose.
|
||||
*/
|
||||
|
||||
#include <Client.h>
|
||||
#include <WiFi101.h>
|
||||
#include <CooperativeMultitasking.h>
|
||||
#include <MQTTClient.h>
|
||||
|
||||
char ssid[] = "...";
|
||||
char pass[] = "...";
|
||||
char host[] = "broker.hivemq.com";
|
||||
char clientid[] = " ";
|
||||
char topicname[] = "amotzek/hello";
|
||||
|
||||
CooperativeMultitasking tasks;
|
||||
WiFiClient wificlient;
|
||||
MQTTClient mqttclient(&tasks, &wificlient, host, 1883, clientid, NULL, NULL);
|
||||
MQTTTopic topic(&mqttclient, topicname);
|
||||
|
||||
void setup() {
|
||||
Serial.begin(9600);
|
||||
//
|
||||
while (!Serial) {
|
||||
delay(1000);
|
||||
}
|
||||
//
|
||||
WiFi.begin(ssid, pass);
|
||||
delay(10000);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (mqttclient.connect()) {
|
||||
topic.publish("Hello");
|
||||
//
|
||||
while (tasks.available()) {
|
||||
tasks.run();
|
||||
}
|
||||
//
|
||||
mqttclient.disconnect();
|
||||
}
|
||||
//
|
||||
switch (WiFi.status()) {
|
||||
case WL_CONNECT_FAILED:
|
||||
case WL_CONNECTION_LOST:
|
||||
case WL_DISCONNECTED: WiFi.begin(ssid, pass);
|
||||
}
|
||||
//
|
||||
delay(30000);
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Publishes "Hello" at regular intervals. The message can be subscribed and observed in HiveMQs
|
||||
* Websockets client http://www.hivemq.com/demos/websocket-client/
|
||||
*
|
||||
* If the libraries Arduino Low Power and RTC Zero are included then Cooperative Multitasking
|
||||
* uses LowPower.idle(...) instead of delay(...).
|
||||
*
|
||||
* Copyright (C) 2018 Andreas Motzek andreas-motzek@t-online.de
|
||||
*
|
||||
* You can use, redistribute and/or modify this file under the terms of the Modified Artistic License.
|
||||
* See http://simplysomethings.de/open+source/modified+artistic+license.html for details.
|
||||
*
|
||||
* This file is distributed in the hope that it will be useful, but without any warranty; without even
|
||||
* the implied warranty of merchantability or fitness for a particular purpose.
|
||||
*/
|
||||
|
||||
#include <Client.h>
|
||||
#include <WiFi101.h>
|
||||
#include <ArduinoLowPower.h>
|
||||
#include <CooperativeMultitasking.h>
|
||||
#include <MQTTClient.h>
|
||||
|
||||
char ssid[] = "...";
|
||||
char pass[] = "...";
|
||||
char host[] = "broker.hivemq.com";
|
||||
char clientid[] = " ";
|
||||
char topicname[] = "amotzek/hello";
|
||||
|
||||
CooperativeMultitasking tasks;
|
||||
WiFiClient wificlient;
|
||||
MQTTClient mqttclient(&tasks, &wificlient, host, 1883, clientid, NULL, NULL);
|
||||
MQTTTopic topic(&mqttclient, topicname);
|
||||
|
||||
void setup() {
|
||||
Serial.begin(9600);
|
||||
//
|
||||
while (!Serial) {
|
||||
delay(1000);
|
||||
}
|
||||
//
|
||||
WiFi.begin(ssid, pass);
|
||||
tasks.after(10000, checkWiFi); // after 10 seconds call checkWiFi()
|
||||
tasks.after(10000, checkBroker); // after 10 seconds call checkBroker()
|
||||
tasks.after(30000, publishHello); // after 30 seconds call publishHello()
|
||||
}
|
||||
|
||||
void loop() {
|
||||
tasks.run();
|
||||
}
|
||||
|
||||
void checkWiFi() {
|
||||
switch (WiFi.status()) {
|
||||
case WL_CONNECT_FAILED:
|
||||
case WL_CONNECTION_LOST:
|
||||
case WL_DISCONNECTED:
|
||||
Serial.println("wifi not connected");
|
||||
WiFi.begin(ssid, pass);
|
||||
tasks.after(10000, checkWiFi); // after 10 seconds call checkWiFi()
|
||||
//
|
||||
return;
|
||||
}
|
||||
//
|
||||
tasks.after(30000, checkWiFi); // after 30 seconds call checkWiFi()
|
||||
}
|
||||
|
||||
void checkBroker() {
|
||||
if (!mqttclient.connected()) {
|
||||
Serial.println("mqtt client not connected");
|
||||
mqttclient.connect();
|
||||
}
|
||||
//
|
||||
tasks.after(30000, checkBroker); // after 30 seconds call checkBroker()
|
||||
}
|
||||
|
||||
void publishHello() {
|
||||
if (mqttclient.connected()) topic.publish("Hello");
|
||||
//
|
||||
tasks.after(30000, publishHello); // after 30 seconds call publishHello()
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* Make the builtin LED blink, but only when it's dark. Publish light or dark condition to a
|
||||
* MQTT topic.
|
||||
*
|
||||
* Connect an analog light sensor with the Arduino: GND to GND,
|
||||
* VCC to VCC, SIG to A1.
|
||||
*
|
||||
* Copyright (C) 2018 Andreas Motzek andreas-motzek@t-online.de
|
||||
*
|
||||
* You can use, redistribute and/or modify this file under the terms of the Modified Artistic License.
|
||||
* See http://simplysomethings.de/open+source/modified+artistic+license.html for details.
|
||||
*
|
||||
* This file is distributed in the hope that it will be useful, but without any warranty; without even
|
||||
* the implied warranty of merchantability or fitness for a particular purpose.
|
||||
*/
|
||||
|
||||
#include <Client.h>
|
||||
#include <WiFi101.h>
|
||||
#include <CooperativeMultitasking.h>
|
||||
#include <MQTTClient.h>
|
||||
|
||||
char ssid[] = "... WLAN SSID ...";
|
||||
char pass[] = "... WLAN password ...";
|
||||
char host[] = "... MQTT broker host name ...";
|
||||
char clientid[] = "... MQTT client id ...";
|
||||
char username[] = "... MQTT user name ...";
|
||||
char password[] = "... MQTT password ...";
|
||||
char topicname[] = "... MQTT topic name ....";
|
||||
|
||||
CooperativeMultitasking tasks;
|
||||
Continuation beginWiFiIfNeeded;
|
||||
Continuation connectMQTTClientIfNeeded;
|
||||
Continuation light;
|
||||
Continuation dark;
|
||||
Guard isDark;
|
||||
Continuation on;
|
||||
Continuation off;
|
||||
Guard isLight;
|
||||
|
||||
WiFiClient wificlient;
|
||||
MQTTClient mqttclient(&tasks, &wificlient, host, 1883, clientid, username, password);
|
||||
MQTTTopic topic(&mqttclient, topicname);
|
||||
|
||||
void setup() {
|
||||
Serial.begin(9600);
|
||||
//
|
||||
while (!Serial) {
|
||||
delay(1000);
|
||||
}
|
||||
//
|
||||
Serial.println("begin wifi");
|
||||
//
|
||||
WiFi.begin(ssid, pass);
|
||||
tasks.after(10000, beginWiFiIfNeeded); // after 10 seconds call beginWiFiIfNeeded()
|
||||
//
|
||||
Serial.println("connect mqtt client");
|
||||
//
|
||||
mqttclient.connect();
|
||||
tasks.after(15000, connectMQTTClientIfNeeded); // after 15 seconds call connectMQTTClientIfNeeded()
|
||||
//
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
tasks.now(light); // call light() now
|
||||
}
|
||||
|
||||
void loop() {
|
||||
tasks.run();
|
||||
}
|
||||
|
||||
void light() {
|
||||
topic.publish("\"light\"");
|
||||
//
|
||||
tasks.ifForThen(isDark, 5000, dark); // if isDark() for 5 seconds then call dark()
|
||||
}
|
||||
|
||||
void dark() {
|
||||
topic.publish("\"dark\"");
|
||||
//
|
||||
tasks.now(on); // call on() now
|
||||
}
|
||||
|
||||
bool isDark() {
|
||||
return analogRead(A1) < 300;
|
||||
}
|
||||
|
||||
void on() {
|
||||
digitalWrite(LED_BUILTIN, HIGH);
|
||||
tasks.after(1000, off); // after 1 second call off()
|
||||
}
|
||||
|
||||
void off() {
|
||||
digitalWrite(LED_BUILTIN, LOW);
|
||||
auto task1 = tasks.after(1000, on); // after 1 second call on()
|
||||
auto task2 = tasks.ifForThen(isLight, 0, light); // if isLight() then call light()
|
||||
tasks.onlyOneOf(task1, task2); // do either task1 or task2
|
||||
}
|
||||
|
||||
bool isLight() {
|
||||
return analogRead(A1) >= 400;
|
||||
}
|
||||
|
||||
void beginWiFiIfNeeded() {
|
||||
switch (WiFi.status()) {
|
||||
case WL_IDLE_STATUS:
|
||||
case WL_CONNECTED: tasks.after(30000, beginWiFiIfNeeded); return; // after 30 seconds call beginWiFiIfNeeded() again
|
||||
case WL_NO_SHIELD: Serial.println("no wifi shield"); return; // do not check again
|
||||
case WL_CONNECT_FAILED: Serial.println("wifi connect failed"); break;
|
||||
case WL_CONNECTION_LOST: Serial.println("wifi connection lost"); break;
|
||||
case WL_DISCONNECTED: Serial.println("wifi disconnected"); break;
|
||||
}
|
||||
//
|
||||
WiFi.begin(ssid, pass);
|
||||
tasks.after(10000, beginWiFiIfNeeded); // after 10 seconds call beginWiFiIfNeeded() again
|
||||
}
|
||||
|
||||
void connectMQTTClientIfNeeded() {
|
||||
if (!mqttclient.connected()) {
|
||||
Serial.println("mqtt client not connected");
|
||||
//
|
||||
mqttclient.connect();
|
||||
}
|
||||
//
|
||||
tasks.after(30000, connectMQTTClientIfNeeded); // after 30 seconds call connectMQTTClientIfNeeded() again
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
MQTTClient KEYWORD1
|
||||
MQTTTopic KEYWORD1
|
||||
connect KEYWORD2
|
||||
connected KEYWORD2
|
||||
publishAcknowledged KEYWORD2
|
||||
disconnect KEYWORD2
|
||||
publish KEYWORD2
|
|
@ -0,0 +1,9 @@
|
|||
name=MQTT Client
|
||||
version=1.0.1
|
||||
author=Andreas Motzek <andreas-motzek@t-online.de>
|
||||
maintainer=Andreas Motzek <andreas-motzek@t-online.de>
|
||||
sentence=MQTT Client lets you connect to a MQTT broker and publish strings to a topic.
|
||||
paragraph=First create a MQTTClient. You have to pass a task list from Cooperative Multitasking, a client (e.g. WiFiClient from WiFi101), host, port and credentials of the MQTT broker. Then create a MQTTTopic. Use the MQTTClient and the topic name as arguments when doing that. To connect to the broker call MQTTClient::connect(). After that you can call MQTTTopic:publish("...") to publish a string. Requires Cooperative Multitasking for processing the acknowledgement packets of the broker. So you have call CooperativeMultitasking::run() in your loop() function. See the examples for details.
|
||||
category=Communication
|
||||
url=https://bitbucket.org/amotzek/arduino/src/fab21e1e7785fe9473d83107048d4431c8fd25a9/src/main/cpp/MQTTClient/?at=master
|
||||
architectures=samd
|
|
@ -0,0 +1,76 @@
|
|||
2.7
|
||||
* Fix remaining-length handling to prevent buffer overrun
|
||||
* Add large-payload API - beginPublish/write/publish/endPublish
|
||||
* Add yield call to improve reliability on ESP
|
||||
* Add Clean Session flag to connect options
|
||||
* Add ESP32 support for functional callback signature
|
||||
* Various other fixes
|
||||
|
||||
2.4
|
||||
* Add MQTT_SOCKET_TIMEOUT to prevent it blocking indefinitely
|
||||
whilst waiting for inbound data
|
||||
* Fixed return code when publishing >256 bytes
|
||||
|
||||
2.3
|
||||
* Add publish(topic,payload,retained) function
|
||||
|
||||
2.2
|
||||
* Change code layout to match Arduino Library reqs
|
||||
|
||||
2.1
|
||||
* Add MAX_TRANSFER_SIZE def to chunk messages if needed
|
||||
* Reject topic/payloads that exceed MQTT_MAX_PACKET_SIZE
|
||||
|
||||
2.0
|
||||
* Add (and default to) MQTT 3.1.1 support
|
||||
* Fix PROGMEM handling for Intel Galileo/ESP8266
|
||||
* Add overloaded constructors for convenience
|
||||
* Add chainable setters for server/callback/client/stream
|
||||
* Add state function to return connack return code
|
||||
|
||||
1.9
|
||||
* Do not split MQTT packets over multiple calls to _client->write()
|
||||
* API change: All constructors now require an instance of Client
|
||||
to be passed in.
|
||||
* Fixed example to match 1.8 api changes - dpslwk
|
||||
* Added username/password support - WilHall
|
||||
* Added publish_P - publishes messages from PROGMEM - jobytaffey
|
||||
|
||||
1.8
|
||||
* KeepAlive interval is configurable in PubSubClient.h
|
||||
* Maximum packet size is configurable in PubSubClient.h
|
||||
* API change: Return boolean rather than int from various functions
|
||||
* API change: Length parameter in message callback changed
|
||||
from int to unsigned int
|
||||
* Various internal tidy-ups around types
|
||||
1.7
|
||||
* Improved keepalive handling
|
||||
* Updated to the Arduino-1.0 API
|
||||
1.6
|
||||
* Added the ability to publish a retained message
|
||||
|
||||
1.5
|
||||
* Added default constructor
|
||||
* Fixed compile error when used with arduino-0021 or later
|
||||
|
||||
1.4
|
||||
* Fixed connection lost handling
|
||||
|
||||
1.3
|
||||
* Fixed packet reading bug in PubSubClient.readPacket
|
||||
|
||||
1.2
|
||||
* Fixed compile error when used with arduino-0016 or later
|
||||
|
||||
|
||||
1.1
|
||||
* Reduced size of library
|
||||
* Added support for Will messages
|
||||
* Clarified licensing - see LICENSE.txt
|
||||
|
||||
|
||||
1.0
|
||||
* Only Quality of Service (QOS) 0 messaging is supported
|
||||
* The maximum message size, including header, is 128 bytes
|
||||
* The keepalive interval is set to 30 seconds
|
||||
* No support for Will messages
|
|
@ -0,0 +1,20 @@
|
|||
Copyright (c) 2008-2015 Nicholas O'Leary
|
||||
|
||||
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.
|
|
@ -0,0 +1,48 @@
|
|||
# Arduino Client for MQTT
|
||||
|
||||
This library provides a client for doing simple publish/subscribe messaging with
|
||||
a server that supports MQTT.
|
||||
|
||||
## Examples
|
||||
|
||||
The library comes with a number of example sketches. See File > Examples > PubSubClient
|
||||
within the Arduino application.
|
||||
|
||||
Full API documentation is available here: https://pubsubclient.knolleary.net
|
||||
|
||||
## Limitations
|
||||
|
||||
- It can only publish QoS 0 messages. It can subscribe at QoS 0 or QoS 1.
|
||||
- The maximum message size, including header, is **128 bytes** by default. This
|
||||
is configurable via `MQTT_MAX_PACKET_SIZE` in `PubSubClient.h`.
|
||||
- The keepalive interval is set to 15 seconds by default. This is configurable
|
||||
via `MQTT_KEEPALIVE` in `PubSubClient.h`.
|
||||
- The client uses MQTT 3.1.1 by default. It can be changed to use MQTT 3.1 by
|
||||
changing value of `MQTT_VERSION` in `PubSubClient.h`.
|
||||
|
||||
|
||||
## Compatible Hardware
|
||||
|
||||
The library uses the Arduino Ethernet Client api for interacting with the
|
||||
underlying network hardware. This means it Just Works with a growing number of
|
||||
boards and shields, including:
|
||||
|
||||
- Arduino Ethernet
|
||||
- Arduino Ethernet Shield
|
||||
- Arduino YUN – use the included `YunClient` in place of `EthernetClient`, and
|
||||
be sure to do a `Bridge.begin()` first
|
||||
- Arduino WiFi Shield - if you want to send packets > 90 bytes with this shield,
|
||||
enable the `MQTT_MAX_TRANSFER_SIZE` define in `PubSubClient.h`.
|
||||
- Sparkfun WiFly Shield – [library](https://github.com/dpslwk/WiFly)
|
||||
- TI CC3000 WiFi - [library](https://github.com/sparkfun/SFE_CC3000_Library)
|
||||
- Intel Galileo/Edison
|
||||
- ESP8266
|
||||
- ESP32
|
||||
|
||||
The library cannot currently be used with hardware based on the ENC28J60 chip –
|
||||
such as the Nanode or the Nuelectronics Ethernet Shield. For those, there is an
|
||||
[alternative library](https://github.com/njh/NanodeMQTT) available.
|
||||
|
||||
## License
|
||||
|
||||
This code is released under the MIT License.
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
Basic MQTT example with Authentication
|
||||
|
||||
- connects to an MQTT server, providing username
|
||||
and password
|
||||
- publishes "hello world" to the topic "outTopic"
|
||||
- subscribes to the topic "inTopic"
|
||||
*/
|
||||
|
||||
#include <SPI.h>
|
||||
#include <Ethernet.h>
|
||||
#include <PubSubClient.h>
|
||||
|
||||
// Update these with values suitable for your network.
|
||||
byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED };
|
||||
IPAddress ip(172, 16, 0, 100);
|
||||
IPAddress server(172, 16, 0, 2);
|
||||
|
||||
void callback(char* topic, byte* payload, unsigned int length) {
|
||||
// handle message arrived
|
||||
}
|
||||
|
||||
EthernetClient ethClient;
|
||||
PubSubClient client(server, 1883, callback, ethClient);
|
||||
|
||||
void setup()
|
||||
{
|
||||
Ethernet.begin(mac, ip);
|
||||
// Note - the default maximum packet size is 128 bytes. If the
|
||||
// combined length of clientId, username and password exceed this,
|
||||
// you will need to increase the value of MQTT_MAX_PACKET_SIZE in
|
||||
// PubSubClient.h
|
||||
|
||||
if (client.connect("arduinoClient", "testuser", "testpass")) {
|
||||
client.publish("outTopic","hello world");
|
||||
client.subscribe("inTopic");
|
||||
}
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
client.loop();
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
Basic MQTT example
|
||||
|
||||
This sketch demonstrates the basic capabilities of the library.
|
||||
It connects to an MQTT server then:
|
||||
- publishes "hello world" to the topic "outTopic"
|
||||
- subscribes to the topic "inTopic", printing out any messages
|
||||
it receives. NB - it assumes the received payloads are strings not binary
|
||||
|
||||
It will reconnect to the server if the connection is lost using a blocking
|
||||
reconnect function. See the 'mqtt_reconnect_nonblocking' example for how to
|
||||
achieve the same result without blocking the main loop.
|
||||
|
||||
*/
|
||||
|
||||
#include <SPI.h>
|
||||
#include <Ethernet.h>
|
||||
#include <PubSubClient.h>
|
||||
|
||||
// Update these with values suitable for your network.
|
||||
byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED };
|
||||
IPAddress ip(172, 16, 0, 100);
|
||||
IPAddress server(172, 16, 0, 2);
|
||||
|
||||
void callback(char* topic, byte* payload, unsigned int length) {
|
||||
Serial.print("Message arrived [");
|
||||
Serial.print(topic);
|
||||
Serial.print("] ");
|
||||
for (int i=0;i<length;i++) {
|
||||
Serial.print((char)payload[i]);
|
||||
}
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
EthernetClient ethClient;
|
||||
PubSubClient client(ethClient);
|
||||
|
||||
void reconnect() {
|
||||
// Loop until we're reconnected
|
||||
while (!client.connected()) {
|
||||
Serial.print("Attempting MQTT connection...");
|
||||
// Attempt to connect
|
||||
if (client.connect("arduinoClient")) {
|
||||
Serial.println("connected");
|
||||
// Once connected, publish an announcement...
|
||||
client.publish("outTopic","hello world");
|
||||
// ... and resubscribe
|
||||
client.subscribe("inTopic");
|
||||
} else {
|
||||
Serial.print("failed, rc=");
|
||||
Serial.print(client.state());
|
||||
Serial.println(" try again in 5 seconds");
|
||||
// Wait 5 seconds before retrying
|
||||
delay(5000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(57600);
|
||||
|
||||
client.setServer(server, 1883);
|
||||
client.setCallback(callback);
|
||||
|
||||
Ethernet.begin(mac, ip);
|
||||
// Allow the hardware to sort itself out
|
||||
delay(1500);
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
if (!client.connected()) {
|
||||
reconnect();
|
||||
}
|
||||
client.loop();
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
Basic ESP8266 MQTT example
|
||||
|
||||
This sketch demonstrates the capabilities of the pubsub library in combination
|
||||
with the ESP8266 board/library.
|
||||
|
||||
It connects to an MQTT server then:
|
||||
- publishes "hello world" to the topic "outTopic" every two seconds
|
||||
- subscribes to the topic "inTopic", printing out any messages
|
||||
it receives. NB - it assumes the received payloads are strings not binary
|
||||
- If the first character of the topic "inTopic" is an 1, switch ON the ESP Led,
|
||||
else switch it off
|
||||
|
||||
It will reconnect to the server if the connection is lost using a blocking
|
||||
reconnect function. See the 'mqtt_reconnect_nonblocking' example for how to
|
||||
achieve the same result without blocking the main loop.
|
||||
|
||||
To install the ESP8266 board, (using Arduino 1.6.4+):
|
||||
- Add the following 3rd party board manager under "File -> Preferences -> Additional Boards Manager URLs":
|
||||
http://arduino.esp8266.com/stable/package_esp8266com_index.json
|
||||
- Open the "Tools -> Board -> Board Manager" and click install for the ESP8266"
|
||||
- Select your ESP8266 in "Tools -> Board"
|
||||
|
||||
*/
|
||||
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <PubSubClient.h>
|
||||
|
||||
// Update these with values suitable for your network.
|
||||
|
||||
const char* ssid = "........";
|
||||
const char* password = "........";
|
||||
const char* mqtt_server = "broker.mqtt-dashboard.com";
|
||||
|
||||
WiFiClient espClient;
|
||||
PubSubClient client(espClient);
|
||||
long lastMsg = 0;
|
||||
char msg[50];
|
||||
int value = 0;
|
||||
|
||||
void setup_wifi() {
|
||||
|
||||
delay(10);
|
||||
// We start by connecting to a WiFi network
|
||||
Serial.println();
|
||||
Serial.print("Connecting to ");
|
||||
Serial.println(ssid);
|
||||
|
||||
WiFi.begin(ssid, password);
|
||||
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
}
|
||||
|
||||
randomSeed(micros());
|
||||
|
||||
Serial.println("");
|
||||
Serial.println("WiFi connected");
|
||||
Serial.println("IP address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
}
|
||||
|
||||
void callback(char* topic, byte* payload, unsigned int length) {
|
||||
Serial.print("Message arrived [");
|
||||
Serial.print(topic);
|
||||
Serial.print("] ");
|
||||
for (int i = 0; i < length; i++) {
|
||||
Serial.print((char)payload[i]);
|
||||
}
|
||||
Serial.println();
|
||||
|
||||
// Switch on the LED if an 1 was received as first character
|
||||
if ((char)payload[0] == '1') {
|
||||
digitalWrite(BUILTIN_LED, LOW); // Turn the LED on (Note that LOW is the voltage level
|
||||
// but actually the LED is on; this is because
|
||||
// it is active low on the ESP-01)
|
||||
} else {
|
||||
digitalWrite(BUILTIN_LED, HIGH); // Turn the LED off by making the voltage HIGH
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void reconnect() {
|
||||
// Loop until we're reconnected
|
||||
while (!client.connected()) {
|
||||
Serial.print("Attempting MQTT connection...");
|
||||
// Create a random client ID
|
||||
String clientId = "ESP8266Client-";
|
||||
clientId += String(random(0xffff), HEX);
|
||||
// Attempt to connect
|
||||
if (client.connect(clientId.c_str())) {
|
||||
Serial.println("connected");
|
||||
// Once connected, publish an announcement...
|
||||
client.publish("outTopic", "hello world");
|
||||
// ... and resubscribe
|
||||
client.subscribe("inTopic");
|
||||
} else {
|
||||
Serial.print("failed, rc=");
|
||||
Serial.print(client.state());
|
||||
Serial.println(" try again in 5 seconds");
|
||||
// Wait 5 seconds before retrying
|
||||
delay(5000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setup() {
|
||||
pinMode(BUILTIN_LED, OUTPUT); // Initialize the BUILTIN_LED pin as an output
|
||||
Serial.begin(115200);
|
||||
setup_wifi();
|
||||
client.setServer(mqtt_server, 1883);
|
||||
client.setCallback(callback);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
|
||||
if (!client.connected()) {
|
||||
reconnect();
|
||||
}
|
||||
client.loop();
|
||||
|
||||
long now = millis();
|
||||
if (now - lastMsg > 2000) {
|
||||
lastMsg = now;
|
||||
++value;
|
||||
snprintf (msg, 50, "hello world #%ld", value);
|
||||
Serial.print("Publish message: ");
|
||||
Serial.println(msg);
|
||||
client.publish("outTopic", msg);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,179 @@
|
|||
/*
|
||||
Long message ESP8266 MQTT example
|
||||
|
||||
This sketch demonstrates sending arbitrarily large messages in combination
|
||||
with the ESP8266 board/library.
|
||||
|
||||
It connects to an MQTT server then:
|
||||
- publishes "hello world" to the topic "outTopic"
|
||||
- subscribes to the topic "greenBottles/#", printing out any messages
|
||||
it receives. NB - it assumes the received payloads are strings not binary
|
||||
- If the sub-topic is a number, it publishes a "greenBottles/lyrics" message
|
||||
with a payload consisting of the lyrics to "10 green bottles", replacing
|
||||
10 with the number given in the sub-topic.
|
||||
|
||||
It will reconnect to the server if the connection is lost using a blocking
|
||||
reconnect function. See the 'mqtt_reconnect_nonblocking' example for how to
|
||||
achieve the same result without blocking the main loop.
|
||||
|
||||
To install the ESP8266 board, (using Arduino 1.6.4+):
|
||||
- Add the following 3rd party board manager under "File -> Preferences -> Additional Boards Manager URLs":
|
||||
http://arduino.esp8266.com/stable/package_esp8266com_index.json
|
||||
- Open the "Tools -> Board -> Board Manager" and click install for the ESP8266"
|
||||
- Select your ESP8266 in "Tools -> Board"
|
||||
|
||||
*/
|
||||
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <PubSubClient.h>
|
||||
|
||||
// Update these with values suitable for your network.
|
||||
|
||||
const char* ssid = "........";
|
||||
const char* password = "........";
|
||||
const char* mqtt_server = "broker.mqtt-dashboard.com";
|
||||
|
||||
WiFiClient espClient;
|
||||
PubSubClient client(espClient);
|
||||
long lastMsg = 0;
|
||||
char msg[50];
|
||||
int value = 0;
|
||||
|
||||
void setup_wifi() {
|
||||
|
||||
delay(10);
|
||||
// We start by connecting to a WiFi network
|
||||
Serial.println();
|
||||
Serial.print("Connecting to ");
|
||||
Serial.println(ssid);
|
||||
|
||||
WiFi.begin(ssid, password);
|
||||
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
}
|
||||
|
||||
randomSeed(micros());
|
||||
|
||||
Serial.println("");
|
||||
Serial.println("WiFi connected");
|
||||
Serial.println("IP address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
}
|
||||
|
||||
void callback(char* topic, byte* payload, unsigned int length) {
|
||||
Serial.print("Message arrived [");
|
||||
Serial.print(topic);
|
||||
Serial.print("] ");
|
||||
for (int i = 0; i < length; i++) {
|
||||
Serial.print((char)payload[i]);
|
||||
}
|
||||
Serial.println();
|
||||
|
||||
// Find out how many bottles we should generate lyrics for
|
||||
String topicStr(topic);
|
||||
int bottleCount = 0; // assume no bottles unless we correctly parse a value from the topic
|
||||
if (topicStr.indexOf('/') >= 0) {
|
||||
// The topic includes a '/', we'll try to read the number of bottles from just after that
|
||||
topicStr.remove(0, topicStr.indexOf('/')+1);
|
||||
// Now see if there's a number of bottles after the '/'
|
||||
bottleCount = topicStr.toInt();
|
||||
}
|
||||
|
||||
if (bottleCount > 0) {
|
||||
// Work out how big our resulting message will be
|
||||
int msgLen = 0;
|
||||
for (int i = bottleCount; i > 0; i--) {
|
||||
String numBottles(i);
|
||||
msgLen += 2*numBottles.length();
|
||||
if (i == 1) {
|
||||
msgLen += 2*String(" green bottle, standing on the wall\n").length();
|
||||
} else {
|
||||
msgLen += 2*String(" green bottles, standing on the wall\n").length();
|
||||
}
|
||||
msgLen += String("And if one green bottle should accidentally fall\nThere'll be ").length();
|
||||
switch (i) {
|
||||
case 1:
|
||||
msgLen += String("no green bottles, standing on the wall\n\n").length();
|
||||
break;
|
||||
case 2:
|
||||
msgLen += String("1 green bottle, standing on the wall\n\n").length();
|
||||
break;
|
||||
default:
|
||||
numBottles = i-1;
|
||||
msgLen += numBottles.length();
|
||||
msgLen += String(" green bottles, standing on the wall\n\n").length();
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
// Now we can start to publish the message
|
||||
client.beginPublish("greenBottles/lyrics", msgLen, false);
|
||||
for (int i = bottleCount; i > 0; i--) {
|
||||
for (int j = 0; j < 2; j++) {
|
||||
client.print(i);
|
||||
if (i == 1) {
|
||||
client.print(" green bottle, standing on the wall\n");
|
||||
} else {
|
||||
client.print(" green bottles, standing on the wall\n");
|
||||
}
|
||||
}
|
||||
client.print("And if one green bottle should accidentally fall\nThere'll be ");
|
||||
switch (i) {
|
||||
case 1:
|
||||
client.print("no green bottles, standing on the wall\n\n");
|
||||
break;
|
||||
case 2:
|
||||
client.print("1 green bottle, standing on the wall\n\n");
|
||||
break;
|
||||
default:
|
||||
client.print(i-1);
|
||||
client.print(" green bottles, standing on the wall\n\n");
|
||||
break;
|
||||
};
|
||||
}
|
||||
// Now we're done!
|
||||
client.endPublish();
|
||||
}
|
||||
}
|
||||
|
||||
void reconnect() {
|
||||
// Loop until we're reconnected
|
||||
while (!client.connected()) {
|
||||
Serial.print("Attempting MQTT connection...");
|
||||
// Create a random client ID
|
||||
String clientId = "ESP8266Client-";
|
||||
clientId += String(random(0xffff), HEX);
|
||||
// Attempt to connect
|
||||
if (client.connect(clientId.c_str())) {
|
||||
Serial.println("connected");
|
||||
// Once connected, publish an announcement...
|
||||
client.publish("outTopic", "hello world");
|
||||
// ... and resubscribe
|
||||
client.subscribe("greenBottles/#");
|
||||
} else {
|
||||
Serial.print("failed, rc=");
|
||||
Serial.print(client.state());
|
||||
Serial.println(" try again in 5 seconds");
|
||||
// Wait 5 seconds before retrying
|
||||
delay(5000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setup() {
|
||||
pinMode(BUILTIN_LED, OUTPUT); // Initialize the BUILTIN_LED pin as an output
|
||||
Serial.begin(115200);
|
||||
setup_wifi();
|
||||
client.setServer(mqtt_server, 1883);
|
||||
client.setCallback(callback);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
|
||||
if (!client.connected()) {
|
||||
reconnect();
|
||||
}
|
||||
client.loop();
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
Publishing in the callback
|
||||
|
||||
- connects to an MQTT server
|
||||
- subscribes to the topic "inTopic"
|
||||
- when a message is received, republishes it to "outTopic"
|
||||
|
||||
This example shows how to publish messages within the
|
||||
callback function. The callback function header needs to
|
||||
be declared before the PubSubClient constructor and the
|
||||
actual callback defined afterwards.
|
||||
This ensures the client reference in the callback function
|
||||
is valid.
|
||||
|
||||
*/
|
||||
|
||||
#include <SPI.h>
|
||||
#include <Ethernet.h>
|
||||
#include <PubSubClient.h>
|
||||
|
||||
// Update these with values suitable for your network.
|
||||
byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED };
|
||||
IPAddress ip(172, 16, 0, 100);
|
||||
IPAddress server(172, 16, 0, 2);
|
||||
|
||||
// Callback function header
|
||||
void callback(char* topic, byte* payload, unsigned int length);
|
||||
|
||||
EthernetClient ethClient;
|
||||
PubSubClient client(server, 1883, callback, ethClient);
|
||||
|
||||
// Callback function
|
||||
void callback(char* topic, byte* payload, unsigned int length) {
|
||||
// In order to republish this payload, a copy must be made
|
||||
// as the orignal payload buffer will be overwritten whilst
|
||||
// constructing the PUBLISH packet.
|
||||
|
||||
// Allocate the correct amount of memory for the payload copy
|
||||
byte* p = (byte*)malloc(length);
|
||||
// Copy the payload to the new buffer
|
||||
memcpy(p,payload,length);
|
||||
client.publish("outTopic", p, length);
|
||||
// Free the memory
|
||||
free(p);
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
|
||||
Ethernet.begin(mac, ip);
|
||||
if (client.connect("arduinoClient")) {
|
||||
client.publish("outTopic","hello world");
|
||||
client.subscribe("inTopic");
|
||||
}
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
client.loop();
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
Reconnecting MQTT example - non-blocking
|
||||
|
||||
This sketch demonstrates how to keep the client connected
|
||||
using a non-blocking reconnect function. If the client loses
|
||||
its connection, it attempts to reconnect every 5 seconds
|
||||
without blocking the main loop.
|
||||
|
||||
*/
|
||||
|
||||
#include <SPI.h>
|
||||
#include <Ethernet.h>
|
||||
#include <PubSubClient.h>
|
||||
|
||||
// Update these with values suitable for your hardware/network.
|
||||
byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED };
|
||||
IPAddress ip(172, 16, 0, 100);
|
||||
IPAddress server(172, 16, 0, 2);
|
||||
|
||||
void callback(char* topic, byte* payload, unsigned int length) {
|
||||
// handle message arrived
|
||||
}
|
||||
|
||||
EthernetClient ethClient;
|
||||
PubSubClient client(ethClient);
|
||||
|
||||
long lastReconnectAttempt = 0;
|
||||
|
||||
boolean reconnect() {
|
||||
if (client.connect("arduinoClient")) {
|
||||
// Once connected, publish an announcement...
|
||||
client.publish("outTopic","hello world");
|
||||
// ... and resubscribe
|
||||
client.subscribe("inTopic");
|
||||
}
|
||||
return client.connected();
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
client.setServer(server, 1883);
|
||||
client.setCallback(callback);
|
||||
|
||||
Ethernet.begin(mac, ip);
|
||||
delay(1500);
|
||||
lastReconnectAttempt = 0;
|
||||
}
|
||||
|
||||
|
||||
void loop()
|
||||
{
|
||||
if (!client.connected()) {
|
||||
long now = millis();
|
||||
if (now - lastReconnectAttempt > 5000) {
|
||||
lastReconnectAttempt = now;
|
||||
// Attempt to reconnect
|
||||
if (reconnect()) {
|
||||
lastReconnectAttempt = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Client connected
|
||||
|
||||
client.loop();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
Example of using a Stream object to store the message payload
|
||||
|
||||
Uses SRAM library: https://github.com/ennui2342/arduino-sram
|
||||
but could use any Stream based class such as SD
|
||||
|
||||
- connects to an MQTT server
|
||||
- publishes "hello world" to the topic "outTopic"
|
||||
- subscribes to the topic "inTopic"
|
||||
*/
|
||||
|
||||
#include <SPI.h>
|
||||
#include <Ethernet.h>
|
||||
#include <PubSubClient.h>
|
||||
#include <SRAM.h>
|
||||
|
||||
// Update these with values suitable for your network.
|
||||
byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED };
|
||||
IPAddress ip(172, 16, 0, 100);
|
||||
IPAddress server(172, 16, 0, 2);
|
||||
|
||||
SRAM sram(4, SRAM_1024);
|
||||
|
||||
void callback(char* topic, byte* payload, unsigned int length) {
|
||||
sram.seek(1);
|
||||
|
||||
// do something with the message
|
||||
for(uint8_t i=0; i<length; i++) {
|
||||
Serial.write(sram.read());
|
||||
}
|
||||
Serial.println();
|
||||
|
||||
// Reset position for the next message to be stored
|
||||
sram.seek(1);
|
||||
}
|
||||
|
||||
EthernetClient ethClient;
|
||||
PubSubClient client(server, 1883, callback, ethClient, sram);
|
||||
|
||||
void setup()
|
||||
{
|
||||
Ethernet.begin(mac, ip);
|
||||
if (client.connect("arduinoClient")) {
|
||||
client.publish("outTopic","hello world");
|
||||
client.subscribe("inTopic");
|
||||
}
|
||||
|
||||
sram.begin();
|
||||
sram.seek(1);
|
||||
|
||||
Serial.begin(9600);
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
client.loop();
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
#######################################
|
||||
# Syntax Coloring Map For PubSubClient
|
||||
#######################################
|
||||
|
||||
#######################################
|
||||
# Datatypes (KEYWORD1)
|
||||
#######################################
|
||||
|
||||
PubSubClient KEYWORD1
|
||||
|
||||
#######################################
|
||||
# Methods and Functions (KEYWORD2)
|
||||
#######################################
|
||||
|
||||
connect KEYWORD2
|
||||
disconnect KEYWORD2
|
||||
publish KEYWORD2
|
||||
publish_P KEYWORD2
|
||||
beginPublish KEYWORD2
|
||||
endPublish KEYWORD2
|
||||
write KEYWORD2
|
||||
subscribe KEYWORD2
|
||||
unsubscribe KEYWORD2
|
||||
loop KEYWORD2
|
||||
connected KEYWORD2
|
||||
setServer KEYWORD2
|
||||
setCallback KEYWORD2
|
||||
setClient KEYWORD2
|
||||
setStream KEYWORD2
|
||||
|
||||
#######################################
|
||||
# Constants (LITERAL1)
|
||||
#######################################
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "PubSubClient",
|
||||
"keywords": "ethernet, mqtt, m2m, iot",
|
||||
"description": "A client library for MQTT messaging. MQTT is a lightweight messaging protocol ideal for small devices. This library allows you to send and receive MQTT messages. It supports the latest MQTT 3.1.1 protocol and can be configured to use the older MQTT 3.1 if needed. It supports all Arduino Ethernet Client compatible hardware, including the Intel Galileo/Edison, ESP8266 and TI CC3000.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/knolleary/pubsubclient.git"
|
||||
},
|
||||
"version": "2.7",
|
||||
"exclude": "tests",
|
||||
"examples": "examples/*/*.ino",
|
||||
"frameworks": "arduino",
|
||||
"platforms": [
|
||||
"atmelavr",
|
||||
"espressif"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
name=PubSubClient
|
||||
version=2.7
|
||||
author=Nick O'Leary <nick.oleary@gmail.com>
|
||||
maintainer=Nick O'Leary <nick.oleary@gmail.com>
|
||||
sentence=A client library for MQTT messaging.
|
||||
paragraph=MQTT is a lightweight messaging protocol ideal for small devices. This library allows you to send and receive MQTT messages. It supports the latest MQTT 3.1.1 protocol and can be configured to use the older MQTT 3.1 if needed. It supports all Arduino Ethernet Client compatible hardware, including the Intel Galileo/Edison, ESP8266 and TI CC3000.
|
||||
category=Communication
|
||||
url=http://pubsubclient.knolleary.net
|
||||
architectures=*
|
|
@ -0,0 +1,653 @@
|
|||
/*
|
||||
PubSubClient.cpp - A simple client for MQTT.
|
||||
Nick O'Leary
|
||||
http://knolleary.net
|
||||
*/
|
||||
|
||||
#include "PubSubClient.h"
|
||||
#include "Arduino.h"
|
||||
|
||||
PubSubClient::PubSubClient() {
|
||||
this->_state = MQTT_DISCONNECTED;
|
||||
this->_client = NULL;
|
||||
this->stream = NULL;
|
||||
setCallback(NULL);
|
||||
}
|
||||
|
||||
PubSubClient::PubSubClient(Client& client) {
|
||||
this->_state = MQTT_DISCONNECTED;
|
||||
setClient(client);
|
||||
this->stream = NULL;
|
||||
}
|
||||
|
||||
PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client) {
|
||||
this->_state = MQTT_DISCONNECTED;
|
||||
setServer(addr, port);
|
||||
setClient(client);
|
||||
this->stream = NULL;
|
||||
}
|
||||
PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client, Stream& stream) {
|
||||
this->_state = MQTT_DISCONNECTED;
|
||||
setServer(addr,port);
|
||||
setClient(client);
|
||||
setStream(stream);
|
||||
}
|
||||
PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) {
|
||||
this->_state = MQTT_DISCONNECTED;
|
||||
setServer(addr, port);
|
||||
setCallback(callback);
|
||||
setClient(client);
|
||||
this->stream = NULL;
|
||||
}
|
||||
PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) {
|
||||
this->_state = MQTT_DISCONNECTED;
|
||||
setServer(addr,port);
|
||||
setCallback(callback);
|
||||
setClient(client);
|
||||
setStream(stream);
|
||||
}
|
||||
|
||||
PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client) {
|
||||
this->_state = MQTT_DISCONNECTED;
|
||||
setServer(ip, port);
|
||||
setClient(client);
|
||||
this->stream = NULL;
|
||||
}
|
||||
PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client, Stream& stream) {
|
||||
this->_state = MQTT_DISCONNECTED;
|
||||
setServer(ip,port);
|
||||
setClient(client);
|
||||
setStream(stream);
|
||||
}
|
||||
PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) {
|
||||
this->_state = MQTT_DISCONNECTED;
|
||||
setServer(ip, port);
|
||||
setCallback(callback);
|
||||
setClient(client);
|
||||
this->stream = NULL;
|
||||
}
|
||||
PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) {
|
||||
this->_state = MQTT_DISCONNECTED;
|
||||
setServer(ip,port);
|
||||
setCallback(callback);
|
||||
setClient(client);
|
||||
setStream(stream);
|
||||
}
|
||||
|
||||
PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client) {
|
||||
this->_state = MQTT_DISCONNECTED;
|
||||
setServer(domain,port);
|
||||
setClient(client);
|
||||
this->stream = NULL;
|
||||
}
|
||||
PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client, Stream& stream) {
|
||||
this->_state = MQTT_DISCONNECTED;
|
||||
setServer(domain,port);
|
||||
setClient(client);
|
||||
setStream(stream);
|
||||
}
|
||||
PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) {
|
||||
this->_state = MQTT_DISCONNECTED;
|
||||
setServer(domain,port);
|
||||
setCallback(callback);
|
||||
setClient(client);
|
||||
this->stream = NULL;
|
||||
}
|
||||
PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) {
|
||||
this->_state = MQTT_DISCONNECTED;
|
||||
setServer(domain,port);
|
||||
setCallback(callback);
|
||||
setClient(client);
|
||||
setStream(stream);
|
||||
}
|
||||
|
||||
boolean PubSubClient::connect(const char *id) {
|
||||
return connect(id,NULL,NULL,0,0,0,0,1);
|
||||
}
|
||||
|
||||
boolean PubSubClient::connect(const char *id, const char *user, const char *pass) {
|
||||
return connect(id,user,pass,0,0,0,0,1);
|
||||
}
|
||||
|
||||
boolean PubSubClient::connect(const char *id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) {
|
||||
return connect(id,NULL,NULL,willTopic,willQos,willRetain,willMessage,1);
|
||||
}
|
||||
|
||||
boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) {
|
||||
return connect(id,user,pass,willTopic,willQos,willRetain,willMessage,1);
|
||||
}
|
||||
|
||||
boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage, boolean cleanSession) {
|
||||
if (!connected()) {
|
||||
int result = 0;
|
||||
|
||||
if (domain != NULL) {
|
||||
result = _client->connect(this->domain, this->port);
|
||||
} else {
|
||||
result = _client->connect(this->ip, this->port);
|
||||
}
|
||||
if (result == 1) {
|
||||
nextMsgId = 1;
|
||||
// Leave room in the buffer for header and variable length field
|
||||
uint16_t length = MQTT_MAX_HEADER_SIZE;
|
||||
unsigned int j;
|
||||
|
||||
#if MQTT_VERSION == MQTT_VERSION_3_1
|
||||
uint8_t d[9] = {0x00,0x06,'M','Q','I','s','d','p', MQTT_VERSION};
|
||||
#define MQTT_HEADER_VERSION_LENGTH 9
|
||||
#elif MQTT_VERSION == MQTT_VERSION_3_1_1
|
||||
uint8_t d[7] = {0x00,0x04,'M','Q','T','T',MQTT_VERSION};
|
||||
#define MQTT_HEADER_VERSION_LENGTH 7
|
||||
#endif
|
||||
for (j = 0;j<MQTT_HEADER_VERSION_LENGTH;j++) {
|
||||
buffer[length++] = d[j];
|
||||
}
|
||||
|
||||
uint8_t v;
|
||||
if (willTopic) {
|
||||
v = 0x04|(willQos<<3)|(willRetain<<5);
|
||||
} else {
|
||||
v = 0x00;
|
||||
}
|
||||
if (cleanSession) {
|
||||
v = v|0x02;
|
||||
}
|
||||
|
||||
if(user != NULL) {
|
||||
v = v|0x80;
|
||||
|
||||
if(pass != NULL) {
|
||||
v = v|(0x80>>1);
|
||||
}
|
||||
}
|
||||
|
||||
buffer[length++] = v;
|
||||
|
||||
buffer[length++] = ((MQTT_KEEPALIVE) >> 8);
|
||||
buffer[length++] = ((MQTT_KEEPALIVE) & 0xFF);
|
||||
|
||||
CHECK_STRING_LENGTH(length,id)
|
||||
length = writeString(id,buffer,length);
|
||||
if (willTopic) {
|
||||
CHECK_STRING_LENGTH(length,willTopic)
|
||||
length = writeString(willTopic,buffer,length);
|
||||
CHECK_STRING_LENGTH(length,willMessage)
|
||||
length = writeString(willMessage,buffer,length);
|
||||
}
|
||||
|
||||
if(user != NULL) {
|
||||
CHECK_STRING_LENGTH(length,user)
|
||||
length = writeString(user,buffer,length);
|
||||
if(pass != NULL) {
|
||||
CHECK_STRING_LENGTH(length,pass)
|
||||
length = writeString(pass,buffer,length);
|
||||
}
|
||||
}
|
||||
|
||||
write(MQTTCONNECT,buffer,length-MQTT_MAX_HEADER_SIZE);
|
||||
|
||||
lastInActivity = lastOutActivity = millis();
|
||||
|
||||
while (!_client->available()) {
|
||||
unsigned long t = millis();
|
||||
if (t-lastInActivity >= ((int32_t) MQTT_SOCKET_TIMEOUT*1000UL)) {
|
||||
_state = MQTT_CONNECTION_TIMEOUT;
|
||||
_client->stop();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
uint8_t llen;
|
||||
uint16_t len = readPacket(&llen);
|
||||
|
||||
if (len == 4) {
|
||||
if (buffer[3] == 0) {
|
||||
lastInActivity = millis();
|
||||
pingOutstanding = false;
|
||||
_state = MQTT_CONNECTED;
|
||||
return true;
|
||||
} else {
|
||||
_state = buffer[3];
|
||||
}
|
||||
}
|
||||
_client->stop();
|
||||
} else {
|
||||
_state = MQTT_CONNECT_FAILED;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// reads a byte into result
|
||||
boolean PubSubClient::readByte(uint8_t * result) {
|
||||
uint32_t previousMillis = millis();
|
||||
while(!_client->available()) {
|
||||
yield();
|
||||
uint32_t currentMillis = millis();
|
||||
if(currentMillis - previousMillis >= ((int32_t) MQTT_SOCKET_TIMEOUT * 1000)){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
*result = _client->read();
|
||||
return true;
|
||||
}
|
||||
|
||||
// reads a byte into result[*index] and increments index
|
||||
boolean PubSubClient::readByte(uint8_t * result, uint16_t * index){
|
||||
uint16_t current_index = *index;
|
||||
uint8_t * write_address = &(result[current_index]);
|
||||
if(readByte(write_address)){
|
||||
*index = current_index + 1;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t PubSubClient::readPacket(uint8_t* lengthLength) {
|
||||
uint16_t len = 0;
|
||||
if(!readByte(buffer, &len)) return 0;
|
||||
bool isPublish = (buffer[0]&0xF0) == MQTTPUBLISH;
|
||||
uint32_t multiplier = 1;
|
||||
uint16_t length = 0;
|
||||
uint8_t digit = 0;
|
||||
uint16_t skip = 0;
|
||||
uint8_t start = 0;
|
||||
|
||||
do {
|
||||
if (len == 5) {
|
||||
// Invalid remaining length encoding - kill the connection
|
||||
_state = MQTT_DISCONNECTED;
|
||||
_client->stop();
|
||||
return 0;
|
||||
}
|
||||
if(!readByte(&digit)) return 0;
|
||||
buffer[len++] = digit;
|
||||
length += (digit & 127) * multiplier;
|
||||
multiplier *= 128;
|
||||
} while ((digit & 128) != 0);
|
||||
*lengthLength = len-1;
|
||||
|
||||
if (isPublish) {
|
||||
// Read in topic length to calculate bytes to skip over for Stream writing
|
||||
if(!readByte(buffer, &len)) return 0;
|
||||
if(!readByte(buffer, &len)) return 0;
|
||||
skip = (buffer[*lengthLength+1]<<8)+buffer[*lengthLength+2];
|
||||
start = 2;
|
||||
if (buffer[0]&MQTTQOS1) {
|
||||
// skip message id
|
||||
skip += 2;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint16_t i = start;i<length;i++) {
|
||||
if(!readByte(&digit)) return 0;
|
||||
if (this->stream) {
|
||||
if (isPublish && len-*lengthLength-2>skip) {
|
||||
this->stream->write(digit);
|
||||
}
|
||||
}
|
||||
if (len < MQTT_MAX_PACKET_SIZE) {
|
||||
buffer[len] = digit;
|
||||
}
|
||||
len++;
|
||||
}
|
||||
|
||||
if (!this->stream && len > MQTT_MAX_PACKET_SIZE) {
|
||||
len = 0; // This will cause the packet to be ignored.
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
boolean PubSubClient::loop() {
|
||||
if (connected()) {
|
||||
unsigned long t = millis();
|
||||
if ((t - lastInActivity > MQTT_KEEPALIVE*1000UL) || (t - lastOutActivity > MQTT_KEEPALIVE*1000UL)) {
|
||||
if (pingOutstanding) {
|
||||
this->_state = MQTT_CONNECTION_TIMEOUT;
|
||||
_client->stop();
|
||||
return false;
|
||||
} else {
|
||||
buffer[0] = MQTTPINGREQ;
|
||||
buffer[1] = 0;
|
||||
_client->write(buffer,2);
|
||||
lastOutActivity = t;
|
||||
lastInActivity = t;
|
||||
pingOutstanding = true;
|
||||
}
|
||||
}
|
||||
if (_client->available()) {
|
||||
uint8_t llen;
|
||||
uint16_t len = readPacket(&llen);
|
||||
uint16_t msgId = 0;
|
||||
uint8_t *payload;
|
||||
if (len > 0) {
|
||||
lastInActivity = t;
|
||||
uint8_t type = buffer[0]&0xF0;
|
||||
if (type == MQTTPUBLISH) {
|
||||
if (callback) {
|
||||
uint16_t tl = (buffer[llen+1]<<8)+buffer[llen+2]; /* topic length in bytes */
|
||||
memmove(buffer+llen+2,buffer+llen+3,tl); /* move topic inside buffer 1 byte to front */
|
||||
buffer[llen+2+tl] = 0; /* end the topic as a 'C' string with \x00 */
|
||||
char *topic = (char*) buffer+llen+2;
|
||||
// msgId only present for QOS>0
|
||||
if ((buffer[0]&0x06) == MQTTQOS1) {
|
||||
msgId = (buffer[llen+3+tl]<<8)+buffer[llen+3+tl+1];
|
||||
payload = buffer+llen+3+tl+2;
|
||||
callback(topic,payload,len-llen-3-tl-2);
|
||||
|
||||
buffer[0] = MQTTPUBACK;
|
||||
buffer[1] = 2;
|
||||
buffer[2] = (msgId >> 8);
|
||||
buffer[3] = (msgId & 0xFF);
|
||||
_client->write(buffer,4);
|
||||
lastOutActivity = t;
|
||||
|
||||
} else {
|
||||
payload = buffer+llen+3+tl;
|
||||
callback(topic,payload,len-llen-3-tl);
|
||||
}
|
||||
}
|
||||
} else if (type == MQTTPINGREQ) {
|
||||
buffer[0] = MQTTPINGRESP;
|
||||
buffer[1] = 0;
|
||||
_client->write(buffer,2);
|
||||
} else if (type == MQTTPINGRESP) {
|
||||
pingOutstanding = false;
|
||||
}
|
||||
} else if (!connected()) {
|
||||
// readPacket has closed the connection
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean PubSubClient::publish(const char* topic, const char* payload) {
|
||||
return publish(topic,(const uint8_t*)payload,strlen(payload),false);
|
||||
}
|
||||
|
||||
boolean PubSubClient::publish(const char* topic, const char* payload, boolean retained) {
|
||||
return publish(topic,(const uint8_t*)payload,strlen(payload),retained);
|
||||
}
|
||||
|
||||
boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength) {
|
||||
return publish(topic, payload, plength, false);
|
||||
}
|
||||
|
||||
boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) {
|
||||
if (connected()) {
|
||||
if (MQTT_MAX_PACKET_SIZE < MQTT_MAX_HEADER_SIZE + 2+strlen(topic) + plength) {
|
||||
// Too long
|
||||
return false;
|
||||
}
|
||||
// Leave room in the buffer for header and variable length field
|
||||
uint16_t length = MQTT_MAX_HEADER_SIZE;
|
||||
length = writeString(topic,buffer,length);
|
||||
uint16_t i;
|
||||
for (i=0;i<plength;i++) {
|
||||
buffer[length++] = payload[i];
|
||||
}
|
||||
uint8_t header = MQTTPUBLISH;
|
||||
if (retained) {
|
||||
header |= 1;
|
||||
}
|
||||
return write(header,buffer,length-MQTT_MAX_HEADER_SIZE);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean PubSubClient::publish_P(const char* topic, const char* payload, boolean retained) {
|
||||
return publish_P(topic, (const uint8_t*)payload, strlen(payload), retained);
|
||||
}
|
||||
|
||||
boolean PubSubClient::publish_P(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) {
|
||||
uint8_t llen = 0;
|
||||
uint8_t digit;
|
||||
unsigned int rc = 0;
|
||||
uint16_t tlen;
|
||||
unsigned int pos = 0;
|
||||
unsigned int i;
|
||||
uint8_t header;
|
||||
unsigned int len;
|
||||
|
||||
if (!connected()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
tlen = strlen(topic);
|
||||
|
||||
header = MQTTPUBLISH;
|
||||
if (retained) {
|
||||
header |= 1;
|
||||
}
|
||||
buffer[pos++] = header;
|
||||
len = plength + 2 + tlen;
|
||||
do {
|
||||
digit = len % 128;
|
||||
len = len / 128;
|
||||
if (len > 0) {
|
||||
digit |= 0x80;
|
||||
}
|
||||
buffer[pos++] = digit;
|
||||
llen++;
|
||||
} while(len>0);
|
||||
|
||||
pos = writeString(topic,buffer,pos);
|
||||
|
||||
rc += _client->write(buffer,pos);
|
||||
|
||||
for (i=0;i<plength;i++) {
|
||||
rc += _client->write((char)pgm_read_byte_near(payload + i));
|
||||
}
|
||||
|
||||
lastOutActivity = millis();
|
||||
|
||||
return rc == tlen + 4 + plength;
|
||||
}
|
||||
|
||||
boolean PubSubClient::beginPublish(const char* topic, unsigned int plength, boolean retained) {
|
||||
if (connected()) {
|
||||
// Send the header and variable length field
|
||||
uint16_t length = MQTT_MAX_HEADER_SIZE;
|
||||
length = writeString(topic,buffer,length);
|
||||
uint16_t i;
|
||||
uint8_t header = MQTTPUBLISH;
|
||||
if (retained) {
|
||||
header |= 1;
|
||||
}
|
||||
size_t hlen = buildHeader(header, buffer, plength+length-MQTT_MAX_HEADER_SIZE);
|
||||
uint16_t rc = _client->write(buffer+(MQTT_MAX_HEADER_SIZE-hlen),length-(MQTT_MAX_HEADER_SIZE-hlen));
|
||||
lastOutActivity = millis();
|
||||
return (rc == (length-(MQTT_MAX_HEADER_SIZE-hlen)));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int PubSubClient::endPublish() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t PubSubClient::write(uint8_t data) {
|
||||
lastOutActivity = millis();
|
||||
return _client->write(data);
|
||||
}
|
||||
|
||||
size_t PubSubClient::write(const uint8_t *buffer, size_t size) {
|
||||
lastOutActivity = millis();
|
||||
return _client->write(buffer,size);
|
||||
}
|
||||
|
||||
size_t PubSubClient::buildHeader(uint8_t header, uint8_t* buf, uint16_t length) {
|
||||
uint8_t lenBuf[4];
|
||||
uint8_t llen = 0;
|
||||
uint8_t digit;
|
||||
uint8_t pos = 0;
|
||||
uint16_t len = length;
|
||||
do {
|
||||
digit = len % 128;
|
||||
len = len / 128;
|
||||
if (len > 0) {
|
||||
digit |= 0x80;
|
||||
}
|
||||
lenBuf[pos++] = digit;
|
||||
llen++;
|
||||
} while(len>0);
|
||||
|
||||
buf[4-llen] = header;
|
||||
for (int i=0;i<llen;i++) {
|
||||
buf[MQTT_MAX_HEADER_SIZE-llen+i] = lenBuf[i];
|
||||
}
|
||||
return llen+1; // Full header size is variable length bit plus the 1-byte fixed header
|
||||
}
|
||||
|
||||
boolean PubSubClient::write(uint8_t header, uint8_t* buf, uint16_t length) {
|
||||
uint16_t rc;
|
||||
uint8_t hlen = buildHeader(header, buf, length);
|
||||
|
||||
#ifdef MQTT_MAX_TRANSFER_SIZE
|
||||
uint8_t* writeBuf = buf+(MQTT_MAX_HEADER_SIZE-hlen);
|
||||
uint16_t bytesRemaining = length+hlen; //Match the length type
|
||||
uint8_t bytesToWrite;
|
||||
boolean result = true;
|
||||
while((bytesRemaining > 0) && result) {
|
||||
bytesToWrite = (bytesRemaining > MQTT_MAX_TRANSFER_SIZE)?MQTT_MAX_TRANSFER_SIZE:bytesRemaining;
|
||||
rc = _client->write(writeBuf,bytesToWrite);
|
||||
result = (rc == bytesToWrite);
|
||||
bytesRemaining -= rc;
|
||||
writeBuf += rc;
|
||||
}
|
||||
return result;
|
||||
#else
|
||||
rc = _client->write(buf+(MQTT_MAX_HEADER_SIZE-hlen),length+hlen);
|
||||
lastOutActivity = millis();
|
||||
return (rc == hlen+length);
|
||||
#endif
|
||||
}
|
||||
|
||||
boolean PubSubClient::subscribe(const char* topic) {
|
||||
return subscribe(topic, 0);
|
||||
}
|
||||
|
||||
boolean PubSubClient::subscribe(const char* topic, uint8_t qos) {
|
||||
if (qos > 1) {
|
||||
return false;
|
||||
}
|
||||
if (MQTT_MAX_PACKET_SIZE < 9 + strlen(topic)) {
|
||||
// Too long
|
||||
return false;
|
||||
}
|
||||
if (connected()) {
|
||||
// Leave room in the buffer for header and variable length field
|
||||
uint16_t length = MQTT_MAX_HEADER_SIZE;
|
||||
nextMsgId++;
|
||||
if (nextMsgId == 0) {
|
||||
nextMsgId = 1;
|
||||
}
|
||||
buffer[length++] = (nextMsgId >> 8);
|
||||
buffer[length++] = (nextMsgId & 0xFF);
|
||||
length = writeString((char*)topic, buffer,length);
|
||||
buffer[length++] = qos;
|
||||
return write(MQTTSUBSCRIBE|MQTTQOS1,buffer,length-MQTT_MAX_HEADER_SIZE);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean PubSubClient::unsubscribe(const char* topic) {
|
||||
if (MQTT_MAX_PACKET_SIZE < 9 + strlen(topic)) {
|
||||
// Too long
|
||||
return false;
|
||||
}
|
||||
if (connected()) {
|
||||
uint16_t length = MQTT_MAX_HEADER_SIZE;
|
||||
nextMsgId++;
|
||||
if (nextMsgId == 0) {
|
||||
nextMsgId = 1;
|
||||
}
|
||||
buffer[length++] = (nextMsgId >> 8);
|
||||
buffer[length++] = (nextMsgId & 0xFF);
|
||||
length = writeString(topic, buffer,length);
|
||||
return write(MQTTUNSUBSCRIBE|MQTTQOS1,buffer,length-MQTT_MAX_HEADER_SIZE);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PubSubClient::disconnect() {
|
||||
buffer[0] = MQTTDISCONNECT;
|
||||
buffer[1] = 0;
|
||||
_client->write(buffer,2);
|
||||
_state = MQTT_DISCONNECTED;
|
||||
_client->flush();
|
||||
_client->stop();
|
||||
lastInActivity = lastOutActivity = millis();
|
||||
}
|
||||
|
||||
uint16_t PubSubClient::writeString(const char* string, uint8_t* buf, uint16_t pos) {
|
||||
const char* idp = string;
|
||||
uint16_t i = 0;
|
||||
pos += 2;
|
||||
while (*idp) {
|
||||
buf[pos++] = *idp++;
|
||||
i++;
|
||||
}
|
||||
buf[pos-i-2] = (i >> 8);
|
||||
buf[pos-i-1] = (i & 0xFF);
|
||||
return pos;
|
||||
}
|
||||
|
||||
|
||||
boolean PubSubClient::connected() {
|
||||
boolean rc;
|
||||
if (_client == NULL ) {
|
||||
rc = false;
|
||||
} else {
|
||||
rc = (int)_client->connected();
|
||||
if (!rc) {
|
||||
if (this->_state == MQTT_CONNECTED) {
|
||||
this->_state = MQTT_CONNECTION_LOST;
|
||||
_client->flush();
|
||||
_client->stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
PubSubClient& PubSubClient::setServer(uint8_t * ip, uint16_t port) {
|
||||
IPAddress addr(ip[0],ip[1],ip[2],ip[3]);
|
||||
return setServer(addr,port);
|
||||
}
|
||||
|
||||
PubSubClient& PubSubClient::setServer(IPAddress ip, uint16_t port) {
|
||||
this->ip = ip;
|
||||
this->port = port;
|
||||
this->domain = NULL;
|
||||
return *this;
|
||||
}
|
||||
|
||||
PubSubClient& PubSubClient::setServer(const char * domain, uint16_t port) {
|
||||
this->domain = domain;
|
||||
this->port = port;
|
||||
return *this;
|
||||
}
|
||||
|
||||
PubSubClient& PubSubClient::setCallback(MQTT_CALLBACK_SIGNATURE) {
|
||||
this->callback = callback;
|
||||
return *this;
|
||||
}
|
||||
|
||||
PubSubClient& PubSubClient::setClient(Client& client){
|
||||
this->_client = &client;
|
||||
return *this;
|
||||
}
|
||||
|
||||
PubSubClient& PubSubClient::setStream(Stream& stream){
|
||||
this->stream = &stream;
|
||||
return *this;
|
||||
}
|
||||
|
||||
int PubSubClient::state() {
|
||||
return this->_state;
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
PubSubClient.h - A simple client for MQTT.
|
||||
Nick O'Leary
|
||||
http://knolleary.net
|
||||
*/
|
||||
|
||||
#ifndef PubSubClient_h
|
||||
#define PubSubClient_h
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "IPAddress.h"
|
||||
#include "Client.h"
|
||||
#include "Stream.h"
|
||||
|
||||
#define MQTT_VERSION_3_1 3
|
||||
#define MQTT_VERSION_3_1_1 4
|
||||
|
||||
// MQTT_VERSION : Pick the version
|
||||
//#define MQTT_VERSION MQTT_VERSION_3_1
|
||||
#ifndef MQTT_VERSION
|
||||
#define MQTT_VERSION MQTT_VERSION_3_1_1
|
||||
#endif
|
||||
|
||||
// MQTT_MAX_PACKET_SIZE : Maximum packet size
|
||||
#ifndef MQTT_MAX_PACKET_SIZE
|
||||
#define MQTT_MAX_PACKET_SIZE 128
|
||||
#endif
|
||||
|
||||
// MQTT_KEEPALIVE : keepAlive interval in Seconds
|
||||
#ifndef MQTT_KEEPALIVE
|
||||
#define MQTT_KEEPALIVE 15
|
||||
#endif
|
||||
|
||||
// MQTT_SOCKET_TIMEOUT: socket timeout interval in Seconds
|
||||
#ifndef MQTT_SOCKET_TIMEOUT
|
||||
#define MQTT_SOCKET_TIMEOUT 15
|
||||
#endif
|
||||
|
||||
// MQTT_MAX_TRANSFER_SIZE : limit how much data is passed to the network client
|
||||
// in each write call. Needed for the Arduino Wifi Shield. Leave undefined to
|
||||
// pass the entire MQTT packet in each write call.
|
||||
//#define MQTT_MAX_TRANSFER_SIZE 80
|
||||
|
||||
// Possible values for client.state()
|
||||
#define MQTT_CONNECTION_TIMEOUT -4
|
||||
#define MQTT_CONNECTION_LOST -3
|
||||
#define MQTT_CONNECT_FAILED -2
|
||||
#define MQTT_DISCONNECTED -1
|
||||
#define MQTT_CONNECTED 0
|
||||
#define MQTT_CONNECT_BAD_PROTOCOL 1
|
||||
#define MQTT_CONNECT_BAD_CLIENT_ID 2
|
||||
#define MQTT_CONNECT_UNAVAILABLE 3
|
||||
#define MQTT_CONNECT_BAD_CREDENTIALS 4
|
||||
#define MQTT_CONNECT_UNAUTHORIZED 5
|
||||
|
||||
#define MQTTCONNECT 1 << 4 // Client request to connect to Server
|
||||
#define MQTTCONNACK 2 << 4 // Connect Acknowledgment
|
||||
#define MQTTPUBLISH 3 << 4 // Publish message
|
||||
#define MQTTPUBACK 4 << 4 // Publish Acknowledgment
|
||||
#define MQTTPUBREC 5 << 4 // Publish Received (assured delivery part 1)
|
||||
#define MQTTPUBREL 6 << 4 // Publish Release (assured delivery part 2)
|
||||
#define MQTTPUBCOMP 7 << 4 // Publish Complete (assured delivery part 3)
|
||||
#define MQTTSUBSCRIBE 8 << 4 // Client Subscribe request
|
||||
#define MQTTSUBACK 9 << 4 // Subscribe Acknowledgment
|
||||
#define MQTTUNSUBSCRIBE 10 << 4 // Client Unsubscribe request
|
||||
#define MQTTUNSUBACK 11 << 4 // Unsubscribe Acknowledgment
|
||||
#define MQTTPINGREQ 12 << 4 // PING Request
|
||||
#define MQTTPINGRESP 13 << 4 // PING Response
|
||||
#define MQTTDISCONNECT 14 << 4 // Client is Disconnecting
|
||||
#define MQTTReserved 15 << 4 // Reserved
|
||||
|
||||
#define MQTTQOS0 (0 << 1)
|
||||
#define MQTTQOS1 (1 << 1)
|
||||
#define MQTTQOS2 (2 << 1)
|
||||
|
||||
// Maximum size of fixed header and variable length size header
|
||||
#define MQTT_MAX_HEADER_SIZE 5
|
||||
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
#include <functional>
|
||||
#define MQTT_CALLBACK_SIGNATURE std::function<void(char*, uint8_t*, unsigned int)> callback
|
||||
#else
|
||||
#define MQTT_CALLBACK_SIGNATURE void (*callback)(char*, uint8_t*, unsigned int)
|
||||
#endif
|
||||
|
||||
#define CHECK_STRING_LENGTH(l,s) if (l+2+strlen(s) > MQTT_MAX_PACKET_SIZE) {_client->stop();return false;}
|
||||
|
||||
class PubSubClient : public Print {
|
||||
private:
|
||||
Client* _client;
|
||||
uint8_t buffer[MQTT_MAX_PACKET_SIZE];
|
||||
uint16_t nextMsgId;
|
||||
unsigned long lastOutActivity;
|
||||
unsigned long lastInActivity;
|
||||
bool pingOutstanding;
|
||||
MQTT_CALLBACK_SIGNATURE;
|
||||
uint16_t readPacket(uint8_t*);
|
||||
boolean readByte(uint8_t * result);
|
||||
boolean readByte(uint8_t * result, uint16_t * index);
|
||||
boolean write(uint8_t header, uint8_t* buf, uint16_t length);
|
||||
uint16_t writeString(const char* string, uint8_t* buf, uint16_t pos);
|
||||
// Build up the header ready to send
|
||||
// Returns the size of the header
|
||||
// Note: the header is built at the end of the first MQTT_MAX_HEADER_SIZE bytes, so will start
|
||||
// (MQTT_MAX_HEADER_SIZE - <returned size>) bytes into the buffer
|
||||
size_t buildHeader(uint8_t header, uint8_t* buf, uint16_t length);
|
||||
IPAddress ip;
|
||||
const char* domain;
|
||||
uint16_t port;
|
||||
Stream* stream;
|
||||
int _state;
|
||||
public:
|
||||
PubSubClient();
|
||||
PubSubClient(Client& client);
|
||||
PubSubClient(IPAddress, uint16_t, Client& client);
|
||||
PubSubClient(IPAddress, uint16_t, Client& client, Stream&);
|
||||
PubSubClient(IPAddress, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client);
|
||||
PubSubClient(IPAddress, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&);
|
||||
PubSubClient(uint8_t *, uint16_t, Client& client);
|
||||
PubSubClient(uint8_t *, uint16_t, Client& client, Stream&);
|
||||
PubSubClient(uint8_t *, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client);
|
||||
PubSubClient(uint8_t *, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&);
|
||||
PubSubClient(const char*, uint16_t, Client& client);
|
||||
PubSubClient(const char*, uint16_t, Client& client, Stream&);
|
||||
PubSubClient(const char*, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client);
|
||||
PubSubClient(const char*, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&);
|
||||
|
||||
PubSubClient& setServer(IPAddress ip, uint16_t port);
|
||||
PubSubClient& setServer(uint8_t * ip, uint16_t port);
|
||||
PubSubClient& setServer(const char * domain, uint16_t port);
|
||||
PubSubClient& setCallback(MQTT_CALLBACK_SIGNATURE);
|
||||
PubSubClient& setClient(Client& client);
|
||||
PubSubClient& setStream(Stream& stream);
|
||||
|
||||
boolean connect(const char* id);
|
||||
boolean connect(const char* id, const char* user, const char* pass);
|
||||
boolean connect(const char* id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage);
|
||||
boolean connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage);
|
||||
boolean connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage, boolean cleanSession);
|
||||
void disconnect();
|
||||
boolean publish(const char* topic, const char* payload);
|
||||
boolean publish(const char* topic, const char* payload, boolean retained);
|
||||
boolean publish(const char* topic, const uint8_t * payload, unsigned int plength);
|
||||
boolean publish(const char* topic, const uint8_t * payload, unsigned int plength, boolean retained);
|
||||
boolean publish_P(const char* topic, const char* payload, boolean retained);
|
||||
boolean publish_P(const char* topic, const uint8_t * payload, unsigned int plength, boolean retained);
|
||||
// Start to publish a message.
|
||||
// This API:
|
||||
// beginPublish(...)
|
||||
// one or more calls to write(...)
|
||||
// endPublish()
|
||||
// Allows for arbitrarily large payloads to be sent without them having to be copied into
|
||||
// a new buffer and held in memory at one time
|
||||
// Returns 1 if the message was started successfully, 0 if there was an error
|
||||
boolean beginPublish(const char* topic, unsigned int plength, boolean retained);
|
||||
// Finish off this publish message (started with beginPublish)
|
||||
// Returns 1 if the packet was sent successfully, 0 if there was an error
|
||||
int endPublish();
|
||||
// Write a single byte of payload (only to be used with beginPublish/endPublish)
|
||||
virtual size_t write(uint8_t);
|
||||
// Write size bytes from buffer into the payload (only to be used with beginPublish/endPublish)
|
||||
// Returns the number of bytes written
|
||||
virtual size_t write(const uint8_t *buffer, size_t size);
|
||||
boolean subscribe(const char* topic);
|
||||
boolean subscribe(const char* topic, uint8_t qos);
|
||||
boolean unsubscribe(const char* topic);
|
||||
boolean loop();
|
||||
boolean connected();
|
||||
int state();
|
||||
};
|
||||
|
||||
|
||||
#endif
|
|
@ -0,0 +1,4 @@
|
|||
.build
|
||||
tmpbin
|
||||
logs
|
||||
*.pyc
|
|
@ -0,0 +1,25 @@
|
|||
SRC_PATH=./src
|
||||
OUT_PATH=./bin
|
||||
TEST_SRC=$(wildcard ${SRC_PATH}/*_spec.cpp)
|
||||
TEST_BIN= $(TEST_SRC:${SRC_PATH}/%.cpp=${OUT_PATH}/%)
|
||||
VPATH=${SRC_PATH}
|
||||
SHIM_FILES=${SRC_PATH}/lib/*.cpp
|
||||
PSC_FILE=../src/PubSubClient.cpp
|
||||
CC=g++
|
||||
CFLAGS=-I${SRC_PATH}/lib -I../src
|
||||
|
||||
all: $(TEST_BIN)
|
||||
|
||||
${OUT_PATH}/%: ${SRC_PATH}/%.cpp ${PSC_FILE} ${SHIM_FILES}
|
||||
mkdir -p ${OUT_PATH}
|
||||
${CC} ${CFLAGS} $^ -o $@
|
||||
|
||||
clean:
|
||||
@rm -rf ${OUT_PATH}
|
||||
|
||||
test:
|
||||
@bin/connect_spec
|
||||
@bin/publish_spec
|
||||
@bin/receive_spec
|
||||
@bin/subscribe_spec
|
||||
@bin/keepalive_spec
|
|
@ -0,0 +1,93 @@
|
|||
# Arduino Client for MQTT Test Suite
|
||||
|
||||
This is a regression test suite for the `PubSubClient` library.
|
||||
|
||||
There are two parts:
|
||||
|
||||
- Tests that can be compiled and run on any machine
|
||||
- Tests that build the example sketches using the Arduino IDE
|
||||
|
||||
|
||||
It is a work-in-progress and is subject to complete refactoring as the whim takes
|
||||
me.
|
||||
|
||||
|
||||
## Local tests
|
||||
|
||||
These are a set of executables that can be run to test specific areas of functionality.
|
||||
They do not require a real Arduino to be attached, nor the use of the Arduino IDE.
|
||||
|
||||
The tests include a set of mock files to stub out the parts of the Arduino environment the library
|
||||
depends on.
|
||||
|
||||
### Dependencies
|
||||
|
||||
- g++
|
||||
|
||||
### Running
|
||||
|
||||
Build the tests using the provided `Makefile`:
|
||||
|
||||
$ make
|
||||
|
||||
This will create a set of executables in `./bin/`. Run each of these executables to test the corresponding functionality.
|
||||
|
||||
*Note:* the `connect_spec` and `keepalive_spec` tests involve testing keepalive timers so naturally take a few minutes to run through.
|
||||
|
||||
## Arduino tests
|
||||
|
||||
*Note:* INO Tool doesn't currently play nicely with Arduino 1.5. This has broken this test suite.
|
||||
|
||||
Without a suitable arduino plugged in, the test suite will only check the
|
||||
example sketches compile cleanly against the library.
|
||||
|
||||
With an arduino plugged in, each sketch that has a corresponding python
|
||||
test case is built, uploaded and then the tests run.
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Python 2.7+
|
||||
- [INO Tool](http://inotool.org/) - this provides command-line build/upload of Arduino sketches
|
||||
|
||||
### Running
|
||||
|
||||
The test suite _does not_ run an MQTT server - it is assumed to be running already.
|
||||
|
||||
$ python testsuite.py
|
||||
|
||||
A summary of activity is printed to the console. More comprehensive logs are written
|
||||
to the `logs` directory.
|
||||
|
||||
### What it does
|
||||
|
||||
For each sketch in the library's `examples` directory, e.g. `mqtt_basic.ino`, the suite looks for a matching test case
|
||||
`testcases/mqtt_basic.py`.
|
||||
|
||||
The test case must follow these conventions:
|
||||
- sub-class `unittest.TestCase`
|
||||
- provide the class methods `setUpClass` and `tearDownClass` (TODO: make this optional)
|
||||
- all test method names begin with `test_`
|
||||
|
||||
The suite will call the `setUpClass` method _before_ uploading the sketch. This
|
||||
allows any test setup to be performed before the sketch runs - such as connecting
|
||||
a client and subscribing to topics.
|
||||
|
||||
|
||||
### Settings
|
||||
|
||||
The file `testcases/settings.py` is used to config the test environment.
|
||||
|
||||
- `server_ip` - the IP address of the broker the client should connect to (the broker port is assumed to be 1883).
|
||||
- `arduino_ip` - the IP address the arduino should use (when not testing DHCP).
|
||||
|
||||
Before each sketch is compiled, these values are automatically substituted in. To
|
||||
do this, the suite looks for lines that _start_ with the following:
|
||||
|
||||
byte server[] = {
|
||||
byte ip[] = {
|
||||
|
||||
and replaces them with the appropriate values.
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,302 @@
|
|||
#include "PubSubClient.h"
|
||||
#include "ShimClient.h"
|
||||
#include "Buffer.h"
|
||||
#include "BDDTest.h"
|
||||
#include "trace.h"
|
||||
|
||||
|
||||
byte server[] = { 172, 16, 0, 2 };
|
||||
|
||||
void callback(char* topic, byte* payload, unsigned int length) {
|
||||
// handle message arrived
|
||||
}
|
||||
|
||||
|
||||
int test_connect_fails_no_network() {
|
||||
IT("fails to connect if underlying client doesn't connect");
|
||||
ShimClient shimClient;
|
||||
shimClient.setAllowConnect(false);
|
||||
PubSubClient client(server, 1883, callback, shimClient);
|
||||
int rc = client.connect((char*)"client_test1");
|
||||
IS_FALSE(rc);
|
||||
int state = client.state();
|
||||
IS_TRUE(state == MQTT_CONNECT_FAILED);
|
||||
END_IT
|
||||
}
|
||||
|
||||
int test_connect_fails_on_no_response() {
|
||||
IT("fails to connect if no response received after 15 seconds");
|
||||
ShimClient shimClient;
|
||||
shimClient.setAllowConnect(true);
|
||||
PubSubClient client(server, 1883, callback, shimClient);
|
||||
int rc = client.connect((char*)"client_test1");
|
||||
IS_FALSE(rc);
|
||||
int state = client.state();
|
||||
IS_TRUE(state == MQTT_CONNECTION_TIMEOUT);
|
||||
END_IT
|
||||
}
|
||||
|
||||
int test_connect_properly_formatted() {
|
||||
IT("sends a properly formatted connect packet and succeeds");
|
||||
ShimClient shimClient;
|
||||
|
||||
shimClient.setAllowConnect(true);
|
||||
byte expectServer[] = { 172, 16, 0, 2 };
|
||||
shimClient.expectConnect(expectServer,1883);
|
||||
byte connect[] = {0x10,0x18,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0x2,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31};
|
||||
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
|
||||
|
||||
shimClient.expect(connect,26);
|
||||
shimClient.respond(connack,4);
|
||||
|
||||
PubSubClient client(server, 1883, callback, shimClient);
|
||||
int state = client.state();
|
||||
IS_TRUE(state == MQTT_DISCONNECTED);
|
||||
|
||||
int rc = client.connect((char*)"client_test1");
|
||||
IS_TRUE(rc);
|
||||
IS_FALSE(shimClient.error());
|
||||
|
||||
state = client.state();
|
||||
IS_TRUE(state == MQTT_CONNECTED);
|
||||
|
||||
END_IT
|
||||
}
|
||||
|
||||
int test_connect_properly_formatted_hostname() {
|
||||
IT("accepts a hostname");
|
||||
ShimClient shimClient;
|
||||
|
||||
shimClient.setAllowConnect(true);
|
||||
shimClient.expectConnect((char* const)"localhost",1883);
|
||||
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
|
||||
shimClient.respond(connack,4);
|
||||
|
||||
PubSubClient client((char* const)"localhost", 1883, callback, shimClient);
|
||||
int rc = client.connect((char*)"client_test1");
|
||||
IS_TRUE(rc);
|
||||
IS_FALSE(shimClient.error());
|
||||
|
||||
END_IT
|
||||
}
|
||||
|
||||
|
||||
int test_connect_fails_on_bad_rc() {
|
||||
IT("fails to connect if a bad return code is received");
|
||||
ShimClient shimClient;
|
||||
shimClient.setAllowConnect(true);
|
||||
byte connack[] = { 0x20, 0x02, 0x00, 0x01 };
|
||||
shimClient.respond(connack,4);
|
||||
|
||||
PubSubClient client(server, 1883, callback, shimClient);
|
||||
int rc = client.connect((char*)"client_test1");
|
||||
IS_FALSE(rc);
|
||||
|
||||
int state = client.state();
|
||||
IS_TRUE(state == 0x01);
|
||||
|
||||
END_IT
|
||||
}
|
||||
|
||||
int test_connect_non_clean_session() {
|
||||
IT("sends a properly formatted non-clean session connect packet and succeeds");
|
||||
ShimClient shimClient;
|
||||
|
||||
shimClient.setAllowConnect(true);
|
||||
byte expectServer[] = { 172, 16, 0, 2 };
|
||||
shimClient.expectConnect(expectServer,1883);
|
||||
byte connect[] = {0x10,0x18,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0x0,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31};
|
||||
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
|
||||
|
||||
shimClient.expect(connect,26);
|
||||
shimClient.respond(connack,4);
|
||||
|
||||
PubSubClient client(server, 1883, callback, shimClient);
|
||||
int state = client.state();
|
||||
IS_TRUE(state == MQTT_DISCONNECTED);
|
||||
|
||||
int rc = client.connect((char*)"client_test1",0,0,0,0,0,0,0);
|
||||
IS_TRUE(rc);
|
||||
IS_FALSE(shimClient.error());
|
||||
|
||||
state = client.state();
|
||||
IS_TRUE(state == MQTT_CONNECTED);
|
||||
|
||||
END_IT
|
||||
}
|
||||
|
||||
int test_connect_accepts_username_password() {
|
||||
IT("accepts a username and password");
|
||||
ShimClient shimClient;
|
||||
shimClient.setAllowConnect(true);
|
||||
|
||||
byte connect[] = { 0x10,0x24,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0xc2,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31,0x0,0x4,0x75,0x73,0x65,0x72,0x0,0x4,0x70,0x61,0x73,0x73};
|
||||
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
|
||||
shimClient.expect(connect,0x26);
|
||||
shimClient.respond(connack,4);
|
||||
|
||||
PubSubClient client(server, 1883, callback, shimClient);
|
||||
int rc = client.connect((char*)"client_test1",(char*)"user",(char*)"pass");
|
||||
IS_TRUE(rc);
|
||||
IS_FALSE(shimClient.error());
|
||||
|
||||
END_IT
|
||||
}
|
||||
|
||||
int test_connect_accepts_username_no_password() {
|
||||
IT("accepts a username but no password");
|
||||
ShimClient shimClient;
|
||||
shimClient.setAllowConnect(true);
|
||||
|
||||
byte connect[] = { 0x10,0x1e,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0x82,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31,0x0,0x4,0x75,0x73,0x65,0x72};
|
||||
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
|
||||
shimClient.expect(connect,0x20);
|
||||
shimClient.respond(connack,4);
|
||||
|
||||
PubSubClient client(server, 1883, callback, shimClient);
|
||||
int rc = client.connect((char*)"client_test1",(char*)"user",0);
|
||||
IS_TRUE(rc);
|
||||
IS_FALSE(shimClient.error());
|
||||
|
||||
END_IT
|
||||
}
|
||||
int test_connect_accepts_username_blank_password() {
|
||||
IT("accepts a username and blank password");
|
||||
ShimClient shimClient;
|
||||
shimClient.setAllowConnect(true);
|
||||
|
||||
byte connect[] = { 0x10,0x20,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0xc2,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31,0x0,0x4,0x75,0x73,0x65,0x72,0x0,0x0};
|
||||
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
|
||||
shimClient.expect(connect,0x26);
|
||||
shimClient.respond(connack,4);
|
||||
|
||||
PubSubClient client(server, 1883, callback, shimClient);
|
||||
int rc = client.connect((char*)"client_test1",(char*)"user",(char*)"pass");
|
||||
IS_TRUE(rc);
|
||||
IS_FALSE(shimClient.error());
|
||||
|
||||
END_IT
|
||||
}
|
||||
|
||||
int test_connect_ignores_password_no_username() {
|
||||
IT("ignores a password but no username");
|
||||
ShimClient shimClient;
|
||||
shimClient.setAllowConnect(true);
|
||||
|
||||
byte connect[] = {0x10,0x18,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0x2,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31};
|
||||
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
|
||||
shimClient.expect(connect,26);
|
||||
shimClient.respond(connack,4);
|
||||
|
||||
PubSubClient client(server, 1883, callback, shimClient);
|
||||
int rc = client.connect((char*)"client_test1",0,(char*)"pass");
|
||||
IS_TRUE(rc);
|
||||
IS_FALSE(shimClient.error());
|
||||
|
||||
END_IT
|
||||
}
|
||||
|
||||
int test_connect_with_will() {
|
||||
IT("accepts a will");
|
||||
ShimClient shimClient;
|
||||
shimClient.setAllowConnect(true);
|
||||
|
||||
byte connect[] = {0x10,0x30,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0xe,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31,0x0,0x9,0x77,0x69,0x6c,0x6c,0x54,0x6f,0x70,0x69,0x63,0x0,0xb,0x77,0x69,0x6c,0x6c,0x4d,0x65,0x73,0x73,0x61,0x67,0x65};
|
||||
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
|
||||
shimClient.expect(connect,0x32);
|
||||
shimClient.respond(connack,4);
|
||||
|
||||
PubSubClient client(server, 1883, callback, shimClient);
|
||||
int rc = client.connect((char*)"client_test1",(char*)"willTopic",1,0,(char*)"willMessage");
|
||||
IS_TRUE(rc);
|
||||
IS_FALSE(shimClient.error());
|
||||
|
||||
END_IT
|
||||
}
|
||||
|
||||
int test_connect_with_will_username_password() {
|
||||
IT("accepts a will, username and password");
|
||||
ShimClient shimClient;
|
||||
shimClient.setAllowConnect(true);
|
||||
|
||||
byte connect[] = {0x10,0x40,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0xce,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31,0x0,0x9,0x77,0x69,0x6c,0x6c,0x54,0x6f,0x70,0x69,0x63,0x0,0xb,0x77,0x69,0x6c,0x6c,0x4d,0x65,0x73,0x73,0x61,0x67,0x65,0x0,0x4,0x75,0x73,0x65,0x72,0x0,0x8,0x70,0x61,0x73,0x73,0x77,0x6f,0x72,0x64};
|
||||
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
|
||||
shimClient.expect(connect,0x42);
|
||||
shimClient.respond(connack,4);
|
||||
|
||||
PubSubClient client(server, 1883, callback, shimClient);
|
||||
int rc = client.connect((char*)"client_test1",(char*)"user",(char*)"password",(char*)"willTopic",1,0,(char*)"willMessage");
|
||||
IS_TRUE(rc);
|
||||
IS_FALSE(shimClient.error());
|
||||
|
||||
END_IT
|
||||
}
|
||||
|
||||
int test_connect_disconnect_connect() {
|
||||
IT("connects, disconnects and connects again");
|
||||
ShimClient shimClient;
|
||||
|
||||
shimClient.setAllowConnect(true);
|
||||
byte expectServer[] = { 172, 16, 0, 2 };
|
||||
shimClient.expectConnect(expectServer,1883);
|
||||
byte connect[] = {0x10,0x18,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0x2,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31};
|
||||
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
|
||||
|
||||
shimClient.expect(connect,26);
|
||||
shimClient.respond(connack,4);
|
||||
|
||||
PubSubClient client(server, 1883, callback, shimClient);
|
||||
|
||||
int state = client.state();
|
||||
IS_TRUE(state == MQTT_DISCONNECTED);
|
||||
|
||||
int rc = client.connect((char*)"client_test1");
|
||||
IS_TRUE(rc);
|
||||
IS_FALSE(shimClient.error());
|
||||
|
||||
state = client.state();
|
||||
IS_TRUE(state == MQTT_CONNECTED);
|
||||
|
||||
byte disconnect[] = {0xE0,0x00};
|
||||
shimClient.expect(disconnect,2);
|
||||
|
||||
client.disconnect();
|
||||
|
||||
IS_FALSE(client.connected());
|
||||
IS_FALSE(shimClient.connected());
|
||||
IS_FALSE(shimClient.error());
|
||||
|
||||
state = client.state();
|
||||
IS_TRUE(state == MQTT_DISCONNECTED);
|
||||
|
||||
shimClient.expect(connect,28);
|
||||
shimClient.respond(connack,4);
|
||||
rc = client.connect((char*)"client_test1");
|
||||
IS_TRUE(rc);
|
||||
IS_FALSE(shimClient.error());
|
||||
state = client.state();
|
||||
IS_TRUE(state == MQTT_CONNECTED);
|
||||
|
||||
END_IT
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
SUITE("Connect");
|
||||
|
||||
test_connect_fails_no_network();
|
||||
test_connect_fails_on_no_response();
|
||||
|
||||
test_connect_properly_formatted();
|
||||
test_connect_non_clean_session();
|
||||
test_connect_accepts_username_password();
|
||||
test_connect_fails_on_bad_rc();
|
||||
test_connect_properly_formatted_hostname();
|
||||
|
||||
test_connect_accepts_username_no_password();
|
||||
test_connect_ignores_password_no_username();
|
||||
test_connect_with_will();
|
||||
test_connect_with_will_username_password();
|
||||
test_connect_disconnect_connect();
|
||||
FINISH
|
||||
}
|
|
@ -0,0 +1,185 @@
|
|||
#include "PubSubClient.h"
|
||||
#include "ShimClient.h"
|
||||
#include "Buffer.h"
|
||||
#include "BDDTest.h"
|
||||
#include "trace.h"
|
||||
#include <unistd.h>
|
||||
|
||||
byte server[] = { 172, 16, 0, 2 };
|
||||
|
||||
void callback(char* topic, byte* payload, unsigned int length) {
|
||||
// handle message arrived
|
||||
}
|
||||
|
||||
|
||||
int test_keepalive_pings_idle() {
|
||||
IT("keeps an idle connection alive (takes 1 minute)");
|
||||
|
||||
ShimClient shimClient;
|
||||
shimClient.setAllowConnect(true);
|
||||
|
||||
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
|
||||
shimClient.respond(connack,4);
|
||||
|
||||
PubSubClient client(server, 1883, callback, shimClient);
|
||||
int rc = client.connect((char*)"client_test1");
|
||||
IS_TRUE(rc);
|
||||
|
||||
byte pingreq[] = { 0xC0,0x0 };
|
||||
shimClient.expect(pingreq,2);
|
||||
byte pingresp[] = { 0xD0,0x0 };
|
||||
shimClient.respond(pingresp,2);
|
||||
|
||||
for (int i = 0; i < 50; i++) {
|
||||
sleep(1);
|
||||
if ( i == 15 || i == 31 || i == 47) {
|
||||
shimClient.expect(pingreq,2);
|
||||
shimClient.respond(pingresp,2);
|
||||
}
|
||||
rc = client.loop();
|
||||
IS_TRUE(rc);
|
||||
}
|
||||
|
||||
IS_FALSE(shimClient.error());
|
||||
|
||||
END_IT
|
||||
}
|
||||
|
||||
int test_keepalive_pings_with_outbound_qos0() {
|
||||
IT("keeps a connection alive that only sends qos0 (takes 1 minute)");
|
||||
|
||||
ShimClient shimClient;
|
||||
shimClient.setAllowConnect(true);
|
||||
|
||||
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
|
||||
shimClient.respond(connack,4);
|
||||
|
||||
PubSubClient client(server, 1883, callback, shimClient);
|
||||
int rc = client.connect((char*)"client_test1");
|
||||
IS_TRUE(rc);
|
||||
|
||||
byte publish[] = {0x30,0xe,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64};
|
||||
|
||||
for (int i = 0; i < 50; i++) {
|
||||
TRACE(i<<":");
|
||||
shimClient.expect(publish,16);
|
||||
rc = client.publish((char*)"topic",(char*)"payload");
|
||||
IS_TRUE(rc);
|
||||
IS_FALSE(shimClient.error());
|
||||
sleep(1);
|
||||
if ( i == 15 || i == 31 || i == 47) {
|
||||
byte pingreq[] = { 0xC0,0x0 };
|
||||
shimClient.expect(pingreq,2);
|
||||
byte pingresp[] = { 0xD0,0x0 };
|
||||
shimClient.respond(pingresp,2);
|
||||
}
|
||||
rc = client.loop();
|
||||
IS_TRUE(rc);
|
||||
IS_FALSE(shimClient.error());
|
||||
}
|
||||
|
||||
END_IT
|
||||
}
|
||||
|
||||
int test_keepalive_pings_with_inbound_qos0() {
|
||||
IT("keeps a connection alive that only receives qos0 (takes 1 minute)");
|
||||
|
||||
ShimClient shimClient;
|
||||
shimClient.setAllowConnect(true);
|
||||
|
||||
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
|
||||
shimClient.respond(connack,4);
|
||||
|
||||
PubSubClient client(server, 1883, callback, shimClient);
|
||||
int rc = client.connect((char*)"client_test1");
|
||||
IS_TRUE(rc);
|
||||
|
||||
byte publish[] = {0x30,0xe,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64};
|
||||
|
||||
for (int i = 0; i < 50; i++) {
|
||||
TRACE(i<<":");
|
||||
sleep(1);
|
||||
if ( i == 15 || i == 31 || i == 47) {
|
||||
byte pingreq[] = { 0xC0,0x0 };
|
||||
shimClient.expect(pingreq,2);
|
||||
byte pingresp[] = { 0xD0,0x0 };
|
||||
shimClient.respond(pingresp,2);
|
||||
}
|
||||
shimClient.respond(publish,16);
|
||||
rc = client.loop();
|
||||
IS_TRUE(rc);
|
||||
IS_FALSE(shimClient.error());
|
||||
}
|
||||
|
||||
END_IT
|
||||
}
|
||||
|
||||
int test_keepalive_no_pings_inbound_qos1() {
|
||||
IT("does not send pings for connections with inbound qos1 (takes 1 minute)");
|
||||
|
||||
ShimClient shimClient;
|
||||
shimClient.setAllowConnect(true);
|
||||
|
||||
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
|
||||
shimClient.respond(connack,4);
|
||||
|
||||
PubSubClient client(server, 1883, callback, shimClient);
|
||||
int rc = client.connect((char*)"client_test1");
|
||||
IS_TRUE(rc);
|
||||
|
||||
byte publish[] = {0x32,0x10,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x12,0x34,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64};
|
||||
byte puback[] = {0x40,0x2,0x12,0x34};
|
||||
|
||||
for (int i = 0; i < 50; i++) {
|
||||
shimClient.respond(publish,18);
|
||||
shimClient.expect(puback,4);
|
||||
sleep(1);
|
||||
rc = client.loop();
|
||||
IS_TRUE(rc);
|
||||
IS_FALSE(shimClient.error());
|
||||
}
|
||||
|
||||
END_IT
|
||||
}
|
||||
|
||||
int test_keepalive_disconnects_hung() {
|
||||
IT("disconnects a hung connection (takes 30 seconds)");
|
||||
|
||||
ShimClient shimClient;
|
||||
shimClient.setAllowConnect(true);
|
||||
|
||||
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
|
||||
shimClient.respond(connack,4);
|
||||
|
||||
PubSubClient client(server, 1883, callback, shimClient);
|
||||
int rc = client.connect((char*)"client_test1");
|
||||
IS_TRUE(rc);
|
||||
|
||||
byte pingreq[] = { 0xC0,0x0 };
|
||||
shimClient.expect(pingreq,2);
|
||||
|
||||
for (int i = 0; i < 32; i++) {
|
||||
sleep(1);
|
||||
rc = client.loop();
|
||||
}
|
||||
IS_FALSE(rc);
|
||||
|
||||
int state = client.state();
|
||||
IS_TRUE(state == MQTT_CONNECTION_TIMEOUT);
|
||||
|
||||
IS_FALSE(shimClient.error());
|
||||
|
||||
END_IT
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
SUITE("Keep-alive");
|
||||
test_keepalive_pings_idle();
|
||||
test_keepalive_pings_with_outbound_qos0();
|
||||
test_keepalive_pings_with_inbound_qos0();
|
||||
test_keepalive_no_pings_inbound_qos1();
|
||||
test_keepalive_disconnects_hung();
|
||||
|
||||
FINISH
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
#ifndef Arduino_h
|
||||
#define Arduino_h
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include "Print.h"
|
||||
|
||||
|
||||
extern "C"{
|
||||
typedef uint8_t byte ;
|
||||
typedef uint8_t boolean ;
|
||||
|
||||
/* sketch */
|
||||
extern void setup( void ) ;
|
||||
extern void loop( void ) ;
|
||||
uint32_t millis( void );
|
||||
}
|
||||
|
||||
#define PROGMEM
|
||||
#define pgm_read_byte_near(x) *(x)
|
||||
|
||||
#define yield(x) {}
|
||||
|
||||
#endif // Arduino_h
|
|
@ -0,0 +1,50 @@
|
|||
#include "BDDTest.h"
|
||||
#include "trace.h"
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <list>
|
||||
|
||||
int testCount = 0;
|
||||
int testPasses = 0;
|
||||
const char* testDescription;
|
||||
|
||||
std::list<std::string> failureList;
|
||||
|
||||
void bddtest_suite(const char* name) {
|
||||
LOG(name << "\n");
|
||||
}
|
||||
|
||||
int bddtest_test(const char* file, int line, const char* assertion, int result) {
|
||||
if (!result) {
|
||||
LOG("✗\n");
|
||||
std::ostringstream os;
|
||||
os << " ! "<<testDescription<<"\n " <<file << ":" <<line<<" : "<<assertion<<" ["<<result<<"]";
|
||||
failureList.push_back(os.str());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void bddtest_start(const char* description) {
|
||||
LOG(" - "<<description<<" ");
|
||||
testDescription = description;
|
||||
testCount ++;
|
||||
}
|
||||
void bddtest_end() {
|
||||
LOG("✓\n");
|
||||
testPasses ++;
|
||||
}
|
||||
|
||||
int bddtest_summary() {
|
||||
for (std::list<std::string>::iterator it = failureList.begin(); it != failureList.end(); it++) {
|
||||
LOG("\n");
|
||||
LOG(*it);
|
||||
LOG("\n");
|
||||
}
|
||||
|
||||
LOG(std::dec << testPasses << "/" << testCount << " tests passed\n\n");
|
||||
if (testPasses == testCount) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
#ifndef bddtest_h
|
||||
#define bddtest_h
|
||||
|
||||
void bddtest_suite(const char* name);
|
||||
int bddtest_test(const char*, int, const char*, int);
|
||||
void bddtest_start(const char*);
|
||||
void bddtest_end();
|
||||
int bddtest_summary();
|
||||
|
||||
#define SUITE(x) { bddtest_suite(x); }
|
||||
#define TEST(x) { if (!bddtest_test(__FILE__, __LINE__, #x, (x))) return false; }
|
||||
|
||||
#define IT(x) { bddtest_start(x); }
|
||||
#define END_IT { bddtest_end();return true;}
|
||||
|
||||
#define FINISH { return bddtest_summary(); }
|
||||
|
||||
#define IS_TRUE(x) TEST(x)
|
||||
#define IS_FALSE(x) TEST(!(x))
|
||||
#define IS_EQUAL(x,y) TEST(x==y)
|
||||
#define IS_NOT_EQUAL(x,y) TEST(x!=y)
|
||||
|
||||
#endif
|
|
@ -0,0 +1,34 @@
|
|||
#include "Buffer.h"
|
||||
#include "Arduino.h"
|
||||
|
||||
Buffer::Buffer() {
|
||||
this->pos = 0;
|
||||
this->length = 0;
|
||||
}
|
||||
|
||||
Buffer::Buffer(uint8_t* buf, size_t size) {
|
||||
this->pos = 0;
|
||||
this->length = 0;
|
||||
this->add(buf,size);
|
||||
}
|
||||
bool Buffer::available() {
|
||||
return this->pos < this->length;
|
||||
}
|
||||
|
||||
uint8_t Buffer::next() {
|
||||
if (this->available()) {
|
||||
return this->buffer[this->pos++];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Buffer::reset() {
|
||||
this->pos = 0;
|
||||
}
|
||||
|
||||
void Buffer::add(uint8_t* buf, size_t size) {
|
||||
uint16_t i = 0;
|
||||
for (;i<size;i++) {
|
||||
this->buffer[this->length++] = buf[i];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
#ifndef buffer_h
|
||||
#define buffer_h
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
class Buffer {
|
||||
private:
|
||||
uint8_t buffer[1024];
|
||||
uint16_t pos;
|
||||
uint16_t length;
|
||||
|
||||
public:
|
||||
Buffer();
|
||||
Buffer(uint8_t* buf, size_t size);
|
||||
|
||||
virtual bool available();
|
||||
virtual uint8_t next();
|
||||
virtual void reset();
|
||||
|
||||
virtual void add(uint8_t* buf, size_t size);
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,21 @@
|
|||
#ifndef client_h
|
||||
#define client_h
|
||||
#include "IPAddress.h"
|
||||
|
||||
class Client {
|
||||
public:
|
||||
virtual int connect(IPAddress ip, uint16_t port) =0;
|
||||
virtual int connect(const char *host, uint16_t port) =0;
|
||||
virtual size_t write(uint8_t) =0;
|
||||
virtual size_t write(const uint8_t *buf, size_t size) =0;
|
||||
virtual int available() = 0;
|
||||
virtual int read() = 0;
|
||||
virtual int read(uint8_t *buf, size_t size) = 0;
|
||||
virtual int peek() = 0;
|
||||
virtual void flush() = 0;
|
||||
virtual void stop() = 0;
|
||||
virtual uint8_t connected() = 0;
|
||||
virtual operator bool() = 0;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,44 @@
|
|||
|
||||
#include <Arduino.h>
|
||||
#include <IPAddress.h>
|
||||
|
||||
IPAddress::IPAddress()
|
||||
{
|
||||
memset(_address, 0, sizeof(_address));
|
||||
}
|
||||
|
||||
IPAddress::IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet)
|
||||
{
|
||||
_address[0] = first_octet;
|
||||
_address[1] = second_octet;
|
||||
_address[2] = third_octet;
|
||||
_address[3] = fourth_octet;
|
||||
}
|
||||
|
||||
IPAddress::IPAddress(uint32_t address)
|
||||
{
|
||||
memcpy(_address, &address, sizeof(_address));
|
||||
}
|
||||
|
||||
IPAddress::IPAddress(const uint8_t *address)
|
||||
{
|
||||
memcpy(_address, address, sizeof(_address));
|
||||
}
|
||||
|
||||
IPAddress& IPAddress::operator=(const uint8_t *address)
|
||||
{
|
||||
memcpy(_address, address, sizeof(_address));
|
||||
return *this;
|
||||
}
|
||||
|
||||
IPAddress& IPAddress::operator=(uint32_t address)
|
||||
{
|
||||
memcpy(_address, (const uint8_t *)&address, sizeof(_address));
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool IPAddress::operator==(const uint8_t* addr)
|
||||
{
|
||||
return memcmp(addr, _address, sizeof(_address)) == 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
*
|
||||
* MIT License:
|
||||
* Copyright (c) 2011 Adrian McEwen
|
||||
* 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.
|
||||
*
|
||||
* adrianm@mcqn.com 1/1/2011
|
||||
*/
|
||||
|
||||
#ifndef IPAddress_h
|
||||
#define IPAddress_h
|
||||
|
||||
|
||||
// A class to make it easier to handle and pass around IP addresses
|
||||
|
||||
class IPAddress {
|
||||
private:
|
||||
uint8_t _address[4]; // IPv4 address
|
||||
// Access the raw byte array containing the address. Because this returns a pointer
|
||||
// to the internal structure rather than a copy of the address this function should only
|
||||
// be used when you know that the usage of the returned uint8_t* will be transient and not
|
||||
// stored.
|
||||
uint8_t* raw_address() { return _address; };
|
||||
|
||||
public:
|
||||
// Constructors
|
||||
IPAddress();
|
||||
IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet);
|
||||
IPAddress(uint32_t address);
|
||||
IPAddress(const uint8_t *address);
|
||||
|
||||
// Overloaded cast operator to allow IPAddress objects to be used where a pointer
|
||||
// to a four-byte uint8_t array is expected
|
||||
operator uint32_t() { return *((uint32_t*)_address); };
|
||||
bool operator==(const IPAddress& addr) { return (*((uint32_t*)_address)) == (*((uint32_t*)addr._address)); };
|
||||
bool operator==(const uint8_t* addr);
|
||||
|
||||
// Overloaded index operator to allow getting and setting individual octets of the address
|
||||
uint8_t operator[](int index) const { return _address[index]; };
|
||||
uint8_t& operator[](int index) { return _address[index]; };
|
||||
|
||||
// Overloaded copy operators to allow initialisation of IPAddress objects from other types
|
||||
IPAddress& operator=(const uint8_t *address);
|
||||
IPAddress& operator=(uint32_t address);
|
||||
|
||||
|
||||
friend class EthernetClass;
|
||||
friend class UDP;
|
||||
friend class Client;
|
||||
friend class Server;
|
||||
friend class DhcpClass;
|
||||
friend class DNSClient;
|
||||
};
|
||||
|
||||
|
||||
#endif
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
Print.h - Base class that provides print() and println()
|
||||
Copyright (c) 2008 David A. Mellis. All right reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef Print_h
|
||||
#define Print_h
|
||||
|
||||
class Print {
|
||||
public:
|
||||
virtual size_t write(uint8_t) = 0;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,153 @@
|
|||
#include "ShimClient.h"
|
||||
#include "trace.h"
|
||||
#include <iostream>
|
||||
#include <Arduino.h>
|
||||
#include <ctime>
|
||||
|
||||
extern "C" {
|
||||
uint32_t millis(void) {
|
||||
return time(0)*1000;
|
||||
}
|
||||
}
|
||||
|
||||
ShimClient::ShimClient() {
|
||||
this->responseBuffer = new Buffer();
|
||||
this->expectBuffer = new Buffer();
|
||||
this->_allowConnect = true;
|
||||
this->_connected = false;
|
||||
this->_error = false;
|
||||
this->expectAnything = true;
|
||||
this->_received = 0;
|
||||
this->_expectedPort = 0;
|
||||
}
|
||||
|
||||
int ShimClient::connect(IPAddress ip, uint16_t port) {
|
||||
if (this->_allowConnect) {
|
||||
this->_connected = true;
|
||||
}
|
||||
if (this->_expectedPort !=0) {
|
||||
// if (memcmp(ip,this->_expectedIP,4) != 0) {
|
||||
// TRACE( "ip mismatch\n");
|
||||
// this->_error = true;
|
||||
// }
|
||||
if (port != this->_expectedPort) {
|
||||
TRACE( "port mismatch\n");
|
||||
this->_error = true;
|
||||
}
|
||||
}
|
||||
return this->_connected;
|
||||
}
|
||||
int ShimClient::connect(const char *host, uint16_t port) {
|
||||
if (this->_allowConnect) {
|
||||
this->_connected = true;
|
||||
}
|
||||
if (this->_expectedPort !=0) {
|
||||
if (strcmp(host,this->_expectedHost) != 0) {
|
||||
TRACE( "host mismatch\n");
|
||||
this->_error = true;
|
||||
}
|
||||
if (port != this->_expectedPort) {
|
||||
TRACE( "port mismatch\n");
|
||||
this->_error = true;
|
||||
}
|
||||
|
||||
}
|
||||
return this->_connected;
|
||||
}
|
||||
size_t ShimClient::write(uint8_t b) {
|
||||
this->_received += 1;
|
||||
TRACE(std::hex << (unsigned int)b);
|
||||
if (!this->expectAnything) {
|
||||
if (this->expectBuffer->available()) {
|
||||
uint8_t expected = this->expectBuffer->next();
|
||||
if (expected != b) {
|
||||
this->_error = true;
|
||||
TRACE("!=" << (unsigned int)expected);
|
||||
}
|
||||
} else {
|
||||
this->_error = true;
|
||||
}
|
||||
}
|
||||
TRACE("\n"<< std::dec);
|
||||
return 1;
|
||||
}
|
||||
size_t ShimClient::write(const uint8_t *buf, size_t size) {
|
||||
this->_received += size;
|
||||
TRACE( "[" << std::dec << (unsigned int)(size) << "] ");
|
||||
uint16_t i=0;
|
||||
for (;i<size;i++) {
|
||||
if (i>0) {
|
||||
TRACE(":");
|
||||
}
|
||||
TRACE(std::hex << (unsigned int)(buf[i]));
|
||||
|
||||
if (!this->expectAnything) {
|
||||
if (this->expectBuffer->available()) {
|
||||
uint8_t expected = this->expectBuffer->next();
|
||||
if (expected != buf[i]) {
|
||||
this->_error = true;
|
||||
TRACE("!=" << (unsigned int)expected);
|
||||
}
|
||||
} else {
|
||||
this->_error = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
TRACE("\n"<<std::dec);
|
||||
return size;
|
||||
}
|
||||
int ShimClient::available() {
|
||||
return this->responseBuffer->available();
|
||||
}
|
||||
int ShimClient::read() { return this->responseBuffer->next(); }
|
||||
int ShimClient::read(uint8_t *buf, size_t size) {
|
||||
uint16_t i = 0;
|
||||
for (;i<size;i++) {
|
||||
buf[i] = this->read();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
int ShimClient::peek() { return 0; }
|
||||
void ShimClient::flush() {}
|
||||
void ShimClient::stop() {
|
||||
this->setConnected(false);
|
||||
}
|
||||
uint8_t ShimClient::connected() { return this->_connected; }
|
||||
ShimClient::operator bool() { return true; }
|
||||
|
||||
|
||||
ShimClient* ShimClient::respond(uint8_t *buf, size_t size) {
|
||||
this->responseBuffer->add(buf,size);
|
||||
return this;
|
||||
}
|
||||
|
||||
ShimClient* ShimClient::expect(uint8_t *buf, size_t size) {
|
||||
this->expectAnything = false;
|
||||
this->expectBuffer->add(buf,size);
|
||||
return this;
|
||||
}
|
||||
|
||||
void ShimClient::setConnected(bool b) {
|
||||
this->_connected = b;
|
||||
}
|
||||
void ShimClient::setAllowConnect(bool b) {
|
||||
this->_allowConnect = b;
|
||||
}
|
||||
|
||||
bool ShimClient::error() {
|
||||
return this->_error;
|
||||
}
|
||||
|
||||
uint16_t ShimClient::received() {
|
||||
return this->_received;
|
||||
}
|
||||
|
||||
void ShimClient::expectConnect(IPAddress ip, uint16_t port) {
|
||||
this->_expectedIP = ip;
|
||||
this->_expectedPort = port;
|
||||
}
|
||||
|
||||
void ShimClient::expectConnect(const char *host, uint16_t port) {
|
||||
this->_expectedHost = host;
|
||||
this->_expectedPort = port;
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
#ifndef shimclient_h
|
||||
#define shimclient_h
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "Client.h"
|
||||
#include "IPAddress.h"
|
||||
#include "Buffer.h"
|
||||
|
||||
|
||||
class ShimClient : public Client {
|
||||
private:
|
||||
Buffer* responseBuffer;
|
||||
Buffer* expectBuffer;
|
||||
bool _allowConnect;
|
||||
bool _connected;
|
||||
bool expectAnything;
|
||||
bool _error;
|
||||
uint16_t _received;
|
||||
IPAddress _expectedIP;
|
||||
uint16_t _expectedPort;
|
||||
const char* _expectedHost;
|
||||
|
||||
public:
|
||||
ShimClient();
|
||||
virtual int connect(IPAddress ip, uint16_t port);
|
||||
virtual int connect(const char *host, uint16_t port);
|
||||
virtual size_t write(uint8_t);
|
||||
virtual size_t write(const uint8_t *buf, size_t size);
|
||||
virtual int available();
|
||||
virtual int read();
|
||||
virtual int read(uint8_t *buf, size_t size);
|
||||
virtual int peek();
|
||||
virtual void flush();
|
||||
virtual void stop();
|
||||
virtual uint8_t connected();
|
||||
virtual operator bool();
|
||||
|
||||
virtual ShimClient* respond(uint8_t *buf, size_t size);
|
||||
virtual ShimClient* expect(uint8_t *buf, size_t size);
|
||||
|
||||
virtual void expectConnect(IPAddress ip, uint16_t port);
|
||||
virtual void expectConnect(const char *host, uint16_t port);
|
||||
|
||||
virtual uint16_t received();
|
||||
virtual bool error();
|
||||
|
||||
virtual void setAllowConnect(bool b);
|
||||
virtual void setConnected(bool b);
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,39 @@
|
|||
#include "Stream.h"
|
||||
#include "trace.h"
|
||||
#include <iostream>
|
||||
#include <Arduino.h>
|
||||
|
||||
Stream::Stream() {
|
||||
this->expectBuffer = new Buffer();
|
||||
this->_error = false;
|
||||
this->_written = 0;
|
||||
}
|
||||
|
||||
size_t Stream::write(uint8_t b) {
|
||||
this->_written++;
|
||||
TRACE(std::hex << (unsigned int)b);
|
||||
if (this->expectBuffer->available()) {
|
||||
uint8_t expected = this->expectBuffer->next();
|
||||
if (expected != b) {
|
||||
this->_error = true;
|
||||
TRACE("!=" << (unsigned int)expected);
|
||||
}
|
||||
} else {
|
||||
this->_error = true;
|
||||
}
|
||||
TRACE("\n"<< std::dec);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
bool Stream::error() {
|
||||
return this->_error;
|
||||
}
|
||||
|
||||
void Stream::expect(uint8_t *buf, size_t size) {
|
||||
this->expectBuffer->add(buf,size);
|
||||
}
|
||||
|
||||
uint16_t Stream::length() {
|
||||
return this->_written;
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
#ifndef Stream_h
|
||||
#define Stream_h
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "Buffer.h"
|
||||
|
||||
class Stream {
|
||||
private:
|
||||
Buffer* expectBuffer;
|
||||
bool _error;
|
||||
uint16_t _written;
|
||||
|
||||
public:
|
||||
Stream();
|
||||
virtual size_t write(uint8_t);
|
||||
|
||||
virtual bool error();
|
||||
virtual void expect(uint8_t *buf, size_t size);
|
||||
virtual uint16_t length();
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,10 @@
|
|||
#ifndef trace_h
|
||||
#define trace_h
|
||||
#include <iostream>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#define LOG(x) {std::cout << x << std::flush; }
|
||||
#define TRACE(x) {if (getenv("TRACE")) { std::cout << x << std::flush; }}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,190 @@
|
|||
#include "PubSubClient.h"
|
||||
#include "ShimClient.h"
|
||||
#include "Buffer.h"
|
||||
#include "BDDTest.h"
|
||||
#include "trace.h"
|
||||
|
||||
|
||||
byte server[] = { 172, 16, 0, 2 };
|
||||
|
||||
void callback(char* topic, byte* payload, unsigned int length) {
|
||||
// handle message arrived
|
||||
}
|
||||
|
||||
int test_publish() {
|
||||
IT("publishes a null-terminated string");
|
||||
ShimClient shimClient;
|
||||
shimClient.setAllowConnect(true);
|
||||
|
||||
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
|
||||
shimClient.respond(connack,4);
|
||||
|
||||
PubSubClient client(server, 1883, callback, shimClient);
|
||||
int rc = client.connect((char*)"client_test1");
|
||||
IS_TRUE(rc);
|
||||
|
||||
byte publish[] = {0x30,0xe,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64};
|
||||
shimClient.expect(publish,16);
|
||||
|
||||
rc = client.publish((char*)"topic",(char*)"payload");
|
||||
IS_TRUE(rc);
|
||||
|
||||
IS_FALSE(shimClient.error());
|
||||
|
||||
END_IT
|
||||
}
|
||||
|
||||
|
||||
int test_publish_bytes() {
|
||||
IT("publishes a byte array");
|
||||
ShimClient shimClient;
|
||||
shimClient.setAllowConnect(true);
|
||||
|
||||
byte payload[] = { 0x01,0x02,0x03,0x0,0x05 };
|
||||
int length = 5;
|
||||
|
||||
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
|
||||
shimClient.respond(connack,4);
|
||||
|
||||
PubSubClient client(server, 1883, callback, shimClient);
|
||||
int rc = client.connect((char*)"client_test1");
|
||||
IS_TRUE(rc);
|
||||
|
||||
byte publish[] = {0x30,0xc,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x1,0x2,0x3,0x0,0x5};
|
||||
shimClient.expect(publish,14);
|
||||
|
||||
rc = client.publish((char*)"topic",payload,length);
|
||||
IS_TRUE(rc);
|
||||
|
||||
IS_FALSE(shimClient.error());
|
||||
|
||||
END_IT
|
||||
}
|
||||
|
||||
|
||||
int test_publish_retained() {
|
||||
IT("publishes retained - 1");
|
||||
ShimClient shimClient;
|
||||
shimClient.setAllowConnect(true);
|
||||
|
||||
byte payload[] = { 0x01,0x02,0x03,0x0,0x05 };
|
||||
int length = 5;
|
||||
|
||||
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
|
||||
shimClient.respond(connack,4);
|
||||
|
||||
PubSubClient client(server, 1883, callback, shimClient);
|
||||
int rc = client.connect((char*)"client_test1");
|
||||
IS_TRUE(rc);
|
||||
|
||||
byte publish[] = {0x31,0xc,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x1,0x2,0x3,0x0,0x5};
|
||||
shimClient.expect(publish,14);
|
||||
|
||||
rc = client.publish((char*)"topic",payload,length,true);
|
||||
IS_TRUE(rc);
|
||||
|
||||
IS_FALSE(shimClient.error());
|
||||
|
||||
END_IT
|
||||
}
|
||||
|
||||
int test_publish_retained_2() {
|
||||
IT("publishes retained - 2");
|
||||
ShimClient shimClient;
|
||||
shimClient.setAllowConnect(true);
|
||||
|
||||
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
|
||||
shimClient.respond(connack,4);
|
||||
|
||||
PubSubClient client(server, 1883, callback, shimClient);
|
||||
int rc = client.connect((char*)"client_test1");
|
||||
IS_TRUE(rc);
|
||||
|
||||
byte publish[] = {0x31,0xc,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,'A','B','C','D','E'};
|
||||
shimClient.expect(publish,14);
|
||||
|
||||
rc = client.publish((char*)"topic",(char*)"ABCDE",true);
|
||||
IS_TRUE(rc);
|
||||
|
||||
IS_FALSE(shimClient.error());
|
||||
|
||||
END_IT
|
||||
}
|
||||
|
||||
int test_publish_not_connected() {
|
||||
IT("publish fails when not connected");
|
||||
ShimClient shimClient;
|
||||
|
||||
PubSubClient client(server, 1883, callback, shimClient);
|
||||
|
||||
int rc = client.publish((char*)"topic",(char*)"payload");
|
||||
IS_FALSE(rc);
|
||||
|
||||
IS_FALSE(shimClient.error());
|
||||
|
||||
END_IT
|
||||
}
|
||||
|
||||
int test_publish_too_long() {
|
||||
IT("publish fails when topic/payload are too long");
|
||||
ShimClient shimClient;
|
||||
shimClient.setAllowConnect(true);
|
||||
|
||||
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
|
||||
shimClient.respond(connack,4);
|
||||
|
||||
PubSubClient client(server, 1883, callback, shimClient);
|
||||
int rc = client.connect((char*)"client_test1");
|
||||
IS_TRUE(rc);
|
||||
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2
|
||||
rc = client.publish((char*)"topic",(char*)"123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890");
|
||||
IS_FALSE(rc);
|
||||
|
||||
IS_FALSE(shimClient.error());
|
||||
|
||||
END_IT
|
||||
}
|
||||
|
||||
int test_publish_P() {
|
||||
IT("publishes using PROGMEM");
|
||||
ShimClient shimClient;
|
||||
shimClient.setAllowConnect(true);
|
||||
|
||||
byte payload[] = { 0x01,0x02,0x03,0x0,0x05 };
|
||||
int length = 5;
|
||||
|
||||
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
|
||||
shimClient.respond(connack,4);
|
||||
|
||||
PubSubClient client(server, 1883, callback, shimClient);
|
||||
int rc = client.connect((char*)"client_test1");
|
||||
IS_TRUE(rc);
|
||||
|
||||
byte publish[] = {0x31,0xc,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x1,0x2,0x3,0x0,0x5};
|
||||
shimClient.expect(publish,14);
|
||||
|
||||
rc = client.publish_P((char*)"topic",payload,length,true);
|
||||
IS_TRUE(rc);
|
||||
|
||||
IS_FALSE(shimClient.error());
|
||||
|
||||
END_IT
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
int main()
|
||||
{
|
||||
SUITE("Publish");
|
||||
test_publish();
|
||||
test_publish_bytes();
|
||||
test_publish_retained();
|
||||
test_publish_retained_2();
|
||||
test_publish_not_connected();
|
||||
test_publish_too_long();
|
||||
test_publish_P();
|
||||
|
||||
FINISH
|
||||
}
|
|
@ -0,0 +1,279 @@
|
|||
#include "PubSubClient.h"
|
||||
#include "ShimClient.h"
|
||||
#include "Buffer.h"
|
||||
#include "BDDTest.h"
|
||||
#include "trace.h"
|
||||
|
||||
|
||||
byte server[] = { 172, 16, 0, 2 };
|
||||
|
||||
bool callback_called = false;
|
||||
char lastTopic[1024];
|
||||
char lastPayload[1024];
|
||||
unsigned int lastLength;
|
||||
|
||||
void reset_callback() {
|
||||
callback_called = false;
|
||||
lastTopic[0] = '\0';
|
||||
lastPayload[0] = '\0';
|
||||
lastLength = 0;
|
||||
}
|
||||
|
||||
void callback(char* topic, byte* payload, unsigned int length) {
|
||||
callback_called = true;
|
||||
strcpy(lastTopic,topic);
|
||||
memcpy(lastPayload,payload,length);
|
||||
lastLength = length;
|
||||
}
|
||||
|
||||
int test_receive_callback() {
|
||||
IT("receives a callback message");
|
||||
reset_callback();
|
||||
|
||||
ShimClient shimClient;
|
||||
shimClient.setAllowConnect(true);
|
||||
|
||||
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
|
||||
shimClient.respond(connack,4);
|
||||
|
||||
PubSubClient client(server, 1883, callback, shimClient);
|
||||
int rc = client.connect((char*)"client_test1");
|
||||
IS_TRUE(rc);
|
||||
|
||||
byte publish[] = {0x30,0xe,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64};
|
||||
shimClient.respond(publish,16);
|
||||
|
||||
rc = client.loop();
|
||||
|
||||
IS_TRUE(rc);
|
||||
|
||||
IS_TRUE(callback_called);
|
||||
IS_TRUE(strcmp(lastTopic,"topic")==0);
|
||||
IS_TRUE(memcmp(lastPayload,"payload",7)==0);
|
||||
IS_TRUE(lastLength == 7);
|
||||
|
||||
IS_FALSE(shimClient.error());
|
||||
|
||||
END_IT
|
||||
}
|
||||
|
||||
int test_receive_stream() {
|
||||
IT("receives a streamed callback message");
|
||||
reset_callback();
|
||||
|
||||
Stream stream;
|
||||
stream.expect((uint8_t*)"payload",7);
|
||||
|
||||
ShimClient shimClient;
|
||||
shimClient.setAllowConnect(true);
|
||||
|
||||
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
|
||||
shimClient.respond(connack,4);
|
||||
|
||||
PubSubClient client(server, 1883, callback, shimClient, stream);
|
||||
int rc = client.connect((char*)"client_test1");
|
||||
IS_TRUE(rc);
|
||||
|
||||
byte publish[] = {0x30,0xe,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64};
|
||||
shimClient.respond(publish,16);
|
||||
|
||||
rc = client.loop();
|
||||
|
||||
IS_TRUE(rc);
|
||||
|
||||
IS_TRUE(callback_called);
|
||||
IS_TRUE(strcmp(lastTopic,"topic")==0);
|
||||
IS_TRUE(lastLength == 7);
|
||||
|
||||
IS_FALSE(stream.error());
|
||||
IS_FALSE(shimClient.error());
|
||||
|
||||
END_IT
|
||||
}
|
||||
|
||||
int test_receive_max_sized_message() {
|
||||
IT("receives an max-sized message");
|
||||
reset_callback();
|
||||
|
||||
ShimClient shimClient;
|
||||
shimClient.setAllowConnect(true);
|
||||
|
||||
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
|
||||
shimClient.respond(connack,4);
|
||||
|
||||
PubSubClient client(server, 1883, callback, shimClient);
|
||||
int rc = client.connect((char*)"client_test1");
|
||||
IS_TRUE(rc);
|
||||
|
||||
int length = MQTT_MAX_PACKET_SIZE;
|
||||
byte publish[] = {0x30,length-2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64};
|
||||
byte bigPublish[length];
|
||||
memset(bigPublish,'A',length);
|
||||
bigPublish[length] = 'B';
|
||||
memcpy(bigPublish,publish,16);
|
||||
shimClient.respond(bigPublish,length);
|
||||
|
||||
rc = client.loop();
|
||||
|
||||
IS_TRUE(rc);
|
||||
|
||||
IS_TRUE(callback_called);
|
||||
IS_TRUE(strcmp(lastTopic,"topic")==0);
|
||||
IS_TRUE(lastLength == length-9);
|
||||
IS_TRUE(memcmp(lastPayload,bigPublish+9,lastLength)==0);
|
||||
|
||||
IS_FALSE(shimClient.error());
|
||||
|
||||
END_IT
|
||||
}
|
||||
|
||||
int test_receive_oversized_message() {
|
||||
IT("drops an oversized message");
|
||||
reset_callback();
|
||||
|
||||
ShimClient shimClient;
|
||||
shimClient.setAllowConnect(true);
|
||||
|
||||
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
|
||||
shimClient.respond(connack,4);
|
||||
|
||||
PubSubClient client(server, 1883, callback, shimClient);
|
||||
int rc = client.connect((char*)"client_test1");
|
||||
IS_TRUE(rc);
|
||||
|
||||
int length = MQTT_MAX_PACKET_SIZE+1;
|
||||
byte publish[] = {0x30,length-2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64};
|
||||
byte bigPublish[length];
|
||||
memset(bigPublish,'A',length);
|
||||
bigPublish[length] = 'B';
|
||||
memcpy(bigPublish,publish,16);
|
||||
shimClient.respond(bigPublish,length);
|
||||
|
||||
rc = client.loop();
|
||||
|
||||
IS_TRUE(rc);
|
||||
|
||||
IS_FALSE(callback_called);
|
||||
|
||||
IS_FALSE(shimClient.error());
|
||||
|
||||
END_IT
|
||||
}
|
||||
|
||||
int test_drop_invalid_remaining_length_message() {
|
||||
IT("drops invalid remaining length message");
|
||||
reset_callback();
|
||||
|
||||
ShimClient shimClient;
|
||||
shimClient.setAllowConnect(true);
|
||||
|
||||
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
|
||||
shimClient.respond(connack,4);
|
||||
|
||||
PubSubClient client(server, 1883, callback, shimClient);
|
||||
int rc = client.connect((char*)"client_test1");
|
||||
IS_TRUE(rc);
|
||||
|
||||
byte publish[] = {0x30,0x92,0x92,0x92,0x92,0x01,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64};
|
||||
shimClient.respond(publish,20);
|
||||
|
||||
rc = client.loop();
|
||||
|
||||
IS_FALSE(rc);
|
||||
|
||||
IS_FALSE(callback_called);
|
||||
|
||||
IS_FALSE(shimClient.error());
|
||||
|
||||
END_IT
|
||||
}
|
||||
|
||||
|
||||
int test_receive_oversized_stream_message() {
|
||||
IT("drops an oversized message");
|
||||
reset_callback();
|
||||
|
||||
Stream stream;
|
||||
|
||||
ShimClient shimClient;
|
||||
shimClient.setAllowConnect(true);
|
||||
|
||||
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
|
||||
shimClient.respond(connack,4);
|
||||
|
||||
PubSubClient client(server, 1883, callback, shimClient, stream);
|
||||
int rc = client.connect((char*)"client_test1");
|
||||
IS_TRUE(rc);
|
||||
|
||||
int length = MQTT_MAX_PACKET_SIZE+1;
|
||||
byte publish[] = {0x30,length-2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64};
|
||||
|
||||
byte bigPublish[length];
|
||||
memset(bigPublish,'A',length);
|
||||
bigPublish[length] = 'B';
|
||||
memcpy(bigPublish,publish,16);
|
||||
|
||||
shimClient.respond(bigPublish,length);
|
||||
stream.expect(bigPublish+9,length-9);
|
||||
|
||||
rc = client.loop();
|
||||
|
||||
IS_TRUE(rc);
|
||||
|
||||
IS_TRUE(callback_called);
|
||||
IS_TRUE(strcmp(lastTopic,"topic")==0);
|
||||
IS_TRUE(lastLength == length-9);
|
||||
|
||||
IS_FALSE(stream.error());
|
||||
IS_FALSE(shimClient.error());
|
||||
|
||||
END_IT
|
||||
}
|
||||
|
||||
int test_receive_qos1() {
|
||||
IT("receives a qos1 message");
|
||||
reset_callback();
|
||||
|
||||
ShimClient shimClient;
|
||||
shimClient.setAllowConnect(true);
|
||||
|
||||
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
|
||||
shimClient.respond(connack,4);
|
||||
|
||||
PubSubClient client(server, 1883, callback, shimClient);
|
||||
int rc = client.connect((char*)"client_test1");
|
||||
IS_TRUE(rc);
|
||||
|
||||
byte publish[] = {0x32,0x10,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x12,0x34,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64};
|
||||
shimClient.respond(publish,18);
|
||||
|
||||
byte puback[] = {0x40,0x2,0x12,0x34};
|
||||
shimClient.expect(puback,4);
|
||||
|
||||
rc = client.loop();
|
||||
|
||||
IS_TRUE(rc);
|
||||
|
||||
IS_TRUE(callback_called);
|
||||
IS_TRUE(strcmp(lastTopic,"topic")==0);
|
||||
IS_TRUE(memcmp(lastPayload,"payload",7)==0);
|
||||
IS_TRUE(lastLength == 7);
|
||||
|
||||
IS_FALSE(shimClient.error());
|
||||
|
||||
END_IT
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
SUITE("Receive");
|
||||
test_receive_callback();
|
||||
test_receive_stream();
|
||||
test_receive_max_sized_message();
|
||||
test_drop_invalid_remaining_length_message();
|
||||
test_receive_oversized_message();
|
||||
test_receive_oversized_stream_message();
|
||||
test_receive_qos1();
|
||||
|
||||
FINISH
|
||||
}
|
|
@ -0,0 +1,177 @@
|
|||
#include "PubSubClient.h"
|
||||
#include "ShimClient.h"
|
||||
#include "Buffer.h"
|
||||
#include "BDDTest.h"
|
||||
#include "trace.h"
|
||||
|
||||
|
||||
byte server[] = { 172, 16, 0, 2 };
|
||||
|
||||
void callback(char* topic, byte* payload, unsigned int length) {
|
||||
// handle message arrived
|
||||
}
|
||||
|
||||
int test_subscribe_no_qos() {
|
||||
IT("subscribe without qos defaults to 0");
|
||||
ShimClient shimClient;
|
||||
shimClient.setAllowConnect(true);
|
||||
|
||||
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
|
||||
shimClient.respond(connack,4);
|
||||
|
||||
PubSubClient client(server, 1883, callback, shimClient);
|
||||
int rc = client.connect((char*)"client_test1");
|
||||
IS_TRUE(rc);
|
||||
|
||||
byte subscribe[] = { 0x82,0xa,0x0,0x2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x0 };
|
||||
shimClient.expect(subscribe,12);
|
||||
byte suback[] = { 0x90,0x3,0x0,0x2,0x0 };
|
||||
shimClient.respond(suback,5);
|
||||
|
||||
rc = client.subscribe((char*)"topic");
|
||||
IS_TRUE(rc);
|
||||
|
||||
IS_FALSE(shimClient.error());
|
||||
|
||||
END_IT
|
||||
}
|
||||
|
||||
int test_subscribe_qos_1() {
|
||||
IT("subscribes qos 1");
|
||||
ShimClient shimClient;
|
||||
shimClient.setAllowConnect(true);
|
||||
|
||||
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
|
||||
shimClient.respond(connack,4);
|
||||
|
||||
PubSubClient client(server, 1883, callback, shimClient);
|
||||
int rc = client.connect((char*)"client_test1");
|
||||
IS_TRUE(rc);
|
||||
|
||||
byte subscribe[] = { 0x82,0xa,0x0,0x2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x1 };
|
||||
shimClient.expect(subscribe,12);
|
||||
byte suback[] = { 0x90,0x3,0x0,0x2,0x1 };
|
||||
shimClient.respond(suback,5);
|
||||
|
||||
rc = client.subscribe((char*)"topic",1);
|
||||
IS_TRUE(rc);
|
||||
|
||||
IS_FALSE(shimClient.error());
|
||||
|
||||
END_IT
|
||||
}
|
||||
|
||||
int test_subscribe_not_connected() {
|
||||
IT("subscribe fails when not connected");
|
||||
ShimClient shimClient;
|
||||
|
||||
PubSubClient client(server, 1883, callback, shimClient);
|
||||
|
||||
int rc = client.subscribe((char*)"topic");
|
||||
IS_FALSE(rc);
|
||||
|
||||
IS_FALSE(shimClient.error());
|
||||
|
||||
END_IT
|
||||
}
|
||||
|
||||
int test_subscribe_invalid_qos() {
|
||||
IT("subscribe fails with invalid qos values");
|
||||
ShimClient shimClient;
|
||||
shimClient.setAllowConnect(true);
|
||||
|
||||
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
|
||||
shimClient.respond(connack,4);
|
||||
|
||||
PubSubClient client(server, 1883, callback, shimClient);
|
||||
int rc = client.connect((char*)"client_test1");
|
||||
IS_TRUE(rc);
|
||||
|
||||
rc = client.subscribe((char*)"topic",2);
|
||||
IS_FALSE(rc);
|
||||
rc = client.subscribe((char*)"topic",254);
|
||||
IS_FALSE(rc);
|
||||
|
||||
IS_FALSE(shimClient.error());
|
||||
|
||||
END_IT
|
||||
}
|
||||
|
||||
int test_subscribe_too_long() {
|
||||
IT("subscribe fails with too long topic");
|
||||
ShimClient shimClient;
|
||||
shimClient.setAllowConnect(true);
|
||||
|
||||
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
|
||||
shimClient.respond(connack,4);
|
||||
|
||||
PubSubClient client(server, 1883, callback, shimClient);
|
||||
int rc = client.connect((char*)"client_test1");
|
||||
IS_TRUE(rc);
|
||||
|
||||
// max length should be allowed
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2
|
||||
rc = client.subscribe((char*)"12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789");
|
||||
IS_TRUE(rc);
|
||||
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2
|
||||
rc = client.subscribe((char*)"123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890");
|
||||
IS_FALSE(rc);
|
||||
|
||||
IS_FALSE(shimClient.error());
|
||||
|
||||
END_IT
|
||||
}
|
||||
|
||||
|
||||
int test_unsubscribe() {
|
||||
IT("unsubscribes");
|
||||
ShimClient shimClient;
|
||||
shimClient.setAllowConnect(true);
|
||||
|
||||
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
|
||||
shimClient.respond(connack,4);
|
||||
|
||||
PubSubClient client(server, 1883, callback, shimClient);
|
||||
int rc = client.connect((char*)"client_test1");
|
||||
IS_TRUE(rc);
|
||||
|
||||
byte unsubscribe[] = { 0xA2,0x9,0x0,0x2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63 };
|
||||
shimClient.expect(unsubscribe,12);
|
||||
byte unsuback[] = { 0xB0,0x2,0x0,0x2 };
|
||||
shimClient.respond(unsuback,4);
|
||||
|
||||
rc = client.unsubscribe((char*)"topic");
|
||||
IS_TRUE(rc);
|
||||
|
||||
IS_FALSE(shimClient.error());
|
||||
|
||||
END_IT
|
||||
}
|
||||
|
||||
int test_unsubscribe_not_connected() {
|
||||
IT("unsubscribe fails when not connected");
|
||||
ShimClient shimClient;
|
||||
|
||||
PubSubClient client(server, 1883, callback, shimClient);
|
||||
|
||||
int rc = client.unsubscribe((char*)"topic");
|
||||
IS_FALSE(rc);
|
||||
|
||||
IS_FALSE(shimClient.error());
|
||||
|
||||
END_IT
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
SUITE("Subscribe");
|
||||
test_subscribe_no_qos();
|
||||
test_subscribe_qos_1();
|
||||
test_subscribe_not_connected();
|
||||
test_subscribe_invalid_qos();
|
||||
test_subscribe_too_long();
|
||||
test_unsubscribe();
|
||||
test_unsubscribe_not_connected();
|
||||
FINISH
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
import unittest
|
||||
import settings
|
||||
import time
|
||||
import mosquitto
|
||||
|
||||
|
||||
def on_message(mosq, obj, msg):
|
||||
obj.message_queue.append(msg)
|
||||
|
||||
|
||||
class mqtt_basic(unittest.TestCase):
|
||||
|
||||
message_queue = []
|
||||
|
||||
@classmethod
|
||||
def setUpClass(self):
|
||||
self.client = mosquitto.Mosquitto("pubsubclient_ut", clean_session=True, obj=self)
|
||||
self.client.connect(settings.server_ip)
|
||||
self.client.on_message = on_message
|
||||
self.client.subscribe("outTopic", 0)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(self):
|
||||
self.client.disconnect()
|
||||
|
||||
def test_one(self):
|
||||
i = 30
|
||||
while len(self.message_queue) == 0 and i > 0:
|
||||
self.client.loop()
|
||||
time.sleep(0.5)
|
||||
i -= 1
|
||||
self.assertTrue(i > 0, "message receive timed-out")
|
||||
self.assertEqual(len(self.message_queue), 1, "unexpected number of messages received")
|
||||
msg = self.message_queue[0]
|
||||
self.assertEqual(msg.mid, 0, "message id not 0")
|
||||
self.assertEqual(msg.topic, "outTopic", "message topic incorrect")
|
||||
self.assertEqual(msg.payload, "hello world")
|
||||
self.assertEqual(msg.qos, 0, "message qos not 0")
|
||||
self.assertEqual(msg.retain, False, "message retain flag incorrect")
|
|
@ -0,0 +1,59 @@
|
|||
import unittest
|
||||
import settings
|
||||
import time
|
||||
import mosquitto
|
||||
|
||||
|
||||
def on_message(mosq, obj, msg):
|
||||
obj.message_queue.append(msg)
|
||||
|
||||
|
||||
class mqtt_publish_in_callback(unittest.TestCase):
|
||||
|
||||
message_queue = []
|
||||
|
||||
@classmethod
|
||||
def setUpClass(self):
|
||||
self.client = mosquitto.Mosquitto("pubsubclient_ut", clean_session=True, obj=self)
|
||||
self.client.connect(settings.server_ip)
|
||||
self.client.on_message = on_message
|
||||
self.client.subscribe("outTopic", 0)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(self):
|
||||
self.client.disconnect()
|
||||
|
||||
def test_connect(self):
|
||||
i = 30
|
||||
while len(self.message_queue) == 0 and i > 0:
|
||||
self.client.loop()
|
||||
time.sleep(0.5)
|
||||
i -= 1
|
||||
self.assertTrue(i > 0, "message receive timed-out")
|
||||
self.assertEqual(len(self.message_queue), 1, "unexpected number of messages received")
|
||||
msg = self.message_queue.pop(0)
|
||||
self.assertEqual(msg.mid, 0, "message id not 0")
|
||||
self.assertEqual(msg.topic, "outTopic", "message topic incorrect")
|
||||
self.assertEqual(msg.payload, "hello world")
|
||||
self.assertEqual(msg.qos, 0, "message qos not 0")
|
||||
self.assertEqual(msg.retain, False, "message retain flag incorrect")
|
||||
|
||||
def test_publish(self):
|
||||
self.assertEqual(len(self.message_queue), 0, "message queue not empty")
|
||||
payload = "abcdefghij"
|
||||
self.client.publish("inTopic", payload)
|
||||
|
||||
i = 30
|
||||
while len(self.message_queue) == 0 and i > 0:
|
||||
self.client.loop()
|
||||
time.sleep(0.5)
|
||||
i -= 1
|
||||
|
||||
self.assertTrue(i > 0, "message receive timed-out")
|
||||
self.assertEqual(len(self.message_queue), 1, "unexpected number of messages received")
|
||||
msg = self.message_queue.pop(0)
|
||||
self.assertEqual(msg.mid, 0, "message id not 0")
|
||||
self.assertEqual(msg.topic, "outTopic", "message topic incorrect")
|
||||
self.assertEqual(msg.payload, payload)
|
||||
self.assertEqual(msg.qos, 0, "message qos not 0")
|
||||
self.assertEqual(msg.retain, False, "message retain flag incorrect")
|
|
@ -0,0 +1,2 @@
|
|||
server_ip = "172.16.0.2"
|
||||
arduino_ip = "172.16.0.100"
|
|
@ -0,0 +1,181 @@
|
|||
#!/usr/bin/env python
|
||||
import os
|
||||
import os.path
|
||||
import sys
|
||||
import shutil
|
||||
from subprocess import call
|
||||
import importlib
|
||||
import unittest
|
||||
import re
|
||||
|
||||
from testcases import settings
|
||||
|
||||
|
||||
class Workspace(object):
|
||||
|
||||
def __init__(self):
|
||||
self.root_dir = os.getcwd()
|
||||
self.build_dir = os.path.join(self.root_dir, "tmpbin")
|
||||
self.log_dir = os.path.join(self.root_dir, "logs")
|
||||
self.tests_dir = os.path.join(self.root_dir, "testcases")
|
||||
self.examples_dir = os.path.join(self.root_dir, "../PubSubClient/examples")
|
||||
self.examples = []
|
||||
self.tests = []
|
||||
if not os.path.isdir("../PubSubClient"):
|
||||
raise Exception("Cannot find PubSubClient library")
|
||||
try:
|
||||
return __import__('ino')
|
||||
except ImportError:
|
||||
raise Exception("ino tool not installed")
|
||||
|
||||
def init(self):
|
||||
if os.path.isdir(self.build_dir):
|
||||
shutil.rmtree(self.build_dir)
|
||||
os.mkdir(self.build_dir)
|
||||
if os.path.isdir(self.log_dir):
|
||||
shutil.rmtree(self.log_dir)
|
||||
os.mkdir(self.log_dir)
|
||||
|
||||
os.chdir(self.build_dir)
|
||||
call(["ino", "init"])
|
||||
|
||||
shutil.copytree("../../PubSubClient", "lib/PubSubClient")
|
||||
|
||||
filenames = []
|
||||
for root, dirs, files in os.walk(self.examples_dir):
|
||||
filenames += [os.path.join(root, f) for f in files if f.endswith(".ino")]
|
||||
filenames.sort()
|
||||
for e in filenames:
|
||||
self.examples.append(Sketch(self, e))
|
||||
|
||||
filenames = []
|
||||
for root, dirs, files in os.walk(self.tests_dir):
|
||||
filenames += [os.path.join(root, f) for f in files if f.endswith(".ino")]
|
||||
filenames.sort()
|
||||
for e in filenames:
|
||||
self.tests.append(Sketch(self, e))
|
||||
|
||||
def clean(self):
|
||||
shutil.rmtree(self.build_dir)
|
||||
|
||||
|
||||
class Sketch(object):
|
||||
def __init__(self, wksp, fn):
|
||||
self.w = wksp
|
||||
self.filename = fn
|
||||
self.basename = os.path.basename(self.filename)
|
||||
self.build_log = os.path.join(self.w.log_dir, "%s.log" % (os.path.basename(self.filename),))
|
||||
self.build_err_log = os.path.join(self.w.log_dir, "%s.err.log" % (os.path.basename(self.filename),))
|
||||
self.build_upload_log = os.path.join(self.w.log_dir, "%s.upload.log" % (os.path.basename(self.filename),))
|
||||
|
||||
def build(self):
|
||||
sys.stdout.write(" Build: ")
|
||||
sys.stdout.flush()
|
||||
|
||||
# Copy sketch over, replacing IP addresses as necessary
|
||||
fin = open(self.filename, "r")
|
||||
lines = fin.readlines()
|
||||
fin.close()
|
||||
fout = open(os.path.join(self.w.build_dir, "src", "sketch.ino"), "w")
|
||||
for l in lines:
|
||||
if re.match(r"^byte server\[\] = {", l):
|
||||
fout.write("byte server[] = { %s };\n" % (settings.server_ip.replace(".", ", "),))
|
||||
elif re.match(r"^byte ip\[\] = {", l):
|
||||
fout.write("byte ip[] = { %s };\n" % (settings.arduino_ip.replace(".", ", "),))
|
||||
else:
|
||||
fout.write(l)
|
||||
fout.flush()
|
||||
fout.close()
|
||||
|
||||
# Run build
|
||||
fout = open(self.build_log, "w")
|
||||
ferr = open(self.build_err_log, "w")
|
||||
rc = call(["ino", "build"], stdout=fout, stderr=ferr)
|
||||
fout.close()
|
||||
ferr.close()
|
||||
if rc == 0:
|
||||
sys.stdout.write("pass")
|
||||
sys.stdout.write("\n")
|
||||
return True
|
||||
else:
|
||||
sys.stdout.write("fail")
|
||||
sys.stdout.write("\n")
|
||||
with open(self.build_err_log) as f:
|
||||
for line in f:
|
||||
print(" " + line)
|
||||
return False
|
||||
|
||||
def upload(self):
|
||||
sys.stdout.write(" Upload: ")
|
||||
sys.stdout.flush()
|
||||
fout = open(self.build_upload_log, "w")
|
||||
rc = call(["ino", "upload"], stdout=fout, stderr=fout)
|
||||
fout.close()
|
||||
if rc == 0:
|
||||
sys.stdout.write("pass")
|
||||
sys.stdout.write("\n")
|
||||
return True
|
||||
else:
|
||||
sys.stdout.write("fail")
|
||||
sys.stdout.write("\n")
|
||||
with open(self.build_upload_log) as f:
|
||||
for line in f:
|
||||
print(" " + line)
|
||||
return False
|
||||
|
||||
def test(self):
|
||||
# import the matching test case, if it exists
|
||||
try:
|
||||
basename = os.path.basename(self.filename)[:-4]
|
||||
i = importlib.import_module("testcases." + basename)
|
||||
except:
|
||||
sys.stdout.write(" Test: no tests found")
|
||||
sys.stdout.write("\n")
|
||||
return
|
||||
c = getattr(i, basename)
|
||||
|
||||
testmethods = [m for m in dir(c) if m.startswith("test_")]
|
||||
testmethods.sort()
|
||||
tests = []
|
||||
for m in testmethods:
|
||||
tests.append(c(m))
|
||||
|
||||
result = unittest.TestResult()
|
||||
c.setUpClass()
|
||||
if self.upload():
|
||||
sys.stdout.write(" Test: ")
|
||||
sys.stdout.flush()
|
||||
for t in tests:
|
||||
t.run(result)
|
||||
print(str(result.testsRun - len(result.failures) - len(result.errors)) + "/" + str(result.testsRun))
|
||||
if not result.wasSuccessful():
|
||||
if len(result.failures) > 0:
|
||||
for f in result.failures:
|
||||
print("-- " + str(f[0]))
|
||||
print(f[1])
|
||||
if len(result.errors) > 0:
|
||||
print(" Errors:")
|
||||
for f in result.errors:
|
||||
print("-- " + str(f[0]))
|
||||
print(f[1])
|
||||
c.tearDownClass()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
run_tests = True
|
||||
|
||||
w = Workspace()
|
||||
w.init()
|
||||
|
||||
for e in w.examples:
|
||||
print("--------------------------------------")
|
||||
print("[" + e.basename + "]")
|
||||
if e.build() and run_tests:
|
||||
e.test()
|
||||
for e in w.tests:
|
||||
print("--------------------------------------")
|
||||
print("[" + e.basename + "]")
|
||||
if e.build() and run_tests:
|
||||
e.test()
|
||||
|
||||
w.clean()
|
|
@ -0,0 +1,53 @@
|
|||
#include "MqttWildcard.h"
|
||||
|
||||
int MqttWildcard::explode(String *results, String source, char delimiter) {
|
||||
int count = 0;
|
||||
int index = 0;
|
||||
|
||||
for (int i = 0; i < source.length(); i++) {
|
||||
if (source.charAt(i) == delimiter) {
|
||||
results[count++] = source.substring(index, i);
|
||||
index = i+1;
|
||||
}
|
||||
}
|
||||
results[count++] = source.substring(index);
|
||||
|
||||
return count;
|
||||
}
|
||||
bool MqttWildcard::wildcardMatch(String topic, String wildcard) {
|
||||
// Catch trivial matches
|
||||
if (topic == wildcard) return true;
|
||||
if (wildcard == "#") return true;
|
||||
|
||||
String exploded_topic[TOPIC_BUFFER_SIZE];
|
||||
int exploded_topic_count = MqttWildcard::explode(exploded_topic, topic, '/');
|
||||
|
||||
String exploded_wildcard[TOPIC_BUFFER_SIZE];
|
||||
int exploded_wildcard_count = MqttWildcard::explode(exploded_wildcard, wildcard, '/');
|
||||
|
||||
// Impossible to match since wildcard "+/+/#" is not matched by topic foo/bar
|
||||
if (exploded_wildcard_count > exploded_topic_count) return false;
|
||||
|
||||
int match_count = 0;
|
||||
for (int i = 0; i < exploded_wildcard_count; i++) {
|
||||
if (exploded_wildcard[i] == "+") {
|
||||
continue;
|
||||
}
|
||||
if (exploded_wildcard[i] == "#") {
|
||||
return true;
|
||||
}
|
||||
if (exploded_wildcard[i] != exploded_topic[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
If this point is reached and we did not return yet,
|
||||
topic- and wildcard-depth must be equal, otherwise it cant't be a valid match:
|
||||
topic: foo/bar/example
|
||||
wildcard_1: foo/bar/+
|
||||
wildcard_2: foo/+
|
||||
Both wildcards would make it to this point, bot only wildcard_1 would be a valid match.
|
||||
*/
|
||||
return (exploded_wildcard_count == exploded_topic_count);
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
#ifndef MqttWildcard_h
|
||||
#define MqttWildcard_h
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <PubSubClientTools.h>
|
||||
|
||||
#ifndef TOPIC_BUFFER_SIZE
|
||||
#define TOPIC_BUFFER_SIZE 100
|
||||
#endif
|
||||
|
||||
class MqttWildcard {
|
||||
public:
|
||||
static int explode(String *results, String source, char delimiter);
|
||||
static bool wildcardMatch(String topic, String wildcard);
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,83 @@
|
|||
#include "PubSubClientTools.h"
|
||||
|
||||
// Public
|
||||
PubSubClientTools::PubSubClientTools(PubSubClient& _pubSub) : pubSub(_pubSub) {
|
||||
pubSub.setCallback(mqtt_callback);
|
||||
};
|
||||
|
||||
bool PubSubClientTools::connected() {
|
||||
return pubSub.connected();
|
||||
}
|
||||
bool PubSubClientTools::connect(String clientId) {
|
||||
char client_char[CLIENTID_BUFFER_SIZE];
|
||||
clientId.toCharArray(client_char, CLIENTID_BUFFER_SIZE);
|
||||
return pubSub.connect(client_char);
|
||||
}
|
||||
bool PubSubClientTools::connect(String clientId, String willTopic, int willQoS, bool willRetain, String willMessage) {
|
||||
char client_char[CLIENTID_BUFFER_SIZE];
|
||||
char topic_char[TOPIC_BUFFER_SIZE];
|
||||
char msg_char[MESSAGE_BUFFER_SIZE];
|
||||
|
||||
clientId.toCharArray(client_char, CLIENTID_BUFFER_SIZE);
|
||||
willTopic.toCharArray(topic_char, TOPIC_BUFFER_SIZE);
|
||||
willMessage.toCharArray(msg_char, MESSAGE_BUFFER_SIZE);
|
||||
|
||||
return pubSub.connect(client_char, topic_char, willQoS, willRetain, msg_char);
|
||||
}
|
||||
|
||||
bool PubSubClientTools::publish(String topic, String message) {
|
||||
return this->publish(topic, message, false);
|
||||
}
|
||||
bool PubSubClientTools::publish(String topic, String message, bool retained) {
|
||||
char topic_char[TOPIC_BUFFER_SIZE];
|
||||
char msg_char[MESSAGE_BUFFER_SIZE];
|
||||
|
||||
topic.toCharArray(topic_char, TOPIC_BUFFER_SIZE);
|
||||
message.toCharArray(msg_char, MESSAGE_BUFFER_SIZE);
|
||||
|
||||
return pubSub.publish(topic_char, msg_char, retained);
|
||||
}
|
||||
|
||||
bool PubSubClientTools::subscribe(String topic, CALLBACK_SIGNATURE) {
|
||||
if (callbackCount >= CALLBACK_LIST_SIZE) return false;
|
||||
|
||||
char topic_char[TOPIC_BUFFER_SIZE];
|
||||
topic.toCharArray(topic_char, TOPIC_BUFFER_SIZE);
|
||||
|
||||
callbackList[callbackCount].topic = topic;
|
||||
callbackList[callbackCount].callback = callback;
|
||||
callbackCount++;
|
||||
|
||||
return pubSub.subscribe(topic_char);
|
||||
}
|
||||
|
||||
int PubSubClientTools::resubscribe() {
|
||||
int count = 0;
|
||||
|
||||
pubSub.setCallback(mqtt_callback);
|
||||
|
||||
for (int i = 0; i < callbackCount; i++) {
|
||||
char topic_char[TOPIC_BUFFER_SIZE];
|
||||
callbackList[i].topic.toCharArray(topic_char, TOPIC_BUFFER_SIZE);
|
||||
|
||||
if ( pubSub.subscribe(topic_char) ) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// Private
|
||||
void PubSubClientTools::callback(PUBSUBCLIENT_CALLBACK_PARAMETERS) {
|
||||
String topic = String(topicChar);
|
||||
String message = "";
|
||||
for (int i = 0; i < length; i++) {
|
||||
message += (char)payload[i];
|
||||
}
|
||||
|
||||
for (int i = 0; i < callbackCount; i++) {
|
||||
if (MqttWildcard::wildcardMatch(topic, callbackList[i].topic)) {
|
||||
(*callbackList[i].callback)(topic,message);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
#ifndef PubSubClientTools_h
|
||||
#define PubSubClientTools_h
|
||||
|
||||
#if !defined(ESP8266) && !defined(ESP32)
|
||||
#warning This library was developed for ESP8266 and ESP32 microcontrollers
|
||||
#endif
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include <inttypes.h>
|
||||
#include "PubSubClient.h"
|
||||
#include "MqttWildcard.h"
|
||||
|
||||
#define CALLBACK_SIGNATURE void (*callback)(String topic, String message)
|
||||
#define PUBSUBCLIENT_CALLBACK_PARAMETERS char* topicChar, uint8_t* payload, unsigned int length
|
||||
|
||||
#ifndef CLIENTID_BUFFER_SIZE
|
||||
#define CLIENTID_BUFFER_SIZE 50
|
||||
#endif
|
||||
#ifndef TOPIC_BUFFER_SIZE
|
||||
#define TOPIC_BUFFER_SIZE 100
|
||||
#endif
|
||||
#ifndef MESSAGE_BUFFER_SIZE
|
||||
#define MESSAGE_BUFFER_SIZE MQTT_MAX_PACKET_SIZE
|
||||
#endif
|
||||
#ifndef CALLBACK_LIST_SIZE
|
||||
#define CALLBACK_LIST_SIZE 50
|
||||
#endif
|
||||
|
||||
struct callbackTopic {
|
||||
String topic;
|
||||
void (*callback)(String topic, String message);
|
||||
};
|
||||
|
||||
class PubSubClientTools {
|
||||
private:
|
||||
PubSubClient& pubSub;
|
||||
struct callbackTopic callbackList[CALLBACK_LIST_SIZE];
|
||||
int callbackCount = 0;
|
||||
|
||||
std::function<void(PUBSUBCLIENT_CALLBACK_PARAMETERS)> mqtt_callback = std::bind(&PubSubClientTools::callback, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
|
||||
void callback(PUBSUBCLIENT_CALLBACK_PARAMETERS);
|
||||
|
||||
public:
|
||||
PubSubClientTools(PubSubClient& pubSub);
|
||||
|
||||
bool connected();
|
||||
bool connect(String clientId);
|
||||
bool connect(String clientId, String willTopic, int willQoS, bool willRetain, String willMessage);
|
||||
|
||||
bool publish(String topic, String message);
|
||||
bool publish(String topic, String message, bool retained);
|
||||
|
||||
bool subscribe(String topic, CALLBACK_SIGNATURE);
|
||||
|
||||
int resubscribe();
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,10 @@
|
|||
This library provides some functions to make life easier when using PubSubClient for Arduino
|
||||
|
||||
## Examples
|
||||
|
||||
The library comes with a number of example sketches. See File > Examples > PubSubClientTools
|
||||
within the Arduino application.
|
||||
|
||||
## Credits
|
||||
|
||||
[PubSubClient](https://github.com/knolleary/pubsubclient)
|
|
@ -0,0 +1,70 @@
|
|||
#include <WiFi.h>
|
||||
#include <PubSubClient.h>
|
||||
#include <PubSubClientTools.h>
|
||||
|
||||
#include <Thread.h> // https://github.com/ivanseidel/ArduinoThread
|
||||
#include <ThreadController.h>
|
||||
|
||||
#define WIFI_SSID "........"
|
||||
#define WIFI_PASS "........"
|
||||
#define MQTT_SERVER "broker.mqtt-dashboard.com"
|
||||
|
||||
WiFiClient espClient;
|
||||
PubSubClient client(MQTT_SERVER, 1883, espClient);
|
||||
PubSubClientTools mqtt(client);
|
||||
|
||||
ThreadController threadControl = ThreadController();
|
||||
Thread thread = Thread();
|
||||
|
||||
int value = 0;
|
||||
const String s = "";
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
Serial.println();
|
||||
|
||||
// Connect to WiFi
|
||||
Serial.print(s+"Connecting to WiFi: "+WIFI_SSID+" ");
|
||||
WiFi.begin(WIFI_SSID, WIFI_PASS);
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
}
|
||||
Serial.println("connected");
|
||||
|
||||
// Connect to MQTT
|
||||
Serial.print(s+"Connecting to MQTT: "+MQTT_SERVER+" ... ");
|
||||
if (client.connect("ESP32Client")) {
|
||||
Serial.println("connected");
|
||||
|
||||
mqtt.subscribe("test_in/foo/bar", topic1_subscriber);
|
||||
mqtt.subscribe("test_in/+/bar", topic2_subscriber);
|
||||
mqtt.subscribe("test_in/#", topic3_subscriber);
|
||||
} else {
|
||||
Serial.println(s+"failed, rc="+client.state());
|
||||
}
|
||||
|
||||
// Enable Thread
|
||||
thread.onRun(publisher);
|
||||
thread.setInterval(2000);
|
||||
threadControl.add(&thread);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
client.loop();
|
||||
threadControl.run();
|
||||
}
|
||||
|
||||
void publisher() {
|
||||
++value;
|
||||
mqtt.publish("test_out/hello_world", s+"Hello World! - No. "+value);
|
||||
}
|
||||
void topic1_subscriber(String topic, String message) {
|
||||
Serial.println(s+"Message arrived in function 1 ["+topic+"] "+message);
|
||||
}
|
||||
void topic2_subscriber(String topic, String message) {
|
||||
Serial.println(s+"Message arrived in function 2 ["+topic+"] "+message);
|
||||
}
|
||||
void topic3_subscriber(String topic, String message) {
|
||||
Serial.println(s+"Message arrived in function 3 ["+topic+"] "+message);
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
#include <ESP8266WiFi.h>
|
||||
#include <PubSubClient.h>
|
||||
#include <PubSubClientTools.h>
|
||||
|
||||
#include <Thread.h> // https://github.com/ivanseidel/ArduinoThread
|
||||
#include <ThreadController.h>
|
||||
|
||||
#define WIFI_SSID "........"
|
||||
#define WIFI_PASS "........"
|
||||
#define MQTT_SERVER "broker.mqtt-dashboard.com"
|
||||
|
||||
WiFiClient espClient;
|
||||
PubSubClient client(MQTT_SERVER, 1883, espClient);
|
||||
PubSubClientTools mqtt(client);
|
||||
|
||||
ThreadController threadControl = ThreadController();
|
||||
Thread thread = Thread();
|
||||
|
||||
int value = 0;
|
||||
const String s = "";
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
Serial.println();
|
||||
|
||||
// Connect to WiFi
|
||||
Serial.print(s+"Connecting to WiFi: "+WIFI_SSID+" ");
|
||||
WiFi.begin(WIFI_SSID, WIFI_PASS);
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
}
|
||||
Serial.println("connected");
|
||||
|
||||
// Connect to MQTT
|
||||
Serial.print(s+"Connecting to MQTT: "+MQTT_SERVER+" ... ");
|
||||
if (client.connect("ESP8266Client")) {
|
||||
Serial.println("connected");
|
||||
|
||||
mqtt.subscribe("test_in/foo/bar", topic1_subscriber);
|
||||
mqtt.subscribe("test_in/+/bar", topic2_subscriber);
|
||||
mqtt.subscribe("test_in/#", topic3_subscriber);
|
||||
} else {
|
||||
Serial.println(s+"failed, rc="+client.state());
|
||||
}
|
||||
|
||||
// Enable Thread
|
||||
thread.onRun(publisher);
|
||||
thread.setInterval(2000);
|
||||
threadControl.add(&thread);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
client.loop();
|
||||
threadControl.run();
|
||||
}
|
||||
|
||||
void publisher() {
|
||||
++value;
|
||||
mqtt.publish("test_out/hello_world", s+"Hello World! - No. "+value);
|
||||
}
|
||||
void topic1_subscriber(String topic, String message) {
|
||||
Serial.println(s+"Message arrived in function 1 ["+topic+"] "+message);
|
||||
}
|
||||
void topic2_subscriber(String topic, String message) {
|
||||
Serial.println(s+"Message arrived in function 2 ["+topic+"] "+message);
|
||||
}
|
||||
void topic3_subscriber(String topic, String message) {
|
||||
Serial.println(s+"Message arrived in function 3 ["+topic+"] "+message);
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
#######################################
|
||||
# Syntax Coloring
|
||||
#######################################
|
||||
|
||||
#######################################
|
||||
# Datatypes (KEYWORD1)
|
||||
#######################################
|
||||
|
||||
PubSubClientTools KEYWORD1
|
||||
|
||||
#######################################
|
||||
# Methods and Functions (KEYWORD2)
|
||||
#######################################
|
||||
|
||||
publish KEYWORD2
|
||||
subscribe KEYWORD2
|
||||
resubscribe KEYWORD2
|
||||
|
||||
#######################################
|
||||
# Constants (LITERAL1)
|
||||
#######################################
|
|
@ -0,0 +1,9 @@
|
|||
name=PubSubClientTools
|
||||
version=0.6
|
||||
author=Simon Christmann <simon@christmann.email>
|
||||
maintainer=Simon Christmann <simon@christmann.email>
|
||||
sentence=Tools for easier usage of PubSubClient
|
||||
paragraph=Provides useful tools for PubSubClient, however they may consume more power and storage. Therefore it's recommended for powerful microcontrollers like ESP8266.
|
||||
category=Communication
|
||||
url=https://github.com/dersimn/ArduinoPubSubClientTools
|
||||
architectures=*
|
|
@ -0,0 +1,27 @@
|
|||
Copyright (c) 2015, Anatoli Arkhipenko.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,211 @@
|
|||
Task Scheduler – cooperative multitasking for Arduino and ESP8266 microcontrollers
|
||||
Version 3.0.2 2018-11-14
|
||||
|
||||
If you find TaskScheduler useful for your Arduino project, please drop me an email: arkhipenko@hotmail.com
|
||||
----------------------------------------------------------------------------------------------------------
|
||||
|
||||
OVERVIEW:
|
||||
A lightweight implementation of cooperative multitasking (task scheduling) supporting:
|
||||
1. Periodic task execution (with dynamic execution period in milliseconds or microseconds)
|
||||
2. Number of iterations (n times)
|
||||
3. Execution of tasks in predefined sequence
|
||||
4. Dynamic change of task execution parameters (frequency, number of iterations, callback function)
|
||||
5. Power saving via entering IDLE sleep mode between tasks are scheduled to run
|
||||
6. Task invocation via Status Request object
|
||||
7. Task IDs and Control Points for error handling and watchdog timer
|
||||
8. Local Task Storage pointer (allowing use of same callback code for multiple tasks)
|
||||
9. Layered task prioritization
|
||||
10. Support for std::functions (ESP8266 only)
|
||||
11. Overall task timeout
|
||||
12. Static and dynamic callback method binding
|
||||
13. Support for STM32F1 ARM Cortex-M3 boards
|
||||
|
||||
Scheduling overhead: between 15 and 18 microseconds per scheduling pass (check the benchmark example).
|
||||
|
||||
Tested on the following microcontrollers:
|
||||
- Arduino Uno R3
|
||||
- Arduino Nano
|
||||
- Arduino Micro
|
||||
- ATtiny85
|
||||
- ESP8266 (Node MCU v2.0)
|
||||
- ESP32
|
||||
- Teensy (tested on Teensy 3.5)
|
||||
- STM32F1 (tested on Mini USB STM32F103RCBT6 ARM Cortex-M3 leaflabs Leaf maple mini module F)
|
||||
|
||||
For detailed functionality overview please refer to TaskScheduler documentation in the 'extras' folder.
|
||||
=======================================================================================================
|
||||
|
||||
Check out what TaskScheduler can do:
|
||||
====================================
|
||||
Endeavor - build a space capable craft, with the ability to maneuver both in and out of atmosphere.
|
||||
(by Radical Space Technologies
|
||||
http://radicalspacetechnologies.com/2015/10/01/endeavor-phase-i/
|
||||
|
||||
"So after some digging I found TaskScheduler an awesome little library developed by Anatoli Arkhipenko,
|
||||
his github is here and the documentation is here. Its fantastic, so far I’ve been able to replicate
|
||||
some things I’ve seen in Ardupilot. ..."
|
||||
link: http://radicalspacetechnologies.com/2015/10/25/endeavors-code-definitely-progress/
|
||||
|
||||
|
||||
3 Devo - Quality 3D printing filament, now made accessible and affordable
|
||||
http://3devo.eu/ (http://3devo.eu/license-information/)
|
||||
|
||||
|
||||
Houston midi clock project - TaskScheduler with microseconds resolution
|
||||
(by chaffneue: My first arduino project. It's a multi-master midi controller with a shared clock and
|
||||
auto count in behaviour. Project files on github: https://github.com/chaffneue/houston
|
||||
youtube: https://www.youtube.com/watch?v=QRof550TtXo)
|
||||
|
||||
|
||||
Hackabot Nano - Compact Plug and Play Arduino compatible robotic kit
|
||||
(by Funnyvale: http://hackarobot.com/
|
||||
also: https://www.kickstarter.com/projects/hackarobot/hackabot-nano-compact-plug-and-play-arduino-robot)
|
||||
|
||||
|
||||
Arduino Nano based Hexbug Scarab Robotic Spider
|
||||
(by arkhipenko: http://www.instructables.com/id/Arduino-Nano-based-Hexbug-Scarab-Robotic-Spider/)
|
||||
|
||||
|
||||
Wave your hand to control OWI Robotic Arm... no strings attached
|
||||
(by arkhipenko: http://www.instructables.com/id/Wave-your-hand-to-control-OWI-Robotic-Arm-no-strin/)
|
||||
|
||||
|
||||
APIS - Automated Plant Irrigation System
|
||||
(by arkhipenko: http://www.instructables.com/id/APIS-Automated-Plant-Irrigation-System/)
|
||||
|
||||
|
||||
IoT APIS v2 - Autonomous IoT-enabled Automated Plant Irrigation System
|
||||
(by arkhipenko: http://www.instructables.com/id/IoT-APIS-V2-Autonomous-IoT-enabled-Automated-Plant/)
|
||||
|
||||
|
||||
Interactive Halloween Pumpkin
|
||||
(by arkhipenko: http://www.instructables.com/id/Interactive-Halloween-Pumpkin/)
|
||||
|
||||
|
||||
Changelog:
|
||||
=========
|
||||
v3.0.0: MAJOR RELEASE
|
||||
2018-03-15 - Optional Dynamic callback method binding (_TASK_OO_CALLBACKS compilation directive).
|
||||
|
||||
v2.6.1:
|
||||
2018-02-13 - Bug: Support for task self-destruction in the OnDisable method.
|
||||
Example 19: dynamic task creation and destruction.
|
||||
2018-03-14 - Bug: high level scheduler ignored if lower level chain is empty
|
||||
Example 20: use of local task storage to work with task-specific class objects
|
||||
|
||||
v2.6.0:
|
||||
2018-01-30 - _TASK_TIMEOUT compilation directive: Task overall timeout functionality
|
||||
2018-01-30 - Support for ESP32
|
||||
(Contributed by Marco Tombesi: https://github.com/baggior)
|
||||
|
||||
v2.5.2:
|
||||
2018-01-09 - _TASK_INLINE compilation directive making all methods declared "inline" (issue #42)
|
||||
|
||||
|
||||
v2.5.1:
|
||||
2018-01-06 - support for IDLE sleep on Teensy boards (tested on Teensy 3.6)
|
||||
|
||||
v2.5.0:
|
||||
2017-04-27 - added optional support for std::functions via _TASK_STD_FUNCTION
|
||||
(Contributed by Edwin van Leeuwen [BlackEdder - https://github.com/BlackEdder)
|
||||
2017-08-30 - add _TASK_DEBUG making all methods and variables public FOR DEBUGGING PURPOSES ONLY!
|
||||
*** USE AT YOUR OWN RISK ***
|
||||
2017-08-30 - bug fix: Scheduler::addTask() checks if task is already part of an execution chain (github issue #37)
|
||||
2017-08-30 - support for multi-tab sketches (Contributed by Adam Ryczkowski - https://github.com/adamryczkowski)
|
||||
|
||||
v2.4.0:
|
||||
2017-04-27 - added destructor to the Task class to ensure tasks are disables and taken off the execution chain
|
||||
upon destruction. (Contributed by Edwin van Leeuwen [BlackEdder - https://github.com/BlackEdder)
|
||||
|
||||
v2.3.0:
|
||||
2017-02-24 - new timeUntilNextIteration() method within Scheduler class - inquire when a particlar task is
|
||||
scheduled to run next time
|
||||
|
||||
v2.2.1:
|
||||
2016-11-30 - inlined constructors. Added "yield()" and "yieldOnce()" functions to easily break down and
|
||||
chain back together long running callback methods
|
||||
2016-12-16 - added "getCount()" to StatusRequest objects, made every task StatusRequest enabled.
|
||||
Internal StatusRequest objects are accessible via "getInternalStatusRequest()" method.
|
||||
|
||||
v2.2.0:
|
||||
2016-11-17 - all methods made 'inline' to support inclusion of TaskSchedule.h file into other header files
|
||||
|
||||
v2.1.0:
|
||||
2016-02-01 - support for microsecond resolution
|
||||
2016-02-02 - added Scheduler baseline start time reset method: startNow()
|
||||
|
||||
v2.0.1:
|
||||
2016-01-02 - bug fix: issue#11 Xtensa compiler (esp8266): Declaration of constructor does not match implementation
|
||||
|
||||
v2.0.0:
|
||||
2015-12-22 - _TASK_PRIORITY - support for layered task prioritization
|
||||
|
||||
v1.9.2:
|
||||
2015-11-28 - _TASK_ROLLOVER_FIX is deprecated (not necessary)
|
||||
2015-12-16 - bug fixes: automatic millis rollover support for delay methods
|
||||
2015-12-17 - new method for _TASK_TIMECRITICAL option: getStartDelay()
|
||||
|
||||
v1.9.0:
|
||||
2015-11-24 - packed three byte-long status variables into one byte-long bit array structure data type - saving 2 bytes per each task instance
|
||||
|
||||
v1.8.5:
|
||||
2015-11-23 - bug fix: incorrect calculation of next task invocation in case callback changed the interval
|
||||
2015-11-23 - bug fix: Task::set() method calls setInterval() explicitly, therefore delaying the task in the same manner
|
||||
|
||||
v1.8.4:
|
||||
2015-11-15 - bug fix: Task alignment with millis() for scheduling purposes should be done after OnEnable, not before. Especially since OnEnable method can change the interval
|
||||
|
||||
v1.8.3:
|
||||
2015-11-05 - support for task activation on a status request with arbitrary interval and number of iterations (0 and 1 are still default values)
|
||||
2015-11-05 - implement waitForDelayed() method to allow task activation on the status request completion delayed for one current interval
|
||||
2015-11-09 - added callback methods prototypes to all examples for Arduino IDE 1.6.6 compatibility
|
||||
2015-11-14 - added several constants to be used as task parameters for readability (e.g, TASK_FOREVER, TASK_SECOND, etc.)
|
||||
2015-11-14 - significant optimization of the scheduler's execute loop, including millis() rollover fix option
|
||||
|
||||
v1.8.2:
|
||||
2015-10-27 - implement Local Task Storage Pointer (allow use of same callback code for different tasks)
|
||||
2015-10-27 - bug: currentTask() method returns incorrect Task reference if called within OnEnable and OnDisable methods
|
||||
2015-10-27 - protection against infinite loop in OnEnable (if enable() methods are called within OnEnable)
|
||||
2015-10-29 - new currentLts() method in the scheduler class returns current task's LTS pointer in one call
|
||||
|
||||
v1.8.1:
|
||||
2015-10-22 - implement Task id and control points to support identification of failure points for watchdog timer logging
|
||||
|
||||
v1.8.0:
|
||||
2015-10-13 - support for status request objects allowing tasks waiting on requests
|
||||
2015-10-13 - moved to a single header file to allow compilation control via #defines from the main sketch
|
||||
|
||||
v1.7.0:
|
||||
2015-10-08 - introduced callback run counter - callback functions can branch on the iteration number.
|
||||
2015-10-11 - enableIfNot() - enable a task only if it is not already enabled. Returns true if was already enabled, false if was disabled.
|
||||
2015-10-11 - disable() returns previous enable state (true if was enabled, false if was already disabled)
|
||||
2015-10-11 - introduced callback functions "on enable" and "on disable". On enable runs every time enable is called, on disable runs only if task was enabled
|
||||
2015-10-12 - new Task method: forceNextIteration() - makes next iteration happen immediately during the next pass regardless how much time is left
|
||||
|
||||
v1.6.0:
|
||||
2015-09-22 - revert back to having all tasks disable on last iteration.
|
||||
2015-09-22 - deprecated disableOnLastIteration method as a result
|
||||
2015-10-01 - made version numbers semver compliant (documentation only)
|
||||
|
||||
v1.5.1:
|
||||
2015-09-21 - bug fix: incorrect handling of active tasks via set() and setIterations().
|
||||
Thanks to Hannes Morgenstern for catching this one
|
||||
|
||||
v1.5.0:
|
||||
2015-09-20 - access to currently executing task (for callback functions)
|
||||
2015-09-20 - pass scheduler as a parameter to the task constructor to append the task to the end of the chain
|
||||
2015-09-20 - option to create a task already enabled
|
||||
|
||||
v1.4.1:
|
||||
2015-09-15 - more careful placement of AVR-specific includes for sleep functions (compatibility with DUE)
|
||||
sleep on idle run is no longer a default and should be explicitly compiled with _TASK_SLEEP_ON_IDLE_RUN defined
|
||||
|
||||
v1.0.0:
|
||||
2015-02-24 - Initial release
|
||||
2015-02-28 - added delay() and disableOnLastIteration() functions
|
||||
2015-03-25 - changed scheduler execute() function for a more precise delay calculation:
|
||||
1. Do not delay if any of the tasks ran (making request for immediate execution redundant)
|
||||
2. Delay is invoked only if none of the tasks ran
|
||||
3. Delay is based on the min anticipated wait until next task _AND_ the runtime of execute function itself.
|
||||
2015-05-11 - added restart() and restartDelayed() functions to restart tasks which are on hold after running all iterations
|
||||
2015-05-19 - completely removed delay from the scheduler since there are no power saving there. using 1 ms sleep instead
|
|
@ -0,0 +1,77 @@
|
|||
# Task Scheduler
|
||||
### Cooperative multitasking for Arduino microcontrollers
|
||||
#### Version 3.0.2: 2018-11-14
|
||||
|
||||
### OVERVIEW:
|
||||
A lightweight implementation of cooperative multitasking (task scheduling) supporting:
|
||||
1. Periodic task execution, with dynamic execution period in `milliseconds` (default) or `microseconds` (if explicitly enabled) – frequency of execution
|
||||
2. Number of iterations (limited or infinite number of iterations)
|
||||
3. Execution of tasks in predefined sequence
|
||||
4. Dynamic change of task execution parameters (frequency, number of iterations, callback methods)
|
||||
5. Power saving via entering **IDLE** sleep mode when tasks are not scheduled to run
|
||||
6. Support for event-driven task invocation via Status Request object
|
||||
7. Support for task IDs and Control Points for error handling and watchdog timer
|
||||
8. Support for Local Task Storage pointer (allowing use of same callback code for multiple tasks)
|
||||
9. Support for layered task prioritization
|
||||
10. Support for `std::functions` (`ESP8266` only)
|
||||
11. Overall task timeout
|
||||
12. Static and dynamic callback method binding
|
||||
13. Support for STM32F1 ARM Cortex-M3 boards
|
||||
|
||||
Scheduling overhead: between `15` and `18` microseconds per scheduling pass (Arduino UNO rev 3 @ `16MHz` clock, single scheduler w/o prioritization)
|
||||
|
||||
**TaskScheduler** was tested on the following platforms:
|
||||
* Arduino Uno R3
|
||||
* Arduino Nano
|
||||
* Arduino Micro
|
||||
* ATtiny85
|
||||
* ESP8266 (Node MCU v2.0)
|
||||
* ESP32
|
||||
* Teensy (tested on Teensy 3.5)
|
||||
* STM32F1 (tested on Mini USB STM32F103RCBT6 ARM Cortex-M3 leaflabs Leaf maple mini module F)
|
||||
---
|
||||
![TaskScheduler process diagram](https://github.com/arkhipenko/TaskScheduler/raw/master/extras/TaskScheduler_html.png)
|
||||
---
|
||||
### Changelog is located [here.](https://github.com/arkhipenko/TaskScheduler/wiki/Changelog)
|
||||
|
||||
|
||||
#### For detailed functionality overview please refer to TaskScheduler documentation in the 'extras' folder or in the [Wiki page](https://github.com/arkhipenko/TaskScheduler/wiki).
|
||||
|
||||
### Check out what TaskScheduler can do:
|
||||
|
||||
* [3 Devo](http://3devo.eu/) - Quality 3D printing filament, now made accessible and affordable
|
||||
(http://3devo.eu/license-information/)
|
||||
|
||||
|
||||
* [Houston midi](https://github.com/chaffneue/houston) clock project - TaskScheduler with microseconds resolution
|
||||
>by chaffneue:
|
||||
>>My first arduino project. It's a multi-master midi controller with a shared clock and
|
||||
auto count in behaviour.
|
||||
|
||||
youtube: https://www.youtube.com/watch?v=QRof550TtXo
|
||||
|
||||
|
||||
* [Hackabot Nano](http://hackarobot.com/) by Funnyvale - Compact Plug and Play Arduino compatible robotic kit
|
||||
https://www.kickstarter.com/projects/hackarobot/hackabot-nano-compact-plug-and-play-arduino-robot
|
||||
|
||||
|
||||
* Arduino Nano based Hexbug Scarab Robotic Spider
|
||||
(by arkhipenko: http://www.instructables.com/id/Arduino-Nano-based-Hexbug-Scarab-Robotic-Spider/)
|
||||
|
||||
* Wave your hand to control OWI Robotic Arm... no strings attached
|
||||
(by arkhipenko: http://www.instructables.com/id/Wave-your-hand-to-control-OWI-Robotic-Arm-no-strin/)
|
||||
|
||||
|
||||
* APIS - Automated Plant Irrigation System
|
||||
(by arkhipenko: http://www.instructables.com/id/APIS-Automated-Plant-Irrigation-System/)
|
||||
|
||||
|
||||
* IoT APIS v2 - Autonomous IoT-enabled Automated Plant Irrigation System
|
||||
(by arkhipenko: http://www.instructables.com/id/IoT-APIS-V2-Autonomous-IoT-enabled-Automated-Plant/)
|
||||
|
||||
* Interactive Halloween Pumpkin
|
||||
(by arkhipenko: http://www.instructables.com/id/Interactive-Halloween-Pumpkin/)
|
||||
|
||||
* Interactive Predator Costume with Real-Time Head Tracking Plasma Cannon
|
||||
(by arkhipenko: https://www.instructables.com/id/Interactive-Predator-Costume-With-Head-Tracking-Pl/)
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
/**
|
||||
* TaskScheduler Test
|
||||
*
|
||||
* Initially only tasks 1 and 2 are enabled
|
||||
* Task1 runs every 2 seconds 10 times and then stops
|
||||
* Task2 runs every 3 seconds indefinitely
|
||||
* Task1 enables Task3 at its first run
|
||||
* Task3 run every 5 seconds
|
||||
* Task1 disables Task3 on its last iteration and changed Task2 to run every 1/2 seconds
|
||||
* At the end Task2 is the only task running every 1/2 seconds
|
||||
*/
|
||||
|
||||
|
||||
#include <TaskScheduler.h>
|
||||
|
||||
// Callback methods prototypes
|
||||
void t1Callback();
|
||||
void t2Callback();
|
||||
void t3Callback();
|
||||
|
||||
//Tasks
|
||||
Task t4();
|
||||
Task t1(2000, 10, &t1Callback);
|
||||
Task t2(3000, TASK_FOREVER, &t2Callback);
|
||||
Task t3(5000, TASK_FOREVER, &t3Callback);
|
||||
|
||||
Scheduler runner;
|
||||
|
||||
|
||||
void t1Callback() {
|
||||
Serial.print("t1: ");
|
||||
Serial.println(millis());
|
||||
|
||||
if (t1.isFirstIteration()) {
|
||||
runner.addTask(t3);
|
||||
t3.enable();
|
||||
Serial.println("t1: enabled t3 and added to the chain");
|
||||
}
|
||||
|
||||
if (t1.isLastIteration()) {
|
||||
t3.disable();
|
||||
runner.deleteTask(t3);
|
||||
t2.setInterval(500);
|
||||
Serial.println("t1: disable t3 and delete it from the chain. t2 interval set to 500");
|
||||
}
|
||||
}
|
||||
|
||||
void t2Callback() {
|
||||
Serial.print("t2: ");
|
||||
Serial.println(millis());
|
||||
|
||||
}
|
||||
|
||||
void t3Callback() {
|
||||
Serial.print("t3: ");
|
||||
Serial.println(millis());
|
||||
|
||||
}
|
||||
|
||||
void setup () {
|
||||
Serial.begin(115200);
|
||||
Serial.println("Scheduler TEST");
|
||||
|
||||
runner.init();
|
||||
Serial.println("Initialized scheduler");
|
||||
|
||||
runner.addTask(t1);
|
||||
Serial.println("added t1");
|
||||
|
||||
runner.addTask(t2);
|
||||
Serial.println("added t2");
|
||||
|
||||
delay(5000);
|
||||
|
||||
t1.enable();
|
||||
Serial.println("Enabled t1");
|
||||
t2.enable();
|
||||
Serial.println("Enabled t2");
|
||||
}
|
||||
|
||||
|
||||
void loop () {
|
||||
runner.execute();
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
#define _TASK_SLEEP_ON_IDLE_RUN
|
||||
#include <TaskScheduler.h>
|
||||
|
||||
Scheduler runner;
|
||||
// Callback methods prototypes
|
||||
void t1Callback();
|
||||
void t2Callback();
|
||||
void t3Callback();
|
||||
|
||||
// Tasks
|
||||
Task t4();
|
||||
Task t1(2000, 10, &t1Callback, &runner, true); //adding task to the chain on creation
|
||||
Task t2(3000, TASK_FOREVER, &t2Callback, &runner, true); //adding task to the chain on creation
|
||||
Task t3(5000, TASK_FOREVER, &t3Callback);
|
||||
|
||||
// Test
|
||||
// Initially only tasks 1 and 2 are enabled
|
||||
// Task1 runs every 2 seconds 10 times and then stops
|
||||
// Task2 runs every 3 seconds indefinitely
|
||||
// Task1 enables Task3 at its first run
|
||||
// Task3 run every 5 seconds
|
||||
// loop() runs every 1 second (a default scheduler delay, if no shorter tasks' interval is detected)
|
||||
// Task1 disables Task3 on its last iteration and changed Task2 to run every 1/2 seconds
|
||||
// Because Task2 interval is shorter than Scheduler default tick, loop() executes ecery 1/2 seconds now
|
||||
// At the end Task2 is the only task running every 1/2 seconds
|
||||
//
|
||||
// NOTE that t1 and t2 are affected by the delay() function in the setup() method and are scheduled immediately twice to "catch up" with millis().
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void t1Callback() {
|
||||
Serial.print("t1: ");
|
||||
Serial.println(millis());
|
||||
|
||||
if (t1.isFirstIteration()) {
|
||||
runner.addTask(t3);
|
||||
t3.enable();
|
||||
Serial.println("t1: enabled t3 and added to the chain");
|
||||
}
|
||||
|
||||
if (t1.isLastIteration()) {
|
||||
t3.disable();
|
||||
runner.deleteTask(t3);
|
||||
t2.setInterval(500);
|
||||
Serial.println("t1: disable t3 and delete it from the chain. t2 interval set to 500");
|
||||
}
|
||||
}
|
||||
|
||||
void t2Callback() {
|
||||
Serial.print("t2: ");
|
||||
Serial.println(millis());
|
||||
|
||||
}
|
||||
|
||||
void t3Callback() {
|
||||
Serial.print("t3: ");
|
||||
Serial.println(millis());
|
||||
|
||||
}
|
||||
|
||||
void setup () {
|
||||
Serial.begin(115200);
|
||||
delay(5000);
|
||||
Serial.println("Scheduler TEST");
|
||||
|
||||
runner.startNow(); // set point-in-time for scheduling start
|
||||
}
|
||||
|
||||
|
||||
void loop () {
|
||||
|
||||
runner.execute();
|
||||
|
||||
// Serial.println("Loop ticks at: ");
|
||||
// Serial.println(millis());
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
* TaskScheduler Test of OnEnable and OnDisable methods and illustration of using wrapper tasks for timout purposes
|
||||
*
|
||||
* A wrapper task runs every 10 seconds and initiates the test case
|
||||
* Another task is run once for 5 seconds, and serves as a LED blinking timeout - 5 seconds
|
||||
* Finally, a dedicated task which controls LED is running periodically until stopped, and makes the LED blink with 0.5 to 1 second interval.
|
||||
*
|
||||
*/
|
||||
|
||||
#define _TASK_SLEEP_ON_IDLE_RUN
|
||||
#include <TaskScheduler.h>
|
||||
|
||||
#ifndef LED_BUILTIN
|
||||
#define LED_BUILTIN 13 // define appropriate pin for your board
|
||||
#endif
|
||||
|
||||
Scheduler ts;
|
||||
|
||||
// Callback methods prototypes
|
||||
void WrapperCallback();
|
||||
bool BlinkOnEnable();
|
||||
void BlinkOnDisable();
|
||||
void LEDOn();
|
||||
void LEDOff();
|
||||
|
||||
// Tasks
|
||||
Task tWrapper(10000L, TASK_FOREVER, &WrapperCallback, &ts, true);
|
||||
Task tBlink(5000, TASK_ONCE, NULL, &ts, false, &BlinkOnEnable, &BlinkOnDisable);
|
||||
Task tLED(0, TASK_FOREVER, NULL, &ts, false, NULL, &LEDOff);
|
||||
|
||||
void WrapperCallback() {
|
||||
tBlink.restartDelayed(); // LED blinking is initiated
|
||||
//every 30 seconds for 5 seconds
|
||||
}
|
||||
|
||||
|
||||
// Upon being enabled, tBlink will define the parameters
|
||||
// and enable LED blinking task, which actually controls
|
||||
// the hardware (LED in this example)
|
||||
bool BlinkOnEnable() {
|
||||
tLED.setInterval( 200 + random(801) );
|
||||
tLED.setCallback( &LEDOn);
|
||||
tLED.enable();
|
||||
|
||||
return true; // Task should be enabled
|
||||
}
|
||||
|
||||
// tBlink does not really need a callback function
|
||||
// since it just waits for 5 seconds for the first
|
||||
// and only iteration to occur. Once the iteration
|
||||
// takes place, tBlink is disabled by the Scheduler,
|
||||
// thus executing its OnDisable method below.
|
||||
|
||||
void BlinkOnDisable() {
|
||||
tLED.disable();
|
||||
}
|
||||
|
||||
void LEDOn () {
|
||||
digitalWrite(LED_BUILTIN , HIGH);
|
||||
tLED.setCallback( &LEDOff);
|
||||
}
|
||||
|
||||
void LEDOff () {
|
||||
digitalWrite(LED_BUILTIN , LOW);
|
||||
tLED.setCallback( &LEDOn);
|
||||
}
|
||||
|
||||
// Note that LEDOff method serves as OnDisable method
|
||||
// to make sure the LED is turned off when the tBlink
|
||||
// task finishes (or disabled ahead of time)
|
||||
|
||||
void setup() {
|
||||
// put your setup code here, to run once:
|
||||
pinMode(LED_BUILTIN , OUTPUT);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// put your main code here, to run repeatedly:
|
||||
ts.execute();
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
/** This test demonstrates interaction between three simple tasks via StatusRequest object.
|
||||
* Task T1 runs every 5 seconds and signals completion of a status request st.
|
||||
* Tasks T2 and T3 are waiting on the same request (st)
|
||||
* Task T3 does not renew its interest in status request st, so it is only invoked once (first iteration)
|
||||
* Task T2 is invoked every time st completes, because it renews its interest in status of status request object st every iteration of T1
|
||||
*/
|
||||
|
||||
#define _TASK_SLEEP_ON_IDLE_RUN
|
||||
#define _TASK_STATUS_REQUEST
|
||||
#include <TaskScheduler.h>
|
||||
|
||||
StatusRequest st;
|
||||
|
||||
Scheduler ts;
|
||||
|
||||
// Callback methods prototypes
|
||||
void Callback1();
|
||||
void Disable1();
|
||||
void Callback2();
|
||||
void Callback3();
|
||||
void PrepareStatus();
|
||||
|
||||
// Tasks
|
||||
Task t1(5000, TASK_ONCE, &Callback1, &ts, true, NULL, &Disable1);
|
||||
Task t2(&Callback2, &ts);
|
||||
Task t3(&Callback3, &ts);
|
||||
|
||||
/** T1 callback
|
||||
* T1 just signals completion of st every 5 seconds
|
||||
*/
|
||||
void Callback1() {
|
||||
Serial.println("T1: Signaling completion of ST");
|
||||
st.signalComplete();
|
||||
}
|
||||
|
||||
/** T1 On Disable callback
|
||||
* This callback renews the status request and restarts T1 delayed to run again in 5 seconds
|
||||
*/
|
||||
void Disable1() {
|
||||
PrepareStatus();
|
||||
t1.restartDelayed();
|
||||
}
|
||||
|
||||
/** T2 callback
|
||||
* Invoked when status request st completes
|
||||
*/
|
||||
void Callback2() {
|
||||
Serial.println("T2: Invoked due to completion of ST");
|
||||
}
|
||||
|
||||
|
||||
/** T3 callback
|
||||
* Invoked when status request st completes.
|
||||
* This is only run once since T3 does not renew its interest in the status request st after first iteration
|
||||
*/
|
||||
void Callback3() {
|
||||
Serial.println("T3: Invoked due to completion of ST");
|
||||
|
||||
}
|
||||
|
||||
/** Prepare Status request st for another iteration
|
||||
*
|
||||
*/
|
||||
void PrepareStatus() {
|
||||
st.setWaiting(); // set the statusrequest object for waiting
|
||||
t2.waitFor(&st); // request tasks 1 & 2 to wait on the object st
|
||||
}
|
||||
|
||||
|
||||
/** Main Arduino code
|
||||
* Not much to do here. Just init Serial and set the initial status request
|
||||
*/
|
||||
void setup() {
|
||||
|
||||
Serial.begin(115200);
|
||||
delay(1000);
|
||||
Serial.println("TaskScheduler: Status Request Test 1. Simple Test.");
|
||||
|
||||
ts.startNow();
|
||||
PrepareStatus();
|
||||
t3.waitFor(&st);
|
||||
|
||||
t1.delay();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
|
||||
ts.execute();
|
||||
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
/** This test emulates querying 3 sensors once every 10 seconds, each could respond with a different delay
|
||||
* (ultrasonic sensors for instance) and printing a min value of the three when all three have reported their values.
|
||||
* The overall timeout of 1 second is setup as well.
|
||||
* An error message needs to be printed if a timeout occurred instead of a value.
|
||||
*/
|
||||
|
||||
|
||||
#define _TASK_SLEEP_ON_IDLE_RUN
|
||||
#define _TASK_STATUS_REQUEST
|
||||
#include <TaskScheduler.h>
|
||||
|
||||
#ifdef ARDUINO_ARCH_STM32F1
|
||||
#define A0 3
|
||||
#endif
|
||||
|
||||
StatusRequest measure;
|
||||
|
||||
Scheduler ts;
|
||||
|
||||
// Callback methods prototypes
|
||||
void CycleCallback();
|
||||
void MeasureCallback();
|
||||
bool MeasureEnable();
|
||||
void MeasureDisable();
|
||||
void CalcCallback();
|
||||
void S1Callback(); bool S1Enable();
|
||||
void S2Callback(); bool S2Enable();
|
||||
void S3Callback(); bool S3Enable();
|
||||
|
||||
// Tasks
|
||||
Task tCycle(10000, TASK_FOREVER, &CycleCallback, &ts, true);
|
||||
Task tMeasure(1000, TASK_ONCE, &MeasureCallback, &ts, false, &MeasureEnable, &MeasureDisable);
|
||||
Task tCalculate(&CalcCallback, &ts);
|
||||
Task tSensor1(0, TASK_ONCE, &S1Callback, &ts, false, &S1Enable);
|
||||
Task tSensor2(0, TASK_ONCE, &S2Callback, &ts, false, &S2Enable);
|
||||
Task tSensor3(0, TASK_ONCE, &S3Callback, &ts, false, &S3Enable);
|
||||
|
||||
|
||||
long distance, d1, d2, d3;
|
||||
|
||||
void CycleCallback() {
|
||||
Serial.println("CycleCallback: Initiating measurement cycle every 10 seconds");
|
||||
|
||||
tMeasure.restartDelayed();
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool MeasureEnable() {
|
||||
Serial.println("MeasureEnable: Activating sensors");
|
||||
|
||||
distance = 0;
|
||||
measure.setWaiting(3); // Set the StatusRequest to wait for 3 signals.
|
||||
tCalculate.waitFor(&measure);
|
||||
|
||||
tSensor1.restartDelayed();
|
||||
tSensor2.restartDelayed();
|
||||
tSensor3.restartDelayed();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MeasureCallback() {
|
||||
Serial.println("MeasureCallback: Invoked by calculate task or one second later");
|
||||
|
||||
if (measure.pending()) {
|
||||
tCalculate.disable();
|
||||
measure.signalComplete(-1); // signal error
|
||||
Serial.println("MeasureCallback: Timeout!");
|
||||
}
|
||||
else {
|
||||
Serial.print("MeasureCallback: Min distance=");Serial.println(distance);
|
||||
}
|
||||
}
|
||||
|
||||
void MeasureDisable() {
|
||||
Serial.println("MeasureDisable: Cleaning up");
|
||||
|
||||
tSensor1.disable();
|
||||
tSensor2.disable();
|
||||
tSensor3.disable();
|
||||
}
|
||||
|
||||
|
||||
void CalcCallback() {
|
||||
Serial.println("CalcCallback: calculating");
|
||||
distance = -1;
|
||||
if ( measure.getStatus() >= 0) { // only calculate if statusrequest ended successfully
|
||||
distance = d1 < d2 ? d1 : d2;
|
||||
distance = d3 < distance ? d3 : distance;
|
||||
tMeasure.forceNextIteration();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Simulation code for sensor 1
|
||||
* ----------------------------
|
||||
*/
|
||||
bool S1Enable() {
|
||||
Serial.print("S1Enable: Triggering sensor1. Delay=");
|
||||
|
||||
tSensor1.setInterval( random(1200) ); // Simulating sensor delay, which could go over 1 second and cause timeout
|
||||
d1 = 0;
|
||||
|
||||
Serial.println( tSensor1.getInterval() );
|
||||
return true;
|
||||
}
|
||||
|
||||
void S1Callback() {
|
||||
Serial.print("S1Callback: Emulating measurement. d1=");
|
||||
d1 = random(501); // pick a value from 0 to 500 "centimeters" simulating a measurement
|
||||
measure.signal();
|
||||
|
||||
Serial.println(d1);
|
||||
}
|
||||
|
||||
|
||||
/** Simulation code for sensor 2
|
||||
* ----------------------------
|
||||
*/
|
||||
bool S2Enable() {
|
||||
Serial.print("S2Enable: Triggering sensor2. Delay=");
|
||||
|
||||
tSensor2.setInterval( random(1200) ); // Simulating sensor delay, which could go over 1 second and cause timeout
|
||||
d2 = 0;
|
||||
|
||||
Serial.println( tSensor2.getInterval() );
|
||||
return true;
|
||||
}
|
||||
|
||||
void S2Callback() {
|
||||
Serial.print("S2Callback: Emulating measurement. d2=");
|
||||
d2 = random(501); // pick a value from 0 to 500 "centimeters" simulating a measurement
|
||||
measure.signal();
|
||||
|
||||
Serial.println(d2);
|
||||
}
|
||||
|
||||
|
||||
/** Simulation code for sensor 3
|
||||
* ----------------------------
|
||||
*/
|
||||
bool S3Enable() {
|
||||
Serial.print("S3Enable: Triggering sensor3. Delay=");
|
||||
|
||||
tSensor3.setInterval( random(1200) ); // Simulating sensor delay, which could go over 1 second and cause timeout
|
||||
d3 = 0;
|
||||
|
||||
Serial.println( tSensor3.getInterval() );
|
||||
return true;
|
||||
}
|
||||
|
||||
void S3Callback() {
|
||||
Serial.print("S3Callback: Emulating measurement. d3=");
|
||||
d3 = random(501); // pick a value from 0 to 500 "centimeters" simulating a measurement
|
||||
measure.signal();
|
||||
|
||||
Serial.println(d3);
|
||||
}
|
||||
|
||||
|
||||
/** Main Arduino code
|
||||
* Not much is left here - everything is taken care of by the framework
|
||||
*/
|
||||
void setup() {
|
||||
|
||||
Serial.begin(115200);
|
||||
Serial.println("TaskScheduler StatusRequest Sensor Emulation Test. Complex Test.");
|
||||
|
||||
#ifdef ARDUINO_ARCH_STM32F1
|
||||
pinMode(A0, INPUT_ANALOG);
|
||||
#endif
|
||||
|
||||
randomSeed(analogRead(A0)+millis());
|
||||
}
|
||||
|
||||
void loop() {
|
||||
|
||||
ts.execute();
|
||||
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
|
||||
/**
|
||||
* This is a test to prove that processor really goes into IDLE sleep.
|
||||
* For this setup:
|
||||
*
|
||||
*
|
||||
|
||||
Task c(10, -1, &Count, &ts);
|
||||
Task t(10000, 1, NULL, &ts, true, &tOn, &tOff);
|
||||
|
||||
The result are:
|
||||
|
||||
1): With #define _TASK_SLEEP_ON_IDLE_RUN enabled
|
||||
On Arduino Uno:
|
||||
Start
|
||||
c1=10771 - v2.5.0 (v1.9.0: same)
|
||||
c2=1001
|
||||
|
||||
On Teensy 3.5 (120MHz ARM):
|
||||
Start
|
||||
c1=21065
|
||||
c2=1001
|
||||
|
||||
On esp8266 (80 MHz)
|
||||
Start
|
||||
c1=10492
|
||||
c2=1001
|
||||
|
||||
On STM32F103RCBT6 (Maple Mini @72 MHz)
|
||||
Start
|
||||
c1=21004
|
||||
c2=1001
|
||||
|
||||
and
|
||||
|
||||
2): With #define _TASK_SLEEP_ON_IDLE_RUN disabled (commented out)
|
||||
Arduino Uno:
|
||||
Start
|
||||
c1=722426 - v3.0.2
|
||||
c1=635735 - v2.5.0
|
||||
c1=551947 - v1.9.0
|
||||
c2=1001
|
||||
|
||||
On Teensy 3.5 (120MHz ARM):
|
||||
Start
|
||||
c1=2690322
|
||||
c2=1001
|
||||
|
||||
On esp8266 (80 MHz)
|
||||
Start
|
||||
c1=351085 (689833 at 160Mhz)
|
||||
c2=1001
|
||||
|
||||
On STM32F103RCBT6 (Maple Mini @72 MHz)
|
||||
Start
|
||||
c1=4665019
|
||||
c2=1001
|
||||
|
||||
C1 in scenario 2) is much higher than in scenario 1) because processor is put to sleep for 1), but not for 2)
|
||||
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Compile and run once with _TASK_SLEEP_ON_IDLE_RUN enabled, then with _TASK_SLEEP_ON_IDLE_RUN disabled.
|
||||
* Compare the results.
|
||||
*/
|
||||
|
||||
//#define _TASK_SLEEP_ON_IDLE_RUN
|
||||
#include <TaskScheduler.h>
|
||||
|
||||
Scheduler ts;
|
||||
|
||||
// Callback methods prototypes
|
||||
void Count();
|
||||
bool tOn(); void tOff();
|
||||
|
||||
// Tasks
|
||||
Task c(10, TASK_FOREVER, &Count, &ts);
|
||||
Task t(10000, TASK_ONCE, NULL, &ts, true, &tOn, &tOff);
|
||||
|
||||
|
||||
volatile unsigned long c1, c2;
|
||||
bool tOn() {
|
||||
c1 = 0;
|
||||
c2 = 0;
|
||||
c.enable();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void tOff() {
|
||||
c.disable();
|
||||
Serial.print("c1=");Serial.println(c1);
|
||||
Serial.print("c2=");Serial.println(c2);
|
||||
}
|
||||
|
||||
void setup() {
|
||||
// put your setup code here, to run once:
|
||||
Serial.begin(115200);
|
||||
delay(1000);
|
||||
Serial.println("Start");
|
||||
|
||||
ts.startNow();
|
||||
t.delay();
|
||||
}
|
||||
|
||||
void Count() {
|
||||
c2++;
|
||||
}
|
||||
|
||||
|
||||
void loop() {
|
||||
// put your main code here, to run repeatedly:
|
||||
ts.execute();
|
||||
c1++;
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
/**
|
||||
* TaskScheduler Test sketch - use of task IDs and watchdog timer to identify hung tasks
|
||||
* THIS SKETCH RUNS ON AVR BOARDS ONLY
|
||||
* Test case:
|
||||
* Watchdog timer is set to 2 seconds (interrupt + reset)
|
||||
* A hearbeat task (resetting the watchdog timer) is scheduled with 500 ms interval
|
||||
* A number of tasks are running every 1 second and "rolling the dice" 0..19. If 5, task is made to enter infinite loop
|
||||
* Device should reset in 2 seconds after a task enters infinite loop
|
||||
* A task id and a control point number are saved to EEPROM prior to device reset, and are displayed after reboot.
|
||||
* In real life, device might chose to NOT activate certain tasks which failed previously (failed sensors for instance)
|
||||
*/
|
||||
|
||||
#define _TASK_SLEEP_ON_IDLE_RUN
|
||||
#define _TASK_WDT_IDS
|
||||
#include <TaskScheduler.h>
|
||||
|
||||
#include <EEPROM.h>
|
||||
#include <avr/wdt.h>
|
||||
|
||||
Scheduler ts;
|
||||
|
||||
// Callback methods prototypes
|
||||
void TaskCB();
|
||||
void HB(); bool HBOn(); void HBOff();
|
||||
|
||||
// Three tasks emulating accidental infinite loop
|
||||
Task tTask1(TASK_SECOND, TASK_FOREVER, &TaskCB, &ts, true);
|
||||
Task tTask2(TASK_SECOND, TASK_FOREVER, &TaskCB, &ts, true);
|
||||
Task tTask3(TASK_SECOND, TASK_FOREVER, &TaskCB, &ts, true);
|
||||
|
||||
// Heartbeat task - resetting the watchdog timer periodically
|
||||
// Initiates WDT on enable, and deactivates it on disable
|
||||
Task tHB(500, TASK_FOREVER, &HB, &ts, false, &HBOn, &HBOff);
|
||||
|
||||
/**
|
||||
* Emulating task callback function
|
||||
* Prints task id and randomly "hangs" in two places.
|
||||
* Control points are stored on the task prior to section which might hang,
|
||||
* making this information available to the WDT interrupt handler
|
||||
*/
|
||||
void TaskCB() {
|
||||
Task& T = ts.currentTask();
|
||||
|
||||
Serial.print("Task #:");
|
||||
Serial.print(T.getId());
|
||||
Serial.print(" current iteration = ");
|
||||
Serial.println(T.getRunCounter());
|
||||
|
||||
// Hang if random number between 0 and 19 is 5 (5% probability)
|
||||
T.setControlPoint(10);
|
||||
if (random(20) == 5) for(;;);
|
||||
|
||||
// Hang if random number between 0 and 99 is more that 95 (5% probability)
|
||||
T.setControlPoint(95);
|
||||
if (random(100) > 94) for(;;);
|
||||
}
|
||||
|
||||
/**
|
||||
* This On Enable method sets up the WDT
|
||||
* for interrupt and reset after 2 seconds
|
||||
*/
|
||||
bool HBOn() {
|
||||
|
||||
//disable interrupts
|
||||
cli();
|
||||
//reset watchdog
|
||||
wdt_reset();
|
||||
//set up WDT interrupt
|
||||
WDTCSR = (1<<WDCE)|(1<<WDE);
|
||||
//Start watchdog timer with aDelay prescaller
|
||||
WDTCSR = (1<<WDIE)|(1<<WDE)|(WDTO_2S & 0x2F);
|
||||
// WDTCSR = (1<<WDIE)|(WDTO_2S & 0x2F); // interrupt only without reset
|
||||
//Enable global interrupts
|
||||
sei();
|
||||
}
|
||||
|
||||
/**
|
||||
* This On Disable method disables WDT
|
||||
*/
|
||||
void HBOff() {
|
||||
wdt_disable();
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a periodic reset of WDT
|
||||
*/
|
||||
void HB() {
|
||||
wdt_reset();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Watchdog timeout ISR
|
||||
*
|
||||
*/
|
||||
ISR(WDT_vect)
|
||||
{
|
||||
Task& T = ts.currentTask();
|
||||
|
||||
digitalWrite(13, HIGH);
|
||||
EEPROM.write(0, (byte)T.getId());
|
||||
EEPROM.write(1, (byte)T.getControlPoint());
|
||||
digitalWrite(13, LOW);
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard arduino setup routine
|
||||
*/
|
||||
void setup() {
|
||||
|
||||
Serial.begin(115200);
|
||||
|
||||
randomSeed(analogRead(0)+analogRead(5));
|
||||
|
||||
pinMode(13, OUTPUT);
|
||||
digitalWrite(13, LOW);
|
||||
|
||||
|
||||
Serial.println("WDT heartbeat test");
|
||||
Serial.print("Last task before reset="); Serial.println(EEPROM.read(0));
|
||||
Serial.print("Last control point before reset="); Serial.println(EEPROM.read(1));
|
||||
|
||||
delay(2000);
|
||||
|
||||
tHB.enableDelayed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Not much is left for the loop()
|
||||
*/
|
||||
void loop() {
|
||||
ts.execute();
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
/**
|
||||
* TaskScheduler Test sketch - use of task's Local Task Storage pointer
|
||||
* Test case:
|
||||
* Overall test runs for 5 seconds
|
||||
* A number of calculator tasks run every one second, and update their respective variables using Local Task Storage pointer
|
||||
* All calculator tasks use the same callback code, which obtains reference to appropriate variables via LTS pointer
|
||||
* Calculaotr tasks perform simple calculation (as an example):
|
||||
* adding task id number to itself
|
||||
* multiplying task id number by 10
|
||||
*
|
||||
* Upon completion of the overall test, all results are printed out.
|
||||
* Test could be repeated with various number of calculator tasks.
|
||||
* All that needs to change is data definitions - code is completely agnostic of number of tasks
|
||||
*/
|
||||
|
||||
#define _TASK_SLEEP_ON_IDLE_RUN // Compile with support for entering IDLE SLEEP state for 1 ms if not tasks are scheduled to run
|
||||
#define _TASK_WDT_IDS // Compile with support for Task IDs and Watchdog timer
|
||||
#define _TASK_LTS_POINTER // Compile with support for Local Task Storage pointer
|
||||
#include <TaskScheduler.h>
|
||||
|
||||
// Overall number of calculator tasks:
|
||||
#define NO_TASKS 3
|
||||
|
||||
Scheduler ts;
|
||||
|
||||
// Callback methods prototypes
|
||||
void Calculate(); bool CalcOn();
|
||||
bool WrapperOn(); void WrapperOff();
|
||||
|
||||
// Tasks
|
||||
// Calculator tasks.
|
||||
// Note that all three tasks use the same callback methods
|
||||
// They will be updating specific variables based on the
|
||||
// Locat Task Storage pointers
|
||||
Task t1(TASK_SECOND, TASK_FOREVER, &Calculate, &ts, false, &CalcOn);
|
||||
Task t2(TASK_SECOND, TASK_FOREVER, &Calculate, &ts, false, &CalcOn);
|
||||
Task t3(TASK_SECOND, TASK_FOREVER, &Calculate, &ts, false, &CalcOn);
|
||||
// add more calc tasks here if necessary
|
||||
|
||||
Task tWrapper(5*TASK_SECOND, TASK_ONCE, NULL, &ts, false, &WrapperOn, &WrapperOff);
|
||||
|
||||
// The below structure is an object referenced by LTS pointer
|
||||
typedef struct {
|
||||
unsigned int id;
|
||||
long sum;
|
||||
long product;
|
||||
} task_var;
|
||||
|
||||
// These are actual structures which hold tasks specific values
|
||||
task_var v1;
|
||||
task_var v2;
|
||||
task_var v3;
|
||||
|
||||
// Arrays below allow indexed access to specific tasks and tasks variables
|
||||
Task *tasks[] = { &t1, &t2, &t3 };
|
||||
task_var *vars[] = { &v1, &v2, &v3 };
|
||||
|
||||
|
||||
/**
|
||||
* This method is called when a wrapper task is enabled
|
||||
* The purpose is to supply LTS pointers to all the tasks
|
||||
*/
|
||||
bool WrapperOn() {
|
||||
|
||||
for (int i=0; i < NO_TASKS; i++) {
|
||||
Task& T = *tasks[i];
|
||||
T.setLtsPointer( vars[i] );
|
||||
T.enableDelayed();
|
||||
}
|
||||
|
||||
return true; // Signal that Task could be enabled
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called when Wrapper task is disabled (after first and only iteration is executed)
|
||||
* For each of the calculor tasks the results are printed out.
|
||||
*/
|
||||
void WrapperOff() {
|
||||
Serial.println("Finished processing");
|
||||
|
||||
ts.disableAll();
|
||||
|
||||
for (int i=0; i < NO_TASKS; i++) {
|
||||
Serial.print("ID: "); Serial.println(vars[i]->id);
|
||||
Serial.print("Sum: "); Serial.println(vars[i]->sum);
|
||||
Serial.print("Product: "); Serial.println(vars[i]->product);
|
||||
Serial.println();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method is executed when each calculator task is enabled
|
||||
* The purpose is to initiate all local variables
|
||||
*/
|
||||
bool CalcOn() {
|
||||
Task& T = ts.currentTask();
|
||||
task_var& var = *((task_var*) T.getLtsPointer());
|
||||
|
||||
// Initialize local variables
|
||||
var.id = T.getId();
|
||||
var.sum = 0;
|
||||
var.product = var.id;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method performs simple calculations on task's local variables
|
||||
*/
|
||||
void Calculate() {
|
||||
Task& T = ts.currentTask();
|
||||
// Another way to get to LTS pointer:
|
||||
task_var& var = *((task_var*) ts.currentLts());
|
||||
|
||||
|
||||
Serial.print("Calculating for task: ");
|
||||
Serial.print(T.getId());
|
||||
Serial.print("; Task id per LTS is: ");
|
||||
Serial.println( var.id );
|
||||
|
||||
var.sum += T.getId();
|
||||
var.product = var.product * 10;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Standard Arduino setup and loop methods
|
||||
*/
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
randomSeed(analogRead(0)+analogRead(5));
|
||||
|
||||
pinMode(13, OUTPUT);
|
||||
digitalWrite(13, LOW);
|
||||
|
||||
Serial.println("Local Task Storage pointer test");
|
||||
|
||||
tWrapper.enableDelayed();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
ts.execute();
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/**
|
||||
* TaskScheduler Test
|
||||
* Illustration of use of Time Critical Information
|
||||
*
|
||||
* Task1 runs every 1 second indefinitely
|
||||
* On each run it reports how delayed the invokation of the callback method was,
|
||||
* and what was the scheduling overun.
|
||||
* Each run task 1 is dealyed randomly for up to 2 seconds, thus simulating scheduling overrun
|
||||
*/
|
||||
|
||||
#define _TASK_TIMECRITICAL
|
||||
#define _TASK_SLEEP_ON_IDLE_RUN
|
||||
#include <TaskScheduler.h>
|
||||
|
||||
// Callback methods prototypes
|
||||
void t1Callback();
|
||||
|
||||
|
||||
//Tasks
|
||||
Task t1(1000, -1, &t1Callback);
|
||||
|
||||
Scheduler runner;
|
||||
|
||||
|
||||
void t1Callback() {
|
||||
Serial.print(millis());
|
||||
Serial.print(": overrun = ");
|
||||
Serial.print(t1.getOverrun());
|
||||
Serial.print(", start delayed by ");
|
||||
Serial.println(t1.getStartDelay());
|
||||
|
||||
int i = random(2000);
|
||||
Serial.print("Delaying for "); Serial.println(i);
|
||||
delay(i);
|
||||
}
|
||||
|
||||
void setup () {
|
||||
Serial.begin(115200);
|
||||
Serial.println("Scheduler TimeCritical TEST");
|
||||
|
||||
runner.init();
|
||||
Serial.println("Initialized scheduler");
|
||||
|
||||
runner.addTask(t1);
|
||||
Serial.println("added t1. Waiting for 5 seconds.");
|
||||
|
||||
delay(5000);
|
||||
|
||||
t1.enable();
|
||||
|
||||
Serial.println("Enabled t1");
|
||||
}
|
||||
|
||||
|
||||
void loop () {
|
||||
runner.execute();
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
|
||||
/**
|
||||
* This is a test to benchmark TaskScheduler execution.
|
||||
*
|
||||
* This test executes 1,000,000 cycles of a task with empty callback method
|
||||
* Compiled with different options, you can assess the impact of each on the size of the Task object
|
||||
* and the execution overhead of the main execution pass route.
|
||||
*
|
||||
* Sample execution times (in milliseconds per 1M iterations) are provided below.
|
||||
* The test board is Arduino UNO 16MHz processor.
|
||||
*
|
||||
|
||||
TaskScheduler 2.1.0:
|
||||
No modifiers
|
||||
Duration=19869
|
||||
|
||||
with SLEEP
|
||||
Duration=20058
|
||||
|
||||
with status request:
|
||||
Duration=20058
|
||||
|
||||
with time critical:
|
||||
Duration=27289
|
||||
|
||||
|
||||
TaskScheduler 1.9.0:
|
||||
No modifiers
|
||||
Duration=15656
|
||||
|
||||
with SLEEP
|
||||
Duration=16285
|
||||
|
||||
with status request:
|
||||
Duration=16600
|
||||
|
||||
with rollover fix:
|
||||
Duration=18109
|
||||
|
||||
|
||||
TaskScheduler 1.8.5:
|
||||
Duration=15719
|
||||
|
||||
with SLEEP
|
||||
Duration=16348
|
||||
|
||||
with status request:
|
||||
Duration=18360
|
||||
|
||||
with rollover fix:
|
||||
Duration=18423
|
||||
|
||||
*/
|
||||
|
||||
|
||||
//#define _TASK_TIMECRITICAL // Enable monitoring scheduling overruns
|
||||
//#define _TASK_STATUS_REQUEST // Compile with support for StatusRequest functionality - triggering tasks on status change events in addition to time only
|
||||
//#define _TASK_WDT_IDS // Compile with support for wdt control points and task ids
|
||||
//#define _TASK_LTS_POINTER // Compile with support for local task storage pointer
|
||||
//#define _TASK_SLEEP_ON_IDLE_RUN
|
||||
//#define _TASK_MICRO_RES
|
||||
#include <TaskScheduler.h>
|
||||
|
||||
Scheduler ts;
|
||||
|
||||
// Callback methods prototypes
|
||||
bool tOn(); void tOff();
|
||||
void callback();
|
||||
|
||||
// Tasks
|
||||
Task t(TASK_IMMEDIATE, 1000000, &callback, &ts, false, &tOn, &tOff);
|
||||
|
||||
unsigned long c1, c2;
|
||||
|
||||
bool tOn() {
|
||||
c1 = millis();
|
||||
c2 = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void tOff() {
|
||||
c2 = millis();
|
||||
Serial.println("done.");
|
||||
Serial.print("Tstart =");Serial.println(c1);
|
||||
Serial.print("Tfinish=");Serial.println(c2);
|
||||
Serial.print("Duration=");Serial.println(c2-c1);
|
||||
}
|
||||
|
||||
void setup() {
|
||||
// put your setup code here, to run once:
|
||||
Serial.begin(115200);
|
||||
Serial.print("Start...");
|
||||
|
||||
t.enable();
|
||||
}
|
||||
|
||||
void callback() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
void loop() {
|
||||
// put your main code here, to run repeatedly:
|
||||
ts.execute();
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
/**
|
||||
* This is a test of TaskScheduler layered priority funtionality
|
||||
*
|
||||
* Current test employs two priority layers:
|
||||
* Base scheduler runs tasks t1, t2 and t3
|
||||
* High priority scheduler runs tasks t4 and t5
|
||||
*
|
||||
* Sequence of task scheduling (not execution!) is:
|
||||
* 4, 5, 1, 4, 5, 2, 4, 5, 3 = one base scheduler pass
|
||||
*
|
||||
* Scheduling overhead (at 20 micros per one pass) is: (B + B * H) * T = (3 + 3 * 2) * 18 = 162 micros
|
||||
* where
|
||||
* B - number of tasks in the base scheduler's chain
|
||||
* H - number of tasks in the high priority scheduler's chain
|
||||
* T - scheduling overhead for 1 pass (~15-18 microseconds)
|
||||
*
|
||||
* Actual task execution order:
|
||||
|
||||
Scheduler Priority Test
|
||||
Task: 40: 0 Start delay = 0
|
||||
Task: 50: 10 Start delay = 10
|
||||
Task: 1: 21 Start delay = 21
|
||||
Task: 2: 31 Start delay = 31
|
||||
Task: 3: 41 Start delay = 41
|
||||
|
||||
Task: 40: 500 Start delay = 0
|
||||
Task: 40: 1000 Start delay = 0
|
||||
Task: 50: 1010 Start delay = 10
|
||||
Task: 1: 1021 Start delay = 20
|
||||
Task: 40: 1500 Start delay = 0
|
||||
Task: 40: 2000 Start delay = 0
|
||||
Task: 50: 2011 Start delay = 11
|
||||
Task: 1: 2022 Start delay = 21
|
||||
Task: 2: 2032 Start delay = 32
|
||||
Task: 40: 2500 Start delay = 0
|
||||
Task: 40: 3000 Start delay = 0
|
||||
Task: 50: 3010 Start delay = 10
|
||||
Task: 1: 3021 Start delay = 20
|
||||
Task: 3: 3032 Start delay = 32
|
||||
|
||||
Task: 40: 3500 Start delay = 0
|
||||
Task: 40: 4000 Start delay = 0
|
||||
Task: 50: 4011 Start delay = 11
|
||||
Task: 1: 4022 Start delay = 21
|
||||
Task: 2: 4032 Start delay = 32
|
||||
Task: 40: 4500 Start delay = 0
|
||||
Task: 40: 5000 Start delay = 0
|
||||
Task: 50: 5010 Start delay = 10
|
||||
Task: 1: 5021 Start delay = 20
|
||||
Task: 40: 5500 Start delay = 0
|
||||
Task: 40: 6000 Start delay = 0
|
||||
Task: 50: 6010 Start delay = 10
|
||||
Task: 1: 6022 Start delay = 21
|
||||
Task: 2: 6032 Start delay = 32
|
||||
Task: 3: 6043 Start delay = 42
|
||||
|
||||
*/
|
||||
|
||||
#define _TASK_SLEEP_ON_IDLE_RUN
|
||||
#define _TASK_PRIORITY
|
||||
#define _TASK_WDT_IDS
|
||||
#define _TASK_TIMECRITICAL
|
||||
#include <TaskScheduler.h>
|
||||
|
||||
Scheduler r, hpr;
|
||||
|
||||
// Callback methods prototypes
|
||||
void tCallback();
|
||||
|
||||
// Tasks
|
||||
Task t1(1000, TASK_FOREVER, &tCallback, &r); //adding task to the chain on creation
|
||||
Task t2(2000, TASK_FOREVER, &tCallback, &r);
|
||||
Task t3(3000, TASK_FOREVER, &tCallback, &r);
|
||||
|
||||
Task t4(500, TASK_FOREVER, &tCallback, &hpr); //adding task to the chain on creation
|
||||
Task t5(1000, TASK_FOREVER, &tCallback, &hpr); //adding task to the chain on creation
|
||||
|
||||
void tCallback() {
|
||||
Scheduler &s = Scheduler::currentScheduler();
|
||||
Task &t = s.currentTask();
|
||||
|
||||
Serial.print("Task: "); Serial.print(t.getId());Serial.print(":\t");
|
||||
Serial.print(millis()); Serial.print("\tStart delay = "); Serial.println(t.getStartDelay());
|
||||
delay(10);
|
||||
|
||||
if (t.getId() == 3) Serial.println();
|
||||
}
|
||||
|
||||
void setup () {
|
||||
Serial.begin(115200);
|
||||
Serial.println("Scheduler Priority Test");
|
||||
|
||||
t4.setId(40);
|
||||
t5.setId(50);
|
||||
|
||||
r.setHighPriorityScheduler(&hpr);
|
||||
r.enableAll(true); // this will recursively enable the higher priority tasks as well
|
||||
}
|
||||
|
||||
|
||||
void loop () {
|
||||
|
||||
r.execute();
|
||||
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
/**
|
||||
* This is a test of TaskScheduler layered priority funtionality
|
||||
*
|
||||
* Current test employs three priority layers:
|
||||
* Base scheduler runs tasks t1, t2 and t3
|
||||
* High priority scheduler runs tasks t4 and t5
|
||||
* Highest priority scheduler runs tasks t6 and t7
|
||||
*
|
||||
* Sequence of task scheduling (not execution!) is:
|
||||
* 6, 7, 4, 6, 7, 5, 1, 6, 7, 4, 6, 7, 5, 2, 6, 7, 4, 6, 7, 5, 3 = one base scheduler pass
|
||||
*
|
||||
* Scheduling overhead (at 20 micros per one pass) is: (B + B * H + B * H * C) * T = (3 + 3 * 2 + 3 * 2 * 2) * 18 = 378 micros
|
||||
* where
|
||||
* B - number of tasks in the base scheduler's chain
|
||||
* H - number of tasks in the high priority scheduler's chain
|
||||
* C - number of tasks in the critical priority scheduler's chain
|
||||
* T - scheduling overhead for 1 pass (~15-18 microseconds)
|
||||
*
|
||||
* Actual task execution order:
|
||||
|
||||
Scheduler Priority Test
|
||||
Task: 600: 0 Start delay = 0
|
||||
Task: 700: 10 Start delay = 10
|
||||
Task: 40: 21 Start delay = 21
|
||||
Task: 50: 31 Start delay = 31
|
||||
Task: 1: 43 Start delay = 41
|
||||
Task: 2: 53 Start delay = 53
|
||||
Task: 3: 63 Start delay = 63
|
||||
|
||||
Task: 600: 500 Start delay = 0
|
||||
Task: 40: 510 Start delay = 10
|
||||
Task: 600: 1000 Start delay = 0
|
||||
Task: 700: 1010 Start delay = 10
|
||||
Task: 40: 1021 Start delay = 21
|
||||
Task: 50: 1032 Start delay = 32
|
||||
Task: 1: 1043 Start delay = 43
|
||||
Task: 600: 1500 Start delay = 0
|
||||
Task: 40: 1510 Start delay = 10
|
||||
Task: 600: 2000 Start delay = 0
|
||||
Task: 700: 2011 Start delay = 11
|
||||
Task: 40: 2022 Start delay = 22
|
||||
Task: 50: 2032 Start delay = 32
|
||||
Task: 1: 2043 Start delay = 43
|
||||
Task: 2: 2054 Start delay = 54
|
||||
Task: 600: 2500 Start delay = 0
|
||||
Task: 40: 2510 Start delay = 10
|
||||
Task: 600: 3000 Start delay = 0
|
||||
Task: 700: 3010 Start delay = 10
|
||||
Task: 40: 3021 Start delay = 21
|
||||
Task: 50: 3032 Start delay = 32
|
||||
Task: 1: 3043 Start delay = 43
|
||||
Task: 3: 3053 Start delay = 53
|
||||
|
||||
Task: 600: 3500 Start delay = 0
|
||||
Task: 40: 3510 Start delay = 10
|
||||
Task: 600: 4000 Start delay = 0
|
||||
Task: 700: 4011 Start delay = 11
|
||||
Task: 40: 4022 Start delay = 22
|
||||
Task: 50: 4032 Start delay = 32
|
||||
Task: 1: 4043 Start delay = 43
|
||||
Task: 2: 4054 Start delay = 54
|
||||
Task: 600: 4500 Start delay = 0
|
||||
Task: 40: 4510 Start delay = 10
|
||||
Task: 600: 5000 Start delay = 0
|
||||
Task: 700: 5010 Start delay = 10
|
||||
Task: 40: 5021 Start delay = 21
|
||||
Task: 50: 5031 Start delay = 31
|
||||
Task: 1: 5043 Start delay = 43
|
||||
Task: 600: 5500 Start delay = 0
|
||||
Task: 40: 5511 Start delay = 11
|
||||
Task: 600: 6000 Start delay = 0
|
||||
Task: 700: 6010 Start delay = 10
|
||||
Task: 40: 6022 Start delay = 22
|
||||
Task: 50: 6032 Start delay = 32
|
||||
Task: 1: 6043 Start delay = 43
|
||||
Task: 2: 6053 Start delay = 53
|
||||
Task: 3: 6065 Start delay = 65
|
||||
|
||||
*/
|
||||
|
||||
#define _TASK_SLEEP_ON_IDLE_RUN
|
||||
#define _TASK_PRIORITY
|
||||
#define _TASK_WDT_IDS
|
||||
#define _TASK_TIMECRITICAL
|
||||
#include <TaskScheduler.h>
|
||||
|
||||
Scheduler r;
|
||||
Scheduler hpr;
|
||||
Scheduler cpr;
|
||||
|
||||
// Callback methods prototypes
|
||||
void tCallback();
|
||||
|
||||
// Tasks
|
||||
Task t1(1000, TASK_FOREVER, &tCallback, &r); //adding task to the chain on creation
|
||||
Task t2(2000, TASK_FOREVER, &tCallback, &r);
|
||||
Task t3(3000, TASK_FOREVER, &tCallback, &r);
|
||||
|
||||
Task t4(500, TASK_FOREVER, &tCallback, &hpr); //adding task to the chain on creation
|
||||
Task t5(1000, TASK_FOREVER, &tCallback, &hpr); //adding task to the chain on creation
|
||||
|
||||
Task t6(500, TASK_FOREVER, &tCallback, &cpr); //adding task to the chain on creation
|
||||
Task t7(1000, TASK_FOREVER, &tCallback, &cpr); //adding task to the chain on creation
|
||||
|
||||
void tCallback() {
|
||||
Scheduler &s = Scheduler::currentScheduler();
|
||||
Task &t = s.currentTask();
|
||||
|
||||
Serial.print("Task: "); Serial.print(t.getId());Serial.print(":\t");
|
||||
Serial.print(millis()); Serial.print("\tStart delay = "); Serial.println(t.getStartDelay());
|
||||
delay(10);
|
||||
|
||||
if (t.getId() == 3) Serial.println();
|
||||
}
|
||||
|
||||
void setup () {
|
||||
Serial.begin(115200);
|
||||
Serial.println("Scheduler Priority Test");
|
||||
|
||||
t4.setId(40);
|
||||
t5.setId(50);
|
||||
|
||||
t6.setId(600);
|
||||
t7.setId(700);
|
||||
|
||||
r.setHighPriorityScheduler(&hpr);
|
||||
hpr.setHighPriorityScheduler(&cpr);
|
||||
r.enableAll(true); // this will recursively enable the higher priority tasks as well
|
||||
}
|
||||
|
||||
|
||||
void loop () {
|
||||
|
||||
r.execute();
|
||||
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/**
|
||||
* TaskScheduler Test of microsecond scheduling resolution
|
||||
*
|
||||
* Task 1 runs starting with 211 microseconds intervals, doubling the interval on every iteration
|
||||
* until it wraps when interval reaches about 72 minutes mark
|
||||
*
|
||||
* Task 2 provides heartbeat at a steady 5 seconds intervals
|
||||
*
|
||||
*/
|
||||
|
||||
#define _TASK_MICRO_RES
|
||||
#include <TaskScheduler.h>
|
||||
|
||||
#define T1_INIT (211L)
|
||||
|
||||
Scheduler runner;
|
||||
// Callback methods prototypes
|
||||
void t1Callback();
|
||||
void t1OnDisable();
|
||||
void t2Callback();
|
||||
|
||||
|
||||
unsigned long t1_interval = T1_INIT;
|
||||
|
||||
// Tasks
|
||||
Task t1(t1_interval, 1, &t1Callback, &runner, true, NULL, &t1OnDisable); //adding task to the chain on creation
|
||||
Task t2(5 * TASK_SECOND, TASK_FOREVER, &t2Callback, &runner, true); //adding task to the chain on creation
|
||||
|
||||
|
||||
|
||||
void t1Callback() {
|
||||
unsigned long t = micros();
|
||||
Serial.print("t1: ");
|
||||
Serial.println(t);
|
||||
}
|
||||
|
||||
void t1OnDisable() {
|
||||
t1_interval += t1_interval;
|
||||
if (t1_interval < T1_INIT) t1_interval = T1_INIT;
|
||||
t1.setInterval(t1_interval);
|
||||
t1.restartDelayed();
|
||||
}
|
||||
|
||||
void t2Callback() {
|
||||
unsigned long t = micros();
|
||||
Serial.print("t2: ");
|
||||
Serial.print(t);
|
||||
Serial.println(" heartbeat");
|
||||
}
|
||||
|
||||
|
||||
void setup () {
|
||||
Serial.begin(115200);
|
||||
Serial.println("Scheduler TEST Microsecond Resolution");
|
||||
|
||||
Serial.println("5 seconds delay");
|
||||
delay(5000);
|
||||
|
||||
runner.startNow(); // This creates a new scheduling starting point for all ACTIVE tasks.
|
||||
// PLEASE NOTE - THIS METHOD DOES NOT ACTIVATE TASKS, JUST RESETS THE START TIME
|
||||
t1.delay(); // Tasks which need to start delayed, need to be delayed again after startNow();
|
||||
|
||||
// Alternatively, tasks should be just enabled at the bottom of setup() method
|
||||
// runner.enableAll();
|
||||
// t1.delay();
|
||||
}
|
||||
|
||||
|
||||
void loop () {
|
||||
runner.execute();
|
||||
}
|
|
@ -0,0 +1,339 @@
|
|||
/**
|
||||
This test illustrates the use if yield methods and internal StatusRequest objects
|
||||
THIS TEST HAS BEEN TESTED ON NODEMCU V.2 (ESP8266)
|
||||
|
||||
The WiFi initialization and NTP update is executed in parallel to blinking the onboard LED
|
||||
and an external LED connected to D2 (GPIO04)
|
||||
Try running with and without correct WiFi parameters to observe the difference in behaviour
|
||||
*/
|
||||
|
||||
#define _TASK_SLEEP_ON_IDLE_RUN
|
||||
#define _TASK_STATUS_REQUEST
|
||||
#include <TaskScheduler.h>
|
||||
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <WiFiUdp.h>
|
||||
|
||||
Scheduler ts;
|
||||
|
||||
// Callback methods prototypes
|
||||
void connectInit();
|
||||
void ledCallback();
|
||||
bool ledOnEnable();
|
||||
void ledOnDisable();
|
||||
void ledOn();
|
||||
void ledOff();
|
||||
void ntpUpdateInit();
|
||||
|
||||
// Tasks
|
||||
|
||||
Task tConnect (TASK_SECOND, TASK_FOREVER, &connectInit, &ts, true);
|
||||
Task tLED (TASK_IMMEDIATE, TASK_FOREVER, &ledCallback, &ts, false, &ledOnEnable, &ledOnDisable);
|
||||
|
||||
// Tasks running on events
|
||||
Task tNtpUpdate (&ntpUpdateInit, &ts);
|
||||
|
||||
// Replace with WiFi parameters of your Access Point/Router:
|
||||
const char *ssid = "wifi_network";
|
||||
const char *pwd = "wifi_password";
|
||||
|
||||
long ledDelayOn, ledDelayOff;
|
||||
|
||||
#define LEDPIN D0 // Onboard LED pin - linked to WiFi
|
||||
#define LEDPIN2 D2 // External LED
|
||||
#define CONNECT_TIMEOUT 30 // Seconds
|
||||
#define CONNECT_OK 0 // Status of successful connection to WiFi
|
||||
#define CONNECT_FAILED (-99) // Status of failed connection to WiFi
|
||||
|
||||
// NTP Related Definitions
|
||||
#define NTP_PACKET_SIZE 48 // NTP time stamp is in the first 48 bytes of the message
|
||||
|
||||
IPAddress timeServerIP; // time.nist.gov NTP server address
|
||||
const char* ntpServerName = "time.nist.gov";
|
||||
byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets
|
||||
unsigned long epoch;
|
||||
|
||||
WiFiUDP udp; // A UDP instance to let us send and receive packets over UDP
|
||||
|
||||
#define LOCAL_NTP_PORT 2390 // Local UDP port for NTP update
|
||||
|
||||
|
||||
|
||||
void setup() {
|
||||
Serial.begin(74880);
|
||||
Serial.println(F("TaskScheduler test #14 - Yield and internal StatusRequests"));
|
||||
Serial.println(F("=========================================================="));
|
||||
Serial.println();
|
||||
|
||||
pinMode (LEDPIN, OUTPUT);
|
||||
pinMode (LEDPIN2, OUTPUT);
|
||||
|
||||
tNtpUpdate.waitFor( tConnect.getInternalStatusRequest() ); // NTP Task will start only after connection is made
|
||||
}
|
||||
|
||||
void loop() {
|
||||
ts.execute(); // Only Scheduler should be executed in the loop
|
||||
}
|
||||
|
||||
/**
|
||||
Initiate connection to the WiFi network
|
||||
*/
|
||||
void connectInit() {
|
||||
Serial.print(millis());
|
||||
Serial.println(F(": connectInit."));
|
||||
Serial.println(F("WiFi parameters: "));
|
||||
Serial.print(F("SSID: ")); Serial.println(ssid);
|
||||
Serial.print(F("PWD : ")); Serial.println(pwd);
|
||||
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.hostname("esp8266");
|
||||
WiFi.begin(ssid, pwd);
|
||||
yield();
|
||||
|
||||
ledDelayOn = TASK_SECOND / 2;
|
||||
ledDelayOff = TASK_SECOND / 4;
|
||||
tLED.enable();
|
||||
|
||||
tConnect.yield(&connectCheck); // This will pass control back to Scheduler and then continue with connection checking
|
||||
}
|
||||
|
||||
/**
|
||||
Periodically check if connected to WiFi
|
||||
Re-request connection every 5 seconds
|
||||
Stop trying after a timeout
|
||||
*/
|
||||
void connectCheck() {
|
||||
Serial.print(millis());
|
||||
Serial.println(F(": connectCheck."));
|
||||
|
||||
if (WiFi.status() == WL_CONNECTED) { // Connection established
|
||||
Serial.print(millis());
|
||||
Serial.print(F(": Connected to AP. Local ip: "));
|
||||
Serial.println(WiFi.localIP());
|
||||
tConnect.disable();
|
||||
}
|
||||
else {
|
||||
|
||||
if (tConnect.getRunCounter() % 5 == 0) { // re-request connection every 5 seconds
|
||||
|
||||
Serial.print(millis());
|
||||
Serial.println(F(": Re-requesting connection to AP..."));
|
||||
|
||||
WiFi.disconnect(true);
|
||||
yield(); // This is an esp8266 standard yield to allow linux wifi stack run
|
||||
WiFi.hostname("esp8266");
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(ssid, pwd);
|
||||
yield(); // This is an esp8266 standard yield to allow linux wifi stack run
|
||||
}
|
||||
|
||||
if (tConnect.getRunCounter() == CONNECT_TIMEOUT) { // Connection Timeout
|
||||
tConnect.getInternalStatusRequest()->signal(CONNECT_FAILED); // Signal unsuccessful completion
|
||||
tConnect.disable();
|
||||
|
||||
Serial.print(millis());
|
||||
Serial.println(F(": connectOnDisable."));
|
||||
Serial.print(millis());
|
||||
Serial.println(F(": Unable to connect to WiFi."));
|
||||
|
||||
ledDelayOn = TASK_SECOND / 16; // Blink LEDs quickly due to error
|
||||
ledDelayOff = TASK_SECOND / 16;
|
||||
tLED.enable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Initiate NTP update if connection was established
|
||||
*/
|
||||
void ntpUpdateInit() {
|
||||
Serial.print(millis());
|
||||
Serial.println(F(": ntpUpdateInit."));
|
||||
|
||||
if ( tConnect.getInternalStatusRequest()->getStatus() != CONNECT_OK ) { // Check status of the Connect Task
|
||||
Serial.print(millis());
|
||||
Serial.println(F(": cannot update NTP - not connected."));
|
||||
return;
|
||||
}
|
||||
|
||||
udp.begin(LOCAL_NTP_PORT);
|
||||
if ( WiFi.hostByName(ntpServerName, timeServerIP) ) { //get a random server from the pool
|
||||
|
||||
Serial.print(millis());
|
||||
Serial.print(F(": timeServerIP = "));
|
||||
Serial.println(timeServerIP);
|
||||
|
||||
sendNTPpacket(timeServerIP); // send an NTP packet to a time server
|
||||
}
|
||||
else {
|
||||
Serial.print(millis());
|
||||
Serial.println(F(": NTP server address lookup failed."));
|
||||
tLED.disable();
|
||||
udp.stop();
|
||||
tNtpUpdate.disable();
|
||||
return;
|
||||
}
|
||||
|
||||
ledDelayOn = TASK_SECOND / 8;
|
||||
ledDelayOff = TASK_SECOND / 8;
|
||||
tLED.enable();
|
||||
|
||||
tNtpUpdate.set( TASK_SECOND, CONNECT_TIMEOUT, &ntpCheck );
|
||||
tNtpUpdate.enableDelayed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if NTP packet was received
|
||||
* Re-request every 5 seconds
|
||||
* Stop trying after a timeout
|
||||
*/
|
||||
void ntpCheck() {
|
||||
Serial.print(millis());
|
||||
Serial.println(F(": ntpCheck."));
|
||||
|
||||
if ( tNtpUpdate.getRunCounter() % 5 == 0) {
|
||||
|
||||
Serial.print(millis());
|
||||
Serial.println(F(": Re-requesting NTP update..."));
|
||||
|
||||
udp.stop();
|
||||
yield();
|
||||
udp.begin(LOCAL_NTP_PORT);
|
||||
sendNTPpacket(timeServerIP);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( doNtpUpdateCheck()) {
|
||||
Serial.print(millis());
|
||||
Serial.println(F(": NTP Update successful"));
|
||||
|
||||
Serial.print(millis());
|
||||
Serial.print(F(": Unix time = "));
|
||||
Serial.println(epoch);
|
||||
|
||||
tLED.disable();
|
||||
tNtpUpdate.disable();
|
||||
udp.stop();
|
||||
}
|
||||
else {
|
||||
if ( tNtpUpdate.isLastIteration() ) {
|
||||
Serial.print(millis());
|
||||
Serial.println(F(": NTP Update failed"));
|
||||
tLED.disable();
|
||||
udp.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send NTP packet to NTP server
|
||||
*/
|
||||
unsigned long sendNTPpacket(IPAddress & address)
|
||||
{
|
||||
Serial.print(millis());
|
||||
Serial.println(F(": sendNTPpacket."));
|
||||
|
||||
// set all bytes in the buffer to 0
|
||||
memset(packetBuffer, 0, NTP_PACKET_SIZE);
|
||||
// Initialize values needed to form NTP request
|
||||
// (see URL above for details on the packets)
|
||||
packetBuffer[0] = 0b11100011; // LI, Version, Mode
|
||||
packetBuffer[1] = 0; // Stratum, or type of clock
|
||||
packetBuffer[2] = 6; // Polling Interval
|
||||
packetBuffer[3] = 0xEC; // Peer Clock Precision
|
||||
// 8 bytes of zero for Root Delay & Root Dispersion
|
||||
packetBuffer[12] = 49;
|
||||
packetBuffer[13] = 0x4E;
|
||||
packetBuffer[14] = 49;
|
||||
packetBuffer[15] = 52;
|
||||
|
||||
// all NTP fields have been given values, now
|
||||
// you can send a packet requesting a timestamp:
|
||||
udp.beginPacket(address, 123); //NTP requests are to port 123
|
||||
udp.write(packetBuffer, NTP_PACKET_SIZE);
|
||||
udp.endPacket();
|
||||
yield();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a packet was recieved.
|
||||
* Process NTP information if yes
|
||||
*/
|
||||
bool doNtpUpdateCheck() {
|
||||
|
||||
Serial.print(millis());
|
||||
Serial.println(F(": doNtpUpdateCheck."));
|
||||
|
||||
yield();
|
||||
int cb = udp.parsePacket();
|
||||
if (cb) {
|
||||
// We've received a packet, read the data from it
|
||||
udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer
|
||||
|
||||
//the timestamp starts at byte 40 of the received packet and is four bytes,
|
||||
// or two words, long. First, esxtract the two words:
|
||||
|
||||
unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
|
||||
unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
|
||||
// combine the four bytes (two words) into a long integer
|
||||
// this is NTP time (seconds since Jan 1 1900):
|
||||
unsigned long secsSince1900 = highWord << 16 | lowWord;
|
||||
|
||||
// now convert NTP time into everyday time:
|
||||
// Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
|
||||
const unsigned long seventyYears = 2208988800UL;
|
||||
// subtract seventy years:
|
||||
epoch = secsSince1900 - seventyYears;
|
||||
return (epoch != 0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flip the LED state based on the current state
|
||||
*/
|
||||
bool ledState;
|
||||
void ledCallback() {
|
||||
if ( ledState ) ledOff();
|
||||
else ledOn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure the LED starts lit
|
||||
*/
|
||||
bool ledOnEnable() {
|
||||
ledOn();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure LED ends dimmed
|
||||
*/
|
||||
void ledOnDisable() {
|
||||
ledOff();
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn LEDs on.
|
||||
* Set appropriate delay.
|
||||
* PLEASE NOTE: NodeMCU onbaord LED is active-low
|
||||
*/
|
||||
void ledOn() {
|
||||
ledState = true;
|
||||
digitalWrite(LEDPIN, LOW);
|
||||
digitalWrite(LEDPIN2, HIGH);
|
||||
tLED.delay( ledDelayOn );
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn LEDs off.
|
||||
* Set appropriate delay.
|
||||
* PLEASE NOTE: NodeMCU onbaord LED is active-low
|
||||
*/
|
||||
void ledOff() {
|
||||
ledState = false;
|
||||
digitalWrite(LEDPIN, HIGH);
|
||||
digitalWrite(LEDPIN2, LOW);
|
||||
tLED.delay( ledDelayOff );
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* TaskScheduler Test sketch - Showing how to use std::function
|
||||
* to get acces to variables from within the task callback function
|
||||
*
|
||||
* Support for std::function is only available for ESP8266 architecture
|
||||
*/
|
||||
#define _TASK_SLEEP_ON_IDLE_RUN
|
||||
#define _TASK_STD_FUNCTION // Compile with support for std::function
|
||||
#include <TaskScheduler.h>
|
||||
|
||||
Scheduler ts;
|
||||
int counter = 0;
|
||||
|
||||
class Calculator {
|
||||
public:
|
||||
int cumSum = 0; // cumulative sum
|
||||
Calculator(int b) {
|
||||
// Pass the this pointer, so that we get access to this->cumSum
|
||||
// Also pass a copy of b
|
||||
calculateTask.set(TASK_SECOND, TASK_FOREVER, [this, b]() {
|
||||
counter++;
|
||||
Serial.printf("%u. %u: cumSum = %u + %u\t", counter, millis(), cumSum, b);
|
||||
cumSum += b;
|
||||
Serial.printf("Resulting cumulative sum: %u\n", cumSum);
|
||||
});
|
||||
ts.addTask(calculateTask);
|
||||
calculateTask.enable();
|
||||
}
|
||||
|
||||
Task calculateTask;
|
||||
};
|
||||
|
||||
Calculator calc1(2);
|
||||
Calculator calc2(4);
|
||||
Calculator calc3(8);
|
||||
|
||||
// Disable tasks after 10 seconds
|
||||
Task tWrapper(10*TASK_SECOND, TASK_ONCE, []() {
|
||||
ts.disableAll();
|
||||
}, &ts);
|
||||
|
||||
/**
|
||||
* Standard Arduino setup and loop methods
|
||||
*/
|
||||
void setup() {
|
||||
Serial.begin(74880);
|
||||
Serial.println("std::function test");
|
||||
tWrapper.enableDelayed();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
ts.execute();
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
//This file is intentionally left blank.
|
||||
//
|
||||
//Arduino IDE plays some dirty tricks on the main sketch .ino file:
|
||||
//it rearranges #includes, blindly creates forward definitions,
|
||||
//includes every file in the project that does not have .c or .cpp
|
||||
//file extension.
|
||||
//
|
||||
//Usually it all turns well if you have only one source file and you are either
|
||||
//inexperienced or really expert C++ Arduino programmer.
|
||||
//For the folks with the middle ground skills level, when you want
|
||||
//to split your code into several .cpp files, it is best to leave
|
||||
//this main sketch empty.
|
||||
//
|
||||
//It doesn't matter where you define the void loop() and void setup().
|
||||
//Just make sure there is exactly one definition of each.
|
||||
//
|
||||
//And if you want to use standard Arduino functions
|
||||
//like digitalWrite or the Serial object - just add #include<Arduino.h>.
|
|
@ -0,0 +1,31 @@
|
|||
#include <Arduino.h>
|
||||
#include "header.hpp"
|
||||
|
||||
|
||||
//Declare the functions we want to use before we are ready to define them
|
||||
void t1Callback();
|
||||
|
||||
|
||||
// Tasks
|
||||
Task t1(2000, 10, &t1Callback, &runner, true); //adding task to the chain on creation
|
||||
Task t3(5000, TASK_FOREVER, &t3Callback);
|
||||
|
||||
|
||||
void t1Callback() {
|
||||
Serial.print("t1: ");
|
||||
Serial.println(millis());
|
||||
|
||||
if (t1.isFirstIteration()) {
|
||||
runner.addTask(t3);
|
||||
t3.enable();
|
||||
Serial.println("t1: enabled t3 and added to the chain");
|
||||
}
|
||||
|
||||
if (t1.isLastIteration()) {
|
||||
t3.disable();
|
||||
runner.deleteTask(t3);
|
||||
t2.setInterval(500);
|
||||
Serial.println("t1: disable t3 and delete it from the chain. t2 interval set to 500");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
// Test the same as example#2:
|
||||
// Initially only tasks 1 and 2 are enabled
|
||||
// Task1 runs every 2 seconds 10 times and then stops
|
||||
// Task2 runs every 3 seconds indefinitely
|
||||
// Task1 enables Task3 at its first run
|
||||
// Task3 run every 5 seconds
|
||||
// loop() runs every 1 second (a default scheduler delay, if no shorter tasks' interval is detected)
|
||||
// Task1 disables Task3 on its last iteration and changed Task2 to run every 1/2 seconds
|
||||
// Because Task2 interval is shorter than Scheduler default tick, loop() executes ecery 1/2 seconds now
|
||||
// At the end Task2 is the only task running every 1/2 seconds
|
||||
|
||||
|
||||
//Header that declares all shared objects between .cpp files
|
||||
#include "header.hpp"
|
||||
|
||||
#include <Arduino.h> //for Serial and delay
|
||||
|
||||
Scheduler runner; //Let the scheduler live here, in the main file, ok?
|
||||
|
||||
|
||||
//Pretend, that the t2 task is a special task,
|
||||
//that needs to live in file2 object file.
|
||||
void t2Callback() {
|
||||
Serial.print("t2: ");
|
||||
Serial.println(millis());
|
||||
}
|
||||
Task t2(3000, TASK_FOREVER, &t2Callback, &runner, true);
|
||||
|
||||
//Lets define t3Callback here. We are going to use it in file1
|
||||
//for Task 1.
|
||||
void t3Callback() {
|
||||
Serial.print("t3: ");
|
||||
Serial.println(millis());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void setup () {
|
||||
Serial.begin(115200);
|
||||
delay(5000);
|
||||
Serial.println("Scheduler TEST (multi-tab)");
|
||||
|
||||
runner.startNow(); // set point-in-time for scheduling start
|
||||
}
|
||||
|
||||
|
||||
void loop () {
|
||||
runner.execute();
|
||||
|
||||
// Serial.println("Loop ticks at: ");
|
||||
// Serial.println(millis());
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
//This is the place to declare every single function
|
||||
//and global variable that is going to be reused between cpp files.
|
||||
|
||||
|
||||
//We are going to use the TaskScheduler, but only the declarations part.
|
||||
//Remember to put customization macros before the #include:
|
||||
#define _TASK_SLEEP_ON_IDLE_RUN
|
||||
#include <TaskSchedulerDeclarations.h>
|
||||
|
||||
//Let the runner object be a global, single instance shared between object files.
|
||||
extern Scheduler runner;
|
||||
extern Task t2; //the t2 is defined in file2, but we need to access it from file1.
|
||||
|
||||
//This function needs to be shared (between file2 and file1).
|
||||
void t3Callback();
|
|
@ -0,0 +1,20 @@
|
|||
//This is the only .cpp file that gets the #include<TaskScheduler.h>.
|
||||
//Without it, the linker would not find necessary TaskScheduler's compiled code.
|
||||
//
|
||||
//Remember to put customization macros here as well.
|
||||
//
|
||||
//And don't import any common headers (here: header.hpp)
|
||||
//
|
||||
//Really. This file needs to be short. All stuff is in TaskScheduler.h.
|
||||
|
||||
// #define _TASK_TIMECRITICAL // Enable monitoring scheduling overruns
|
||||
#define _TASK_SLEEP_ON_IDLE_RUN // Enable 1 ms SLEEP_IDLE powerdowns between tasks if no callback methods were invoked during the pass
|
||||
// #define _TASK_STATUS_REQUEST // Compile with support for StatusRequest functionality - triggering tasks on status change events in addition to time only
|
||||
// #define _TASK_WDT_IDS // Compile with support for wdt control points and task ids
|
||||
// #define _TASK_LTS_POINTER // Compile with support for local task storage pointer
|
||||
// #define _TASK_PRIORITY // Support for layered scheduling priority
|
||||
// #define _TASK_MICRO_RES // Support for microsecond resolution
|
||||
// #define _TASK_STD_FUNCTION // Support for std::function (ESP8266 ONLY)
|
||||
// #define _TASK_DEBUG // Make all methods and variables public for debug purposes
|
||||
|
||||
#include <TaskScheduler.h>
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
This eaxmple illustrates the use of overall Task timeout functionality:
|
||||
|
||||
Task 1 - runs every 1 seconds and times out in 10 seconds
|
||||
Task 2 - runs every 5 seconds and resets the timeout every run, so runs continuosly even though the timeout is set to 10 seconds
|
||||
*/
|
||||
|
||||
|
||||
// #define _TASK_TIMECRITICAL // Enable monitoring scheduling overruns
|
||||
#define _TASK_SLEEP_ON_IDLE_RUN // Enable 1 ms SLEEP_IDLE powerdowns between tasks if no callback methods were invoked during the pass
|
||||
//#define _TASK_STATUS_REQUEST // Compile with support for StatusRequest functionality - triggering tasks on status change events in addition to time only
|
||||
// #define _TASK_WDT_IDS // Compile with support for wdt control points and task ids
|
||||
// #define _TASK_LTS_POINTER // Compile with support for local task storage pointer
|
||||
// #define _TASK_PRIORITY // Support for layered scheduling priority
|
||||
// #define _TASK_MICRO_RES // Support for microsecond resolution
|
||||
// #define _TASK_STD_FUNCTION // Support for std::function (ESP8266 ONLY)
|
||||
// #define _TASK_DEBUG // Make all methods and variables public for debug purposes
|
||||
// #define _TASK_INLINE // Make all methods "inline" - needed to support some multi-tab, multi-file implementations
|
||||
#define _TASK_TIMEOUT
|
||||
|
||||
#include <TaskScheduler.h>
|
||||
|
||||
Scheduler ts;
|
||||
|
||||
void task1Callback();
|
||||
void task1OnDisable();
|
||||
void task2Callback();
|
||||
void task2OnDisable();
|
||||
|
||||
Task t1(1 * TASK_SECOND, TASK_FOREVER, &task1Callback, &ts, false, NULL, &task1OnDisable);
|
||||
Task t2(5 * TASK_SECOND, TASK_FOREVER, &task2Callback, &ts, false, NULL, &task2OnDisable);
|
||||
|
||||
void setup() {
|
||||
// put your setup code here, to run once:
|
||||
Serial.begin(115200);
|
||||
|
||||
Serial.println("TaskScheduler Timeout example");
|
||||
Serial.println("=============================");
|
||||
|
||||
t1.setTimeout(10 * TASK_SECOND);
|
||||
t2.setTimeout(10 * TASK_SECOND);
|
||||
|
||||
ts.enableAll();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// put your main code here, to run repeatedly:
|
||||
ts.execute();
|
||||
}
|
||||
|
||||
|
||||
void task1Callback() {
|
||||
Serial.print("Task 1:\t");
|
||||
Serial.print(millis());
|
||||
Serial.print(": t/out=");
|
||||
Serial.print(t1.getTimeout());
|
||||
Serial.print("\tms until t/out=");
|
||||
Serial.println( t1.untilTimeout());
|
||||
|
||||
}
|
||||
void task1OnDisable() {
|
||||
if (t1.timedOut()) {
|
||||
Serial.println("Task 1 has timed out. Restarting");
|
||||
t1.setInterval(1 * TASK_SECOND);
|
||||
t1.setIterations(15);
|
||||
t1.setTimeout(TASK_NOTIMEOUT);
|
||||
t1.enable();
|
||||
}
|
||||
else {
|
||||
Serial.println("Task 1 has been disabled");
|
||||
}
|
||||
}
|
||||
|
||||
void task2Callback() {
|
||||
Serial.print("Task 2:\t");
|
||||
Serial.print(millis());
|
||||
Serial.print(": t/out=");
|
||||
Serial.print(t2.getTimeout());
|
||||
Serial.print("\tms until t/out=");
|
||||
Serial.println( t2.untilTimeout());
|
||||
t2.resetTimeout();
|
||||
}
|
||||
void task2OnDisable() {
|
||||
if (t2.timedOut()) {
|
||||
Serial.println("Task 2 has timed out");
|
||||
}
|
||||
else {
|
||||
Serial.println("Task 2 has been disabled");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,172 @@
|
|||
/**
|
||||
This is example 5 rewritten with Timeout, LTS and WDT functioanlity:
|
||||
- 1 second timeout is set for the main calculation task
|
||||
- LTS is used to address individual array elements for each sensor sinlce the callback code is shared
|
||||
- WDT is used to set the Task ID and use that as an index for array of distances (alternative to LTS)
|
||||
|
||||
Original description:
|
||||
====================
|
||||
This test emulates querying 3 sensors once every 10 seconds, each could respond with a different delay
|
||||
(ultrasonic sensors for instance) and printing a min value of the three when all three have reported their values.
|
||||
The overall timeout of 1 second is setup as well.
|
||||
An error message needs to be printed if a timeout occurred instead of a value.
|
||||
|
||||
Example5:
|
||||
Sketch uses 6066 bytes (18%) of program storage space. Maximum is 32256 bytes.
|
||||
Global variables use 1039 bytes (50%) of dynamic memory, leaving 1009 bytes for local variables. Maximum is 2048 bytes.
|
||||
Example 18:
|
||||
Sketch uses 5142 bytes (15%) of program storage space. Maximum is 32256 bytes.
|
||||
Global variables use 878 bytes (42%) of dynamic memory, leaving 1170 bytes for local variables. Maximum is 2048 bytes.
|
||||
*/
|
||||
|
||||
// #define _TASK_TIMECRITICAL // Enable monitoring scheduling overruns
|
||||
#define _TASK_SLEEP_ON_IDLE_RUN // Enable 1 ms SLEEP_IDLE powerdowns between tasks if no callback methods were invoked during the pass
|
||||
#define _TASK_STATUS_REQUEST // Compile with support for StatusRequest functionality - triggering tasks on status change events in addition to time only
|
||||
#define _TASK_WDT_IDS // Compile with support for wdt control points and task ids
|
||||
#define _TASK_LTS_POINTER // Compile with support for local task storage pointer
|
||||
#define _TASK_PRIORITY // Support for layered scheduling priority
|
||||
// #define _TASK_MICRO_RES // Support for microsecond resolution
|
||||
// #define _TASK_STD_FUNCTION // Support for std::function (ESP8266 and ESP32 ONLY)
|
||||
#define _TASK_DEBUG // Make all methods and variables public for debug purposes
|
||||
#define _TASK_INLINE // Make all methods "inline" - needed to support some multi-tab, multi-file implementations
|
||||
#define _TASK_TIMEOUT // Support for overall task timeout
|
||||
|
||||
#include <TaskScheduler.h>
|
||||
|
||||
StatusRequest measure;
|
||||
|
||||
Scheduler ts, hts;
|
||||
|
||||
// Callback methods prototypes
|
||||
void CycleCallback();
|
||||
void CalcCallback();
|
||||
bool CalcEnable();
|
||||
void CalcDisable();
|
||||
void SCallback(); bool SEnable();
|
||||
|
||||
// Tasks
|
||||
Task tSensor1(0, TASK_ONCE, &SCallback, &ts, false, &SEnable); // task ID = 1
|
||||
Task tSensor2(0, TASK_ONCE, &SCallback, &ts, false, &SEnable); // task ID = 2
|
||||
Task tSensor3(0, TASK_ONCE, &SCallback, &ts, false, &SEnable); // task ID = 3
|
||||
|
||||
Task tCycle(10000, TASK_FOREVER, &CycleCallback, &hts);
|
||||
Task tCalculate(TASK_IMMEDIATE , TASK_ONCE, &CalcCallback, &hts, false, &CalcEnable, &CalcDisable);
|
||||
|
||||
#define NO_OF_SENSORS 3
|
||||
long distance, d[NO_OF_SENSORS + 1], d_lts[NO_OF_SENSORS]; // d[] will be populated via task ID used as array indexes, d_lts will be addressed via LTS pointers
|
||||
|
||||
void CycleCallback() {
|
||||
Serial.println();
|
||||
Serial.print(millis()); Serial.print(":\t");
|
||||
Serial.println("CycleCallback: Initiating measurement cycle every 10 seconds");
|
||||
|
||||
distance = 0;
|
||||
measure.setWaiting(NO_OF_SENSORS); // Set the StatusRequest to wait for 3 signals.
|
||||
tCalculate.waitFor(&measure);
|
||||
}
|
||||
|
||||
bool CalcEnable() {
|
||||
Serial.print(millis()); Serial.print(":\t");
|
||||
Serial.println("CalcEnable: OnEnable");
|
||||
Serial.println("Activating sensors and setting timeout");
|
||||
|
||||
tSensor1.restartDelayed();
|
||||
tSensor2.restartDelayed();
|
||||
tSensor3.restartDelayed();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CalcDisable() {
|
||||
if (tCalculate.timedOut()) {
|
||||
measure.signalComplete(-1); // signal error
|
||||
Serial.print(millis()); Serial.print(":\t");
|
||||
Serial.println("MeasureCallback: ***** Timeout *****");
|
||||
// tSensor1.disable();
|
||||
// tSensor2.disable();
|
||||
// tSensor3.disable();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CalcCallback() {
|
||||
Serial.print(millis()); Serial.print(":\t");
|
||||
Serial.println("CalcCallback: calculating");
|
||||
distance = -1;
|
||||
if ( measure.getStatus() >= 0) { // only calculate if statusrequest ended successfully
|
||||
distance = d[1] < d[2] ? d[1] : d[2];
|
||||
distance = d[3] < distance ? d[3] : distance;
|
||||
Serial.print("CalcCallback: Min distance="); Serial.println(distance);
|
||||
Serial.println();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Simulation code for all sensors
|
||||
-------------------------------
|
||||
*/
|
||||
bool SEnable() {
|
||||
Task &t = ts.currentTask();
|
||||
int i = t.getId();
|
||||
|
||||
Serial.print(millis()); Serial.print(":\t");
|
||||
Serial.print("SEnable: TaskID=");
|
||||
Serial.println(i);
|
||||
Serial.print("Triggering sensor. Delay=");
|
||||
|
||||
t.setInterval( random(1200) ); // Simulating sensor delay, which could go over 1 second and cause timeout
|
||||
// One way to update the 3 distances with one codebase - use task id as an index
|
||||
d[i] = 0;
|
||||
|
||||
// Another way to update the 3 distances with one codebase - use LTS pointers
|
||||
int *pd = (int*) t.getLtsPointer();
|
||||
*pd = 0;
|
||||
|
||||
Serial.println( t.getInterval() );
|
||||
return true;
|
||||
}
|
||||
|
||||
void SCallback() {
|
||||
Task &t = ts.currentTask();
|
||||
int i = t.getId();
|
||||
|
||||
Serial.print(millis()); Serial.print(":\t");
|
||||
Serial.print("SCallback: TaskID=");
|
||||
Serial.println(i);
|
||||
Serial.print("Emulating measurement. d=");
|
||||
|
||||
d[i] = random(501); // pick a value from 0 to 500 "centimeters" simulating a measurement
|
||||
int *pd = (int*) t.getLtsPointer();
|
||||
*pd = d[i];
|
||||
|
||||
measure.signal();
|
||||
|
||||
Serial.print(d[i]);
|
||||
Serial.print("\t");
|
||||
Serial.println(*pd);
|
||||
}
|
||||
|
||||
/** Main Arduino code
|
||||
Not much is left here - everything is taken care of by the framework
|
||||
*/
|
||||
void setup() {
|
||||
|
||||
Serial.begin(115200);
|
||||
Serial.println("TaskScheduler StatusRequest Sensor Emulation Test. Complex Test.");
|
||||
randomSeed(analogRead(A0) + millis());
|
||||
|
||||
tSensor1.setLtsPointer(&d_lts[0]);
|
||||
tSensor2.setLtsPointer(&d_lts[1]);
|
||||
tSensor3.setLtsPointer(&d_lts[2]);
|
||||
|
||||
ts.setHighPriorityScheduler(&hts);
|
||||
|
||||
tCalculate.setTimeout(1 * TASK_SECOND);
|
||||
tCycle.enable();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
|
||||
ts.execute();
|
||||
|
||||
}
|
|
@ -0,0 +1,540 @@
|
|||
/**
|
||||
TaskScheduler Test sketch - test of Task destructor
|
||||
Test case:
|
||||
Main task runs every 100 milliseconds 100 times and in 50% cases generates a task object
|
||||
which runs 1 to 10 times with 100 ms to 5 s interval, and then destroyed.
|
||||
|
||||
This sketch uses a FreeMemory library: https://github.com/McNeight/MemoryFree
|
||||
*/
|
||||
|
||||
#define _TASK_WDT_IDS // To enable task unique IDs
|
||||
#define _TASK_SLEEP_ON_IDLE_RUN // Compile with support for entering IDLE SLEEP state for 1 ms if not tasks are scheduled to run
|
||||
#define _TASK_LTS_POINTER // Compile with support for Local Task Storage pointer
|
||||
#include <TaskScheduler.h>
|
||||
|
||||
#include <MemoryFree.h>
|
||||
|
||||
Scheduler ts;
|
||||
|
||||
// Callback methods prototypes
|
||||
void MainLoop();
|
||||
|
||||
// Statis task
|
||||
Task tMain(100*TASK_MILLISECOND, 100, MainLoop, &ts, true);
|
||||
|
||||
|
||||
void Iteration();
|
||||
bool OnEnable();
|
||||
void OnDisable();
|
||||
|
||||
int noOfTasks = 0;
|
||||
|
||||
void MainLoop() {
|
||||
Serial.print(millis()); Serial.print("\t");
|
||||
Serial.print("MainLoop run: ");
|
||||
int i = tMain.getRunCounter();
|
||||
Serial.print(i); Serial.print(F(".\t"));
|
||||
|
||||
if ( random(0, 101) > 50 ) { // generate a new task only in 50% of cases
|
||||
// Generating another task
|
||||
long p = random(100, 5001); // from 100 ms to 5 seconds
|
||||
long j = random(1, 11); // from 1 to 10 iterations)
|
||||
Task *t = new Task(p, j, Iteration, &ts, false, OnEnable, OnDisable);
|
||||
|
||||
Serial.print(F("Generated a new task:\t")); Serial.print(t->getId()); Serial.print(F("\tInt, Iter = \t"));
|
||||
Serial.print(p); Serial.print(", "); Serial.print(j); Serial.print(F("\tFree mem="));
|
||||
Serial.print(freeMemory()); Serial.print(F("\tNo of tasks=")); Serial.println(++noOfTasks);
|
||||
t->enable();
|
||||
}
|
||||
else {
|
||||
Serial.println(F("Skipped generating a task"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Iteration() {
|
||||
Task &t = ts.currentTask();
|
||||
|
||||
Serial.print(millis()); Serial.print("\t");
|
||||
Serial.print("Task N"); Serial.print(t.getId()); Serial.print(F("\tcurrent iteration: "));
|
||||
int i = t.getRunCounter();
|
||||
Serial.println(i);
|
||||
}
|
||||
|
||||
bool OnEnable() {
|
||||
// to-do: think of something to put in here.
|
||||
return true;
|
||||
}
|
||||
|
||||
void OnDisable() {
|
||||
Task *t = &ts.currentTask();
|
||||
unsigned int tid = t->getId();
|
||||
|
||||
delete t;
|
||||
Serial.print(millis()); Serial.print("\t");
|
||||
Serial.print("Task N"); Serial.print(tid); Serial.print(F("\tfinished and destroyed.\tFree mem="));
|
||||
Serial.print(freeMemory());Serial.print(F("\tNo of tasks=")); Serial.println(--noOfTasks);
|
||||
}
|
||||
|
||||
/**
|
||||
Standard Arduino setup and loop methods
|
||||
*/
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
randomSeed(analogRead(0) + analogRead(5));
|
||||
noOfTasks = 0;
|
||||
|
||||
Serial.println(F("Dynamic Task Creation/Destruction Example"));
|
||||
Serial.println();
|
||||
Serial.print(F("Free mem="));
|
||||
Serial.print(freeMemory()); Serial.print(F("\tNo of tasks=")); Serial.println(noOfTasks);
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
ts.execute();
|
||||
}
|
||||
|
||||
|
||||
/* Output on Arduino Uno:
|
||||
*
|
||||
* Compile:
|
||||
|
||||
Sketch uses 5312 bytes (16%) of program storage space. Maximum is 32256 bytes.
|
||||
Global variables use 282 bytes (13%) of dynamic memory, leaving 1766 bytes for local variables. Maximum is 2048 bytes.
|
||||
|
||||
* Execution:
|
||||
|
||||
Dynamic Task Creation/Destruction Example
|
||||
|
||||
Free mem=1758 No of tasks=0
|
||||
|
||||
1 MainLoop run: 1. Generated a new task: 2 Int, Iter = 421, 3 Free mem=1701 No of tasks=1
|
||||
8 Task N2 current iteration: 1
|
||||
100 MainLoop run: 2. Generated a new task: 3 Int, Iter = 4099, 9 Free mem=1656 No of tasks=2
|
||||
102 Task N3 current iteration: 1
|
||||
200 MainLoop run: 3. Skipped generating a task
|
||||
300 MainLoop run: 4. Generated a new task: 4 Int, Iter = 1795, 1 Free mem=1611 No of tasks=3
|
||||
302 Task N4 current iteration: 1
|
||||
305 Task N4 finished and destroyed. Free mem=1613 No of tasks=2
|
||||
400 MainLoop run: 5. Skipped generating a task
|
||||
429 Task N2 current iteration: 2
|
||||
500 MainLoop run: 6. Skipped generating a task
|
||||
600 MainLoop run: 7. Skipped generating a task
|
||||
700 MainLoop run: 8. Generated a new task: 5 Int, Iter = 4623, 7 Free mem=1611 No of tasks=3
|
||||
702 Task N5 current iteration: 1
|
||||
800 MainLoop run: 9. Generated a new task: 6 Int, Iter = 4987, 4 Free mem=1566 No of tasks=4
|
||||
802 Task N6 current iteration: 1
|
||||
850 Task N2 current iteration: 3
|
||||
850 Task N2 finished and destroyed. Free mem=1568 No of tasks=3
|
||||
900 MainLoop run: 10. Generated a new task: 7 Int, Iter = 600, 4 Free mem=1566 No of tasks=4
|
||||
902 Task N7 current iteration: 1
|
||||
1000 MainLoop run: 11. Skipped generating a task
|
||||
1100 MainLoop run: 12. Generated a new task: 8 Int, Iter = 2530, 1 Free mem=1521 No of tasks=5
|
||||
1102 Task N8 current iteration: 1
|
||||
1105 Task N8 finished and destroyed. Free mem=1523 No of tasks=4
|
||||
1200 MainLoop run: 13. Skipped generating a task
|
||||
1300 MainLoop run: 14. Generated a new task: 9 Int, Iter = 2215, 7 Free mem=1521 No of tasks=5
|
||||
1302 Task N9 current iteration: 1
|
||||
1400 MainLoop run: 15. Skipped generating a task
|
||||
1500 MainLoop run: 16. Skipped generating a task
|
||||
1502 Task N7 current iteration: 2
|
||||
1600 MainLoop run: 17. Skipped generating a task
|
||||
1700 MainLoop run: 18. Skipped generating a task
|
||||
1800 MainLoop run: 19. Skipped generating a task
|
||||
1900 MainLoop run: 20. Skipped generating a task
|
||||
2000 MainLoop run: 21. Generated a new task: 10 Int, Iter = 189, 10 Free mem=1476 No of tasks=6
|
||||
2002 Task N10 current iteration: 1
|
||||
2100 MainLoop run: 22. Generated a new task: 11 Int, Iter = 2898, 9 Free mem=1431 No of tasks=7
|
||||
2102 Task N7 current iteration: 3
|
||||
2105 Task N11 current iteration: 1
|
||||
2191 Task N10 current iteration: 2
|
||||
2200 MainLoop run: 23. Generated a new task: 12 Int, Iter = 1691, 6 Free mem=1386 No of tasks=8
|
||||
2202 Task N12 current iteration: 1
|
||||
2300 MainLoop run: 24. Generated a new task: 13 Int, Iter = 1448, 7 Free mem=1341 No of tasks=9
|
||||
2304 Task N13 current iteration: 1
|
||||
2380 Task N10 current iteration: 3
|
||||
2400 MainLoop run: 25. Generated a new task: 14 Int, Iter = 3919, 7 Free mem=1296 No of tasks=10
|
||||
2403 Task N14 current iteration: 1
|
||||
2500 MainLoop run: 26. Generated a new task: 15 Int, Iter = 3745, 5 Free mem=1251 No of tasks=11
|
||||
2503 Task N15 current iteration: 1
|
||||
2569 Task N10 current iteration: 4
|
||||
2600 MainLoop run: 27. Skipped generating a task
|
||||
2700 MainLoop run: 28. Skipped generating a task
|
||||
2702 Task N7 current iteration: 4
|
||||
2702 Task N7 finished and destroyed. Free mem=1253 No of tasks=10
|
||||
2758 Task N10 current iteration: 5
|
||||
2800 MainLoop run: 29. Generated a new task: 16 Int, Iter = 2144, 1 Free mem=1251 No of tasks=11
|
||||
2803 Task N16 current iteration: 1
|
||||
2806 Task N16 finished and destroyed. Free mem=1253 No of tasks=10
|
||||
2900 MainLoop run: 30. Generated a new task: 17 Int, Iter = 4618, 10 Free mem=1251 No of tasks=11
|
||||
2904 Task N17 current iteration: 1
|
||||
2947 Task N10 current iteration: 6
|
||||
3000 MainLoop run: 31. Skipped generating a task
|
||||
3100 MainLoop run: 32. Generated a new task: 18 Int, Iter = 2885, 6 Free mem=1206 No of tasks=12
|
||||
3103 Task N18 current iteration: 1
|
||||
3136 Task N10 current iteration: 7
|
||||
3200 MainLoop run: 33. Skipped generating a task
|
||||
3300 MainLoop run: 34. Skipped generating a task
|
||||
3325 Task N10 current iteration: 8
|
||||
3400 MainLoop run: 35. Skipped generating a task
|
||||
3500 MainLoop run: 36. Generated a new task: 19 Int, Iter = 2250, 4 Free mem=1161 No of tasks=13
|
||||
3503 Task N19 current iteration: 1
|
||||
3514 Task N10 current iteration: 9
|
||||
3518 Task N9 current iteration: 2
|
||||
3600 MainLoop run: 37. Skipped generating a task
|
||||
3700 MainLoop run: 38. Generated a new task: 20 Int, Iter = 1689, 7 Free mem=1116 No of tasks=14
|
||||
3703 Task N10 current iteration: 10
|
||||
3706 Task N20 current iteration: 1
|
||||
3709 Task N10 finished and destroyed. Free mem=1118 No of tasks=13
|
||||
3750 Task N13 current iteration: 2
|
||||
3800 MainLoop run: 39. Generated a new task: 21 Int, Iter = 2607, 5 Free mem=1116 No of tasks=14
|
||||
3803 Task N21 current iteration: 1
|
||||
3893 Task N12 current iteration: 2
|
||||
3900 MainLoop run: 40. Generated a new task: 22 Int, Iter = 1390, 6 Free mem=1071 No of tasks=15
|
||||
3903 Task N22 current iteration: 1
|
||||
4000 MainLoop run: 41. Generated a new task: 23 Int, Iter = 3340, 8 Free mem=1026 No of tasks=16
|
||||
4003 Task N23 current iteration: 1
|
||||
4100 MainLoop run: 42. Skipped generating a task
|
||||
4200 MainLoop run: 43. Skipped generating a task
|
||||
4201 Task N3 current iteration: 2
|
||||
4300 MainLoop run: 44. Skipped generating a task
|
||||
4400 MainLoop run: 45. Generated a new task: 24 Int, Iter = 4083, 2 Free mem=981 No of tasks=17
|
||||
4403 Task N24 current iteration: 1
|
||||
4500 MainLoop run: 46. Generated a new task: 25 Int, Iter = 4510, 1 Free mem=936 No of tasks=18
|
||||
4503 Task N25 current iteration: 1
|
||||
4506 Task N25 finished and destroyed. Free mem=938 No of tasks=17
|
||||
4600 MainLoop run: 47. Generated a new task: 26 Int, Iter = 4782, 10 Free mem=936 No of tasks=18
|
||||
4603 Task N26 current iteration: 1
|
||||
4700 MainLoop run: 48. Generated a new task: 27 Int, Iter = 641, 6 Free mem=891 No of tasks=19
|
||||
4702 Task N27 current iteration: 1
|
||||
4800 MainLoop run: 49. Generated a new task: 28 Int, Iter = 695, 5 Free mem=846 No of tasks=20
|
||||
4802 Task N28 current iteration: 1
|
||||
4900 MainLoop run: 50. Generated a new task: 29 Int, Iter = 3520, 3 Free mem=801 No of tasks=21
|
||||
4903 Task N29 current iteration: 1
|
||||
5000 MainLoop run: 51. Generated a new task: 30 Int, Iter = 3091, 9 Free mem=756 No of tasks=22
|
||||
5002 Task N11 current iteration: 2
|
||||
5006 Task N30 current iteration: 1
|
||||
5100 MainLoop run: 52. Skipped generating a task
|
||||
5198 Task N13 current iteration: 3
|
||||
5200 MainLoop run: 53. Skipped generating a task
|
||||
5293 Task N22 current iteration: 2
|
||||
5300 MainLoop run: 54. Generated a new task: 31 Int, Iter = 4359, 9 Free mem=711 No of tasks=23
|
||||
5303 Task N31 current iteration: 1
|
||||
5325 Task N5 current iteration: 2
|
||||
5343 Task N27 current iteration: 2
|
||||
5392 Task N20 current iteration: 2
|
||||
5400 MainLoop run: 55. Generated a new task: 32 Int, Iter = 837, 4 Free mem=666 No of tasks=24
|
||||
5403 Task N32 current iteration: 1
|
||||
5497 Task N28 current iteration: 2
|
||||
5501 MainLoop run: 56. Generated a new task: 33 Int, Iter = 274, 8 Free mem=621 No of tasks=25
|
||||
5505 Task N33 current iteration: 1
|
||||
5584 Task N12 current iteration: 3
|
||||
5600 MainLoop run: 57. Generated a new task: 34 Int, Iter = 923, 7 Free mem=576 No of tasks=26
|
||||
5603 Task N34 current iteration: 1
|
||||
5700 MainLoop run: 58. Generated a new task: 35 Int, Iter = 1007, 8 Free mem=531 No of tasks=27
|
||||
5703 Task N35 current iteration: 1
|
||||
5732 Task N9 current iteration: 3
|
||||
5753 Task N19 current iteration: 2
|
||||
5778 Task N33 current iteration: 2
|
||||
5789 Task N6 current iteration: 2
|
||||
5800 MainLoop run: 59. Skipped generating a task
|
||||
5900 MainLoop run: 60. Generated a new task: 36 Int, Iter = 608, 4 Free mem=486 No of tasks=28
|
||||
5903 Task N36 current iteration: 1
|
||||
5984 Task N27 current iteration: 3
|
||||
5988 Task N18 current iteration: 2
|
||||
6000 MainLoop run: 61. Generated a new task: 37 Int, Iter = 4043, 3 Free mem=441 No of tasks=29
|
||||
6003 Task N37 current iteration: 1
|
||||
6052 Task N33 current iteration: 3
|
||||
6100 MainLoop run: 62. Skipped generating a task
|
||||
6192 Task N28 current iteration: 3
|
||||
6200 MainLoop run: 63. Skipped generating a task
|
||||
6239 Task N32 current iteration: 2
|
||||
6248 Task N15 current iteration: 2
|
||||
6300 MainLoop run: 64. Skipped generating a task
|
||||
6322 Task N14 current iteration: 2
|
||||
6326 Task N33 current iteration: 4
|
||||
6400 MainLoop run: 65. Skipped generating a task
|
||||
6410 Task N21 current iteration: 2
|
||||
6500 MainLoop run: 66. Skipped generating a task
|
||||
6510 Task N36 current iteration: 2
|
||||
6525 Task N34 current iteration: 2
|
||||
6600 MainLoop run: 67. Skipped generating a task
|
||||
6600 Task N33 current iteration: 5
|
||||
6625 Task N27 current iteration: 4
|
||||
6646 Task N13 current iteration: 4
|
||||
6683 Task N22 current iteration: 3
|
||||
6700 MainLoop run: 68. Generated a new task: 38 Int, Iter = 1907, 9 Free mem=396 No of tasks=30
|
||||
6703 Task N38 current iteration: 1
|
||||
6709 Task N35 current iteration: 2
|
||||
6800 MainLoop run: 69. Skipped generating a task
|
||||
6874 Task N33 current iteration: 6
|
||||
6887 Task N28 current iteration: 4
|
||||
6900 MainLoop run: 70. Generated a new task: 39 Int, Iter = 2697, 1 Free mem=351 No of tasks=31
|
||||
6903 Task N39 current iteration: 1
|
||||
6906 Task N39 finished and destroyed. Free mem=353 No of tasks=30
|
||||
7000 MainLoop run: 71. Generated a new task: 40 Int, Iter = 2849, 4 Free mem=351 No of tasks=31
|
||||
7003 Task N40 current iteration: 1
|
||||
7076 Task N32 current iteration: 3
|
||||
7081 Task N20 current iteration: 3
|
||||
7100 MainLoop run: 72. Skipped generating a task
|
||||
7118 Task N36 current iteration: 3
|
||||
7148 Task N33 current iteration: 7
|
||||
7200 MainLoop run: 73. Skipped generating a task
|
||||
7266 Task N27 current iteration: 5
|
||||
7275 Task N12 current iteration: 4
|
||||
7300 MainLoop run: 74. Generated a new task: 41 Int, Iter = 4466, 4 Free mem=306 No of tasks=32
|
||||
7303 Task N41 current iteration: 1
|
||||
7343 Task N23 current iteration: 2
|
||||
7400 MainLoop run: 75. Skipped generating a task
|
||||
7422 Task N33 current iteration: 8
|
||||
7422 Task N33 finished and destroyed. Free mem=308 No of tasks=31
|
||||
7448 Task N34 current iteration: 3
|
||||
7500 MainLoop run: 76. Generated a new task: 42 Int, Iter = 2133, 3 Free mem=306 No of tasks=32
|
||||
7503 Task N42 current iteration: 1
|
||||
7522 Task N17 current iteration: 2
|
||||
7582 Task N28 current iteration: 5
|
||||
7582 Task N28 finished and destroyed. Free mem=308 No of tasks=31
|
||||
7600 MainLoop run: 77. Skipped generating a task
|
||||
7700 MainLoop run: 78. Skipped generating a task
|
||||
7716 Task N35 current iteration: 3
|
||||
7726 Task N36 current iteration: 4
|
||||
7726 Task N36 finished and destroyed. Free mem=353 No of tasks=30
|
||||
7800 MainLoop run: 79. Generated a new task: 43 Int, Iter = 4113, 6 Free mem=351 No of tasks=31
|
||||
7803 Task N43 current iteration: 1
|
||||
7898 Task N11 current iteration: 3
|
||||
7900 MainLoop run: 80. Skipped generating a task
|
||||
7907 Task N27 current iteration: 6
|
||||
7907 Task N27 finished and destroyed. Free mem=353 No of tasks=30
|
||||
7913 Task N32 current iteration: 4
|
||||
7913 Task N32 finished and destroyed. Free mem=398 No of tasks=29
|
||||
7947 Task N9 current iteration: 4
|
||||
8000 MainLoop run: 81. Skipped generating a task
|
||||
8003 Task N19 current iteration: 3
|
||||
8073 Task N22 current iteration: 4
|
||||
8093 Task N30 current iteration: 2
|
||||
8094 Task N13 current iteration: 5
|
||||
8100 MainLoop run: 82. Skipped generating a task
|
||||
8200 MainLoop run: 83. Generated a new task: 44 Int, Iter = 3389, 1 Free mem=396 No of tasks=30
|
||||
8203 Task N44 current iteration: 1
|
||||
8206 Task N44 finished and destroyed. Free mem=398 No of tasks=29
|
||||
8300 MainLoop run: 84. Generated a new task: 45 Int, Iter = 3548, 10 Free mem=396 No of tasks=30
|
||||
8303 Task N3 current iteration: 3
|
||||
8306 Task N45 current iteration: 1
|
||||
8371 Task N34 current iteration: 4
|
||||
8400 MainLoop run: 85. Skipped generating a task
|
||||
8422 Task N29 current iteration: 2
|
||||
8485 Task N24 current iteration: 2
|
||||
8485 Task N24 finished and destroyed. Free mem=398 No of tasks=29
|
||||
8500 MainLoop run: 86. Generated a new task: 46 Int, Iter = 4962, 2 Free mem=396 No of tasks=30
|
||||
8503 Task N46 current iteration: 1
|
||||
8600 MainLoop run: 87. Skipped generating a task
|
||||
8609 Task N38 current iteration: 2
|
||||
8700 MainLoop run: 88. Skipped generating a task
|
||||
8723 Task N35 current iteration: 4
|
||||
8770 Task N20 current iteration: 4
|
||||
8800 MainLoop run: 89. Skipped generating a task
|
||||
8873 Task N18 current iteration: 3
|
||||
8900 MainLoop run: 90. Skipped generating a task
|
||||
8966 Task N12 current iteration: 5
|
||||
9000 MainLoop run: 91. Skipped generating a task
|
||||
9017 Task N21 current iteration: 3
|
||||
9100 MainLoop run: 92. Skipped generating a task
|
||||
9200 MainLoop run: 93. Skipped generating a task
|
||||
9294 Task N34 current iteration: 5
|
||||
9300 MainLoop run: 94. Skipped generating a task
|
||||
9385 Task N26 current iteration: 2
|
||||
9400 MainLoop run: 95. Generated a new task: 47 Int, Iter = 3556, 9 Free mem=351 No of tasks=31
|
||||
9403 Task N47 current iteration: 1
|
||||
9463 Task N22 current iteration: 5
|
||||
9500 MainLoop run: 96. Generated a new task: 48 Int, Iter = 1226, 3 Free mem=306 No of tasks=32
|
||||
9503 Task N48 current iteration: 1
|
||||
9542 Task N13 current iteration: 6
|
||||
9600 MainLoop run: 97. Generated a new task: 49 Int, Iter = 2850, 9 Free mem=261 No of tasks=33
|
||||
9603 Task N49 current iteration: 1
|
||||
9635 Task N42 current iteration: 2
|
||||
9661 Task N31 current iteration: 2
|
||||
9700 MainLoop run: 98. Skipped generating a task
|
||||
9730 Task N35 current iteration: 5
|
||||
9800 MainLoop run: 99. Generated a new task: 50 Int, Iter = 2782, 10 Free mem=216 No of tasks=34
|
||||
9803 Task N50 current iteration: 1
|
||||
9851 Task N40 current iteration: 2
|
||||
9900 MainLoop run: 100. Skipped generating a task
|
||||
9948 Task N5 current iteration: 3
|
||||
9993 Task N15 current iteration: 3
|
||||
10045 Task N37 current iteration: 2
|
||||
10162 Task N9 current iteration: 5
|
||||
10217 Task N34 current iteration: 6
|
||||
10241 Task N14 current iteration: 3
|
||||
10253 Task N19 current iteration: 4
|
||||
10253 Task N19 finished and destroyed. Free mem=218 No of tasks=33
|
||||
10459 Task N20 current iteration: 5
|
||||
10516 Task N38 current iteration: 3
|
||||
10657 Task N12 current iteration: 6
|
||||
10657 Task N12 finished and destroyed. Free mem=263 No of tasks=32
|
||||
10683 Task N23 current iteration: 3
|
||||
10728 Task N48 current iteration: 2
|
||||
10737 Task N35 current iteration: 6
|
||||
10776 Task N6 current iteration: 3
|
||||
10796 Task N11 current iteration: 4
|
||||
10853 Task N22 current iteration: 6
|
||||
10853 Task N22 finished and destroyed. Free mem=308 No of tasks=31
|
||||
10990 Task N13 current iteration: 7
|
||||
10990 Task N13 finished and destroyed. Free mem=353 No of tasks=30
|
||||
11140 Task N34 current iteration: 7
|
||||
11140 Task N34 finished and destroyed. Free mem=398 No of tasks=29
|
||||
11184 Task N30 current iteration: 3
|
||||
11624 Task N21 current iteration: 4
|
||||
11744 Task N35 current iteration: 7
|
||||
11758 Task N18 current iteration: 4
|
||||
11768 Task N41 current iteration: 2
|
||||
11768 Task N42 current iteration: 3
|
||||
11769 Task N42 finished and destroyed. Free mem=443 No of tasks=28
|
||||
11851 Task N45 current iteration: 2
|
||||
11915 Task N43 current iteration: 2
|
||||
11942 Task N29 current iteration: 3
|
||||
11942 Task N29 finished and destroyed. Free mem=488 No of tasks=27
|
||||
11954 Task N48 current iteration: 3
|
||||
11954 Task N48 finished and destroyed. Free mem=533 No of tasks=26
|
||||
12140 Task N17 current iteration: 3
|
||||
12148 Task N20 current iteration: 6
|
||||
12377 Task N9 current iteration: 6
|
||||
12399 Task N3 current iteration: 4
|
||||
12423 Task N38 current iteration: 4
|
||||
12452 Task N49 current iteration: 2
|
||||
12585 Task N50 current iteration: 2
|
||||
12700 Task N40 current iteration: 3
|
||||
12751 Task N35 current iteration: 8
|
||||
12751 Task N35 finished and destroyed. Free mem=578 No of tasks=25
|
||||
12958 Task N47 current iteration: 2
|
||||
13464 Task N46 current iteration: 2
|
||||
13464 Task N46 finished and destroyed. Free mem=623 No of tasks=24
|
||||
13694 Task N11 current iteration: 5
|
||||
13739 Task N15 current iteration: 4
|
||||
13837 Task N20 current iteration: 7
|
||||
13837 Task N20 finished and destroyed. Free mem=668 No of tasks=23
|
||||
14020 Task N31 current iteration: 3
|
||||
14023 Task N23 current iteration: 4
|
||||
14088 Task N37 current iteration: 3
|
||||
14088 Task N37 finished and destroyed. Free mem=713 No of tasks=22
|
||||
14160 Task N14 current iteration: 4
|
||||
14167 Task N26 current iteration: 3
|
||||
14231 Task N21 current iteration: 5
|
||||
14231 Task N21 finished and destroyed. Free mem=758 No of tasks=21
|
||||
14275 Task N30 current iteration: 4
|
||||
14330 Task N38 current iteration: 5
|
||||
14571 Task N5 current iteration: 4
|
||||
14592 Task N9 current iteration: 7
|
||||
14592 Task N9 finished and destroyed. Free mem=803 No of tasks=20
|
||||
14643 Task N18 current iteration: 5
|
||||
15302 Task N49 current iteration: 3
|
||||
15367 Task N50 current iteration: 3
|
||||
15399 Task N45 current iteration: 3
|
||||
15549 Task N40 current iteration: 4
|
||||
15549 Task N40 finished and destroyed. Free mem=848 No of tasks=19
|
||||
15763 Task N6 current iteration: 4
|
||||
15763 Task N6 finished and destroyed. Free mem=893 No of tasks=18
|
||||
16028 Task N43 current iteration: 3
|
||||
16234 Task N41 current iteration: 3
|
||||
16237 Task N38 current iteration: 6
|
||||
16498 Task N3 current iteration: 5
|
||||
16514 Task N47 current iteration: 3
|
||||
16592 Task N11 current iteration: 6
|
||||
16758 Task N17 current iteration: 4
|
||||
17363 Task N23 current iteration: 5
|
||||
17366 Task N30 current iteration: 5
|
||||
17483 Task N15 current iteration: 5
|
||||
17483 Task N15 finished and destroyed. Free mem=938 No of tasks=17
|
||||
17528 Task N18 current iteration: 6
|
||||
17528 Task N18 finished and destroyed. Free mem=983 No of tasks=16
|
||||
18079 Task N14 current iteration: 5
|
||||
18144 Task N38 current iteration: 7
|
||||
18149 Task N50 current iteration: 4
|
||||
18152 Task N49 current iteration: 4
|
||||
18379 Task N31 current iteration: 4
|
||||
18947 Task N45 current iteration: 4
|
||||
18949 Task N26 current iteration: 4
|
||||
19194 Task N5 current iteration: 5
|
||||
19490 Task N11 current iteration: 7
|
||||
20051 Task N38 current iteration: 8
|
||||
20070 Task N47 current iteration: 4
|
||||
20141 Task N43 current iteration: 4
|
||||
20457 Task N30 current iteration: 6
|
||||
20597 Task N3 current iteration: 6
|
||||
20700 Task N41 current iteration: 4
|
||||
20700 Task N41 finished and destroyed. Free mem=1028 No of tasks=15
|
||||
20703 Task N23 current iteration: 6
|
||||
20931 Task N50 current iteration: 5
|
||||
21002 Task N49 current iteration: 5
|
||||
21376 Task N17 current iteration: 5
|
||||
21958 Task N38 current iteration: 9
|
||||
21958 Task N38 finished and destroyed. Free mem=1073 No of tasks=14
|
||||
21998 Task N14 current iteration: 6
|
||||
22388 Task N11 current iteration: 8
|
||||
22495 Task N45 current iteration: 5
|
||||
22738 Task N31 current iteration: 5
|
||||
23548 Task N30 current iteration: 7
|
||||
23626 Task N47 current iteration: 5
|
||||
23713 Task N50 current iteration: 6
|
||||
23731 Task N26 current iteration: 5
|
||||
23817 Task N5 current iteration: 6
|
||||
23852 Task N49 current iteration: 6
|
||||
24043 Task N23 current iteration: 7
|
||||
24254 Task N43 current iteration: 5
|
||||
24696 Task N3 current iteration: 7
|
||||
25286 Task N11 current iteration: 9
|
||||
25286 Task N11 finished and destroyed. Free mem=1118 No of tasks=13
|
||||
25917 Task N14 current iteration: 7
|
||||
25917 Task N14 finished and destroyed. Free mem=1163 No of tasks=12
|
||||
25994 Task N17 current iteration: 6
|
||||
26043 Task N45 current iteration: 6
|
||||
26496 Task N50 current iteration: 7
|
||||
26639 Task N30 current iteration: 8
|
||||
26702 Task N49 current iteration: 7
|
||||
27097 Task N31 current iteration: 6
|
||||
27182 Task N47 current iteration: 6
|
||||
27383 Task N23 current iteration: 8
|
||||
27383 Task N23 finished and destroyed. Free mem=1208 No of tasks=11
|
||||
28367 Task N43 current iteration: 6
|
||||
28367 Task N43 finished and destroyed. Free mem=1253 No of tasks=10
|
||||
28440 Task N5 current iteration: 7
|
||||
28440 Task N5 finished and destroyed. Free mem=1298 No of tasks=9
|
||||
28513 Task N26 current iteration: 6
|
||||
28795 Task N3 current iteration: 8
|
||||
29277 Task N50 current iteration: 8
|
||||
29552 Task N49 current iteration: 8
|
||||
29591 Task N45 current iteration: 7
|
||||
29730 Task N30 current iteration: 9
|
||||
29730 Task N30 finished and destroyed. Free mem=1343 No of tasks=8
|
||||
30612 Task N17 current iteration: 7
|
||||
30738 Task N47 current iteration: 7
|
||||
31456 Task N31 current iteration: 7
|
||||
32059 Task N50 current iteration: 9
|
||||
32402 Task N49 current iteration: 9
|
||||
32402 Task N49 finished and destroyed. Free mem=1388 No of tasks=7
|
||||
32894 Task N3 current iteration: 9
|
||||
32894 Task N3 finished and destroyed. Free mem=1433 No of tasks=6
|
||||
33139 Task N45 current iteration: 8
|
||||
33295 Task N26 current iteration: 7
|
||||
34294 Task N47 current iteration: 8
|
||||
34841 Task N50 current iteration: 10
|
||||
34841 Task N50 finished and destroyed. Free mem=1478 No of tasks=5
|
||||
35230 Task N17 current iteration: 8
|
||||
35815 Task N31 current iteration: 8
|
||||
36687 Task N45 current iteration: 9
|
||||
37850 Task N47 current iteration: 9
|
||||
37850 Task N47 finished and destroyed. Free mem=1523 No of tasks=4
|
||||
38077 Task N26 current iteration: 8
|
||||
39848 Task N17 current iteration: 9
|
||||
40174 Task N31 current iteration: 9
|
||||
40174 Task N31 finished and destroyed. Free mem=1568 No of tasks=3
|
||||
40235 Task N45 current iteration: 10
|
||||
40235 Task N45 finished and destroyed. Free mem=1613 No of tasks=2
|
||||
42859 Task N26 current iteration: 9
|
||||
44466 Task N17 current iteration: 10
|
||||
44466 Task N17 finished and destroyed. Free mem=1658 No of tasks=1
|
||||
47641 Task N26 current iteration: 10
|
||||
47641 Task N26 finished and destroyed. Free mem=1703 No of tasks=0
|
||||
|
||||
*/
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue