Web Interface User Scheduled Relays

Hey there Particle community!

This is my first post and I am new to web programming and object oriented programming in general. I just finished reading 3 JavaScript books so I do understand it at a basic level on the web side. I have been able to use the Particle JS SDK to program a web page to allow me to login, list devices, select a device, and call a function to display the results and toggle an LED on a web page.

I am working on a project that would require end users of my product to be able to login to my web application using their particle account and schedule 4 different relays to be turned on and off at intervals and specific times throughout the day.

One relay might turn on at 10am and turn off at 5pm. Another might turn on every 30 minutes for a 15 minute duration, then turn off. Another might stay on all the time with the ability for the user to toggle it at anytime from the web page.

I did some research on what other people have done when looking to schedule functions. I found that I could use the built-in Time Library using if statements or I could use the add-on to the Time Library called TimeAlarms that would allow me to create alarm functions to call other functions that trigger the relays.

I want the user to be able to specify through input fields on my webpage, relay 1, I need you to turn on at 10am and turn off at 5pm. Rely 2, I need you to go high every 30 minutes for a 15 minute duration, etc, etc.

I am looking for general guidance on how to allow users accomplish the above. Is there any way for the users to create their own TimeAlarms functions from a web page and flash them to their particle devices? The scheduled task/functions need to be able to execute regardless if the particle is connected to the internet for safety.

Thank you for reading and for any suggestions you may have!

Slatts

Your first start could be this example described in the docs to manage actions over the Internet.
If the LED example works you could expand this with relays and timers step by step.
Good luck!

You could do this, but I don't think the TimeAlarms library is necessarily the best way to go. I had a project where I wanted to do something similar to what you're trying to do, and I did it using a timer object (a C struct) and a little code in loop() to determine when to "fire" the timers. I use a Particle.function to send the parameters into the timers. I modified my code a bit to show you this, that I think will do what you want,

typedef struct {
    int type = -1;
    bool isRunning;
    int pin;
    int onTime;
    int offTime;
    unsigned long startTime;
    unsigned long period; // in seconds
    unsigned long duration; // in seconds
} Alarm;

Alarm alarms[4];

void setup() {
    Time.zone(-7);
    Particle.function("timerParams", setupTimerParameters);
    alarms[0].pin = D0;
    alarms[1].pin = D1;
    alarms[2].pin = D2;
    alarms[3].pin = D7;
    
     for (int i=0; i<4; i++) {
        pinMode(alarms[i].pin, OUTPUT);
     }
}

void loop() {
    int now = Time.hour() * 3600 + Time.minute() * 60 + Time.second();
    for (int i=0; i<4; i++) {
        switch (alarms[i].type) {
            case 0: // type zero is one that starts and stops at particular clock times
                if (now >= alarms[i].onTime && alarms[i].isRunning == false) {
                    digitalWrite(alarms[i].pin, HIGH);
                    alarms[i].isRunning = true;
                }
                
                if (now >= alarms[i].offTime && alarms[i].isRunning == true) {
                    digitalWrite(alarms[i].pin, LOW);
                    alarms[i].isRunning = false;
                }
                break;
                
            case 1: // type one is one that runs for a set duration in a repeating period of time
                if (millis() >= alarms[i].startTime + alarms[i].period && alarms[i].isRunning == false) {
                    alarms[i].isRunning = true;
                    digitalWrite(alarms[i].pin, HIGH);
                    alarms[i].startTime = millis();
                }
                
                if (millis() >= alarms[i].startTime + alarms[i].duration && alarms[i].isRunning == true) {
                    alarms[i].isRunning = false;
                    digitalWrite(alarms[i].pin, LOW);
                }
                break;
                
            case 2:
                break;
                
            default:
                digitalWrite(alarms[i].pin, LOW);
        }
    }

}


int setupTimerParameters(String cmd) { // command sent as "relay#,0,onTime,offTime" or as "relay#,1,period,duration" or "relay#,2" 
    char *str = (char*)cmd.c_str();
    int relay = atoi(strtok(str, ","));
    alarms[relay].type = atoi(strtok(NULL, ","));
    
    if (alarms[relay].type == 0) { // time of day type
        alarms[relay].onTime = atoi(strtok(NULL, ","));
        alarms[relay].offTime = atoi(strtok(NULL, ","));
        alarms[relay].isRunning = false;
    }else if (alarms[relay].type == 1) { // periodic type
        alarms[relay].startTime = millis();
        alarms[relay].isRunning = true;
        digitalWrite(alarms[relay].pin, HIGH);  // turn on the relay immediately
        alarms[relay].period = strtoul(strtok(NULL, ","), NULL, 10) * 1000;
        alarms[relay].duration = strtoul(strtok(NULL, ","), NULL, 10) * 1000;
    }else if (alarms[relay].type == 2) { // toggle type
        if (alarms[relay].isRunning == false) {
            digitalWrite(alarms[relay].pin, HIGH);  // turn on the relay immediately
            alarms[relay].isRunning = true;
        }else{
            digitalWrite(alarms[relay].pin, LOW);  // turn off the relay immediately
            alarms[relay].isRunning = false;
        }
    }
    
    Serial.printlnf("type: %d  isRunning: %d  pin: %d  onTime: %d  offTime: %d  startTime: %d  period: %d  duration: %d", alarms[relay].type, alarms[relay].isRunning, alarms[relay].pin, alarms[relay].onTime, alarms[relay].offTime, alarms[relay].startTime, alarms[relay].period, alarms[relay].duration);
    
    return 1;
}
1 Like

Just my 2 cents… If I was going about this I would have the photon subscribe to an event that set the state of the relays using a bitmask and publish the current state of the relays. Then have your logic in a central place (i.e. where the web pages live for scheduling etc). The general idea is to have the brains somewhere else. So with some bitwise operations you could pass the state of all the relays on a photon. Lets say you have 3 relays:

r1 = 1
r2 = 2
r3 = 4

You want relay 1 and 3 on and 2 off… you pass a message to your event that says 5. Your code on the photon would check these with bitwise operations (bunch of if statements checking if the relay should be on or off). If you change the state a relay, then after all your operations publish what the new state of the pins are.

You could then also put out a particle.variable that contains the current state and interrogate it anytime you didn’t know the value (say startup of your application).

But like @Postler said you should start one step at a time.

Good Luck! Hope that helps, that made sense in my head but sometimes I don’t transpose the thoughts well to posts.

Thanks!

Thanks for the advice and links Postler!

Ric,

You are a genius! Thank you so much for providing your code and modifying it for me to use. I have not had a chance to test it yet on my Photon since I have been travelling for work. One question, what format is the function expecting the onTime and offTime for the parameter string? I have my web page setup with input type=“time” right now. it gives military time down to the minute.

Here is what I have so far. The code for the test html web page and the test JavaScript to control it. I still need to figure out how to make the JavaScript an array. Right now it is all hard coded. I also have not added the particle.callFunction part yet.

HTML

<?xml version="1.0" encoding="UTF-8" ?><?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

<title>Test Outlet Page</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>

<script type="text/javascript" src="timertest2.js"></script>

<!-- css -->
<link href="css/styles.css" rel="stylesheet">
</head>

<body>

<h1>Outlet Scheduler</h1>

<form>
<h2>Outlet 1</h2>
  Schedule Type<br>
  <input type="radio" id="relay1Type0" value="0" name="relay1Type">Time Of Day<br>
  <input type="radio" id="relay1Type1" value="1" name="relay1Type">Repeating<br>
  On Time:<input type="time" id="relay1OnTime"><br>
  Off Time:<input type="time" id="relay1OffTime"><br>
  Interval:<input type="text" id="relay1IntervalHrs" size="2" maxlength="2" value="0">hrs<input type="text" id="relay1IntervalMin" size="2" value="0">min<input type="text" id="relay1IntervalSec" size="2" value="0">sec<br>
  Duration:<input type="text" id="relay1DurationHrs" size="2" maxlength="2" value="0">hrs<input type="text" id="relay1DurationMin" size="2" value="0">min<input type="text" id="relay1DurationSec" size="2" value="0">sec<br>
  <input type="submit" id="saveRelay1Settings" value="Save">
  <br>
  <h3>Current Settings</h3>
  Schedule Type:<span id="currentRelay1Type"></span><br>
  On Time:<span id="currentRelay1OnTime"></span><br>
  Off Time:<span id="currentRelay1OffTime"></span><br>
  Interval:<span id="currentRelay1Interval"></span><br>
  Duration:<span id="currentRelay1Duration"></span><br>
</form>

JavaScript

$(document).ready(function() {
	initPage();
});


function initPage() {
	// Hook up handlers using jquery
	$('#saveRelay1Settings').on('click', setRelay1Settings);
	$('#relay1Type').on('change', hideHTML);
}
	
	
// This funtion is used to set realy1 settings
function setRelay1Settings() {
	var relay1Type = $('input[name=relay1Type]:checked').val();
	var relay1OnTime = $('#relay1OnTime').val();
	var relay1OffTime = $('#relay1OffTime').val();
	var relay1IntervalHrs = $('#relay1IntervalHrs').val();
	var relay1IntervalMin = $('#relay1IntervalMin').val();
	var relay1IntervalSec = $('#relay1IntervalSec').val();
	var relay1DurationHrs = $('#relay1DurationHrs').val();
	var relay1DurationMin = $('#relay1DurationMin').val();
	var relay1DurationSec = $('#relay1DurationSec').val();
	var relay1Interval = ((parseInt(relay1IntervalHrs)*60)*60)+(parseInt(relay1IntervalMin)*60)+(parseInt(relay1IntervalSec));
	var relay1Duration = ((parseInt(relay1DurationHrs)*60)*60)+(parseInt(relay1DurationMin)*60)+(parseInt(relay1DurationSec));
	
	
	if (relay1Type == 0) {
		var cmd = (0 + "," + relay1Type + "," + relay1OnTime + "," + relay1OffTime);
		$('#currentRelay1Type').empty().append("Time Of Day");
		$('#currentRelay1OnTime').empty().append(relay1OnTime);
		$('#currentRelay1OffTime').empty().append(relay1OffTime);
		$('#currentRelay1Interval').empty()
		$('#currentRelay1Duration').empty()
	} else if (relay1Type == 1) {
		var cmd = (0 + "," + relay1Type + "," + relay1Interval + "," + relay1Duration);
		$('#currentRelay1Type').empty().append("Repeating");
		$('#currentRelay1Interval').empty().append(relay1Interval);
		$('#currentRelay1Duration').empty().append(relay1Duration);
		$('#currentRelay1OnTime').empty()
		$('#currentRelay1OffTime').empty()
	}
	
	console.log(cmd)
	event.preventDefault();
}

function hideHTML() {
	
}

Hey SomeFixItDude,

Thank you for the feedback! I like your idea and yes it would work but I am not sure it would be good for this application because if the Particle Photon looses it’s connection to the cloud it wouldn’t get the message to turn on or off the relays. This would be very bad for the application I am intending it to be used for (salt water aquarium status and control) but could be perfectly OK in other situations.

The same format I have for the variable now.

Time.hour() * 3600 + Time.minute() * 60 + Time.second()

Time.hour() does give you 24 hour time (military time, as you say).

@Ric, a quicker way to get the same result would be

  int timeInSeconds = Time.local() % 86400;
1 Like