TimeAlarms scheduler

Sure (b k o at po box dot com) but if you put it in say a gist at github, lots of folks here can help you out, not just me.

You can embed your code by selecting it and then pressing the </> button or pressing CRTL+k. That way it gets formatted nicely in a scrollable box. That’ll make sure that other people can see it, and perhaps learn something from your code. If you’re doing everything via private mail, it’s no use to the community. If you want to contribute, including it is the best option. And like @bko said, more people will be able to help you. If you’re planning on using it for a commercial product, it’s a different story of course.

1 Like

Sure, I have removed some in progress code snippets to make it bit cleaner.
Please see the main loop – Even if i do not perform any action, Spark becomes unresponsive after around 22 hours.

// This #include statement was automatically added by the Spark IDE.
#include “elapsedMillis/elapsedMillis.h”

// This #include statement was automatically added by the Spark IDE.
#include "TimeAlarms/TimeAlarms.h"
   

#define RELAY_SWITCHING_DELAY 300 // Delay of 300 mill seconds to sequentialize the Relay switching
#define CLOUD_CHECK_INT 50000 // Default set at 50 seconds
#define CONDITION_CHECK_INT 20000 // Default set to 20 seconds
#define DATA_PUBLISH_INT 300000    // defaukt set to 5 Minutes Seconds

// allow use of itoa() in this scope
extern char* itoa(int a, char* buffer, unsigned char radix);

#define DEBUG 1  // To debug for all methods except readFromI2C
#define DEBUGI2C 0 // to debug readFromI2C

// Define Memory location for Storing Scheduling related information -- This will start from Location 20 onwards 
#define PORT_D7_ON_MEM  20212223  // First memory location will be to Store ScheduleID, Next  will store Repeatable/Non repeatable along with HH and last location will store MM
#define PORT_D7_OFF_MEM 24252627
#define PORT_D6_ON_MEM  28293031
#define PORT_D6_OFF_MEM 32333435
#define PORT_D5_ON_MEM  36373839
#define PORT_D5_OFF_MEM 40414243
#define PORT_A6_ON_MEM  44454647
#define PORT_A6_OFF_MEM 48495051
#define PORT_A5_ON_MEM  52535455
#define PORT_A5_OFF_MEM 56575859
#define PORT_A4_ON_MEM  60616263
#define PORT_A4_OFF_MEM 64656667


// Port = 0xbeef
#define PORT 48879

// table of action codes
// to do: make this an enum?
#define PIN_MODE                    0x00
#define DIGITAL_WRITE               0x01
#define ANALOG_WRITE                0x02
#define DIGITAL_READ                0x03
#define ANALOG_READ                 0x04
#define REPORTING                   0x05
#define SET_SAMPLE_INTERVAL         0x06
/* NOTE GAP */
// #define SERIAL_BEGIN                0x10
// #define SERIAL_END                  0x11
// #define SERIAL_PEEK                 0x12
// #define SERIAL_AVAILABLE            0x13
// #define SERIAL_WRITE                0x14
// #define SERIAL_READ                 0x15
// #define SERIAL_FLUSH                0x16
/* NOTE GAP */
// #define SPI_BEGIN                   0x20
// #define SPI_END                     0x21
// #define SPI_SET_BIT_ORDER           0x22
// #define SPI_SET_CLOCK               0x23
// #define SPI_SET_DATA_MODE           0x24
// #define SPI_TRANSFER                0x25
// /* NOTE GAP */
// #define WIRE_BEGIN                  0x30
// #define WIRE_REQUEST_FROM           0x31
// #define WIRE_BEGIN_TRANSMISSION     0x32
// #define WIRE_END_TRANSMISSION       0x33
// #define WIRE_WRITE                  0x34
// #define WIRE_AVAILABLE              0x35
// #define WIRE_READ                   0x36
/* NOTE GAP */
#define SERVO_WRITE                 0x41
#define ACTION_RANGE                0x46


uint8_t bytesToExpectByAction[] = {
  // digital/analog I/O
  2,    // PIN_MODE
  2,    // DIGITAL_WRITE
  2,    // ANALOG_WRITE
  1,    // DIGITAL_READ
  1,    // ANALOG_READ
  2,    // REPORTING
  1,    // SET_SAMPLE_INTERVAL
  // gap from 0x07-0x0f
  0,    // 0x07
  0,    // 0x08
  0,    // 0x09
  0,    // 0x0a
  0,    // 0x0b
  0,    // 0x0c
  0,    // 0x0d
  0,    // 0x0e
  0,    // 0x0f
  // serial I/O
  2,    // SERIAL_BEGIN
  1,    // SERIAL_END
  1,    // SERIAL_PEEK
  1,    // SERIAL_AVAILABLE
  2,    // SERIAL_WRITE  -- variable length message!
  1,    // SERIAL_READ
  1,    // SERIAL_FLUSH
  // gap from 0x17-0x1f
  0,    // 0x17
  0,    // 0x18
  0,    // 0x19
  0,    // 0x1a
  0,    // 0x1b
  0,    // 0x1c
  0,    // 0x1d
  0,    // 0x1e
  0,    // 0x1f
  // SPI I/O
  0,    // SPI_BEGIN
  0,    // SPI_END
  1,    // SPI_SET_BIT_ORDER
  1,    // SPI_SET_CLOCK
  1,    // SPI_SET_DATA_MODE
  1,    // SPI_TRANSFER
  // gap from 0x26-0x2f
  0,    // 0x26
  0,    // 0x27
  0,    // 0x28
  0,    // 0x29
  0,    // 0x2a
  0,    // 0x2b
  0,    // 0x2c
  0,    // 0x2d
  0,    // 0x2e
  0,    // 0x2f
  // wire I/O
  1,    // WIRE_BEGIN
  3,    // WIRE_REQUEST_FROM
  1,    // WIRE_BEGIN_TRANSMISSION
  1,    // WIRE_END_TRANSMISSION
  1,    // WIRE_WRITE  -- variable length message!
  0,    // WIRE_AVAILABLE
  0,    // WIRE_READ
  // gap from 0x37-0x3f
  0,    // 0x37
  0,    // 0x38
  0,    // 0x39
  0,    // 0x3a
  0,    // 0x3b
  0,    // 0x3c
  0,    // 0x3d
  0,    // 0x3e
  0,    // 0x3f
  0,    // 0x40
  // servo
  2,    // SERVO_WRITE
  1,    // SERVO_DETACH
};



TCPServer server = TCPServer(PORT);
TCPClient client;

bool hasAction = false;
bool isConnected = false;

byte reporting[20];
byte buffer[16];
byte cached[4];

char charBuffer[5];

int reporters = 0;
int bytesRead = 0;
int bytesExpecting = 0;
int action, available;

unsigned long lastms;
unsigned long nowms;
unsigned long sampleInterval = 100;
unsigned long SerialSpeed[] = {
  600, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, 57600, 115200
};

/*** TINKER CODE STARTS HERE***/

//void cloudConnect();

//WiFi_Status_TypeDef last_wifi_status;

//bool last_cloud_status = 1;
int l_test =0;

//Ping to google DNS Server -- Test not in use currently
IPAddress google(8,8,8,8);

//Variable to store the network strength
int curRssi;


/* Function prototypes -------------------------------------------------------*/
//int psDigitalRead(String pin);
int psDigitalWrite(String command);
int psSetSchedule(String command);
//int psAnalogRead(String pin);
//int psAnalogWrite(String command);

int counter = 1000;
unsigned long intTime,intTime1,intTime2;
int l_retry  = 0;

elapsedMillis timeElapsed;
elapsedMillis alarmCheck;
//TCPServer server = TCPServer(80);
//TCPClient webClient;
char curPinState[110];   // To get the current pin state
char jsonD[110];
//char curPinStr[24];

char sparkID[25];

/* Functions for Interrupt */
void changeStateD7(void);
void changeStateD6(void);
void changeStateD5(void);
void changeStateA6(void);
void changeStateA5(void);
void changeStateA4(void);

volatile int stateA4 = 0;
volatile int stateA5 = 0;
volatile int stateA6 = 0;
volatile int stateD5 = 0;
volatile int stateD6 = 0;
volatile int stateD7 = 0;
int buttonState = 0;

void updatePinJson();

//Local TCP Server
void tcp_server_loop();

// Connection Monitor -- To check the LAN , Internet connection and setup the core accordingly.
void connectionMonitor ();

/* Variables and methods for I2C -- Reading data from Sensor Board */

float current;
float temperature;
int SlaveDeviceId = 2;
char szInfo[64];

AlarmID_t scheduleID1;

void publishSensorData(int publishInterval);

/*
  PWM/Servo support is CONFIRMED available on:

  D0, D1, A0, A1, A4, A5, A6, A7

  Allocate 8 servo objects:
 */
Servo servos[8];
/*
  The Spark board can only support PWM/Servo on specific pins, so
  based on the pin number, determine the servo index for the allocated
  servo object.
 */
int ToServoIndex(int pin) {
  // D0, D1
  if (pin == 0 || pin == 1) return pin;
  // A0, A1
  if (pin == 10 || pin == 11) return pin - 8;
  // A4, A5, A6, A7
  if (pin >= 14) return pin - 10;
}

void send(int action, int pin, int value) {
  // See https://github.com/voodootikigod/voodoospark/issues/20
  // to understand why the send function splits values
  // into two 7-bit bytes before sending.
  //
  int lsb = value & 0x7f;
  int msb = value >> 0x07 & 0x7f;

  server.write(action);
  server.write(pin);

  // Send the LSB
  server.write(lsb);
  // Send the MSB
  server.write(msb);

  // #ifdef DEBUG
  // Serial.print("SENT: ");
  // Serial.print(value);
  // Serial.print(" -> [ ");
  // Serial.print(lsb);
  // Serial.print(", ");
  // Serial.print(msb);
  // Serial.println(" ]");
  // #endif
}

void reset() {
  #ifdef DEBUG
  Serial.println("RESETTING");
  #endif

  hasAction = false;
  isConnected = false;

  reporters = 0;
  bytesRead = 0;
  bytesExpecting = 0;

  lastms = 0;
  nowms = 0;
  sampleInterval = 100;

  for (int i = 0; i < 20; i++) {
    // Clear the pin reporting list
    reporting[i] = 0;

    // Clear the incoming buffer
    if (i < 16) {
      buffer[i] = 0;
    }

    // Clear the action data cache
    if (i < 4) {
      cached[i] = 0;
    }

    // Detach any attached servos
    if (i < 8) {
      if (servos[i].attached()) {
        servos[i].detach();
      }
    }
  }
}

/*** TINKER FUNCTIONS STARTS HERE ***/

void initializePin()
{
    uint8_t pinState;
    
    // This is to initailze the  Digital Pins at at setup
    // Set Interrupt pins to INPUT_PULLUP
    
    pinMode(D4, INPUT_PULLUP);
    pinMode(D3, INPUT_PULLUP);
    pinMode(D2, INPUT_PULLUP);
    pinMode(A3, INPUT_PULLUP);
    pinMode(A1, INPUT_PULLUP);
    pinMode(A0, INPUT_PULLUP);
    
    //Set the Mapped Pins to OUTPUT state
    //read the Last saved Pin state from EEPROM for all mapped OUTPUT Pins set the same accordi
    
    // Set A4, A5, A6 to Output mode
    for(int i=14; i<=16; i++) {
        
        pinMode(i, OUTPUT);
        // read the pin state fron the EEPROM Emulator
        pinState = EEPROM.read(i);
        // Add Delay to switch on the relays in a sequential manner. This will prevent initial current rush
        digitalWrite(i, pinState);
        delay(RELAY_SWITCHING_DELAY);
    
    }
    
    // Set D5, D6 and D7 to OUTPUT mode
    for(int j=5; j<=7; j++) {
        
        pinMode(j, OUTPUT);
        // read the pin state fron the EEPROM Emulator
        pinState = EEPROM.read(j);
        // Add Delay to switch on the relays in a sequential manner. This will prevent initial current rush
        digitalWrite(j, pinState);
        delay(RELAY_SWITCHING_DELAY);
    
    }
    
    // Update State of the CurPinState variable -- Same variable will be used to access the state from local server
    updatePinJson();
    
    pinState = 0; // reset the value after the loop operation
}

// Set the State of the CurPinState variable -- Same variable will be used to access the state from local server
void updatePinJson(){
  
  sprintf(jsonD,"{A4:%d, A5:%d, A6:%d, D5:%d, D6:%d, D7:%d}",digitalRead(14),digitalRead(15),digitalRead(16),digitalRead(5),digitalRead(6),digitalRead(7));   
}


void processInput() {
  int pin, mode, val, type, speed, address, stop, len, k, i;
  int byteCount = bytesRead;

  #ifdef DEBUG
  Serial.println("----------processInput----------");
  Serial.print("Bytes Available: ");
  Serial.println(available, DEC);

  for (i = 0; i < available; i++) {
    Serial.print(i, DEC);
    Serial.print(": ");
    Serial.println(buffer[i], DEC);
  }
  #endif

  // Only check if buffer[0] is possibly an action
  // when there is no action in progress.
  if (hasAction == false) {
    if (buffer[0] < ACTION_RANGE) {
      hasAction = true;
      bytesExpecting = bytesToExpectByAction[action] + 1;
    }
  }

  #ifdef DEBUG
  Serial.print("Bytes Expecting: ");
  Serial.println(bytesExpecting, DEC);
  Serial.print("Bytes Read: ");
  Serial.println(bytesRead, DEC);
  #endif

  // When the first byte of buffer is an action and
  // enough bytes are read, begin processing the action.
  if (hasAction && bytesRead >= bytesExpecting) {

    action = buffer[0];

    #ifdef DEBUG
    Serial.print("Action received: ");
    Serial.println(action, DEC);
    #endif


    // Copy the expected bytes into the cache and shift
    // the unused bytes to the beginning of the buffer
    for (k = 0; k < byteCount; k++) {
      // Cache the bytes that we're expecting for
      // this action.
      if (k < bytesExpecting) {
        cached[k] = buffer[k];

        // Reduce the bytesRead by the number of bytes "taken"
        bytesRead--;

        #ifdef DEBUG
        Serial.print("Cached: ");
        Serial.print(k, DEC);
        Serial.println(cached[k], DEC);
        #endif
      }

      // Shift the unused buffer to the front
      buffer[k] = buffer[k + bytesExpecting];
    }

    // Proceed with action processing
    switch (action) {
      case PIN_MODE:  // pinMode
        pin = cached[1];
        mode = cached[2];
        #ifdef DEBUG
        Serial.print("PIN received: ");
        Serial.println(pin);
        Serial.print("MODE received: ");
        Serial.println(mode, HEX);
        #endif

        if (servos[ToServoIndex(pin)].attached()) {
          servos[ToServoIndex(pin)].detach();
        }

        if (mode == 0x00) {
          pinMode(pin, INPUT);
        } else if (mode == 0x02) {
          pinMode(pin, INPUT_PULLUP);
        } else if (mode == 0x03) {
          pinMode(pin, INPUT_PULLDOWN);
        } else if (mode == 0x01) {
          pinMode(pin, OUTPUT);
        } else if (mode == 0x04) {
          pinMode(pin, OUTPUT);
          servos[ToServoIndex(pin)].attach(pin);
        }
        break;

      case DIGITAL_WRITE:  // digitalWrite
        pin = cached[1];
        val = cached[2];
        #ifdef DEBUG
        Serial.print("PIN received: ");
        Serial.println(pin, DEC);
        Serial.print("VALUE received: ");
        Serial.println(val, HEX);
        #endif
        digitalWrite(pin, val);
        // update EEPROM and CurPinState
        EEPROM.write(pin, val);
        updatePinJson();
        break;

      case ANALOG_WRITE:  // analogWrite
        pin = cached[1];
        val = cached[2];
        #ifdef DEBUG
        Serial.print("PIN received: ");
        Serial.println(pin, DEC);
        Serial.print("VALUE received: ");
        Serial.println(val, HEX);
        #endif
        analogWrite(pin, val);
        break;

          
      case SET_SAMPLE_INTERVAL: // set the sampling interval in ms
        sampleInterval = cached[1];

        // Lower than ~100ms will likely crash the spark,
        // but
        if (sampleInterval < 20) {
          sampleInterval = 20;
        }
        break;

      case SERVO_WRITE:
        pin = cached[1];
        val = cached[2];
        #ifdef DEBUG
        Serial.print("PIN: ");
        Serial.println(pin);
        Serial.print("WRITING TO SERVO: ");
        Serial.println(val);
        #endif
        servos[ToServoIndex(pin)].write(val);
        break;

      default: // noop
        break;
    } // <-- This is the end of the switch

    // Clear the cached bytes
    for (i = 0; i < bytesExpecting; i++) {
      cached[i] = 0;
    }

    // Reset hasAction flag (no longer needed for this opertion)
    // action and byte read expectation flags
    hasAction = false;
    bytesExpecting = 0;

    // If there were leftover bytes available,
    // call processInput. This mechanism will continue
    // until there are no bytes available.
    if (bytesRead > 0) {
      #ifdef DEBUG
      Serial.print("# Unprocessed Bytes: ");
      Serial.println(bytesRead, DEC);
      #endif

      available = bytesRead;
      processInput();
    }
  }
}

void tcp_server_loop(){
  
  if (client.connected()) {

    if (!isConnected) {
      #ifdef DEBUG
      Serial.println("--------------CONNECTED--------------");
      #endif
    }

    isConnected = true;

    // Process incoming bytes first
    available = client.available();

    if (available > 0) {
      // Move all available bytes into the buffer,
      // this avoids building up back pressure in
      // the client byte stream.
      for (int i = 0; i < available; i++) {
        buffer[i] = client.read();
        bytesRead++;
      }

      #ifdef DEBUG
      Serial.println("--------------PROCESSING NEW DATA--------------");
      #endif

      processInput();
    }
    
  } else {
    // Upon disconnection, reset all state.
    if (isConnected) {
      reset();
    }

    // If no client is yet connected, check for a new connection
    client.flush();
    delay(20);
    client.stop();
    client = server.available();
  }
}

void connectionMonitor() {
    
    if (!WiFi.ready()){
        Spark.disconnect();
        
        #ifdef DEBUG
        Serial.println("--Attempting Wifi Connection--");
        #endif
        
        while(!WiFi.ready()) SPARK_WLAN_Loop();
        
        #ifdef DEBUG
        Serial.println("--Wifi Successful--");
        #endif
        
        if (WiFi.ping(google) >=1) {
            l_test = 1;
            #ifdef DEBUG
            Serial.println("--Cloud connection will be attempted--");
            #endif
        }
        
    } else if (WiFi.ready() && l_test == 0 && !Spark.connected() && (millis() - intTime1) >= CONDITION_CHECK_INT) {
        #ifdef DEBUG
        Serial.println("--Internet failure, Disconnecting Cloud Connection--");
        #endif
        if (!Spark.connected()) Spark.disconnect();
        l_test = 1;
        
    }
    
    if (l_test == 1 && (millis()- intTime) >= CLOUD_CHECK_INT){
        #ifdef DEBUG
        Serial.println("--Attempting cloud Connection--");
        #endif
        intTime = millis();
        if (WiFi.ping(google) >= 1) {
            #ifdef DEBUG
            Serial.println("--Successful Ping--");
            #endif
            l_test = 0;
            if (!Spark.connected()) Spark.connect();
            delay(1000);
            intTime1 = millis();
        }
    }
    
}


SYSTEM_MODE(SEMI_AUTOMATIC);  // This will ensure, When the Core boots up, the user code will begin running immediately.

void setup() {

  /** TINKER Setup ***/
   
  Spark.function("digitalwrite", psDigitalWrite);
  Spark.function("setschedule", psSetSchedule);
  //Spark.function("analogread", psAnalogRead);
  //Spark.function("analogwrite", psAnalogWrite);
	
  // Initialize Required Analog and Digitial Pins with last known state and set the CurPinState Spark Variables
  initializePin();
  
    // Variable to retrieve the current state of the Pin
  Spark.variable("curPinState", &jsonD, STRING);
  
    
  // Variable to Store Spark ID
  String myID = Spark.deviceID();
  myID.toCharArray(sparkID, 25); 
  
        
  //attachInterrupt(A0, changeStateA4,  CHANGE);
  //attachInterrupt(A1, changeStateA5,  CHANGE);
  //attachInterrupt(A3, changeStateA6,  CHANGE);
  //attachInterrupt(D2, changeStateD5,  CHANGE);
  //attachInterrupt(D3, changeStateD6,  CHANGE);
  //attachInterrupt(D4, changeStateD7,  CHANGE);
  

  /** TINKER Setup ends here */
  
    
  #ifdef DEBUG
  Serial.begin(9600);
  #endif
  
// Initialize the communication for I2C
  Wire.begin();
  Serial1.begin(9600);

  // Initiate the WiFi Connection.
  WiFi.connect();  // Explicit call to WiFi.connect required from v0.3.2 to connect to LAN.
  
  //Let the LAN get avilable for TCP/UDP
  while(!WiFi.ready()) SPARK_WLAN_Loop();
  
  IPAddress ip = WiFi.localIP();
  static char ipAddress[24] = "";
  char octet[5];
  netapp_ipconfig(&ip_config);

  itoa(ip[0], octet, 10); strcat(ipAddress, octet); strcat(ipAddress, ".");
  itoa(ip[1], octet, 10); strcat(ipAddress, octet); strcat(ipAddress, ".");
  itoa(ip[2], octet, 10); strcat(ipAddress, octet); strcat(ipAddress, ".");
  itoa(ip[3], octet, 10); strcat(ipAddress, octet); strcat(ipAddress, ":");
  itoa(PORT, octet, 10);  strcat(ipAddress, octet);

  Spark.variable("endpoint", ipAddress, STRING);
 
  server.begin();
     
  // Connect only if the net is up
  if (WiFi.ping(google) >=1) {
        Spark.connect();
        delay(2000);
        #ifdef DEBUG
        Serial.println("--In Startup Cloud connection will be attempted--");
        #endif
  }
  
  //set the time zone
  Time.zone(+5.5);
  
  // Initialize the scheduler
  initiaizeSchedule();
     
  intTime = millis();
  intTime1 = millis();
  intTime2 = millis();
}

/* User Program / Main loop starts here */

void loop() {
  
 
  /*Local TCP Loop */
  if (!Spark.connected()) tcp_server_loop();  
  
  /* Code to check Cloud Connection and accordingly setup the core for Local or Internet communication without blocking */
  // Run the connectionMonitor every 10 seconds
  if (timeElapsed >= 10000) {
      connectionMonitor();
      timeElapsed = 0; // reset the time
  }
  
  if (alarmCheck >= 20000) {
      Alarm.delay(5);
      alarmCheck = 0; //reset the time
  } 
  
  // This has been added as Core was getting non responsive -- Need to test this.
  //SPARK_WLAN_Loop();

}


/*******************************************************************************
 * Function Name  : tinkerDigitalWrite
 * Description    : Sets the specified pin HIGH or LOW
 * Input          : Pin and value
 * Output         : None.
 * Return         : 1 on success and a negative number on failure
 *******************************************************************************/
int psDigitalWrite(String command)
{
	bool value = 0;
	//convert ascii to integer
	int pinNumber = command.charAt(1) - '0';
	//Sanity check to see if the pin numbers are within limits
	if (pinNumber< 0 || pinNumber >7) return -1;

	if(command.substring(3,7) == "HIGH") 
	{ 
	    value = 1; 
	}
	else if(command.substring(3,6) == "LOW") 
	{ 
	    value = 0; 
	}
	else return -2;

	if(command.startsWith("D"))
	{
		pinMode(pinNumber, OUTPUT);
		
		//use EEPROM emulator to store the state of the Pin
		EEPROM.write(pinNumber, value);
		
		digitalWrite(pinNumber, value);
		
	   // Update State of the CurPinState variable -- Same variable will be used to access the state from local server
       updatePinJson();
		
		return 1;
	}
	
	else if(command.startsWith("A"))
	{
		pinMode(pinNumber+10, OUTPUT);
		
		//use EEPROM emulator to store the state of the Pin
		EEPROM.write(pinNumber+10, value);
		
		digitalWrite(pinNumber+10, value);
		
		// Update State of the CurPinState variable -- Same variable will be used to access the state from local server
        updatePinJson();
		
		
		return 1;
	}
	else return -3;
}


//Function to schedule the OnTimer  -- This will be exposed to cloud -- To build

int psSetSchedule(String command){
    
    bool alarmType = 0;
    bool alarmRepeat = 0;
    scheduleID1 = 255;
    
    // convert ascii to integer
    int portNumber = command.charAt(1) - '0';
	//Sanity check to see if the pin numbers are within limits
	if (portNumber< 0 || portNumber >7) return -1;
    
    //Get the Port, Time , ON / OFF timer and Repeat / Non Repeat details
    int setHour = command.substring(3,5).toInt(); // Retrieve Hour and convert to Integer
    int setMin =  command.substring(5,7).toInt(); // Retrieve Min and convert to Integer
    
    // Get the ON/OFF Alarm Type details -- H for ON and L for OFF
    if (command.substring(8,9) == "H") alarmType = 1;
    else if (command.substring(8,9) == "L") alarmType = 0;
    
    // Get the Repeat / Non Repeat details R for Repeat N - Non Repeat
    if (command.substring(10,11) == "R") alarmRepeat = 1;
    else if (command.substring(10,11) == "N") alarmRepeat = 0;
    
    
    if(command.substring(0,2) == "D7" && alarmType == 1 && alarmRepeat == 0){
        scheduleID1 = Alarm.alarmOnce(setHour,setMin,0, portD7_ON);
        String memoryLOC = String(PORT_D7_ON_MEM);
        storeSchedule(memoryLOC, setHour, setMin, alarmRepeat);
        
        return scheduleID1;
    }
    else if (command.substring(0,2) == "D7" && alarmType == 1 && alarmRepeat == 1){
        scheduleID1 = Alarm.alarmRepeat(setHour,setMin,0, portD7_ON);
        String memoryLOC = String(PORT_D7_ON_MEM);
        storeSchedule(memoryLOC, setHour, setMin, alarmRepeat);
        
        return 2;
    }
    else if (command.substring(0,2) == "D7" && alarmType == 0 && alarmRepeat == 0){
        scheduleID1 = Alarm.alarmOnce(setHour,setMin,0, portD7_OFF); 
        String memoryLOC = String(PORT_D7_OFF_MEM);
        storeSchedule(memoryLOC, setHour, setMin, alarmRepeat);
        
        return scheduleID1;
    }
    else if (command.substring(0,2) == "D7" && alarmType == 0 && alarmRepeat == 1){
        scheduleID1 = Alarm.alarmRepeat(setHour,setMin,0, portD7_OFF); 
        String memoryLOC = String(PORT_D7_OFF_MEM);
        storeSchedule(memoryLOC, setHour, setMin, alarmRepeat);
        
        return scheduleID1;
    }
    else return -4;
    
    
} 

void storeSchedule(String memoryInfo, int storeHour, int storeMin, bool repeatFlag) {
   
   // Common Handler to Store Information in EEPROM
    
     // Store the Information in the allocated memory location
        int scheduleLoc = String(memoryInfo).substring(0,2).toInt();
        Serial.print("Memory Location: "); Serial.println(scheduleLoc);
        
        int hhLoc = String(memoryInfo).substring(2,4).toInt();
        int mmLoc = String(memoryInfo).substring(4,6).toInt();
        int repeatLoc = String(memoryInfo).substring(6,8).toInt();
        
        EEPROM.write(scheduleLoc, scheduleID1);
        EEPROM.write(hhLoc, storeHour);
        EEPROM.write(mmLoc, storeMin); 
        EEPROM.write(repeatLoc, repeatFlag);
}



void initiaizeSchedule(){
    
    // Read All the output port and set the Schedule -- This is requried in case of reboot / reset / power failure
    
    
    // Example -- check for Port D7
    int schLoc, schNum, hourLoc, minLoc, repeatLoc, readHour, readMin, readRepeat;  
    
    for (int i = 20; i<=60; i=i+8) {
    
    
    
    schLoc = String(PORT_D7_ON_MEM).substring(0,2).toInt();
    schNum = EEPROM.read(schLoc);
    
    Serial.print("Memory Location: "); Serial.println(schLoc);
    Serial.print("Schedule ID: "); Serial.println(schNum);
    
    if (0<= schNum <= 15) {
      // Get the Hour and Min Details  -- Need to store ON/OFF timer and Once/repeat info also
      hourLoc = String(PORT_D7_ON_MEM).substring(2,4).toInt();
      minLoc = String(PORT_D7_ON_MEM).substring(4,6).toInt();
      repeatLoc = String(PORT_D7_ON_MEM).substring(6,8).toInt();
      
      readHour = EEPROM.read(hourLoc);
      readMin = EEPROM.read(minLoc);
      readRepeat = EEPROM.read(repeatLoc);
      
      if (readRepeat == 0) { 
          scheduleID1 = Alarm.alarmOnce(readHour,readMin,0, portD7_ON);
          EEPROM.write(schLoc, scheduleID1);
      }
          
      else if (readRepeat == 1) {
          scheduleID1 = Alarm.alarmOnce(readHour,readMin,0, portD7_ON);
          EEPROM.write(schLoc, scheduleID1);
      }
      
    }
  }
}


// Alarm Handlers -- For On and off

void portD7_ON(){
  digitalWrite(D7,HIGH);
  
  // Reset the Schedule ID to Invalid# if Alarm Already executed  -- This iw valid for alarmOnce.
  int scheduleLoc = String(PORT_D7_ON_MEM).substring(0,2).toInt();
  EEPROM.write(scheduleLoc, 255);
  
}

void portD7_OFF(){
  digitalWrite(D7,LOW);    
}

Hi @amit_singh

I tried to put the markup into your posting for C code which is three grave accents and the letters “cpp” on a line above the code and three grave accents on a separate line below the code, but I think you also edited your post and undid my edit.

As I thought, your code absolutely full of Arduino String class objects. I think all of those substring() calls can hurt you since each one creates a new default String object and copies the bytes from the current string into the newly created one. This just churns memory. Using String.startsWith for parsing input is much cheaper, for instance, since it does an underlying strncmp() of the C char array.

I really think your code will be much more stable if you move everything you can to either C char arrays or other datatypes since you are using Strings in a unusual way to hold memory data. Some String class objects are unavoidable since Spark function uses them, but if you can minimize the number of data copies, things will go a lot more smoothly. That may or may not be related to your current problem, but I think you will be happier if you change in the end.

When you say the core is unresponsive, does that mean it does not connect over your TCP server or does not connect to the cloud? What does the main LED do? What debug messages are printed before it fails? I don’t think I am following the logic in your connection monitor, but it should be printing something I think. Are you using the cloud or TCP server connections the whole time? Are you sending schedule updates or just letting it run?

From your description above, it sounded like the TI CC3000 WiFi chip is not doing the right thing but now I am not sure.

Another thing to check is your DHCP lease time on your WiFi router. This could be set to 22 hours in which case the core needs to negotiate a new IP address at that time. I am not seeing what your connection monitor would do in this case.

1 Like

Thanks a lot your details Analysis @bko.

Will surely look into your suggestions and do the required changes.

Regarding some of the questions that you have asked:

When you say the core is unresponsive, does that mean it does not connect over your TCP server or does not connect to the cloud?

It does not connect either to TCP local server as well as to the cloud.

What does the main LED do?

LED continues to breathe cyan.

What debug messages are printed before it fails?

It goes inside ConnectionMonitor Logic – "Attempting cloud Connection"
When the core becomes unresponsive, it is not able to ping to google using WiFi.Ping.

I don't think I am following the logic in your connection monitor, but it should be printing something I think. Are you using the cloud or TCP server connections the whole time?

Any one connection is being used at a time. If cloud is not available say due to Internet failure, it starts the local TCP Server

Summay about connectionMonitor Logic:

  1. It keeps on checking every ten seconds whether Cloud connection is working fine or not.
    if (WiFi.ready() && l_test == 0 && !Spark.connected() && (millis() - intTime1) >= CONDITION_CHECK_INT)

  2. If cloud connection is not available – It explicitly disconnect the Cloud connection using Spark.Disconnect() and sets l_test flag.

  3. It then keeps on checking every 50 seconds whether Internet is up or not using WiFi.ping to google.

  4. Once Internet is up again, It fires Spark.connect(); to establish the connection.

  5. This had been done so that Spark can listen to the local TCP server port with minimal blocking calls to Spark cloud to establish connection.

    Are you sending schedule updates or just letting it run?

No, I am just running that at the moment. I am not sending any new requests.

Next step – With the same code that I had shared, I will try to comment connectionMonitor() to see whether that is causing any issue or not. Not Sure repeated call to WiFi.ready and Spark.connected() is leading to any such problem.

Another thing to check is your DHCP lease time on your WiFi router

There is no issue with DHCP lease time. It is set to a large value.

1 Like

Hi,

does anyone knows if there is any method to check the active alarm and then delete it based on alarm id or something ?

thanks

HI @kefir135

There are a bunch of methods you might want to use:

id = Alarm.getTriggeredAlarmId();  //get the id of the currently triggered alarm
Alarm.enable(id);   //turn it on
Alarm.disable(id);  //turn it off
Alarm.write(id, timevalue);  // change the value
timevalue = Alarm.read(id);   // get the value
alarmtype = Alarm.readType(id);  // get the type 
Alarm.free(id);   // allow id to be reused
bool test = Alarm.isAllocated(id);  //is the id in use?
2 Likes

I commented out connectionMonitor code and now the core is working fine for more than a day.

void connectionMonitor() {
        
        if (!WiFi.ready()){
            Spark.disconnect();
            
            #ifdef DEBUG
            Serial.println("--Attempting Wifi Connection--");
            #endif
            
            while(!WiFi.ready()) SPARK_WLAN_Loop();
            
            #ifdef DEBUG
            Serial.println("--Wifi Successful--");
            #endif
            
            if (WiFi.ping(google) >=1) {
                l_test = 1;
                #ifdef DEBUG
                Serial.println("--Cloud connection will be attempted--");
                #endif
            }
            
        } else if (WiFi.ready() && l_test == 0 && !Spark.connected() && (millis() - intTime1) >= CONDITION_CHECK_INT) {
            #ifdef DEBUG
            Serial.println("--Internet failure, Disconnecting Cloud Connection--");
            #endif
            if (!Spark.connected()) Spark.disconnect();
            l_test = 1;
            
        }
        
        if (l_test == 1 && (millis()- intTime) >= CLOUD_CHECK_INT){
            #ifdef DEBUG
            Serial.println("--Attempting cloud Connection--");
            #endif
            intTime = millis();
            if (WiFi.ping(google) >= 1) {
                #ifdef DEBUG
                Serial.println("--Successful Ping--");
                #endif
                l_test = 0;
                if (!Spark.connected()) Spark.connect();
                delay(1000);
                intTime1 = millis();
            }
        }
        
    }

This logic only checks for the cloud conenction and gets activated in case of Internet failure or Wifi failure.

Is repeated calls to Spark.connected() / WiFi.ready() within a loop at regular interval causing any issue??

1 Like

Hi @bko,

Another thing to check is your DHCP lease time on your WiFi router. This could be set to 22 hours in which case the core needs to negotiate a new IP address at that time. I am not seeing what your connection monitor would do in this case.

Is there anything specific I need to do in a code if core negotiates for a new IP after lease expiry?

Hi @amit_singh

The best thing to do is setup your router to have a long lease time. When the lease expires, the core automatically negotiates for a new lease, but that knocks the cloud connection and any open TCP/UDP connections offline temporarily.

The logic in your connection monitor was hard for me to follow since there are a lot of cases to handle. Some of the if statements are parallel and some are sequential if-then-else but the I wasn’t sure the cases didn’t overlap.

1 Like

I could use some assistance. When I use the library command to load the TimeAlarms library, I then select the example and the use this example button. Then I verify and get the following error message. I get the same result with the default code

//#include "TimeAlarms.h"
or
#include "TimeAlarms.h"

Error Message starts here…

In file included from ../inc/spark_wiring.h:29:0,
from ../inc/application.h:29,
from TimeAlarms/TimeAlarms.h:6,
from TimeAlarms/TimeAlarms.cpp:27:
../../core-common-lib/SPARK_Firmware_Driver/inc/config.h:12:2: warning: #warning "Defaulting to Release Build" [-Wcpp]
#warning "Defaulting to Release Build"
^
TimeAlarms/TimeAlarms.cpp: In member function 'void AlarmClass::updateNextTrigger()':
TimeAlarms/TimeAlarms.cpp:62:48: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
if( value + previousMidnight(now()) <= time)
^
TimeAlarms/TimeAlarms.cpp:73:48: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
if( (value + previousSunday(now())) <= time)
^
In file included from TimeAlarms/TimeAlarms.cpp:27:0:
TimeAlarms/TimeAlarms.cpp: In member function 'AlarmID_t TimeAlarmsClass::alarmOnce(time_t, OnTick_t)':
TimeAlarms/TimeAlarms.h:20:44: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
#define SECS_PER_DAY (SECS_PER_HOUR * 24UL)
^
TimeAlarms/TimeAlarms.cpp:120:19: note: in expansion of macro 'SECS_PER_DAY'
if( value <= SECS_PER_DAY)
^
TimeAlarms/TimeAlarms.cpp: In member function 'AlarmID_t TimeAlarmsClass::alarmRepeat(time_t, OnTick_t)':
TimeAlarms/TimeAlarms.h:20:44: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
#define SECS_PER_DAY (SECS_PER_HOUR * 24UL)
^
TimeAlarms/TimeAlarms.cpp:136:18: note: in expansion of macro 'SECS_PER_DAY'
if( value <= SECS_PER_DAY)
^
TimeAlarms/TimeAlarms.cpp: In member function 'time_t TimeAlarmsClass::getNextTrigger()':
TimeAlarms/TimeAlarms.cpp:326:31: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
return nextTrigger == 0xffffffff ? 0 : nextTrigger; 
^
In file included from TimeAlarms/TimeAlarms.cpp:27:0:
TimeAlarms/TimeAlarms.cpp: In member function 'AlarmID_t TimeAlarmsClass::create(time_t, OnTick_t, uint8_t, dtAlarmPeriod_t, uint8_t)':
TimeAlarms/TimeAlarms.h:23:44: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
#define SECS_PER_YEAR (SECS_PER_WEEK * 52UL)
^
TimeAlarms/TimeAlarms.cpp:332:46: note: in expansion of macro 'SECS_PER_YEAR'
if( ! (dtIsAlarm(alarmType) && now() < SECS_PER_YEAR)) // only create alarm ids if the time is at least Jan 1 1971
^
In file included from ../inc/spark_wiring.h:29:0,
from ../inc/application.h:29,
from timealarmsexample.cpp:3:
../../core-common-lib/SPARK_Firmware_Driver/inc/config.h:12:2: warning: #warning "Defaulting to Release Build" [-Wcpp]
#warning "Defaulting to Release Build"
^
timealarmsexample.cpp: In function 'void setup()':
timealarmsexample.cpp:23:3: error: 'Alarm' was not declared in this scope
* Another timer is called once only after 10 seconds
^
timealarmsexample.cpp:25:21: error: 'dowSaturday' was not declared in this scope
* At startup the time is set to Jan 1 2011 8:29 am
^
timealarmsexample.cpp: In function 'void loop()':
timealarmsexample.cpp:33:3: error: 'Alarm' was not declared in this scope
Time.zone(-4);
^
make: *** [timealarmsexample.o] Error 1

Error: Could not compile. Please review your code.

Well I solved my own problem. I removed the library reference for the app. Then I found the library a 2nd time and clicked on Use this Library and then it properly added the correct include statement.

#include "TimeAlarms/TimeAlarms.h"
2 Likes

@cfox,

I have sent in a PR to get this fixed! Seems like @Hypnopompia noted this already but the PR wasn’t merged.

@bko here’s one for you!

2 Likes

Going to bump this thread.
I wasn’t really happy with the solutions presented here. Either too clunky or flaky. So i wrote my own!
Here is my version of the TimeAlarms for Particle. Generally you setup an alarm with an ON an OFF time. Then you just call isActive() to check if the alarm is on or not.

Feedback appreciated.