Air Dampener

First I got some of these washing machine rubber mat. This helps a little bit is still to stiff, so low frequency vibrations can pass. I googled around for other peoples solutions for noise reduction, but dind't find anything apart from there stupid tennisball feet. So I made a just a quick try with suff I had laying around but i worked out very well, so I leave this as a solution. Just take two old bicycle tubes and stable wooden plate. I wrapped the tubes in strech film (cling film) to keep them in place. Just put the plate on top, put you smart trainer on the plate and done. Maybe add something to lift your front wheel by the same height.

Smart Fan

In my other project posts I had this cooler blower fan from a scrapped Renault ZOE. As is is quite strong I thought it is the perfect fit for this application. So I took a ESP32C3 supermini, a small DCDC converter and a strong 12V power supply (the fan needs 23A@15V at full power). The ESP32 connects to the wahoo kickr core and gets the current power value via BLE GATT. I added a 3 position switch for selecting the power-scaling.

PIO ESP32C3 main.cpp

#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>


#define PIN_PWM 3

#define PIN_SW1 2
#define PIN_SW2 0
// center pin of switch connected to GND

uint32_t last_value_recv = 0;

int get_mode(){
  bool sw1 = digitalRead(PIN_SW1);
  bool sw2 = digitalRead(PIN_SW2);
  if(sw1 && sw2) return 1;
  if(sw2) return 0;
  return 2;
}

void set_pwm(int x) {
  static float hist = 0;
  int max_pow = 800;

// scale with 3 way switch position
  int mode = get_mode();
  max_pow += mode * 600;
  hist = hist *0.7 + x * 0.3;
  int perc = map(hist, 0,max_pow,0,1000);

  int val = map(perc, 0,1000,3440,0);
  ledcWrite(0, val);
  Serial.printf("Perc: %i PWM: %i\r\n", perc/10, val);
}

static BLEUUID serviceUUID(BLEUUID((uint16_t)0x1818));
static BLEUUID    charUUID(BLEUUID((uint16_t)0x2A63));

static BLEAddress *pServerAddress;
static boolean doConnect = false;
static boolean connected = false;
static boolean notification = false;
static BLERemoteCharacteristic* pRemoteCharacteristic;

typedef struct {
    uint16_t flags;
    int16_t instantaneous_power; // Mandatory field

    // Optional fields based on flags
    bool pedal_power_balance_present;
    uint8_t pedal_power_balance; // Only present if pedal_power_balance_present is true

    bool accumulated_torque_present;
    uint16_t accumulated_torque; // Only present if accumulated_torque_present is true

    bool wheel_revolution_data_present;
    uint32_t cumulative_wheel_revolutions;
    uint16_t last_wheel_event_time;

    bool crank_revolution_data_present;
    uint16_t cumulative_crank_revolutions;
    uint16_t last_crank_event_time;

    bool extreme_force_magnitudes_present;
    int16_t max_force_magnitude;
    int16_t min_force_magnitude;

    bool extreme_torque_magnitudes_present;
    int16_t max_torque_magnitude;
    int16_t min_torque_magnitude;

    bool accumulated_energy_present;
    uint16_t accumulated_energy;

    // Additional fields can be added as needed
} CyclingPowerMeasurement;
CyclingPowerMeasurement powerData;

void decode_cycling_power_measurement(uint8_t *data, size_t length, CyclingPowerMeasurement *measurement) {
    if (length < 2) {
        printf("Invalid data length\n");
        return;
    }
    size_t offset = 0;
    measurement->flags = data[offset] | (data[offset + 1] << 8);
    offset += 2;

    measurement->instantaneous_power = data[offset] | (data[offset + 1] << 8);
    offset += 2;

    measurement->pedal_power_balance_present = measurement->flags & (1 << 0);
    measurement->accumulated_torque_present = measurement->flags & (1 << 1);
    measurement->wheel_revolution_data_present = measurement->flags & (1 << 2);
    measurement->crank_revolution_data_present = measurement->flags & (1 << 3);
    measurement->extreme_force_magnitudes_present = measurement->flags & (1 << 4);
    measurement->extreme_torque_magnitudes_present = measurement->flags & (1 << 5);
    measurement->accumulated_energy_present = measurement->flags & (1 << 6);

    if (measurement->pedal_power_balance_present) {
        measurement->pedal_power_balance = data[offset];
        offset += 1;
    }

    if (measurement->accumulated_torque_present) {
        measurement->accumulated_torque = data[offset] | (data[offset + 1] << 8);
        offset += 2;
    }

    if (measurement->wheel_revolution_data_present) {
        measurement->cumulative_wheel_revolutions = data[offset] | (data[offset + 1] << 8) |
                                                    (data[offset + 2] << 16) | (data[offset + 3] << 24);
        offset += 4;

        measurement->last_wheel_event_time = data[offset] | (data[offset + 1] << 8);
        offset += 2;
    }

    if (measurement->crank_revolution_data_present) {
        measurement->cumulative_crank_revolutions = data[offset] | (data[offset + 1] << 8);
        offset += 2;

        measurement->last_crank_event_time = data[offset] | (data[offset + 1] << 8);
        offset += 2;
    }

    if (measurement->extreme_force_magnitudes_present) {
        measurement->max_force_magnitude = data[offset] | (data[offset + 1] << 8);
        offset += 2;

        measurement->min_force_magnitude = data[offset] | (data[offset + 1] << 8);
        offset += 2;
    }

    if (measurement->extreme_torque_magnitudes_present) {
        measurement->max_torque_magnitude = data[offset] | (data[offset + 1] << 8);
        offset += 2;

        measurement->min_torque_magnitude = data[offset] | (data[offset + 1] << 8);
        offset += 2;
    }

    if (measurement->accumulated_energy_present) {
        measurement->accumulated_energy = data[offset] | (data[offset + 1] << 8);
        offset += 2;
    }
}


static void notifyCallback(
  BLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify) {
  decode_cycling_power_measurement(pData, length, &powerData);
  Serial.printf("Instantaneous Power: %d W\n", powerData.instantaneous_power);
  set_pwm(powerData.instantaneous_power);
  last_value_recv = millis();
}


bool connectToServer(BLEAddress pAddress) {
    Serial.print(F("Forming a connection to "));
    Serial.println(pAddress.toString().c_str());
    
    BLEClient*  pClient  = BLEDevice::createClient();

    pClient->connect(pAddress,BLE_ADDR_TYPE_RANDOM);
    Serial.println(F("Connected to server"));

    BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
    if (pRemoteService == nullptr) {
      return false;
    }

    pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);
    if (pRemoteCharacteristic == nullptr) {
      return false;
    }

    pRemoteCharacteristic->registerForNotify(notifyCallback);
    return true;
}


class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {

  void onResult(BLEAdvertisedDevice advertisedDevice) {
    Serial.print("BLE Advertised Device found: ");
    Serial.println(advertisedDevice.toString().c_str());

    // We have found a device, let us now see if it contains the service we are looking for.
    if (advertisedDevice.haveServiceUUID() && advertisedDevice.getServiceUUID().equals(serviceUUID)) {
      Serial.println("Found our device"); 
      advertisedDevice.getScan()->stop();

      pServerAddress = new BLEAddress(advertisedDevice.getAddress());
      doConnect = true;
    }
  }
};


void setup() {
  pinMode(PIN_SW1, INPUT_PULLUP);
  pinMode(PIN_SW2, INPUT_PULLUP);
  pinMode(PIN_PWM, OUTPUT);
  digitalWrite(PIN_PWM, 0);
  Serial.begin(115200);

  BLEDevice::init("BLE_FAN");
  BLEScan* pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  pBLEScan->setActiveScan(true);
  pBLEScan->start(30);
  

  ledcSetup(0, 100, 12); // ledChannel, freq, resolution
  ledcAttachPin(PIN_PWM, 0); // pin, ledChannel
  ledcWrite(0, 4095);
}

void loop() {


  if (doConnect == true) {
    if (connectToServer(*pServerAddress)) {
      Serial.println("connected to BLE Server.");
      connected = true;
    }
    doConnect = false;
  }

  // Turn on notifications 
  if (connected) {
    if (notification == false) {
      const uint8_t dat[] = {0x01, 0x00};
      pRemoteCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)dat, 2, true);
      notification = true;
    }
    
  }

  // turn off fan on timout
  if(last_value_recv && ((millis()+10 - last_value_recv) > 5000)){
    Serial.println(F("Timeout"));
    last_value_recv = 0;
    set_pwm(0);
  }
}