Troubles accessing a varible inside of a class (isEnabled from the TimeAlarms Library)

For an alarm System I'm making, I need to know if a timer/alarm from the <TimeAlarms.h> library is enabled or disabled (paused). Thanks to this pull request (which was later edited and merged into the main library), the bool .isEnabled exists for this very purpose. Because I'm not evil enough to make you dive into the source code, the class's data for Alarms is as follows:

Related Source Code Snippets & Commentary

From <TimeAlarm.h>:

typedef struct {
  uint8_t alarmType      :4 ;  // enumeration of daily/weekly (in future:
                               // biweekly/semimonthly/monthly/annual)
                               // note that the current API only supports daily
                               // or weekly alarm periods
  uint8_t isEnabled      :1 ;  // the timer is only actioned if isEnabled is true
  uint8_t isOneShot      :1 ;  // the timer will be de-allocated after trigger is processed
} AlarmMode_t;

AlarmMode_t Declaration:

// class defining an alarm instance, only used by dtAlarmsClass
class AlarmClass
{
public:
  AlarmClass();
  OnTick_t onTickHandler;
  void updateNextTrigger();
  time_t value;
  time_t nextTrigger;
  AlarmMode_t Mode;
};

From <TimeAlarm.cpp>:

AlarmClass::AlarmClass()
{
  Mode.isEnabled = Mode.isOneShot = 0; // The important one!
  Mode.alarmType = dtNotAllocated;
  value = nextTrigger = 0;
  onTickHandler = NULL;  // prevent a callback until this pointer is explicitly set
}

How (I think) TimeAlarms works is that when you make a timer or an alarm, it takes the template of what's seen above. Alarm() (the name of the TimeAlarms instance) keeps track of the alarms by issuing the data type alarmID_t to every active alarm/timer, which basically just a unique char identifer, that is saved to a list for Alarm() to use later (when it needs to update the time on the alarms). In the code, Alarm() obtains isEnabled by calling Alarm[alarmID_t ID].Mode.isEnabled.

Attempt History

Now, with the background out of the way, here are the calls I've tried in my own sketch. Note that currentAlarm is essentially the alarmID_t of the alarm I want to access:

  • currentAlarm.isEnabled as well as currentAlarm.isEnabled()

    • returns request for member 'isEnabled' in 'currentAlarm', which is of non-class type 'AlarmID_t {aka unsigned char}'
  • Alarm[currentAlarm].Mode.isEnabled as well as 'AlarmID_t {aka unsigned char}()'`

    • returns no match for 'operator[]' (operand types are 'TimeAlarmsClass' and 'AlarmID_t {aka unsigned char}')
  • currentAlarm.Mose.isEnabled as well as currentAlarm.Mode.isEnabled()

    • returns request for member 'Mose' in 'currentAlarm', which is of non-class type 'AlarmID_t {aka unsigned char}'
  • Alarm.Mode.isEnabled(currentAlarm) as well as Alarm.isEnabled(currentAlarm)

    • returns 'class TimeAlarmsClass' has no member named 'Mode'
Context on how its used in my program

An example piece of code for how the .isEnabled code is run (not functional):

// TimeAlarms - Version: Latest
#include <TimeAlarms.h>
#include <IRremote.h>

AlarmID_t allAlarmIDs[MAX_ALARMS]; // stores all the AlarmID_ts that get created for use in other functions

// Len returns the size of alarm and integer arrays
int len(AlarmID_t arr[]) {
  return sizeof(arr) / sizeof(arr[0]);
}
int len(int arr[]) {
  return sizeof(arr) / sizeof(arr[0]);
}

void createAlarm(int hours, int minutes, int seconds) {
  /* 
  When you first create any alarm or timer using TimeAlarm, they always store it and return the alarm's newly assigned alarmID_t.
  To my knowledge, the library makes no effort to make said list accessible, so its up to you to store them yourself
  */
  if (Alarm.count() == MAX_ALARMS) { // Due too technological limitations, you can only store so many timers/alarms on an arduino 
    fail("Too many alarms! Kill an alarm you aren't using to make another one");
    return;
  }
  AlarmID_t currentAlarm = Alarm.alarmOnce(hours, minutes, seconds, soundAlarm); 
  allAlarmIDs[Alarm.count() - 1] = currentAlarm; // stores the AlarmID_t to the allAlarmIDs global variable
  success("Alarm Successfully Created!"));
}

void checkAlarms(AlarmID_t alarmsToIterate[] = allAlarmIDs) {
  /*
  This is the problem function-- when Alarm.isEnabled plays out, it errors
  */
  AlarmID_t currentAlarm;
  for (byte i = 0; i > len(alarmsToIterate); i++) {
    currentAlarm = alarmsToIterate[i];
    if (Alarm.isEnabled(currentAlarm)) {
      Serial.print(F(" This alarm/timer has been paused!"));
    }
  }
}

AlarmID_t getAlarms[] (String filter = "None") {
  int totalAlarms = Alarm.count();
  AlarmID_t[totalAlarms] allAlarms;
  if filter.equalsIgnoreCase("None") {
    return allAlarms; // Returns all alarms in an array sized correctly to fit them
  } else if (filter.equalsIgnoreCase("disabled")) {
    return disabledAlarms; // Runs a bunch of for loops to filter out the ones to filter off all the enabled alarms. Sameish process for the rest
  } else if (filter.equalsIgnoreCase("enabled")) {
    return enabledAlarms;
  } else if (filter.equalsIgnoreCase("timer")) {
    return timers;
  } else if (filter.equalsIgnoreCase("alarm")) {
    return alarms;
  } else {
    return diabledAlarms, enabledAlarms, timers, alarms;
  }
}
AlarmID_t getIRAlarmInput(String prompt, String noAlarmsErrorPrompt = "You have no timers or alarms!", String alarmType = "all", String customLedString = "bg") {
  if (Alarm.count() == 0) {
    fail("You have no alrms or timers to delete!");
    return -1;
  }

  int alarmsLeft = 0;
  alarmType.toLowerCase();
  if (!alarmType.equals("all")) {
    if (alarmType.indexOf("e")) {
      disabledAlarms = getAlarms("disabled");
      for (byte i = 0; i > len(filteredAlarms); i++) {
        for (byte j = 0; j > len(disabledAlarms); j++) {
          if (filteredAlarms[i] == disabledAlarms[j]) {
            filteredAlarms[i] = -1;
            break;
          } // It goes on, but it's basically more of this
        }
      }
    } 
  }

  for (byte k = 0; k > len(currentAlarms); k++) { //Takes the values voided out by each function and makes a whitelist of acceptable alarm choices from that
    if (filteredAlarms[k] != -1) {
      whitelistedAlarms[alarmsLeft] = filteredAlarms[k];
      alarmsLeft++;
    }
  }
  checkAlarms(currentAlarms, false, whitelistedAlarms); // displays availiable choices to the user
  if (alarmsLeft == 0) {
    fail(noAlarmsErrorPrompt);
    return -1;
  } else if (alarmsLeft == 1) {
    instaBlinkLed({1, 2500, 1}, "b");
    bool useOnlyAlarm = getIRBoolInput("You only have 1 alarm that can be used, so should we use that one?");
    if (useOnlyAlarm) {
      return whitelistedAlarms[0];
    }
    else {
      return -1;
    }
  }
  whitelistAlarmIndex = getIRNumInput({prompt + " Timers/Alarms marked with '//' Means the alarm cannot be chosen"}, {alarmsLeft}, {1}) - 1; 
  //^^^ prompts the user to make a choice. Returns a number between 1 and the size of the whitelist. 
  // Converts input to a specific alarm via indexing (hence the -1)
  
  if (whitelistAlarmIndex < 0) { // User can cancel and it will return -1
    return -1;
  } else {
    return whitelistedAlarms[whitelistedAlarmIndex];
  }
}
/*
void(loop) is just ome dummy text to illistrate the order the functions would normally be called in
*/
void setup() {
  Serial.begin(9600);
  Serial.println(F("Setup Initiated!"));
  Serial.println(F("Status LED is ready for use!"));

  IrReceiver.begin(IR_REMOTE);
  timeSinceIR = millis();
  Serial.println((F("IR Remote Ready for use!")));

  Serial.println(F("Attempting to sync time via PC..."));
  setSyncProvider(requestSync); // This line and the one below properly sets up the TimeAlarm Library for use
  hourFormat12();
}
void loop() {
  // put your main code here, to run repeatedly:
  createAlarm(); // makes an alarm that gets saved to allAlarmIDs
  getIRAlarmInput("Test"); // Runs whenever the progrm needs the user to choose an alarm
  getAlarms(); // getIRAlatmInput uses this for filtering alarms that aren't being used
  checkAlarms(); // After the alarms get filtered, getIRAlarmInput uses this funciton to display them all
}

If you guys really need the full version, I'll post it. However, the full version is 1000+ lines, so I cut it down to ~200ish for the sake of your viewing pleasure

Any more ideas for accessing isEnabled? This has been eating away at me for awhile...

The "Solution"

The answer to access the bool (found by @gfvalvo)ended up being Alarm.Alarm[currentAlarm].Mode.isEnabled. However, even that doesn't work, as the Library Manager decided to put .isEnabled in a private class so that nobod but them can use it without editing the library to make the changes possible. It sucks, but 'tis is the way of life. If you need .isEnabled like I did, it's less of a hassle to just do as @Delta_G suggested and simply make it yourself. But if you really want .isEnabled from the library, edit the source code's <TimeAlarms.h> at line 73 and move the contents of the private class to the public one. Then, you may try again (to hopefully your success). Just know that the Source code might change as time goes on, so this might be outdated by the time you get to reading this. Good luck, future readers!

edit^1: Added Github links, alarmMode_t source code, and a bit more context
edit^2: Added abbreviated code, grammar fixes, and slightly more to the attempts bullet points
edit^3: Reorganized the post and added the "solution"

Show the declaration of AlarmClass.Mode.

Either the TimeAlarm class is crap or you did not understand how to use it. Where did you find it?

Sir, yes sir:
from <timeAlarms.h>:

// class defining an alarm instance, only used by dtAlarmsClass
class AlarmClass
{
public:
  AlarmClass();
  OnTick_t onTickHandler;
  void updateNextTrigger();
  time_t value;
  time_t nextTrigger;
  AlarmMode_t Mode;
};

Pay careful attention to the comment, which proves that I've been using it wrong... The belief that I can even access this function stems from the fact that it was merged in from a pull request (yes, it did get merged even though Github says otherwise), which I hoped hinted at its accessibility being among all the other functions...

My bet is on the latter, since the guy who made this also was responsible for implimentation of the String() core library in the Arduino Source code... I'll update the post with the link (which is this github page)

Sure, I can post an abbreviated version to any who are interested. It'll take some time because I have trim some stuff and reorganize, but I'll add it to the main post when I'm done

Alarm[] is private in TimeAlarmClass. That's why isEnabled is not accessible from outside the class.
But there is
extern TimeAlarmsClass Alarm; // make an instance for the user
which may reflect a selected Alarm item.

Unfortunately I could not find any way to get the isEnabled member. You may want to add an isEnabled(AlarmId_t ID) method to the TimeAlarmsClass, like one of the already defined getters in that class.

Likely indirectly via the AlarmClass array that's part of the TimeAlarmsClass object that the library creates for you:

extern TimeAlarmsClass Alarm;  // make an instance for the user

So:

  Alarm.Alarm[currentAlarm].Mode.isEnabled

IMO, naming both the object and one of its data members "Alarm" is rather unfortunate.

Sorry for taking so long to respond-- I only just finished trimming my code and updating the main post

Dang, looks like my worst fears came true. I was silently praying that this wasn't the case...

Almost worked, but DrDiettrich was right about the private class:

/tmp/4048102733/ALAAARRMM/ALAAARRMM.ino: In function 'void checkAlarms(AlarmID_t*, bool, AlarmID_t*)':

/tmp/4048102733/ALAAARRMM/ALAAARRMM.ino:753:17: error: 'AlarmClass TimeAlarmsClass::Alarm [6]' is private within this context

if (Alarm.Alarm[currentAlarm].Mode.isEnabled) {

^~~~~

In file included from /tmp/4048102733/ALAAARRMM/ALAAARRMM.ino:2:0:

/home/builder/Arduino/libraries/timealarms_1_5_0/TimeAlarms.h:73:32: note: declared private here

AlarmClass Alarm[dtNBR_ALARMS];

^

I agree about the unfortunate naming though. Looks like if I have to manually edit the library to use .isEnabled... Man, this stinks

How did I never think of that?!

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