The ProperyChange library implements a variable that knows its previous state, and executes a callback action when the current value changes. Additionally, triggers can be defined, which evaluate when assigning a value and trigger their own actions.
User Manual
The ProperyChange library implements a wrapper around another variable of type T. It stores the last value and the current value of the variable. To update the variable’s value, we can use the Update(T newValue) function, or simply use the = operator as with a conventional variable.
Additionally, we can define a Callback function, which fires when a change in the variable is detected. Callback functions receive the instance itself, so they have access to all of its fields, including the previous value.
The PropertyChange library also allows adding triggers, consisting of a condition and a callback function similar to the previous one. When updating the variable’s value, the triggers are evaluated (based on their type) and, if met, they trigger the assigned function.
The trigger’s behavior is conditioned by its type, with the following options:
- AnyTime, they trigger whenever the condition is met
- OnChange, they trigger at every value change, when the condition is met
- Once, they trigger only once and are disabled until reactivated using the Enable..() functions
Triggers can be disabled or enabled using the Enable..() and Disable..() function groups. Combined with the fact that each Callback has access to the invoking object’s instance, this leads to many powerful possible combinations (see TriggersEnable example).
Finally, the behavior on the first assignment can be addressed using the IgnoreFirst field to consider the first assignment as an initialization, thus ignoring it.
Constructor
The ProperyChange class is instantiated through its constructor.
PropertyChange(PropertyChangeCallback onPropertyChange = nullptr);
PropertyChange(T initialValue, PropertyChangeCallback onPropertyChange = nullptr);
Definitions
typedef bool(*PropertyChangeCondition)(PropertyChange<T> value);
typedef void(*PropertyChangeCallback)(PropertyChange<T> value);
enum TriggerType
{
Once,
OnChange,
AnyTime
};
struct Trigger
{
PropertyChangeCondition Condition;
PropertyChangeCallback Callback;
TriggerType Type;
bool IsEnabled = true;
};
Usage of ProperyChange
// Update the property
void operator=(T newValue);
bool Update(T newValue);
// Add/remove a trigger
uint8_t AddTrigger(PropertyChangeCondition condition, PropertyChangeCallback callback, TriggerType type = TriggerType::AnyTime);
uint8_t RemoveTrigger(uint8_t index);
// Disable triggers
void DisableTrigger(uint8_t index);
void DisableAllExcept(uint8_t index);
void DisableAllTriggers();
// Enable triggers
void EnableTrigger(uint8_t index);
void EnableAllExcept(uint8_t index);
void EnableAllTriggers();
// Control the response to the first value assignment
bool IgnoreFirst = false;
bool IsFirstValue = true;
// Evaluate to true if the last update was a value change
bool HasChanged = false;
// Last registered value and current value
T LastValue;
T CurrentValue;
Examples
The ProperyChange library includes the following examples to illustrate its usage.
- Basic: Example that shows the basic usage of PropertyChange
#include "PropertyChangeLib.h"
void Debug(PropertyChange<int> a)
{
Serial.print("Changed to: ");
Serial.println(a.CurrentValue);
}
PropertyChange<int> value(Debug);
void setup()
{
Serial.begin(9600);
value = 10; // Print Changed to: 10
value = 20; // Print Changed to: 20
value = 20;
value = 20;
value = 30; // Print Changed to: 30
}
void loop()
{
}
- IgnoreFirst: Example that shows the usage of IgnoreFirst
#include "PropertyChangeLib.h"
PropertyChange<int> value([](PropertyChange<int> a) {Serial.print("Changed to: "); Serial.println(a.CurrentValue); });
void setup()
{
while (!Serial);
Serial.begin(9600);
value.IgnoreFirst = true;
value = 10; // Ignored (first value)
value = 20; // Print Changed to: 20
value = 20;
value = 20;
value = 30; // Print Changed to: 30
}
void loop()
{
}
- Triggers: Example that shows the usage of Triggers and the difference between the Once, OnChange, and AnyTime types
#include "PropertyChangeLib.h"
// Auxiliar function for debug. Show current value, and assign new value
void PropertyDebug(PropertyChange<int>&prop, int newvalue)
{
Serial.println();
Serial.println("************************************** ");
Serial.print("Now in: ");
Serial.println(newvalue);
prop = newvalue;
}
// Create property
PropertyChange<int> value(0, [](PropertyChange<int> a) {Serial.print("Changed from "); Serial.print(a.LastValue); Serial.print(" to "); Serial.println(a.CurrentValue); });
void setup()
{
Serial.begin(9600);
// Add triggers
// >= 10, Once trigger
value.AddTrigger(
[](PropertyChange<int> a) { return a.CurrentValue >= 10; },
[](PropertyChange<int> a) { Serial.println(">=10 (Once)"); }, PropertyChange<int>::TriggerType::Once
);
// >= 20, OnChange trigger
value.AddTrigger(
[](PropertyChange<int> a) { return a.CurrentValue >= 20; },
[](PropertyChange<int> a) { Serial.println(">=20 (OnChange)");}, PropertyChange<int>::TriggerType::OnChange
);
// >= 30, Anytime trigger
value.AddTrigger(
[](PropertyChange<int> a) { return a.CurrentValue >= 30; },
[](PropertyChange<int> a) { Serial.println(">=30 (AnyTime)"); }, PropertyChange<int>::TriggerType::AnyTime
);
PropertyDebug(value, 10); //Print Change, >= 10 (Once)
PropertyDebug(value, 10);
PropertyDebug(value, 10);
PropertyDebug(value, 20); //Print Change, >= 20 (OnChange)
PropertyDebug(value, 20);
PropertyDebug(value, 20);
PropertyDebug(value, 30); //Print Change, >= 20 (OnChange), >= 30 (AnyTime)
PropertyDebug(value, 30); //Print >= 30 (AnyTime)
PropertyDebug(value, 30); //Print >= 30 (AnyTime)
PropertyDebug(value, 10); //Print Change
PropertyDebug(value, 40); //Print Change, >= 20 (OnChange), >= 30 (AnyTime)
PropertyDebug(value, 40); //Print Change, >= 30 (AnyTime)
}
void loop()
{
}
- TriggersEnable: Example that shows the usage of enabling/disabling triggers. In this example, we use a PropertyChange to distinguish between a sequence of 00, 01, and 11
#include "PropertyChangeLib.h"
// Create property
PropertyChange<int> value(0);
// Auxiliar function for debug. Show current value, and assign new value
void PropertyDebug(PropertyChange<int>&prop, int newvalue)
{
Serial.print("Now in: ");
Serial.println(newvalue);
prop = newvalue;
}
// Auxiliar function for debug. Make sequence a-b-a-b.. N-times
void makeSequence(size_t number, int a, int b)
{
for (size_t index = 0; index < number; index++)
{
PropertyDebug(value, a);
PropertyDebug(value, b);
}
}
void setup()
{
Serial.begin(9600);
value.IgnoreFirst = true;
// Add triggers. All triggers are Type Once, and enable all the other triggers when firing
value.AddTrigger(
[](PropertyChange<int> a) { return a.CurrentValue == 1 && a.LastValue == 0; },
[](PropertyChange<int> a) { Serial.println("Detected sequence 0-1"); a.EnableAllExcept(0); },
PropertyChange<int>::TriggerType::Once
);
value.AddTrigger(
[](PropertyChange<int> a) { return a.CurrentValue == 0 && a.LastValue == 0; },
[](PropertyChange<int> a) { Serial.println("Detected sequence 0-0"); a.EnableAllExcept(1); },
PropertyChange<int>::TriggerType::Once
);
value.AddTrigger(
[](PropertyChange<int> a) { return a.CurrentValue == 1 && a.LastValue == 1; },
[](PropertyChange<int> a) { Serial.println("Detected sequence 1-1"); a.EnableAllExcept(2); },
PropertyChange<int>::TriggerType::Once
);
// Test several sequences
Serial.println(); Serial.println("**** Testing 0-1 sequence ****");
makeSequence(3, 0, 1);
Serial.println(); Serial.println("**** Testing 0-0 sequence ****");
makeSequence(3, 0, 0);
Serial.println("**** Testing 0-1 sequence ****");
makeSequence(3, 0, 1);
Serial.println(); Serial.println("**** Testing 1-1 sequence ****");
makeSequence(3, 1, 1);
Serial.println(); Serial.println("**** Testing 0-1 sequence ****");
makeSequence(3, 0, 1);
}
void loop()
{
delay(1000);
}
Installation
- Download the latest version from GitHub
- Unzip the file
- Copy it to your libraries folder (usually My Documents\Arduino\libraries)
- Restart the Arduino IDE