Results 1 to 4 of 4

Thread: My NBP Arduino Hardware

  1. #1
    Tuner in Training
    Join Date
    Oct 2020
    Posts
    18

    Post My NBP Arduino Hardware

    Here is a full description of my Arduino based NBP hardware project. What I am posting here is simply a proof of concept to use as a baseline project. There are no sensors attached. Random numbers are used for example data. I have tried my best to comment the code but there are still probably mistakes, oversights, and typos.

    Warning
    • I am not an electrical or systems engineer.
    • I have no idea if this design is safe or fit for use.
    • Prior to 6 weeks ago I had never written a line of C code.
    • Use at your own risk.


    The Hardware
    Arduino Mega 2560 https://www.amazon.com/gp/product/B01H4ZLZLQ
    ESP8266 (Wi-fi development board) https://www.amazon.com/gp/product/B010N1SPRK

    The Connections
    Arduino Diagram Minimum.png

    The Arduino Code
    Code:
    /*
        This Arduino sketch sends NBP formatted packages from an Arudino 2560 Mega
        to an ESP8266 wi-fi board where it is broadcast out in UDP.
    
        This code is provided as an example that simply sends random numbers, the
        broadcast frequency, and a package count to the NBP receiver. For simplicity,
        connections to sensors is not coded in this sketch.
        In a real scenario one or more sensors would be connected to the Arduino
        and their values sent in the same manner as the random numbers shown here.
    
        The red and green LED lights are used to indicate status.
        After a reboot both the red and green lights come on and stay on while the
        setup() method is running.
    
        When an AT error is detected during a broadcast attempt the red LED lights up
        and stays lit until the next successful package broadcast or until it starts
        waiting for a connection.
    
        While waiting for a connection the red LED will slowly glow brighter and reset to
        off over and over.
    
        When a connection is established and packages are being sent successfully the green
        LED will flash every number of packages sent based on the blinkFrequency variable.
    */
    
    // Define Metadata.
    const String DEVICE_NAME = "My NBP Device";
    
    // Define behaviors.
    const bool DEBUG = true; // Sends debugging info back to the Serial device.
    const bool ECHO_PACKAGE = false; // Sends the raw NBP packages back to the Serial device. Only works if DEBUG also true.
    const bool KEEPALIVE_OFF = true; // Only supported in TrackAddict v4.6.4 and higher.
    
    // Define LED Pins used to indicate status.
    const byte RED = 12;
    const byte GREEN = LED_BUILTIN;
    
    // Number of package cycles to blink green status light
    const byte blinkFrequency = 25;
    
    // Keep track of when to send the UpdateAll NBP.
    const unsigned long updateAllFrequency = 4999;
    unsigned long lastUpdateAllMillis = 0;
    unsigned long now = millis();
    unsigned long lastNow = 0;
    
    // Keep track of AT communication errors.
    int consecutiveErrorCount = 0;
    int consecutiveErrorCountLimit = 25;
    unsigned long loopCount = 0;
    
    void setup() {
      // Setup and turn on the LED lights.
      pinMode(RED, OUTPUT);
      pinMode(GREEN, OUTPUT);
      analogWrite(RED, 100);
      analogWrite(GREEN, 100);
    
      // Setup the random number generator.
      randomSeed(analogRead(1));
    
      // Setup local serial connection.
      if (DEBUG) {
        Serial.begin(9600);
      }
    
      // Setup serial connection with ESP8266.
      Serial1.begin(115200);
    
      // Setup the AT server on the ESP8266.
      sendDataUntil("AT+RST\r\n", 1250, "ready"); // reset the server
      sendDataUntil("AT+CIPMUX=1\r\n", 1250, "OK"); // set multi mode
      sendDataUntil("AT+CIPSERVER=1,80\r\n", 1250, "OK"); // setup server
    
      if (DEBUG) {
        Serial.println("ESP8266 AT Commands Configured");
      }
    
      // Turn off the LED lights.
      digitalWrite(RED, LOW);
      digitalWrite(GREEN, LOW);
    
      waitForConnection();
    }
    
    void waitForConnection()
    {
      if (DEBUG) {
        Serial.println("Waiting for connection...");
      }
    
      String response = "";
      bool keepTrying = true;
      bool foundConnect = false;
      bool foundIpd = false;
      byte ledBrightness = 0;
    
      while (keepTrying) {
        while (keepTrying && Serial1.available()) {
          char c = Serial1.read();
          response += c;
          foundConnect = response.endsWith(",CONNECT");
          foundIpd = response.endsWith("+IPD");
          keepTrying = !foundConnect && !foundIpd;
        }
    
        // Slowly increase brightness of the red led.
        analogWrite(RED, ledBrightness / 4);
        ledBrightness += 8;
        delay(50);
      }
    
      if (DEBUG) {
        Serial.println("Connection Established!");
      }
    
      if (KEEPALIVE_OFF) { // Only supported in TrackAddict v4.6.4 and higher.
        sendNbpPackage("@NAME:" + DEVICE_NAME + "\n@KEEPALIVE:OFF\n");
      }
    
      analogWrite(RED, LOW);
    }
    
    // Keep track of the number of broadcasts made each second.
    byte FrequencyIndex = 0;
    float FrequencySum = 0;
    const int FrequencyWindowSize = 50;
    float Frequencies[FrequencyWindowSize];
    float CurrentAverageFrequency = 0.0;
    
    // Calling this method updates when a packet is set and the frequency is calculated.
    void broadcastFrequencyPing() {
      float currentFrequency = 1000.0 / (now - lastNow); // Calculate the current value.
      FrequencySum -= Frequencies[FrequencyIndex]; // Remove oldest from sum.
      Frequencies[FrequencyIndex] = currentFrequency; // Add newest reading to the window.
      FrequencySum += currentFrequency; // Add newest frequency to sum.
      FrequencyIndex = (FrequencyIndex + 1) % FrequencyWindowSize; // Increment index, wrap to 0.
      CurrentAverageFrequency = FrequencySum / FrequencyWindowSize; // Calculate the average.
      lastNow = now;
    }
    
    // This method returns the current average frequency as calculated by broadcastFrequencyPing().
    float getBroadcastFrequency() {
      return CurrentAverageFrequency;
    }
    
    // Over and over...
    void loop() {
      now = millis();
      loopCount++;
    
      broadcastFrequencyPing();
    
      // If we have too many errors assume there is no connection.
      if (consecutiveErrorCount >= consecutiveErrorCountLimit) {
        if (DEBUG) {
          Serial.println("Consecutive Error Count Limit Exceeded");
        }
        waitForConnection();
      }
    
      // Turn on the green LED if the blink frequency matches.
      if (loopCount % blinkFrequency == 0) {
        analogWrite(GREEN, 30);
      }
    
      // Send the UpdateAll package on the specified frequency.
      if ((now - lastUpdateAllMillis) >= updateAllFrequency) {
        sendNbpPackage(GetUpdateNbpPackage(true));
        lastUpdateAllMillis += updateAllFrequency;
      }
      // Send the Update package on all other loops.
      else {
        sendNbpPackage(GetUpdateNbpPackage(false));
      }
    
      // Turn off any LEDs at the end of the loop.
      if (loopCount % blinkFrequency == 0) {
        digitalWrite(RED, LOW);
        digitalWrite(GREEN, LOW);
      }
    }
    
    // Convenience method for buidling a NBP content line with a float value.
    String buildNbpContentLine(String channel, String unit, float value, int precision) {
      return "\"" + channel + "\",\"" + unit + "\":" + String(value, precision) + "\n";
    }
    
    // Convenience method for buidling a NBP content line with an integer value.
    String buildNbpContentLine(String channel, String unit, int value) {
      return "\"" + channel + "\",\"" + unit + "\":" + String(value) + "\n";
    }
    
    // Setup global variables to keep track of value changes.
    float lastBroadcastFrequency;
    float lastRandomFloat;
    int lastRandomInt;
    
    // This method builds a full NBP package. If updateAll is true then the NBP UPDATEALL package
    // is used and the metadata name line is included.
    String GetUpdateNbpPackage(bool updateAll) {
      float currentBroadcastFrequency = getBroadcastFrequency();
      float currentRandomFloat = random(1, 1000) / 10.0;
      int currentRandomInt = random(1, 100);
    
      // Start to build the package based on the updateAll boolean.
      String toReturn = "*NBP1,UPDATE" + String(updateAll ? "ALL" : "") + "," + String(now / 1000.0, 3) + "\n";
    
      // Include the broadcast frequency only if the value has changed or this is an update all request.
      if (updateAll || lastBroadcastFrequency != currentBroadcastFrequency) {
        lastBroadcastFrequency = currentBroadcastFrequency;
        toReturn += buildNbpContentLine("NBP Freq.", "Hz", currentBroadcastFrequency, 1);
      }
    
      // Include the random float only if the value has changed or this is an update all request.
      if (updateAll || lastRandomFloat != currentRandomFloat) {
        lastRandomFloat != currentRandomFloat;
        toReturn += buildNbpContentLine("Random Float", "Num", currentRandomFloat, 1);
      }
    
      // Include the random integer only if the value has changed or this is an update all request.
      if (updateAll || lastRandomInt != currentRandomInt) {
        lastRandomInt != currentRandomInt;
        toReturn += buildNbpContentLine("Random Int", "Num", currentRandomInt);
      }
    
      // Always include the pacakge count, it is always different.
      toReturn += buildNbpContentLine("Pkg. Count", "Num", loopCount);
    
      // End the pacakge.
      toReturn += "#\n";
    
      if (updateAll) {
        toReturn += "@NAME:" + DEVICE_NAME + "\n";
      }
    
      if (ECHO_PACKAGE && DEBUG) {
        Serial.println();
        Serial.println(toReturn);
        Serial.println();
      }
    
      return toReturn;
    }
    
    // Send the nbpPackage wrapped in an AT Send command.
    void sendNbpPackage(String nbpPackage) {
      byte atTimeout = 200;
      String atCipSendCommand = "AT+CIPSEND=0," + String(nbpPackage.length()) + "\r\n";
    
      Serial1.print(atCipSendCommand); // send the command to the esp8266
    
      String response = "";
      unsigned long time = millis();
      bool keepTrying = true;
      bool foundExpected = false;
      bool foundError = false;
    
      while (keepTrying && (time + atTimeout) > millis()) {
        while (keepTrying && Serial1.available()) {
          char c = Serial1.read();
          response += c;
          foundExpected = response.endsWith(">");
          foundError = response.endsWith("ERROR");
          keepTrying = !foundExpected && !foundError;
        }
      }
    
      if (foundError) {
        digitalWrite(RED, HIGH);
        consecutiveErrorCount++;
    
        if (DEBUG) {
          Serial.println("\nError: (#" + String(consecutiveErrorCount) + ")");
          Serial.println(response);
          Serial.println();
        }
    
      } else if (foundExpected) {
        Serial1.print(nbpPackage);
    
        consecutiveErrorCount = 0;
    
        response = "";
        time = millis();
        keepTrying = true;
        foundExpected = false;
        foundError = false;
    
        while (keepTrying && (time + atTimeout) > millis()) {
          while (keepTrying && Serial1.available()) {
            char c = Serial1.read();
            response += c;
            foundExpected = response.endsWith("SEND OK");
            foundError = response.endsWith("ERROR");
            keepTrying = !foundExpected && !foundError;
          }
        }
    
        if (foundError) {
          digitalWrite(RED, HIGH);
    
          if (DEBUG) {
            Serial.println("\nError:");
            Serial.println(response);
            Serial.println();
          }
        }
      }
    }
    
    // This method is used to send the setup commands to the ESP8266.
    void sendDataUntil(String command, const int timeout, String expected) {
      Serial1.print(command); // Send the command to the ESP8266.
    
      String response = "";
      unsigned long time = millis();
      bool keepTrying = true;
      bool foundExpected = false;
      bool foundError = false;
    
      while (keepTrying && (time + timeout) > millis()) {
        while (keepTrying && Serial1.available()) {
          char c = Serial1.read();
          response += c;
          foundExpected = response.endsWith(expected);
          foundError = response.endsWith("ERROR");
          keepTrying = !foundExpected && !foundError;
        }
      }
    
      if (foundError) {
        if (DEBUG) {
          Serial.println("\nError:");
          Serial.println(response);
          Serial.println();
        }
        analogWrite(RED, 200);
      }
    }
    How it works
    The ESP8266 is untouched from the factory. The default software loaded on it offers a basic web server that is controlled through "AT" commands. It appears to be an old version of the AT standard because some documented commands did not work. I was unable to find the correct Windows drivers that would allow me to update the onboard firmware. Simply powering it up will setup a Wi-fi station that can be connected to without a password.

    The Arduino is responsible for collecting the data, formatting it into NBP packages, wrapping it with AT commands, and sending it to the ESP8266 via a serial connection. It has a means of detecting when a connection is made and broken. It uses 2 LED lights as status indicators.

    The Setup
    I know the IP address of the web server running on the ESP8266. I use that IP address with the port number (192.168.4.1:80) in the NBP configuration in TrackAddict.

    After powering up the device, I connect to the ESP8266 wi-fi network with my iPhone and then open up the TrackAddict app.
    I instantly get a green NBP flag and when I go into setup I can see the data included in the NBP package available and live with data.
    image0.png

    While just spitting out random numbers this device will broadcast around 35-40 packages per second. If you trim this down to sending just 1 data parameter it will broadcast in the high 50's per second. On my full device, while reading data from two potentiometers and two thermocouples, it will broadcast in the low 30's per second.

  2. #2
    Potential Tuner
    Join Date
    Oct 2020
    Posts
    3
    This is super cool, I'll give this a go on my ESP32

    I have a github repo where I have been tracking progress and learning:

    https://github.com/CBlumini/ESP32-CA...main/README.md
    Last edited by Blumini509; 12-11-2020 at 06:02 PM.

  3. #3
    Tuner in Training
    Join Date
    Oct 2020
    Posts
    18

    Red face

    This is how I have applied my NBP device to stream data into TrackAddict running on an iPhone.

    https://youtu.be/uHpCs9AE24M

    • 2m 00s: The sensor is shown
    • 2m 52s: A bench test of the device and sensor
    • 3m 42s: On-track, in-car overlay video

  4. #4
    Tuner in Training
    Join Date
    Oct 2020
    Posts
    18
    Over the last 6 months I have refined, reengineered, and rebuilt my NBP data logger multiple times. My ultimate goal was to include data from a thermal imaging camera so I could log the tire surface temperatures while on track. I have somewhat succeeded. My hardware needs some refinement to make it more reliable, but it is logging the data I needed.

    Here is a video I made that briefly covers the device I made and a few minutes of on track overlay.
    https://www.youtube.com/watch?v=5L5y7ucEeZE