首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >开门程序

开门程序
EN

Code Review用户
提问于 2014-08-15 22:00:12
回答 1查看 2K关注 0票数 11

我一直在做一个关于Arduino的小项目,我需要一些帮助,使代码更高效/更干净。我真的不喜欢这三个for循环,我觉得我可以以某种方式压缩它们。

它的工作原理:

  1. 一个HC-05蓝牙模块是通过一个串行连接到一个Arduino微.
  2. Arduino过滤串行数据并提取它找到的MAC地址。
  3. 所发现的MAC地址与白名单进行比较。
  4. 一旦白名单上的设备进入蓝牙模块的范围,并通过一个光屏障,门就会打开。
  5. 只有在新设备进入范围内,或者已经输入的设备离开蓝牙范围至少10秒并重新进入的情况下,才能重新打开门。
代码语言:javascript
运行
复制
#include <SoftwareSerial.h>
#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 verfügbar sind, lese so lange bis end of line
  while (mySerial.available() > 0) {        
    char received = mySerial.read();
    serialData += received;       

    if (received == '\n') { 
      mac = getToken(serialData, ',', 0);

      // Debug
       Serial.println(mac);
       Serial.println(currentTime); 
       Serial.println(time[0]);  
       Serial.println(time[1]); 

      // Lese den Eingang aus. Wenn HIGH und retrigger ist verhindert, setze Zugangsbedingung
      detectionPinRead = digitalRead(detectionPin);

      if (detectionPinRead == HIGH && !retrigger) {
        for (int i = 0; i < 2; i++) {
          if (mac == whitelist[i]) {
              detected = true;
              retrigger = true;    
              break;
          } 
        }        
      }

      // Überprüfe ob MAC seit mindestens 10 Sekunden nicht mehr in Rechweite ist
      for (int i = 0; i < 2; i++) {
        if(mac == whitelist[i] && currentTime - time[i] >= 10) {            
          entered[i] = false; 
          retrigger = false;
          break;
        } 
      }       

      // Öffne Tür sobald Zugansbedingungen erfüllt sind
      for (int i = 0; i < 2; i++) {
        if (mac == whitelist[i]) {
          time[i] = currentTime;
          if (!entered[i] && detected) {                      
            entered[i] = true;
            openDoor();
            break;
          } 
        }      
      }      
      serialData = "\0";
    } 
  }
}

String getToken(String data, char separator, int index) {
  int found = 0;
  int strIndex[] = {0, -1};
  int maxIndex = data.length() - 1;

  for(int i = 0; i <= maxIndex && found <= index; i++) {
    if(data.charAt(i) == separator || i == maxIndex) {
      found++;
      strIndex[0] = strIndex[1] + 1;
      strIndex[1] = (i == maxIndex) ? i + 1 : i;
    }
  }
  return found > index ? data.substring(strIndex[0], strIndex[1]) : "";
}

void openDoor() {  
  digitalWrite(doorPin, HIGH);
  delay(500);
  digitalWrite(doorPin, LOW);
  detected = false;  
}

void startInq() {
  mySerial.println("AT+INQ");
}
EN

回答 1

Code Review用户

发布于 2014-08-17 19:44:17

为了使这段代码更加清晰,您肯定可以做一些事情。

消除了实际

中的全局变量

拥有依赖于全局变量的例程使得理解逻辑变得更加困难,并引入了许多错误的机会。在实用的地方消除全局变量总是一个好主意,无论是为桌面机器编程还是为嵌入式系统编程。对于全局变量(如currentTime ),请考虑将它们封装在对象中,以便于区分读取访问和更新。

合并逻辑

从文本描述中提取逻辑,我们可以用psuedocode来表示:

代码语言:javascript
运行
复制
for each detected mac:
    if (isWhitelisted(mac) && sensor && isEligible(mac)) 
        openDoor()

剩下的就是保持这些变量的更新和正确。sensor变量只是您的detectionPinRead == HIGH语句,所以其中一个非常简单。检查白名单是您在每个循环中已经做的事情,所以这也非常简单。唯一剩下的就是确定给定的mac设备是否有资格打开门。一个设备是合格的,如果它是新的,或如果它已经超出范围至少10秒。在后面的部分中,我将展示一种方法。

使用对象

白名单中的设备具有与每个设备相关联的数据和逻辑。这强烈地暗示了一个物体。我提出这样的建议:

代码语言:javascript
运行
复制
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;
};

您的白名单现在可以由对象构建:

代码语言:javascript
运行
复制
AuthorizedBT whitelist[2] = {"one", "two"};

将I/O与逻辑

分离

您的循环的简化版本可以重写为将I/O与逻辑分开:

代码语言:javascript
运行
复制
void loop() {
    // update time, fetch mac from serial port
    update(currentTime, mac, HIGH == digitalRead(detectionPin));
}

现在,您的代码中执行串行I/O、更新时间和读取端口的活动部分与代码的逻辑部分(现在放置在一个名为update的例程中)完全分开:

代码语言:javascript
运行
复制
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;
}

这就消除了全局变量retriggerdetected。更好的是,您可以从这个例程中删除对openDoor的调用,并使用返回值来确定何时打开门。这导致了更干净的分离。此外,如果您能够确保currentTime始终至少是10,则可以消除outside数据成员和对它的所有引用。

测试您的逻辑

有了I/O与逻辑的清晰分离,就有可能将逻辑与实际机器分开测试。这是非常有益的,特别是在开发一个原型的时候。

代码语言:javascript
运行
复制
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
}

小心整数

代码当前有一个全局变量currentTime,它被声明为unsigned long,但是值存储在time数组中,每个成员都是int。即使您的编译器认为longint具有相同的大小,它也肯定不会以相同的方式对待已签名和无符号的版本。

要小心使用变量名

time数组很容易导致与<ctime>头中内置的time函数的冲突。最好是使用一个变量名,这个变量名还不是一个常用的变量或函数名。

使用bool而不是boolean

标准类型是bool,而不是boolean。除非您的C++编译器有什么问题,否则应该使用bool

小心使用字符串

代码使用String类型,但不显示实现。如果是std::string类型或其他对象,请注意不要无意中创建临时副本。您可以通过仔细选择参数声明来避免这种情况。例如,而不是

代码语言:javascript
运行
复制
String getToken(String data, char separator, int index)

您可以考虑以下形式:

代码语言:javascript
运行
复制
String getToken(const String &data, char separator, int index)

消除了“幻数”

代码中有几个数字,比如210,它们在特定的上下文中有特定的含义。通过使用命名常量(如NUMBER_OF_WHITELISTED_OBJECTSMIN_OUT_OF_RANGE_SECONDS ),程序更易于读取和维护。对于常量只对特定对象有意义的情况,请考虑使对象的常量部分。

票数 5
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/60193

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档