This forum uses cookies
This forum makes use of cookies to store your login information if you are registered, and your last visit if you are not. Cookies are small text documents stored on your computer; the cookies set by this forum can only be used on this website and pose no security risk. Cookies on this forum also track the specific topics you have read and when you last read them. Please confirm whether you accept or reject these cookies being set.

A cookie will be stored in your browser regardless of choice to prevent you being asked this question again. You will be able to change your cookie settings at any time using the link in the footer.

Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Write my own wind sensor code
#11
Hey Ormingtrude! I'm sorry but i'm afraid i won't be of any help. I no longer have that windvane nor that boat :S

I used an ESP32 with SensESP btw, not directly OpenPlotter's I2C app. If it's of any help, this is the last (i think) code I used, and was working quite decently:



Code:
#include <Adafruit_ADS1X15.h>
#include <Wire.h>

//#include "sensesp/sensors/analog_input.h"
#include "sensesp/sensors/digital_input.h"
#include "sensesp/sensors/sensor.h"
#include "sensesp/signalk/signalk_output.h"
#include "sensesp/system/lambda_consumer.h"
#include "sensesp/transforms/curveinterpolator.h"
#include "sensesp/transforms/linear.h"
#include "sensesp_app_builder.h"

using namespace sensesp;

class AngleInterpreter: public CurveInterpolator {
  public:
  AngleInterpreter(String config_path = "")
    : CurveInterpolator(NULL, config_path) {
    // Populate a lookup table tp translate the ohm values returned by
    // our temperature sender to degrees Kelvin
    clear_samples();
    // addSample(CurveInterpolator::Sample(knownOhmValue, knownKelvin));
    add_sample(CurveInterpolator::Sample(-350, 0));
    add_sample(CurveInterpolator::Sample(20, 5));
    add_sample(CurveInterpolator::Sample(3350, 45));
    add_sample(CurveInterpolator::Sample(6950, 90));
    add_sample(CurveInterpolator::Sample(10150, 135));
    add_sample(CurveInterpolator::Sample(13650, 180));
    add_sample(CurveInterpolator::Sample(17350, 225));
    add_sample(CurveInterpolator::Sample(19900, 270));
    add_sample(CurveInterpolator::Sample(22710, 315));
    add_sample(CurveInterpolator::Sample(26000, 360));
  }
};

reactesp::ReactESP app;

Adafruit_ADS1115 ads;

// Pin connected to the ALERT/RDY signal for new sample notification.
// constexpr int READY_PIN = 17;

// This is required on ESP32 to put the ISR in IRAM. Define as
// empty for other platforms. Be careful - other platforms may have
// other requirements.
#ifndef IRAM_ATTR
#define IRAM_ATTR
#endif

// volatile bool new_data = false;
// void IRAM_ATTR NewDataReadyISR() { new_data = true; }

String awa_sk_path = "environment.wind.angleApparent";
String awa_sk_path_config_path = "/windAngleApparent/skKey";
String awa_options_config_path = "/windAngleApparent/options";

String awa_full_range_config_path = "/windAngleApparent/useFullRange";
String awa_use_average_config_path = "/windAngleApparent/useAverage";

String awa_curve_config_path = "/windAngleApparent/curve";

// GPIO number to use for the analog input
const uint8_t WIND_ANGLE_PIN = 16;  // 32;//34 inestable
// Define how often (in milliseconds) new samples are acquired
const unsigned int AWA_READ_INTERVAL = 50;
const unsigned int AWA_SEND_INTERVAL = 100;

bool _default_FullRange = false;
bool _default_UseAverage = true;

double angleToSend = 0;

int lastReading = -1;
const int READ_BUFFER_SIZE = 10;
int readBuffer[READ_BUFFER_SIZE] = {};

int readingCount = 1;
int readingCumulated = 1;

const int maxAngleValue = 26000;
const int maxAngleDifferential = 512;
const double maxAnglePossibleValue = 26350.0f;

int getReadingAverage() { return readingCumulated / readingCount; }
void resetReadingCount() {
  readingCount = 1;
  readingCumulated = 1;
}

int averagedValue(int lastValues[], int size, int newValue) {
  int total = 0;
  // Discard the first value and move all elements to the left
  for (int i = 0; i < size - 1; i++) {
    lastValues[i] = lastValues[i + 1];
    total += lastValues[i + 1];
  }

  // Add the new value to the right
  lastValues[size - 1] = newValue;
  total += newValue;

  return total / size;
}

void SetupDavisAngle() {
  SKMetadata* metadata = new SKMetadata();
  metadata->units_ = "rad";
  metadata->description_ = "Aparent Wind Angle";
  metadata->display_name_ = "Aparent Wind Angle";
  metadata->short_name_ = "AWA";

  auto awaOutput =
    new SKOutputFloat(awa_sk_path, awa_sk_path_config_path, metadata);

  /************************** READING AND STORAGE *****************************/

  auto* readingStorage =
    new LambdaConsumer<double>([](double input) { angleToSend = input; });

  auto* windAngleReader =
    new RepeatSensor<double>(AWA_READ_INTERVAL, []() -> double {
    int reading = getReadingAverage();

    resetReadingCount();

    int raw = reading;
    int filtered = reading;

    if (reading > maxAngleValue) filtered = maxAngleValue - reading;

    double deg = (double)filtered / maxAnglePossibleValue * 360.0f;

    // Serial.printf("Raw: %d | ", raw);
    // Serial.printf("Filtered: %d | Deg: %.1f | ", filtered, deg);

    if (lastReading < 0) lastReading = filtered;

    int averaged = averagedValue(readBuffer, READ_BUFFER_SIZE, filtered);

    //Serial.printf("Avg: %d | ", averaged);

    int16_t result = 0;

    if (abs(lastReading - averaged) > maxAngleDifferential) {
      Serial.print("-------IGNORED-------");
      result = lastReading;
    }
    else {
      result = averaged;
    }

    //Serial.println("");
    lastReading = averaged;
    return result;
      });

  /******************************** SENDING ***********************************/
  auto rangeConverterFunction = [](double angle, bool fullRange) -> float {
    if (!fullRange && angle > 180) {
      angle -= 360;
    }
    return angle;
  };

  auto* rangeConverter = new LambdaTransform<float, float, bool>(
    rangeConverterFunction, _default_FullRange,
    new ParamInfo[1]{ {"fullRange", "Use Full 360 Range"} },
    awa_options_config_path);

  auto* radianConverter = new LambdaTransform<double, float>(
    [](double deg) -> float { return deg / 180.0f * PI; });

  auto* windAngleSender = new RepeatSensor<double>(
    AWA_SEND_INTERVAL, []() -> double { return angleToSend; });

  auto* logger = new LambdaConsumer<double>([](double angleToSend) {
    Serial.printf("Reads: %d\r\n", getReadingAverage());
    resetReadingCount();
    });

  windAngleReader->connect_to(new AngleInterpreter(awa_curve_config_path))
    ->connect_to(rangeConverter)
    ->connect_to(radianConverter)
    ->connect_to(readingStorage);

  windAngleSender->connect_to(awaOutput);
  // windAngleSender->connect_to(logger);
}

String aws_sk_path = "environment.wind.speedApparent";
String aws_delay_config_path = "/windSpeedApparent/readInterval";
String aws_sk_path_config_path = "/windSpeedApparent/skKey";

const uint8_t WIND_SPEED_PIN = 25;

unsigned int AWS_READ_DELAY = 1000;
unsigned int AWS_DEBOUNCE_DELAY = 10;

const int SPEED_READ_BUFFER_SIZE = 10;
int speedReadBuffer[SPEED_READ_BUFFER_SIZE] = {};

float speedAveragedValue(int lastValues[], int size, int newValue) {
  int total = 0;
  // Discard the first value and move all elements to the left
  for (int i = 0; i < size - 1; i++) {
    lastValues[i] = lastValues[i + 1];
    total += lastValues[i + 1];
  }

  // Add the new value to the right
  lastValues[size - 1] = newValue;
  total += newValue;
  return (float)total / (float)size;
}

void SetupDavisSpeed() {
  /* Initialize buffer */
   for (int i = 0; i < SPEED_READ_BUFFER_SIZE; i++) {
    speedReadBuffer[i] = 0;
  }


  auto* wind_speed_rev_counter = new DigitalInputDebounceCounter(
    WIND_SPEED_PIN, INPUT_PULLUP, RISING, AWS_READ_DELAY, AWS_DEBOUNCE_DELAY,
    aws_delay_config_path);

  SKMetadata* metadata = new SKMetadata();
  metadata->units_ = "m/s";
  metadata->description_ = "Aparent Wind Speed";
  metadata->display_name_ = "Aparent Wind Speed";
  metadata->short_name_ = "AWS";

  auto awsOutput =
    new SKOutputFloat(aws_sk_path, aws_sk_path_config_path, metadata);

  auto speed_detector_function = [](int input) -> float {
    // mph = P * (2.25 / I)  Where P is the pulse count, and I is the interval
    // in SECONDS (i.e: AWS_READ_DELAY / 1000)
    float mph = input * (2.25f / (AWS_READ_DELAY / 1000));
    float m_s = mph * 0.44704;

    //Serial.printf("Pulses: %d", input);
    //Serial.println("");

    float averaged = speedAveragedValue(speedReadBuffer, SPEED_READ_BUFFER_SIZE, m_s);

    // Serial.printf(" - Average: %lf", averaged);
    // Serial.println("");

    return averaged;
    //return m_s;
  };

  auto* speedDetector =
    new LambdaTransform<int, float>(speed_detector_function);

  wind_speed_rev_counter->connect_to(speedDetector)->connect_to(awsOutput);
}

/****************  ADS1115 ***********************/

constexpr int READY_PIN = 17;
volatile bool new_data = false;
void IRAM_ATTR NewDataReadyISR() { new_data = true; }

void setupADC() {
  pinMode(READY_PIN, INPUT);
  // We get a falling edge every time a new sample is ready.
  //attachInterrupt(digitalPinToInterrupt(READY_PIN), NewDataReadyISR, FALLING);

  // Start continuous conversions.
  ads.startADCReading(ADS1X15_REG_CONFIG_MUX_SINGLE_0, /*continuous=*/true);
}

void loopADC() {
  // If we don't have new data, skip this iteration.
  if (!new_data) {
    return;
  }

  int16_t reading = ads.getLastConversionResults();

  readingCount++;
  readingCumulated += reading;

  new_data = false;
}

/*************************************************/

// The setup function performs one-time application initialization.
void setup() {
#ifndef SERIAL_DEBUG_DISABLED
  SetupSerialDebug(115200);
#endif

  // Construct the global SensESPApp() object
  SensESPAppBuilder builder;
  sensesp_app = (&builder)
    // Set a custom hostname for the app.
    ->set_hostname("WindESP")
    // Optionally, hard-code the WiFi and Signal K server
    // settings. This is normally not needed.
    ->set_wifi("openplotter", "guardian")
    ->set_sk_server("10.10.10.1", 3000)
    ->get_app();

  /************************** DAVIS *******************************/
  SetupDavisSpeed();
  SetupDavisAngle();
  /****************************************************************/

  /// Usually SDA is 21 and SCL is 22, but in this case I designed the PCB wrong
  Wire.begin(22,21);

  ads.setGain(GAIN_ONE);
  ads.setDataRate(RATE_ADS1115_860SPS);

  if (!ads.begin()) {
    Serial.println("Failed to initialize ADS.");
    while (1)
      ;
  }

  setupADC();

  app.onInterrupt(READY_PIN, INPUT, NewDataReadyISR);

  // Start networking, SK server connections and other SensESP internals
  sensesp_app->start();
}

void loop() {
  loopADC();
  app.tick();
}
Reply
#12
(2025-01-20, 06:16 PM)MigGat Wrote: Hey Ormingtrude! I'm sorry but i'm afraid i won't be of any help. I no longer have that windvane nor that boat :S

I used an ESP32 with SensESP btw, not directly OpenPlotter's I2C app. If it's of any help, this is the last (i think) code I used, and was working quite decently:



Code:
#include <Adafruit_ADS1X15.h>
#include <Wire.h>

//#include "sensesp/sensors/analog_input.h"
#include "sensesp/sensors/digital_input.h"
#include "sensesp/sensors/sensor.h"
#include "sensesp/signalk/signalk_output.h"
#include "sensesp/system/lambda_consumer.h"
#include "sensesp/transforms/curveinterpolator.h"
#include "sensesp/transforms/linear.h"
#include "sensesp_app_builder.h"

using namespace sensesp;

class AngleInterpreter: public CurveInterpolator {
  public:
  AngleInterpreter(String config_path = "")
    : CurveInterpolator(NULL, config_path) {
    // Populate a lookup table tp translate the ohm values returned by
    // our temperature sender to degrees Kelvin
    clear_samples();
    // addSample(CurveInterpolator::Sample(knownOhmValue, knownKelvin));
    add_sample(CurveInterpolator::Sample(-350, 0));
    add_sample(CurveInterpolator::Sample(20, 5));
    add_sample(CurveInterpolator::Sample(3350, 45));
    add_sample(CurveInterpolator::Sample(6950, 90));
    add_sample(CurveInterpolator::Sample(10150, 135));
    add_sample(CurveInterpolator::Sample(13650, 180));
    add_sample(CurveInterpolator::Sample(17350, 225));
    add_sample(CurveInterpolator::Sample(19900, 270));
    add_sample(CurveInterpolator::Sample(22710, 315));
    add_sample(CurveInterpolator::Sample(26000, 360));
  }
};

reactesp::ReactESP app;

Adafruit_ADS1115 ads;

// Pin connected to the ALERT/RDY signal for new sample notification.
// constexpr int READY_PIN = 17;

// This is required on ESP32 to put the ISR in IRAM. Define as
// empty for other platforms. Be careful - other platforms may have
// other requirements.
#ifndef IRAM_ATTR
#define IRAM_ATTR
#endif

// volatile bool new_data = false;
// void IRAM_ATTR NewDataReadyISR() { new_data = true; }

String awa_sk_path = "environment.wind.angleApparent";
String awa_sk_path_config_path = "/windAngleApparent/skKey";
String awa_options_config_path = "/windAngleApparent/options";

String awa_full_range_config_path = "/windAngleApparent/useFullRange";
String awa_use_average_config_path = "/windAngleApparent/useAverage";

String awa_curve_config_path = "/windAngleApparent/curve";

// GPIO number to use for the analog input
const uint8_t WIND_ANGLE_PIN = 16;  // 32;//34 inestable
// Define how often (in milliseconds) new samples are acquired
const unsigned int AWA_READ_INTERVAL = 50;
const unsigned int AWA_SEND_INTERVAL = 100;

bool _default_FullRange = false;
bool _default_UseAverage = true;

double angleToSend = 0;

int lastReading = -1;
const int READ_BUFFER_SIZE = 10;
int readBuffer[READ_BUFFER_SIZE] = {};

int readingCount = 1;
int readingCumulated = 1;

const int maxAngleValue = 26000;
const int maxAngleDifferential = 512;
const double maxAnglePossibleValue = 26350.0f;

int getReadingAverage() { return readingCumulated / readingCount; }
void resetReadingCount() {
  readingCount = 1;
  readingCumulated = 1;
}

int averagedValue(int lastValues[], int size, int newValue) {
  int total = 0;
  // Discard the first value and move all elements to the left
  for (int i = 0; i < size - 1; i++) {
    lastValues[i] = lastValues[i + 1];
    total += lastValues[i + 1];
  }

  // Add the new value to the right
  lastValues[size - 1] = newValue;
  total += newValue;

  return total / size;
}

void SetupDavisAngle() {
  SKMetadata* metadata = new SKMetadata();
  metadata->units_ = "rad";
  metadata->description_ = "Aparent Wind Angle";
  metadata->display_name_ = "Aparent Wind Angle";
  metadata->short_name_ = "AWA";

  auto awaOutput =
    new SKOutputFloat(awa_sk_path, awa_sk_path_config_path, metadata);

  /************************** READING AND STORAGE *****************************/

  auto* readingStorage =
    new LambdaConsumer<double>([](double input) { angleToSend = input; });

  auto* windAngleReader =
    new RepeatSensor<double>(AWA_READ_INTERVAL, []() -> double {
    int reading = getReadingAverage();

    resetReadingCount();

    int raw = reading;
    int filtered = reading;

    if (reading > maxAngleValue) filtered = maxAngleValue - reading;

    double deg = (double)filtered / maxAnglePossibleValue * 360.0f;

    // Serial.printf("Raw: %d | ", raw);
    // Serial.printf("Filtered: %d | Deg: %.1f | ", filtered, deg);

    if (lastReading < 0) lastReading = filtered;

    int averaged = averagedValue(readBuffer, READ_BUFFER_SIZE, filtered);

    //Serial.printf("Avg: %d | ", averaged);

    int16_t result = 0;

    if (abs(lastReading - averaged) > maxAngleDifferential) {
      Serial.print("-------IGNORED-------");
      result = lastReading;
    }
    else {
      result = averaged;
    }

    //Serial.println("");
    lastReading = averaged;
    return result;
      });

  /******************************** SENDING ***********************************/
  auto rangeConverterFunction = [](double angle, bool fullRange) -> float {
    if (!fullRange && angle > 180) {
      angle -= 360;
    }
    return angle;
  };

  auto* rangeConverter = new LambdaTransform<float, float, bool>(
    rangeConverterFunction, _default_FullRange,
    new ParamInfo[1]{ {"fullRange", "Use Full 360 Range"} },
    awa_options_config_path);

  auto* radianConverter = new LambdaTransform<double, float>(
    [](double deg) -> float { return deg / 180.0f * PI; });

  auto* windAngleSender = new RepeatSensor<double>(
    AWA_SEND_INTERVAL, []() -> double { return angleToSend; });

  auto* logger = new LambdaConsumer<double>([](double angleToSend) {
    Serial.printf("Reads: %d\r\n", getReadingAverage());
    resetReadingCount();
    });

  windAngleReader->connect_to(new AngleInterpreter(awa_curve_config_path))
    ->connect_to(rangeConverter)
    ->connect_to(radianConverter)
    ->connect_to(readingStorage);

  windAngleSender->connect_to(awaOutput);
  // windAngleSender->connect_to(logger);
}

String aws_sk_path = "environment.wind.speedApparent";
String aws_delay_config_path = "/windSpeedApparent/readInterval";
String aws_sk_path_config_path = "/windSpeedApparent/skKey";

const uint8_t WIND_SPEED_PIN = 25;

unsigned int AWS_READ_DELAY = 1000;
unsigned int AWS_DEBOUNCE_DELAY = 10;

const int SPEED_READ_BUFFER_SIZE = 10;
int speedReadBuffer[SPEED_READ_BUFFER_SIZE] = {};

float speedAveragedValue(int lastValues[], int size, int newValue) {
  int total = 0;
  // Discard the first value and move all elements to the left
  for (int i = 0; i < size - 1; i++) {
    lastValues[i] = lastValues[i + 1];
    total += lastValues[i + 1];
  }

  // Add the new value to the right
  lastValues[size - 1] = newValue;
  total += newValue;
  return (float)total / (float)size;
}

void SetupDavisSpeed() {
  /* Initialize buffer */
   for (int i = 0; i < SPEED_READ_BUFFER_SIZE; i++) {
    speedReadBuffer[i] = 0;
  }


  auto* wind_speed_rev_counter = new DigitalInputDebounceCounter(
    WIND_SPEED_PIN, INPUT_PULLUP, RISING, AWS_READ_DELAY, AWS_DEBOUNCE_DELAY,
    aws_delay_config_path);

  SKMetadata* metadata = new SKMetadata();
  metadata->units_ = "m/s";
  metadata->description_ = "Aparent Wind Speed";
  metadata->display_name_ = "Aparent Wind Speed";
  metadata->short_name_ = "AWS";

  auto awsOutput =
    new SKOutputFloat(aws_sk_path, aws_sk_path_config_path, metadata);

  auto speed_detector_function = [](int input) -> float {
    // mph = P * (2.25 / I)  Where P is the pulse count, and I is the interval
    // in SECONDS (i.e: AWS_READ_DELAY / 1000)
    float mph = input * (2.25f / (AWS_READ_DELAY / 1000));
    float m_s = mph * 0.44704;

    //Serial.printf("Pulses: %d", input);
    //Serial.println("");

    float averaged = speedAveragedValue(speedReadBuffer, SPEED_READ_BUFFER_SIZE, m_s);

    // Serial.printf(" - Average: %lf", averaged);
    // Serial.println("");

    return averaged;
    //return m_s;
  };

  auto* speedDetector =
    new LambdaTransform<int, float>(speed_detector_function);

  wind_speed_rev_counter->connect_to(speedDetector)->connect_to(awsOutput);
}

/****************  ADS1115 ***********************/

constexpr int READY_PIN = 17;
volatile bool new_data = false;
void IRAM_ATTR NewDataReadyISR() { new_data = true; }

void setupADC() {
  pinMode(READY_PIN, INPUT);
  // We get a falling edge every time a new sample is ready.
  //attachInterrupt(digitalPinToInterrupt(READY_PIN), NewDataReadyISR, FALLING);

  // Start continuous conversions.
  ads.startADCReading(ADS1X15_REG_CONFIG_MUX_SINGLE_0, /*continuous=*/true);
}

void loopADC() {
  // If we don't have new data, skip this iteration.
  if (!new_data) {
    return;
  }

  int16_t reading = ads.getLastConversionResults();

  readingCount++;
  readingCumulated += reading;

  new_data = false;
}

/*************************************************/

// The setup function performs one-time application initialization.
void setup() {
#ifndef SERIAL_DEBUG_DISABLED
  SetupSerialDebug(115200);
#endif

  // Construct the global SensESPApp() object
  SensESPAppBuilder builder;
  sensesp_app = (&builder)
    // Set a custom hostname for the app.
    ->set_hostname("WindESP")
    // Optionally, hard-code the WiFi and Signal K server
    // settings. This is normally not needed.
    ->set_wifi("openplotter", "guardian")
    ->set_sk_server("10.10.10.1", 3000)
    ->get_app();

  /************************** DAVIS *******************************/
  SetupDavisSpeed();
  SetupDavisAngle();
  /****************************************************************/

  /// Usually SDA is 21 and SCL is 22, but in this case I designed the PCB wrong
  Wire.begin(22,21);

  ads.setGain(GAIN_ONE);
  ads.setDataRate(RATE_ADS1115_860SPS);

  if (!ads.begin()) {
    Serial.println("Failed to initialize ADS.");
    while (1)
      ;
  }

  setupADC();

  app.onInterrupt(READY_PIN, INPUT, NewDataReadyISR);

  // Start networking, SK server connections and other SensESP internals
  sensesp_app->start();
}

void loop() {
  loopADC();
  app.tick();
}

I have a feeling the  "class AngleInterpreter" section is going to help.

Thanks
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)