patterncppMinor
Door-opener program
Viewed 0 times
openerprogramdoor
Problem
I've been working on a little project on an Arduino and I need some help with making the code more efficient / cleaner. I really don't like the three
How it works:
```
#include
#include "Timer.h"
SoftwareSerial mySerial(10, 11); // RX, TX
Timer t;
// Pinbelegung
int keyPin = 13;
int detectionPin = 8;
int powerPin = 9;
int doorPin = 4;
// Wert von detectionPin Eingang
int detectionPinRead;
// Serielle Daten
String serialData;
// Rausgefilterte MAC Addresse
String mac;
// Zugangsarrays
const char* whitelist[2] = {"+INQ:BCF5:AC:74DDB7", "+INQ:1068:3F:E1081F"};
int time[2] = {0, 0};
boolean entered[2] = {false, false};
// Zeit seit dem Start
unsigned long currentTime;
// Zugangsbedingung
boolean detected;
boolean retrigger;
void setup() {
Serial.begin(38400);
while (!Serial) { ; }
mySerial.begin(38400);
pinMode(keyPin, OUTPUT);
pinMode(detectionPin, INPUT);
pinMode(powerPin, OUTPUT);
pinMode(doorPin, OUTPUT);
// Versetze Bluetoothmodul in AT Modus
digitalWrite(keyPin, HIGH);
delay(500);
digitalWrite(powerPin, HIGH);
delay(500);
mySerial.println("AT+INIT");
delay(500);
mySerial.println("AT+INQM=1,0,48");
delay(500);
mySerial.println("AT+INQ");
// Ein Scan dauert ca. 62 Sekunden. Starte alle 63 Sekunden einen neuen Scan
t.every(63000, startInq);
}
void loop() {
t.update();
currentTime = millis() / 1000;
//Wenn Serielle Daten v
for loops and feel like I could condense them somehow.How it works:
- An HC-05 Bluetooth module is connected via a serial connection to an Arduino Micro.
- The Arduino filters the serial data and extracts the MAC addresses that it finds.
- The discovered MAC addresses are compared against a white-list.
- Once a white-listed device comes in range of the Bluetooth module AND passes a light barrier, the door opens.
- The door can only be re-opened if either a NEW device comes in range, or if the already entered device has left the Bluetooth range for at least 10 seconds and re-enters.
```
#include
#include "Timer.h"
SoftwareSerial mySerial(10, 11); // RX, TX
Timer t;
// Pinbelegung
int keyPin = 13;
int detectionPin = 8;
int powerPin = 9;
int doorPin = 4;
// Wert von detectionPin Eingang
int detectionPinRead;
// Serielle Daten
String serialData;
// Rausgefilterte MAC Addresse
String mac;
// Zugangsarrays
const char* whitelist[2] = {"+INQ:BCF5:AC:74DDB7", "+INQ:1068:3F:E1081F"};
int time[2] = {0, 0};
boolean entered[2] = {false, false};
// Zeit seit dem Start
unsigned long currentTime;
// Zugangsbedingung
boolean detected;
boolean retrigger;
void setup() {
Serial.begin(38400);
while (!Serial) { ; }
mySerial.begin(38400);
pinMode(keyPin, OUTPUT);
pinMode(detectionPin, INPUT);
pinMode(powerPin, OUTPUT);
pinMode(doorPin, OUTPUT);
// Versetze Bluetoothmodul in AT Modus
digitalWrite(keyPin, HIGH);
delay(500);
digitalWrite(powerPin, HIGH);
delay(500);
mySerial.println("AT+INIT");
delay(500);
mySerial.println("AT+INQM=1,0,48");
delay(500);
mySerial.println("AT+INQ");
// Ein Scan dauert ca. 62 Sekunden. Starte alle 63 Sekunden einen neuen Scan
t.every(63000, startInq);
}
void loop() {
t.update();
currentTime = millis() / 1000;
//Wenn Serielle Daten v
Solution
There are definitely some things you can do to make this code more clear.
Eliminate global variables where practical
Having routines dependent on global variables makes it that much more difficult to understand the logic and introduces many opportunities for error. Eliminating global variables where practical is always a good idea, whether programming for desktop machines or for embedded systems. For global variables such as
Consolidate the logic
Extracting the logic from your text description, we can express it in psuedocode:
All that remains is keeping those variables updated and correct. The
Use objects
Your devices in the whitelist have both data and logic associated with each. This strongly suggests an object. I'd propose something like this:
Your whitelist can now be constructed of objects:
Separate I/O from logic
A simplified version of your loop can be rewritten to separate I/O from logic:
Now the active portion of your code which does serial I/O, updates time and reads the port is cleanly separated from the logic portion of your code which is now placed into a routine called
This now eliminates the global variables
Test your logic
With that clean separation of I/O from logic, it becomes possible to test the logic separately from the actual machine. This is highly beneficial especially when working on a prototype.
Be careful with integers
The code currently has a global variable
Be careful with variable names
The
Eliminate global variables where practical
Having routines dependent on global variables makes it that much more difficult to understand the logic and introduces many opportunities for error. Eliminating global variables where practical is always a good idea, whether programming for desktop machines or for embedded systems. For global variables such as
currentTime, consider wrapping them in objects to make it easy to differentiate between read access and an update.Consolidate the logic
Extracting the logic from your text description, we can express it in psuedocode:
for each detected mac:
if (isWhitelisted(mac) && sensor && isEligible(mac))
openDoor()All that remains is keeping those variables updated and correct. The
sensor variable is simply your detectionPinRead == HIGH statement, so that one is very simple. Checking for whitelisting is what you're already doing inside each loop, so that's very simple, too. The only thing left is to determine whether the given mac device is eligible to open the door. A device is eligible if it's new or if it has been out of range for at least 10 seconds. In later sections, I'll show one way to do this.Use objects
Your devices in the whitelist have both data and logic associated with each. This strongly suggests an object. I'd propose something like this:
class AuthorizedBT
{
public:
AuthorizedBT(const char *mac) : MAC(mac), lasttime(0), outside(true) {}
bool matches(const String& otherMAC) const { return MAC == otherMAC; }
bool isEligible(int currTime) const {
return outside || currTime >= (lasttime + 10); }
void markSeen(int currTime) { lasttime = currTime; }
void markInside() { outside = false; }
private:
const char* MAC;
int lasttime;
bool outside;
};Your whitelist can now be constructed of objects:
AuthorizedBT whitelist[2] = {"one", "two"};Separate I/O from logic
A simplified version of your loop can be rewritten to separate I/O from logic:
void loop() {
// update time, fetch mac from serial port
update(currentTime, mac, HIGH == digitalRead(detectionPin));
}Now the active portion of your code which does serial I/O, updates time and reads the port is cleanly separated from the logic portion of your code which is now placed into a routine called
update:bool update(int currentTime, const char *mac, bool sensor)
{
bool result = false;
for (int i=0; i < 2; ++i) {
if (whitelist[i].matches(mac)) {
if (sensor && whitelist[i].isEligible(currentTime)) {
openDoor();
whitelist[i].markInside();
result = true;
}
whitelist[i].markSeen(currentTime);
}
}
return result;
}This now eliminates the global variables
retrigger and detected. Even better, you could remove the call to openDoor from within this routine and use the return value to determine when to open the door. That results in an even cleaner separation. Also, if you can assure that currentTime will always be at least 10, you can eliminate the outside data member and all references to it.Test your logic
With that clean separation of I/O from logic, it becomes possible to test the logic separately from the actual machine. This is highly beneficial especially when working on a prototype.
int main()
{
assert(false == update(1, "foo", true)); // not whitelisted; no action
assert(false == update(20, "one", false)); // didn't trigger sensor; no action
assert(true == update(21, "one", true)); // "one" is now inside
assert(false == update(22, "one", true)); // "one" is already inside; no action
assert(false == update(31, "one", true)); // "one" is already inside; no action
assert(true == update(41, "one", true)); // "one" was out of range for 10 sec; open door
assert(true == update(42, "two", true)); // "two" is now inside
assert(false == update(43, "one", true)); // "one" is already inside; no action
assert(false == update(43, "two", true)); // "two" is already inside; no action
}Be careful with integers
The code currently has a global variable
currentTime which is declared as unsigned long, but then the value is stored within the time array, each member of which is an int. Even if your compiler treats long and int as having the same size, it surely will not treat signed and unsigned versions identically.Be careful with variable names
The
time array could easily cause a conflict with the built-in time function within the ` header. Best would be to use a variable name which is not already a commonly used variable or function name.
Use bool rather than boolean
The standard type is bool rather than boolean. Unless there's something very wrong with your C++ compiler, you should use bool.
Careful with strings
The code uses a String` type but does not show the implCode Snippets
for each detected mac:
if (isWhitelisted(mac) && sensor && isEligible(mac))
openDoor()class AuthorizedBT
{
public:
AuthorizedBT(const char *mac) : MAC(mac), lasttime(0), outside(true) {}
bool matches(const String& otherMAC) const { return MAC == otherMAC; }
bool isEligible(int currTime) const {
return outside || currTime >= (lasttime + 10); }
void markSeen(int currTime) { lasttime = currTime; }
void markInside() { outside = false; }
private:
const char* MAC;
int lasttime;
bool outside;
};AuthorizedBT whitelist[2] = {"one", "two"};void loop() {
// update time, fetch mac from serial port
update(currentTime, mac, HIGH == digitalRead(detectionPin));
}bool update(int currentTime, const char *mac, bool sensor)
{
bool result = false;
for (int i=0; i < 2; ++i) {
if (whitelist[i].matches(mac)) {
if (sensor && whitelist[i].isEligible(currentTime)) {
openDoor();
whitelist[i].markInside();
result = true;
}
whitelist[i].markSeen(currentTime);
}
}
return result;
}Context
StackExchange Code Review Q#60193, answer score: 5
Revisions (0)
No revisions yet.