onChange function not called on change

Hi
I see similar questions in the forum but not any answers.

I use a Arduino Uno R4 Wifi and creating a device and a thing online all works fine. Also creating cloude variables which I set to read/write works fine.

In thingProperties.h I see that the variables plus the functions are built correctly.

See the simplified code below:

#include <ArduinoIoTCloud.h>
#include <Arduino_ConnectionHandler.h>

const char DEVICE_LOGIN_NAME[]  = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

const char SSID[]               = SECRET_SSID;    // Network SSID (name)
const char PASS[]               = SECRET_OPTIONAL_PASS;    // Network password (use for WPA, or use as key for WEP)
const char DEVICE_KEY[]  = SECRET_DEVICE_KEY;    // Secret device password

void onTotalBatteryVoltageChange();

CloudElectricPotential totalBatteryVoltage;

void initProperties(){

 ArduinoCloud.addProperty(totalBatteryVoltage, READWRITE, ON_CHANGE, onTotalBatteryVoltageChange);

WiFiConnectionHandler ArduinoIoTPreferredConnection(SSID, PASS);

Now, the cloud variables are also regularly updated as I can see in the thing, but the on change function void onTotalBatteryVoltageChange(); in the above example is not called on change.

Am I missing something?

Thank you,

John

Welcome to the forum

Your topic has been moved a more relevant forum category

Thank you Heli Bob!

Is there someone who knows the answer to my question?

I assume that functions automatically built while we create Cloud variables, which are called on......Change should be called automatically while a variable is changed, no?

Thank you,

John

Hi @johnmavic.

Please provide a detailed description of what you are doing to change the variable.

The onChange function should be called automatically when the associated Cloud variable is changed as long as that variable is Read/Write

Your posted code does not actually have such a function in it beyond its definition. Please post the full sketch

ok, here is thingProperties.h

// Code generated by Arduino IoT Cloud, DO NOT EDIT.

#include <ArduinoIoTCloud.h>
#include <Arduino_ConnectionHandler.h>

const char DEVICE_LOGIN_NAME[]  = "a6597eb9-6672-4933-9e44-f9ad03187197";

const char SSID[]               = SECRET_SSID;    // Network SSID (name)
const char PASS[]               = SECRET_OPTIONAL_PASS;    // Network password (use for WPA, or use as key for WEP)
const char DEVICE_KEY[]  = SECRET_DEVICE_KEY;    // Secret device password

void onMosfetBattery01CounterChange();
void onMosfetBattery02CounterChange();
void onBattery02VoltageChange();
void onTotalBatteryVoltageChange();
void onVoltageDifferenceChange();
void onTimeStampChange();
void onBalancerActiveChange();
void onBatteryDifferenceAlertChange();
void onIsBattery01VoltageHighChange();
void onIsBattery01VoltageLowChange();
void onIsBattery02VoltageHighChange();
void onIsBattery02VoltageLowChange();
void onMosfetBattery01Change();
void onMosfetBattery02Change();

CloudCounter mosfetBattery01Counter;
CloudCounter mosfetBattery02Counter;
CloudElectricPotential battery01Voltage;
CloudElectricPotential battery02Voltage;
CloudElectricPotential totalBatteryVoltage;
CloudElectricPotential voltageDifference;
CloudSchedule timeStamp;
bool balancer_Active;
bool batteryDifferenceAlert;
bool isBattery01VoltageHigh;
bool isBattery01VoltageLow;
bool isBattery02VoltageHigh;
bool isBattery02VoltageLow;
bool mosfetBattery01;
bool mosfetBattery02;

void initProperties(){

  ArduinoCloud.setBoardId(DEVICE_LOGIN_NAME);
  ArduinoCloud.setSecretDeviceKey(DEVICE_KEY);
  ArduinoCloud.addProperty(mosfetBattery01Counter, READWRITE, ON_CHANGE, onMosfetBattery01CounterChange);
  ArduinoCloud.addProperty(mosfetBattery02Counter, READWRITE, ON_CHANGE, onMosfetBattery02CounterChange);
  ArduinoCloud.addProperty(battery01Voltage, READ, ON_CHANGE, NULL);
  ArduinoCloud.addProperty(battery02Voltage, READWRITE, ON_CHANGE, onBattery02VoltageChange);
  ArduinoCloud.addProperty(totalBatteryVoltage, READWRITE, ON_CHANGE, onTotalBatteryVoltageChange);
  ArduinoCloud.addProperty(voltageDifference, READWRITE, ON_CHANGE, onVoltageDifferenceChange);
  ArduinoCloud.addProperty(timeStamp, READWRITE, ON_CHANGE, onTimeStampChange);
  ArduinoCloud.addProperty(balancer_Active, READWRITE, ON_CHANGE, onBalancerActiveChange);
  ArduinoCloud.addProperty(batteryDifferenceAlert, READWRITE, ON_CHANGE, onBatteryDifferenceAlertChange);
  ArduinoCloud.addProperty(isBattery01VoltageHigh, READWRITE, ON_CHANGE, onIsBattery01VoltageHighChange);
  ArduinoCloud.addProperty(isBattery01VoltageLow, READWRITE, ON_CHANGE, onIsBattery01VoltageLowChange);
  ArduinoCloud.addProperty(isBattery02VoltageHigh, READWRITE, ON_CHANGE, onIsBattery02VoltageHighChange);
  ArduinoCloud.addProperty(isBattery02VoltageLow, READWRITE, ON_CHANGE, onIsBattery02VoltageLowChange);
  ArduinoCloud.addProperty(mosfetBattery01, READWRITE, ON_CHANGE, onMosfetBattery01Change);
  ArduinoCloud.addProperty(mosfetBattery02, READWRITE, ON_CHANGE, onMosfetBattery02Change);

}

WiFiConnectionHandler ArduinoIoTPreferredConnection(SSID, PASS);

and here is the full Sketch:

/* 
  Sketch generated by the Arduino IoT Cloud Thing "Untitled"
  https://create.arduino.cc/cloud/things/7a034549-50f1-4bd3-abb0-780c15ab3b0d 

  Arduino IoT Cloud Variables description

  The following variables are automatically generated and updated when changes are made to the Thing

  CloudCounter mosfetBattery01Counter;
  CloudCounter mosfetBattery02Counter;
  CloudElectricPotential battery01Voltage;
  CloudElectricPotential battery02Voltage;
  CloudElectricPotential totalBatteryVoltage;
  CloudElectricPotential voltageDifference;
  CloudSchedule timeStamp;
  bool balancer_Active;
  bool batteryDifferenceAlert;
  bool isBattery01VoltageHigh;
  bool isBattery01VoltageLow;
  bool isBattery02VoltageHigh;
  bool isBattery02VoltageLow;
  bool mosfetBattery01;
  bool mosfetBattery02;

  Variables which are marked as READ/WRITE in the Cloud Thing will also have functions
  which are called when their values are changed from the Dashboard.
  These functions are generated with the Thing and added at the end of this sketch.
*/
#include "thingProperties.h"
#include "arduino_secrets.h"


// Pin-Definitionen
const int pinVBat1 = A0;    // Pin für Battery 01 Voltage
const int pinVBatTot = A1;  // Pin für Total Battery Voltage
const int pinOutput7 = 7;   // MOSFET 1
const int pinOutput8 = 8;   // MOSFET 2


// Definiere die Größe des Zwischenspeichers für die Messungen
const int numMeasurements = 5;
int measurementsBattery01[numMeasurements];
int measurementsTotalBattery[numMeasurements];
int measurementCount = 0;


// Definiert wie häufig Zwischenwerte der Spannung gemessen werden
unsigned long previousMillis = 0;  // will store the last time the code was run
const long interval = 1000;  // interval at which to run code (1 seconds x measurements = Loop runs every 5 seconds)

// Brauchen wir um die Batterien zu beruhigen
unsigned long equalizationStartTime = 0;

void setup() {
  // Initialize serial and wait for port to open:
  Serial.begin(9600);
  
  // This delay gives the chance to wait for a Serial Monitor without blocking if none is found
  delay(1500); 

  // Defined in thingProperties.h
  initProperties();

  // Connect to Arduino IoT Cloud
  ArduinoCloud.begin(ArduinoIoTPreferredConnection);

  // Set initial state und Anfangsmesswerte
  batteryDifferenceAlert = false;
  isBattery01VoltageHigh = false;
  isBattery01VoltageLow = false;
  isBattery02VoltageHigh = false;
  isBattery02VoltageLow = false;
  mosfetBattery01 = false;
  mosfetBattery02 = false;
  balancer_Active = false;

  battery01Voltage = 0;
  battery02Voltage = 0;
  totalBatteryVoltage = 0;
  voltageDifference = 0;

  // Setzen der Pin-Modi zur Ansteuerung der Mosfet
  pinMode(pinOutput7, OUTPUT);  // Pin 7 als Ausgang definieren
  pinMode(pinOutput8, OUTPUT);  // Pin 8 als Ausgang definieren

  /*
     The following function allows you to obtain more information
     related to the state of network and IoT Cloud connection and errors
     the higher number the more granular information you’ll get.
     The default is 0 (only errors).
     Maximum is 4
 */
  setDebugMessageLevel(2);
  ArduinoCloud.printDebugInfo();
}

void loop() {

  ArduinoCloud.update();

  // Ermittle den Status des Balancers
  onBalancerActiveChange();
  
  unsigned long currentMillis = millis();  // get the current "time"

  //
  //Spannungsberechnung 
  //Hier werden die Spannungen gemittelt, Die Spannung von Batterie 01 an battery01Voltage und die Gesamtspannung an totalBatteryVoltage zugewiesen und schlussendlich battery02Voltage berechnet
  //
  if (currentMillis - previousMillis >= interval) {  // if enough time has passed
    previousMillis = currentMillis;  // save the current time

    // Führe die Messungen durch und speichere sie
    measurementsBattery01[measurementCount] = analogRead(pinVBat1);
    measurementsTotalBattery[measurementCount] = analogRead(pinVBatTot);
    measurementCount++;

    // Wenn fünf Messungen durchgeführt wurden
    if (measurementCount == numMeasurements) {
      // Berechne den Mittelwert
      float avgBattery01Voltage = 0;
      float avgTotalBatteryVoltage = 0;
      for (int i = 0; i < numMeasurements; i++) {
        avgBattery01Voltage += (measurementsBattery01[i] / 1023.0) * 5.0 * ((10.0 + 3.3) / 3.3);
        avgTotalBatteryVoltage += (measurementsTotalBattery[i] / 1023.0) * 5.0 * ((10.0 + 1.3) / 1.3);
      }
      avgBattery01Voltage /= numMeasurements;
      avgTotalBatteryVoltage /= numMeasurements;

      // Setze den Zähler und die Zwischenspeicher zurück
      measurementCount = 0;


      // Setzen nur neue Werte solange der Balancer nicht aktiv ist
      if (balancer_Active == false) {

      // Speichere die Mittelwerte in den Cloud-Variablen
      battery01Voltage = avgBattery01Voltage;
      totalBatteryVoltage = avgTotalBatteryVoltage;

      // Berechnung von battery02Voltage
      battery02Voltage = totalBatteryVoltage - battery01Voltage;

      //Berechne die absolute Differenz der beiden Batteriespannungen
      voltageDifference = fabs(battery01Voltage - battery02Voltage);
      }

      // Arbeite alle Funktionen ab
      onVoltageDifferenceChange();
      onIsBattery01VoltageHighChange();
      onIsBattery01VoltageLowChange();
      onIsBattery02VoltageHighChange();
      onIsBattery02VoltageLowChange();

      // Falls wir in der Beruhigungsphase sind, verlasse wir loop sofort
      if (equalizationStartTime != 0) {
        if (millis() - equalizationStartTime < 20000) { // 20 seconds pause
          // Deaktiviere die Mosfet Status damit wir in onBalancerActiveChange() den Status von bool Balancer_Active neu bewerten können
          mosfetBattery01 = false;
          mosfetBattery02 = false;
          
          // Status von bool balancer_Active updaten
          onBalancerActiveChange();

          // Apply the MOSFET states
          digitalWrite(pinOutput7, mosfetBattery01 ? HIGH : LOW);
          digitalWrite(pinOutput8, mosfetBattery02 ? HIGH : LOW);
          
          Serial.println("--------------------");
          Serial.println("Ausgleichsphase");
          Serial.println("Batterien sollen nicht gebalanct werden");
          Serial.println("Zeit in Sekunden:");
          Serial.println(millis()/1000);
          Serial.println("--------------------");
          return; // Skip the rest of the loop, delaying any actions or measurements
        } else {
          equalizationStartTime = 0; // Reset the timer, allowing the loop to proceed
        }
      }

      // Mosfet Status zuweisen und Mosfet ansteuern
      updateMosfetStates();
    }
  }
}

//
// Spannungsüberwachung
// Überwacht Variable voltageDifference und setzt bool batteryDifferenceAlert auf true oder false
//
void onVoltageDifferenceChange() {
  Serial.println("------------------");
  Serial.println("Bin in Funktion: ");
  Serial.println("onVoltageDifferenceChange()");
  Serial.println("------------------");
  Serial.println("Voltage Difference: ");
  Serial.print(voltageDifference);
  Serial.println(" V");
  
  // bool pendingAlertStatus wird gebraucht, um bool batteryDifferenceAlert definieren zu können
  static bool pendingAlertStatus = false; // Store the pending status based on the voltage difference

  static unsigned long lastChangeTime = 0; // Store the last time the alert status changed
  const unsigned long debounceDelay = 1000; // Delay in milliseconds (e.g., 1000ms = 1 seconds)
  
  // Check the voltage difference and set the pending alert status
  if (voltageDifference >= 0.6) {
    pendingAlertStatus = true;
  } else {
    pendingAlertStatus = false;
  }
  // Check if the current time has exceeded the debounce delay since the last change
  // Debounce logic, which is  to filter out rapid changes in a signal or input, ensuring that only a stable change is acted upon. Let's break down what each part is doing
  if (millis() - lastChangeTime > debounceDelay) {
    // Only update the batteryDifferenceAlert if the pending status is different
    if (batteryDifferenceAlert != pendingAlertStatus) {
      batteryDifferenceAlert = pendingAlertStatus;
      lastChangeTime = millis(); // Reset the last change time
    }
  }
}

//
// Mosfet Status Zuweisung - Mosfet Ansteuerung
// Die Mosfet Status mosfetBattery01 und mosfetBattery02 definieren und Mosfet situationsgerecht ansteuert
//
void updateMosfetStates() {
  // Check total voltage for shutdown condition
  if (totalBatteryVoltage <= 21.6 || totalBatteryVoltage >= 29.6 || voltageDifference < 0.02 || isBattery01VoltageLow || isBattery01VoltageHigh || isBattery02VoltageLow || isBattery02VoltageHigh) {
    mosfetBattery01 = false;
    mosfetBattery02 = false;
    
    // Status von bool balancer_Active updaten
    onBalancerActiveChange();
    Serial.println("Warning: MOSFETs shutdown due to voltage threshold.");
  }
  // Check for voltage difference condition
  else if (voltageDifference >= 0.03) {
    // Since we can only discharge, we turn on the MOSFET for the higher voltage battery
   
    if (battery01Voltage > battery02Voltage) {
      mosfetBattery01 = true; // Discharge Battery 1
      mosfetBattery02 = false;

      // Status von bool balancer_Active updaten
      onBalancerActiveChange();
    } else {
      mosfetBattery01 = false;
      mosfetBattery02 = true; // Discharge Battery 2

      // Status von bool balancer_Active updaten
      onBalancerActiveChange();
    }
    // Before enabling a MOSFET for equalization
    equalizationStartTime = millis();
  }

  // Apply the MOSFET states
  digitalWrite(pinOutput7, mosfetBattery01 ? HIGH : LOW);
  digitalWrite(pinOutput8, mosfetBattery02 ? HIGH : LOW);

  
  Serial.println("------------------------");
  Serial.println("In updateMosfetStates: ");
  Serial.println("------------------------");
  
  // Ausgabe des Status von pinOutput7
  if(mosfetBattery01) {
    Serial.println("MOSFET 1 (pinOutput7) ist EIN.");
  } else {
    Serial.println("MOSFET 1 (pinOutput7) ist AUS.");
  }

  // Ausgabe des Status von pinOutput8
  if(mosfetBattery02) {
    Serial.println("MOSFET 2 (pinOutput8) ist EIN.");
  } else {
    Serial.println("MOSFET 2 (pinOutput8) ist AUS.");
  }
  Serial.println("------------------------");
  
}

/*
  Since IsBattery01VoltageLow is READ_WRITE variable, onIsBattery01VoltageLowChange() is
  executed every time a new value is received from IoT Cloud.
*/
void onIsBattery01VoltageLowChange()  {
  // Set boolean to false by default
  isBattery01VoltageLow = false;
  if (battery01Voltage <= 11) {
    isBattery01VoltageLow = true;

    //Mosfet sicher abschalten
    mosfetBattery01 = false;
    mosfetBattery02 = false;

    // Status von bool balancer_Active updaten
    onBalancerActiveChange();
  }
}

void onIsBattery02VoltageHighChange()  {
  // Set boolean to false by default
  isBattery02VoltageHigh = false;
  if (battery02Voltage >= 15) {
    isBattery02VoltageHigh = true;
  }
}

void onIsBattery01VoltageHighChange() {
  // Set boolean to false by default
  isBattery01VoltageHigh = false;
  if (battery01Voltage >= 15) {
    isBattery01VoltageHigh = true;
  }
}

void onIsBattery02VoltageLowChange()  {
  // Set boolean to false by default
  isBattery02VoltageLow = false;
  if (battery02Voltage <= 11) {
    isBattery02VoltageLow = true;

    //Mosfet sicher abschalten
    mosfetBattery01 = false;
    mosfetBattery02 = false;

    // Status von bool balancer_Active updaten
    onBalancerActiveChange();
  }
}

void onMosfetBattery01Change()  {
  // Add your code here to act upon MosfetBattery01 change
}

void onMosfetBattery02Change()  {
  // Add your code here to act upon MosfetBattery02 change
}

void onTimeStampChange()  {
  // Add your code here to act upon TimeStamp change
}

/*
  Since MosfetBattery01Counter is READ_WRITE variable, onMosfetBattery01CounterChange() is
  executed every time a new value is received from IoT Cloud.
*/
void onMosfetBattery01CounterChange()  {
  // Add your code here to act upon MosfetBattery01Counter change
}

/*
  Since MosfetBattery02Counter is READ_WRITE variable, onMosfetBattery02CounterChange() is
  executed every time a new value is received from IoT Cloud.
*/
void onMosfetBattery02CounterChange()  {
  // Add your code here to act upon MosfetBattery02Counter change
}

/*
  Since BatteryDifferenceAlert is READ_WRITE variable, onBatteryDifferenceAlertChange() is
  executed every time a new value is received from IoT Cloud.
*/
void onBatteryDifferenceAlertChange()  {
  // Add your code here to act upon BatteryDifferenceAlert change
  Serial.println("------------------");
  Serial.println("Bin in Funktion: ");
  Serial.println("onBatteryDifferenceAlertChange()");
  Serial.println("------------------");
  Serial.print("batteryDifferenceAlert = ");
  Serial.println(batteryDifferenceAlert ? "HIGH" : "LOW");
  Serial.println("------------------");
}

/*
  Since TotalBatteryVoltage is READ_WRITE variable, onTotalBatteryVoltageChange() is
  executed every time a new value is received from IoT Cloud.
*/
void onTotalBatteryVoltageChange()  {
  // Add your code here to act upon TotalBatteryVoltage change
}

/*
  Since Battery02Voltage is READ_WRITE variable, onBattery02VoltageChange() is
  executed every time a new value is received from IoT Cloud.
  Diese Funktion existiert, da Battery02Voltage nicht gemessen, sondern berechnet wird.
*/
void onBattery02VoltageChange()  {
  // Add your code here to act upon Battery02Voltage change
}

/*
  Since BalancerActive is READ_WRITE variable, onBalancerActiveChange() is
  executed every time a new value is received from IoT Cloud.
*/
void onBalancerActiveChange()  {

  if (mosfetBattery01 == true || mosfetBattery02 == true) {
    balancer_Active = true;
  }
  else{
    balancer_Active = false;
  }
}






This code controls a battery balancer for a two-battery system. It runs well but none of the functions are called automatically if variables like the one below are changed:

CloudElectricPotential battery01Voltage;
CloudElectricPotential totalBatteryVoltage;
CloudElectricPotential voltageDifference;

It only works because I call all necessary functions under certain conditions, from within loop().

    // Wenn fünf Messungen durchgeführt wurden
    if (measurementCount == numMeasurements) {
      // Berechne den Mittelwert
      float avgBattery01Voltage = 0;
      float avgTotalBatteryVoltage = 0;
      for (int i = 0; i < numMeasurements; i++) {
        avgBattery01Voltage += (measurementsBattery01[i] / 1023.0) * 5.0 * ((10.0 + 3.3) / 3.3);
        avgTotalBatteryVoltage += (measurementsTotalBattery[i] / 1023.0) * 5.0 * ((10.0 + 1.3) / 1.3);
      }
      avgBattery01Voltage /= numMeasurements;
      avgTotalBatteryVoltage /= numMeasurements;

      // Setze den Zähler und die Zwischenspeicher zurück
      measurementCount = 0;


      // Setzen nur neue Werte solange der Balancer nicht aktiv ist
      if (balancer_Active == false) {

      // Speichere die Mittelwerte in den Cloud-Variablen
      battery01Voltage = avgBattery01Voltage;
      totalBatteryVoltage = avgTotalBatteryVoltage;

      // Berechnung von battery02Voltage
      battery02Voltage = totalBatteryVoltage - battery01Voltage;

      //Berechne die absolute Differenz der beiden Batteriespannungen
      voltageDifference = fabs(battery01Voltage - battery02Voltage);
      }

      // Arbeite alle Funktionen ab
      onVoltageDifferenceChange();
      onIsBattery01VoltageHighChange();
      onIsBattery01VoltageLowChange();
      onIsBattery02VoltageHighChange();
      onIsBattery02VoltageLowChange();

      // Falls wir in der Beruhigungsphase sind, verlasse wir loop sofort
      if (equalizationStartTime != 0) {
        if (millis() - equalizationStartTime < 20000) { // 20 seconds pause
          // Deaktiviere die Mosfet Status damit wir in onBalancerActiveChange() den Status von bool Balancer_Active neu bewerten können
          mosfetBattery01 = false;
          mosfetBattery02 = false;
          
          // Status von bool balancer_Active updaten
          onBalancerActiveChange();

Please make sure to provide the information I requested:

Once you do that, I'll take a closer look and try to help you out.

One example:

Here I calculate the absolut voltage difference (CloudElectricPotential voltageDifference) between the two batteries:

      //Berechne die absolute Differenz der beiden Batteriespannungen
      voltageDifference = fabs(battery01Voltage - battery02Voltage);

Whenever this is calculated I exptected that function onVoltageDifferenceChange(); would be called as there, I do a couple of things like:

  • Deciding if I need to change the state of bool batteryDiffernenceAlert, basically deciding if I have to trigger an alert.
  • Deciding if I have an anusual condition like voltage being to high or to low and deciding if I have to active Mosfet's who will balance the batteries, if triggered.

This works all fine, but not because onVoltageDifferenceChange() is called because 'CloudElectricPotential voltageDifference' has changed, it only works, because I call onVoltageDifferenceChange() regularly from loop().

Does this make sense to you?

OK, I see there is a misconception about how these "callback" functions work. They are only called when the value of the variable is changed through your Arduino Cloud dashboard.

The callback function is not automatically called when the variable value is changed internally by the sketch program code.

Thank you, @ptillisch!

Yes, I assumed these functions would be called upon change, regardless of who changes these variables and where they are changed.

Thank you very much for clarifying!

You are welcome. I'm glad if I was able to be of assistance.

Regards,
Per

Empty function?

This code example is intended to illustrate my question in a simple and clear manner. In the actual code, the function is, of course, not empty.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.