State machine implementation - c++

I have a state machine as described below.
We can start in one of two starting states, but we must hit all 4 states of the handshake. From there, we can either transfer a payload of data or receive a payload of data. Then, we return to our original starting state.
Handshake:
-> StartingState1 -> FinalState1 -> StartingState2 -> FinalState2
-> StartingState2 -> FinalState2 -> StartingState1 -> FinalState1
Payload Transfer:
-> SendPayload -> SendEnd -> StartingState?
-> ReceivePayload -> ReceiveEnd -> StartingState?
The code below represents my current architecture. Unfortunately, at the end of each process, I don't have enough information from within the states to know what the next state is I should hit.
Does anybody have any suggestions on how to improve this architecture based on my requirements?
Thanks,
PaulH
class MyMachine;
class Payload;
class IState
{
MyMachine* context_;
IState( MyMachine* context ) : context_( context) {};
virtual void Consume( byte data );
void ChangeState( IState* state )
{
context_->SetState( state );
}
}
class FinalState1 : IState
{
void Consume( byte data )
{
// Either go to StartingState1, SendPayload, or ReceivePayload.
// How can I tell from within the context of this state where I
// should go?
}
}
class StartingState1 : IState
{
void Consume( byte data )
{
if ( /*some condition*/ )
{
ChangeState( new FinalState1( context_ ) );
}
}
}
class MyMachine
{
IState* state_;
Payload* payload_;
void Start1( Mode mode )
{
state_ = new StartingState1( this );
}
void Start2( Mode mode )
{
state_ = new StartingState2( this );
}
void Consume( byte data )
{
state_->Consume( data );
}
void SetPayload( const Payload* payload )
{
payload_ = payload;
}
const Payload* GetPayload()
{
return payload_;
}
void SetState( State* state )
{
delete state_;
state_ = state;
}
}
// get a byte of data from some source
byte GetData();
void main()
{
MyMachine machine;
Payload payload;
machine.SetPayload( payload );
machine.Start1( Mode::SendPayload );
// could also call:
// machine.Start1( Mode::ReceivePayload );
// machine.Start2( Mode::SendPayload );
// machine.Start2( Mode::ReceivePayload );
for(;;)
{
machine.Consume( GetData() );
}
}

What you have doesn't represent the possible states of your system completely, but it's easy to transform it so that it does. You need additional states to represent the difference between being in state 1 and not having been in state 2, and being in state 1, whilst having been in state 2 (and the same for state 2). So you need:
S1 S2 F1 F2 S12 F12 S21 F21
SP SE
RP RE
with transitions
S1 --> F1
F1 --> S12
S12 --> F12
F12 --> SP or F12 --> RP
S2 --> F2
F2 --> S21
S21 --> F21
F21 --> SP or F21 --> RP
SP --> SE
RP --> RE
SE --> S1 or SE --> S2
RE --> S1 or RE --> S2
The key difference is the introduction of new states S12, F12, S21 and F21. In terms of implementation you could almost certainly just derive S12 from S2, F12 from F2, S21 from S1 and F21 from F2 and override the transition function to go to the correct state.
(Apologies for acronymising all your states).

Did you look at boost::statechart library?

I suggest designing from the point of view of function object or function pointers.
A simple state machine can be implemented using an array or std::map. Use the current state as an index and retrieve either the new state or a pointer to the state function.
More complex state machines travel from one state to another based on a transition or event. Simply implemented, this requires a 'nested' array. A container of transition containers. The first access gives you the transition table for a state. Use the current transition as an index into the transition table to return a function pointer of the function that handles this transition.
There different data structures that can be used, all depending on the complexity of your state machine.
A nice idea is to have a table driven state machine. This allows the engine to be coded and tested once. Changing the state machine involves changing the data in the table. The table may be able to exist outside of the executable, which means that the executable doesn't have to change. This concept can be expanded by using dynamic libraries, reducing the need to change the executable.
This is just my suggestion, I could be wrong (paraphrased from Dennis Miller).

Here is an example using the method suggested by Thomas:
#include <cassert>
#include <iostream>
#include <map>
class Machine;
typedef void (*StateFunctionPtr)(Machine& context);
// State "do" functions
void starting1(Machine& context) {std::cout << "S1 ";}
void final1(Machine& context) {std::cout << "F1 ";}
void starting2(Machine& context) {std::cout << "S2 ";}
void final2(Machine& context) {std::cout << "F2 ";}
void sendPayload(Machine& context) {std::cout << "SP ";}
void sendEnd(Machine& context) {std::cout << "SE ";}
void receivePayload(Machine& context) {std::cout << "RP ";}
void receiveEnd(Machine& context) {std::cout << "RE ";}
namespace State
{
enum Type {start, handshake1, handshake2, handshake3,
handshake4, xferPayload, endPayload};
};
// Aggregate of state, "mode" variables, and events.
struct StateKey
{
// Needed for use as map key
bool operator<(const StateKey& rhs) const
{
return
(state < rhs.state)
|| ( (state == rhs.state) && (isReceiving < rhs.isReceiving) )
|| ( (state == rhs.state) && (isReceiving == rhs.isReceiving)
&& (startsAt2 < rhs.startsAt2) );
}
bool startsAt2;
bool isReceiving;
State::Type state;
};
struct StateEffect
{
StateFunctionPtr function; // "do" function
State::Type newState; // state to transition to
};
struct StatePair
{
StateKey key;
StateEffect effect;
};
const StatePair stateTable[] =
{
{{0, 0, State::start}, {&starting1, State::handshake1}},
{{0, 0, State::handshake1}, {&final1, State::handshake2}},
{{0, 0, State::handshake2}, {&starting2, State::handshake3}},
{{0, 0, State::handshake3}, {&final2, State::handshake4}},
{{0, 0, State::handshake4}, {&sendPayload, State::xferPayload}},
{{0, 0, State::xferPayload}, {&sendEnd, State::endPayload}},
{{0, 0, State::endPayload}, {&starting1, State::handshake1}},
{{0, 1, State::start}, {&starting1, State::handshake1}},
{{0, 1, State::handshake1}, {&final1, State::handshake2}},
{{0, 1, State::handshake2}, {&starting2, State::handshake3}},
{{0, 1, State::handshake3}, {&final2, State::handshake4}},
{{0, 1, State::handshake4}, {&receivePayload, State::xferPayload}},
{{0, 1, State::xferPayload}, {&receiveEnd, State::endPayload}},
{{0, 1, State::endPayload}, {&starting1, State::handshake1}},
{{1, 0, State::start}, {&starting2, State::handshake1}},
{{1, 0, State::handshake1}, {&final2, State::handshake2}},
{{1, 0, State::handshake2}, {&starting1, State::handshake3}},
{{1, 0, State::handshake3}, {&final1, State::handshake4}},
{{1, 0, State::handshake4}, {&sendPayload, State::xferPayload}},
{{1, 0, State::xferPayload}, {&sendEnd, State::endPayload}},
{{1, 0, State::endPayload}, {&starting2, State::handshake1}},
{{1, 1, State::start}, {&starting2, State::handshake1}},
{{1, 1, State::handshake1}, {&final2, State::handshake2}},
{{1, 1, State::handshake2}, {&starting1, State::handshake3}},
{{1, 1, State::handshake3}, {&final1, State::handshake4}},
{{1, 1, State::handshake4}, {&receivePayload, State::xferPayload}},
{{1, 1, State::xferPayload}, {&receiveEnd, State::endPayload}},
{{1, 1, State::endPayload}, {&starting2, State::handshake1}}
};
class Machine
{
public:
Machine()
{
// Initialize state chart map from constant state table
const size_t tableSize = sizeof(stateTable) / sizeof(stateTable[0]);
for (size_t row=0; row<tableSize; ++row)
{
stateChart_[stateTable[row].key] = stateTable[row].effect;
}
}
// If startsAt2==true, then FSM will start with starting2 handshake function
void reset(bool startsAt2, bool isReceiving)
{
stateKey_.startsAt2 = startsAt2;
stateKey_.isReceiving = isReceiving;
stateKey_.state = State::start;
}
void step()
{
StateChart::const_iterator iter = stateChart_.find(stateKey_);
assert(iter != stateChart_.end());
const StateEffect& effect = iter->second;
effect.function(*this);
stateKey_.state = effect.newState;
}
private:
typedef std::map<StateKey, StateEffect> StateChart;
StateChart stateChart_;
StateKey stateKey_;
};
int main()
{
Machine machine;
machine.reset(true, false);
for (int i=0; i<20; ++i)
{
machine.step();
}
}
It compiles and works on my machine. You might want to add the following features:
Entry/exit functions in StateEffect
Event "triggers" in StateKey
Generalize into a template.
Add enough generic features to it, and it will start resembling a Boost.StateChart wannabe. ;-)

You can model your state machine using a Petri net. This allows you to define both very simple and very complex state machines.
To implement the state machine/ petri net you specified you can use an engine like the PTN Engine.
It allows you to declaratively define the whole state machine in the Petri net constructor. You can integrate your own functions to be called when reaching a given state, as well as functions to trigger state changes.

Related

Create variants of templated class with different initialization

I am creating multiple different types of encoders where the main difference is the different data structures used to initialize the class. My header is something like this
struct tagTypeInfo {
uint16_t start;
uint16_t last;
uint16_t count;
std::string name;
rdwrT rdwr;
};
template <typename T>
class encodedTag
{
public:
encodedTag(vector<tagTypeInfo> tagInfo_) : tagInfo(tagInfo_)
{
int start = 0;
for(auto & tag : tagInfo)
{
tag.start = start;
tag.last = start + tag.count - 1;
start = start + tag.count;
}
}
uint16_t encode(uint16_t tag, T tagType)
{
assert(tag<tagInfo[tagType].count)
return( tagInfo[tagType].start + tag );
}
std::tuple<uint16_t, T> decode(uint16_t encodedTag)
{
int type = 0;
uint16_t tag;
// simple linear search as there are only a few entries
for (auto it = begin(tagInfo); it != end(tagInfo); it++)
{
if (encodedTag >= it->start && encodedTag < it->last )
{
// tag is in the range
return {encodedTag - it->start , (T)type};
}
type++;
}
assert(false);
return {0,(T)0};
}
std::string getName(T tagType) {return(tagInfo[tagType].name);}
rdwrT getRdwr(T tagType) {return(tagInfo[tagType].rdwr);}
private:
std::vector<tagTypeInfo> tagInfo;
};
extern std::vector<tagTypeInfo> rdTag;
extern std::vector<tagTypeInfo> wrTag;
//using rdTagEncode = encodedTag<rdTagT>(rdTag) <-- error
The cpp file contains:
std::vector<tagTypeInfo> rdTag {
{0, 0, NUM_HOSTRDTAG, "HostRdTag", RDWR_RD},
{0, 0, NUM_SYSRDTAG, "SysRdTag", RDWR_RD},
{0, 0, NUM_GCRDTAG, "GCRdTag", RDWR_RD}
};
std::vector<tagTypeInfo> wrTag {
{0, 0, NUM_HOSTWRTAG, "HostWrTag", RDWR_WR},
{0, 0, NUM_SYSWRTAG, "SysWrTag", RDWR_WR},
{0, 0, NUM_GCWRTAG, "GCWrTag", RDWR_WR}
};
My goal is to be able to just declare an encoder in the code elsewhere with
rdTagEncode myEncode;
However I cant seem to figure out the right syntax to do this. Any suggestions?
Using a derived class was the best solution. Thanks for the suggestion #appleapple
class encodedRdTag : public encodedTag<rdTagTypeT>
{
public:
encodedRdTag() : encodedTag({
{0, 0, NUM_HOSTRDTAG, "HostRdTag", RDWR_RD},
{0, 0, NUM_SYSRDTAG, "SysRdTag", RDWR_RD},
{0, 0, NUM_GCRDTAG, "GCRdTag", RDWR_RD}
}) {};
};
class encodedWrTag : public encodedTag<wrTagTypeT>
{
public:
encodedWrTag() : encodedTag({
{0, 0, NUM_HOSTRDTAG, "HostRdTag", RDWR_RD},
{0, 0, NUM_SYSRDTAG, "SysRdTag", RDWR_RD},
{0, 0, NUM_GCRDTAG, "GCRdTag", RDWR_RD}
}) {};
};
For completeness, here is the "dispatch based on type" method which I mentioned in comment above.
NOTE: this code doesn't work in original question as the rdTag and wrTag are same type thus independent of the class template T, but according to your own answer this may be actually what happends.
#include <vector>
struct tagTypeInfo{};
struct rdTagTypeT{};
struct wrTagTypeT{};
std::vector<tagTypeInfo> get_default_info(rdTagTypeT); // return wrTag
std::vector<tagTypeInfo> get_default_info(wrTagTypeT); // return wdTag
template <typename T>
struct encodedTag
{
encodedTag():encodedTag(get_default_info(T{})){}
encodedTag(std::vector<tagTypeInfo> tagInfo) : tagInfo(tagInfo){};
std::vector<tagTypeInfo> tagInfo;
};
using encodedRdTag = encodedTag<rdTagTypeT>;
using encodedWrTag = encodedTag<wrTagTypeT>;
void foo(){
encodedRdTag rd;
encodedWrTag rw;
}

Optional node still visited in OR-tools vehicle routing problem

In a simple vehicle routing problem solved by Google OR-tools library, two nodes (2, 3) are marked as optional with visiting penalty set to 0. The shortest path of distance 2 from the depot to the landfill is 0 -> 1 -> 4, however, the solver ends-up with path 0 -> 2 -> 3 -> 1 -> 4 of distance 4.
Where is the problem? Why the solver insists on the longer path through optional nodes and does not skip them?
#include "ortools/constraint_solver/routing.h"
using namespace operations_research;
struct DataModel {
static constexpr int I = 2;
const std::vector<std::vector<int>> dist {
{ 0, 1, 1, I, I},
{ I, 0, I, 1, 1},
{ I, I, 0, 1, 1},
{ I, 1, 1, 0, I},
{ I, I, I, 1, 0},
};
const RoutingIndexManager::NodeIndex depot{0};
const RoutingIndexManager::NodeIndex landfill{4};
};
void printSolution(const RoutingIndexManager& manager,
const RoutingModel& routing,
const Assignment& solution)
{
if (routing.status() != RoutingModel::Status::ROUTING_SUCCESS)
return;
int index = routing.Start(0);
std::ostringstream route;
while (routing.IsEnd(index) == false) {
route << manager.IndexToNode(index).value() << " -> ";
index = solution.Value(routing.NextVar(index));
}
LOG(INFO) << route.str() << manager.IndexToNode(index).value();
LOG(INFO) << "Problem solved in " << routing.solver()->wall_time() << "ms";
}
int main(int /*argc*/, char** /*argv*/)
{
DataModel data;
RoutingIndexManager manager(data.dist.size(), 1, {data.depot}, {data.landfill});
RoutingModel routing(manager);
const int callback = routing.RegisterTransitCallback(
[&data, &manager](int from_index, int to_index) -> int {
auto from_node = manager.IndexToNode(from_index).value();
auto to_node = manager.IndexToNode(to_index).value();
return data.dist[from_node][to_node];
});
routing.SetArcCostEvaluatorOfAllVehicles(callback);
// make nodes 2, 3 optional
routing.AddDisjunction({manager.NodeToIndex(RoutingIndexManager::NodeIndex(2))}, 0, 1);
routing.AddDisjunction({manager.NodeToIndex(RoutingIndexManager::NodeIndex(3))}, 0, 1);
const Assignment* solution = routing.Solve();
printSolution(manager, routing, *solution);
return 0;
}
Interestingly, for I = 1, the correct solution 0 -> 1 -> 4 is found. However, such dist matrix is trivial.
This was answered on the or-tools-discuss mailing list.
You encountered a corner case for the default parameter setup. Thanks for forwarding this, we will work on a proper fix.
To work around the problem, you can modify the default parameters as follows:
Option 1 - activate make_chain_inactive - faster option
RoutingSearchParameters search_parameters = DefaultRoutingSearchParameters();
search_parameters.mutable_local_search_operators()->set_use_make_chain_inactive(OptionalBoolean::BOOL_TRUE);
const Assignment* solution = routing.SolveWithParameters(search_parameters);
Option 2 - activate inactive_lns - slower option but slightly more generic
RoutingSearchParameters search_parameters = DefaultRoutingSearchParameters();
search_parameters.mutable_local_search_operators()->set_use_inactive_lns(OptionalBoolean::BOOL_TRUE);
const Assignment* solution = routing.SolveWithParameters(search_parameters);

Run a repeated test reporting as different tests

I'd like to unit test a function with a set of different inputs and expected outputs.
My function is irrelevant thus I'll instead use an example function which counts english words with the following candidate implementation :
int countEnglishWords( const std::string& text )
{
return 5;
};
The following would be the set of test data. The end of the data is marked by an element with the word "END".
struct TestData {
std::string text;
int englishWords;
};
struct TestData data[] = // Mark end with "END"
{
{ "The car is very fast", 5 },
{ "El coche es muy rapido", 0 },
{ "The rain in Spain stays mainly in the plain", 9},
{ "XXXXX OOOOO TTTT", 0},
{ "Yes Si No No", 3},
{ "I have a cheerful live", 5},
{ "END", 0}
};
I could easily write 6 test cases and I would get the result I want. But this is not maintainable, since any further test added to the test cases would not be tested, it would require another test case to be written, which would be just boiler plate. Thus I've written a single test case which loops through all the test data like this :
#include <cppunit/ui/text/TestRunner.h>
#include <cppunit/extensions/HelperMacros.h>
class cppUnit_test: public CppUnit::TestFixture
{
private:
CPPUNIT_TEST_SUITE (cppUnit_test);
CPPUNIT_TEST(myTest);
CPPUNIT_TEST_SUITE_END();
public:
void myTest();
};
void cppUnit_test::myTest()
{
TestData* p = data;
while ( p->text != "END")
{
std::stringstream ss;
ss << "Text=\"" << p->text << "\" Counted=" <<
countEnglishWords(p->text) << " Expected=" << p->englishWords;
CPPUNIT_ASSERT_MESSAGE( ss.str().c_str(),
countEnglishWords(p->text) == p->englishWords );
++p;
}
}
int main()
{
CPPUNIT_TEST_SUITE_REGISTRATION (cppUnit_test);
CppUnit::Test *suite =
CppUnit::TestFactoryRegistry::getRegistry().makeTest();
CppUnit::TextUi::TestRunner runner;
runner.addTest(suite);
runner.run();
return 0;
}
The problem is that the previous code runs through the 1st test fine and also detects the error in the 2nd test but after that it stops testing. And the report is :
!!!FAILURES!!!
Test Results:
Run: 1 Failures: 1 Errors: 0
While the result I'd like to get is :
!!!FAILURES!!!
Test Results:
Run: 6 Failures: 4 Errors: 0
As I already mentioned in the comment cppunit 1.14.0 can support your use case.
I you want to reference an external array the quickest way is to use CPPUNIT_TEST_PARAMETERIZED. This macro expects two parameters: first similar to CPPUNIT_TEST a test method and then as a second parameter an iteratable.
Based on your code it would look like:
CPPUNIT_TEST_PARAMETERIZED(myTest, aData);
Now we need to adapt your myTest function a little bit.
void cppUnit_test::myTest(const TestData& data)
{
std::stringstream ss;
ss << "Text=\"" << data.text << "\" Counted=" <<
countEnglishWords(data.text) << " Expected=" << data.englishWords;
bool b = countEnglishWords(data.text) == data.englishWords;
std::string a = ss.str();
CPPUNIT_ASSERT_MESSAGE( a,
b);
}
Finally as the framework needs a way to report which test failed it expects that it can print the parameter that is passed to the test function. In this case the easiest way is to add a simple operator<< overload.
std::ostream& operator<<(std::ostream& strm, const TestData& data)
{
strm << data.text;
return strm;
}
If you combine these pieces you should quickly get a generic solution that will allow you to add as much data to your data array as you want without adapting the test code.
CPPUNIT_TEST_SUITE(TestSuite);
CPPUNIT_TEST_PARAMETERIZED(testMethod, {1, 2, 3, 4});
CPPUNIT_TEST_SUITE_END();
void testMethod(int /*val*/)
{
}

C++ member of class updated outside class

I have a question about pointers and references in C++. I am a programmer who normally programs in C# and PHP.
I have two classes (for now) which are the following.
The X/Y in Controller are continuously changing but i want them up to date in the Commands. I have multiple commands like Forward, Turn, Backward etc.
When i make the commands i give them the controller but the state (X, Y) of the controller are updating every second.
How can i fix that the controller attribute in the Commands are getting updated also every second?
class Forward : ICommand
{
Controller ctrl;
void Execute() {
int CurrentX = ctrl.X;
int CurrentY = ctrl.Y;
//Check here for the current location and calculate where he has to go.
}
}
class Controller
{
int X;
int Y;
void ExecuteCommand(ICommand command) {
command.Execute();
}
}
Main.cpp
Controller controller;
Forward cmd1 = new Forward(1, controller);
Turn cmd2 = new Turn(90, controller);
Forward cmd3 = new Forward(2, controller);
controller.Execute(cmd1);
controller.Execute(cmd2);
controller.Execute(cmd3);
I have read something about pointers and references and i think i have to use this but don't know how to use it in this situation.
(code can have some syntax errors but that's because i typed over. Everything is working further except for the updating).
If you use references rather than copy objects you can see changes.
#include <iostream>
using namespace std;
class ICommand
{
public:
virtual ~ICommand() = default;
virtual void Execute() = 0;
};
class Controller
{
public:
int X = 0;
int Y = 0;
void ExecuteCommand(ICommand & command) {
// ^-------
command.Execute();
}
};//,--- semicolons required
class Forward : public ICommand //note public
{
const int step;
Controller ctrlCopy;
Controller & ctrlReference;
public:
Forward(int step, Controller & ctrl) :
step(step),
ctrlCopy(ctrl), //this is a copy of an object
ctrlReference(ctrl) //this is a reference to an object
{
}
void Execute() {
std::cout << "copy: " << ctrlCopy.X << ", " << ctrlCopy.Y << '\n';
std::cout << " ref: " << ctrlReference.X << ", " << ctrlReference.Y << '\n';
//Check here for the current location and calculate where he has to go.
ctrlCopy.X += 10;
ctrlReference.X += 10;
}
};//<--- semicolons required
int main() {
Controller controller;
Forward cmd1(1, controller);
//Turn cmd2(90, controller); //Left for the OP to do
Forward cmd3(2, controller);
controller.ExecuteCommand(cmd1);
//controller.ExecuteCommand(cmd2);
controller.ExecuteCommand(cmd3);
//Do it again to show the copy and reference difference
std::cout << "Once more, with feeling\n";
controller.ExecuteCommand(cmd1);
controller.ExecuteCommand(cmd3);
}
Giving
copy: 0, 0
ref: 0, 0
copy: 0, 0 // [1]
ref: 10, 0 // [2]
Once more, with feeling
copy: 10, 0
ref: 20, 0
copy: 10, 0
ref: 30, 0
1 shows that the copy has X and Y of 0, while the reference shown in [2] has moved by the stated step (in controller.ExecuteCommand(cmd3))
Note, we don't need to use new to make this work (don't forget delete if you use new).
Also, void ExecuteCommand(ICommand command) now takes a reference instead otherwise the by-value copy does "slicing" (e.g. see here)

How to write function for multiple analog pins? (arduino)

So I'm writing this little function for some pot pins. The pot sends a value only when its being turned, at rest, it sends nothing. Which is how I want it to function.
It works fine with one pin.
I've gotten it to a point where it half works with multiple pins. So if I call it twice in the loop with two pins, I get back the right values on both those pins. But I loose the functionality of the if statement. Basically I can't figure out the last half of this. Arrays have been suggested I'm just unsure of how to proceed.
Suggestions? Thank you.
byte pots[2] = {A0, A2};
int lastPotVal = 0;
void setup(){
Serial.begin(9600);
}
void loop(){
// get the pin out of the array
rePot(pots[0]);
rePot(pots[1]);
delay(10);
}
void rePot(const int potPin){
// there is probably an issue around here somewhere...
int potThresh = 2;
int potFinal = 0;
int potVal = 0;
// set and map potVal
potVal = (analogRead(potPin));
potVal = map(potVal, 0, 664, 0, 200);
if(abs(potVal - lastPotVal) >= potThresh){
potFinal = (potVal/2);
Serial.println(potFinal);
lastPotVal = potVal;
} // end of if statement
} // end of rePot
This uses a struct to mange a pot and the data associated with it (the pin it's on, the last reading, threshold, etc). Then, the rePot() function is changed to take one of those structs as input, instead of just the pin number.
struct Pot {
byte pin;
int threshold;
int lastReading;
int currentReading;
};
// defining an array of 2 Pots, one with pin A0 and threshold 2, the
// other with pin A2 and threshold 3. Everything else is automatically
// initialized to 0 (i.e. lastReading, currentReading). The order that
// the fields are entered determines which variable they initialize, so
// {A1, 4, 5} would be pin = A1, threshold = 4 and lastReading = 5
struct Pot pots[] = { {A0, 2}, {A2, 3} };
void rePot(struct Pot * pot) {
int reading = map(analogRead(pot->pin), 0, 664, 0, 200);
if(abs(reading - pot->lastReading) >= pot->threshold) {
pot->currentReading = (reading/2);
Serial.println(pot->currentReading);
pot->lastReading = reading;
}
}
void setup(){
Serial.begin(9600);
}
void loop() {
rePot(&pots[0]);
rePot(&pots[1]);
delay(10);
}
A slightly different take on this is to change rePot() into a function that takes the whole array as input, and then just updates the whole thing. Like this:
void readAllThePots(struct Pot * pot, int potCount) {
for(int i = 0; i < potCount; i++) {
int reading = map(analogRead(pot[i].pin), 0, 664, 0, 200);
if(abs(reading - pot[i].lastReading) >= pot[i].threshold) {
pot[i].currentReading = (reading/2);
Serial.println(pot[i].currentReading);
pot[i].lastReading = reading;
}
}
}
void loop() {
readAllThePots(pots, 2);
delay(10);
}