How to update relay state ON/OFF in Photon & Android apps

Hi guys, I’m making a simple app to turn ON/OFF relays connect with lamps, and in Android I have button to do this.

Ideally, for example, if I turn ON (Lamp 1) and OFF (Lamp 2), when I close and reopen the Android app, it will update the relay state by the color/text of the buttons

However, I’m stucking on it, all mess up :frowning:

Here is the code

For Photon :

int light1 = D0;
int light2 = A0;
int state1 = digitalRead(D0);
int state2 = digitalRead(A0);
int i = -1;
String cmd[] = {"ON_1", "OFF_1", "ON_2", "OFF_2", "ON_ALL", "OFF_ALL"}; 
int status ; 

void setup() {
    //status = 1;
    pinMode(light1, OUTPUT);
    digitalWrite(light1,HIGH);
    pinMode(light2, OUTPUT);
    digitalWrite(light2,HIGH);
    Particle.function("on-off", toggleSwitch);
    Particle.variable("lightstate", status);
}
 
void loop() {
    
    if (state1 == LOW && state2 == LOW)
    {
        status = 0;
    }
   
    if (state1 == LOW && state2 == HIGH)
    {
        status = 1;
    }
    
    if (state1 == HIGH && state2 == LOW)
    {
        status = 2;
    }
    
    if (state1 == HIGH && state2 == HIGH)
    {
        status = 3;
    }
}
 
int toggleSwitch(String command) {
 String convertedCommand = command.toUpperCase();
 
 for (i = 0; i <= sizeof(cmd)/sizeof(cmd[0]); i++) // notice the less than or equal to comparison
  {
    if (convertedCommand.equals(cmd[i])) // compare String myCommand and Array Element
    break;
  }
  
 switch(i) {
     
    case 0:
    digitalWrite(light1,LOW);
    break;
  
    case 1:
    digitalWrite(light1,HIGH);
    break;
    
    case 2:
    digitalWrite(light2,LOW);
    break;
  
    case 3:
    digitalWrite(light2,HIGH);
    break;
   
   case 4:
    digitalWrite(light1,LOW);
    delay(200);
    digitalWrite(light2,LOW);
    break;
    
   case 5:
   digitalWrite(light1,HIGH);
   delay(200);
    digitalWrite(light2,HIGH);
    break;
    }
 }

Android code :

Async.executeAsync(ParticleCloud.get(RemoteControlActivity.this), new Async.ApiWork<ParticleCloud, Integer>() {
           private ParticleDevice mDevice;

           @Override
           public Integer callApi(ParticleCloud particleCloud) throws ParticleCloudException, IOException {
               mDevice = particleCloud.getDevice(getIntent().getStringExtra(ARG_DEVICEID));
               int status = 0;
               try {
                   status = mDevice.getIntVariable("lightstate");
               } catch (ParticleDevice.VariableDoesNotExistException e) {
                   e.printStackTrace();
               }
               return status;
           }

           public void onSuccess(Integer result) {
               Log.d(TAG,"Lightstate :" + String.valueOf(result));

               if (Integer.valueOf(result)== 0)
               {
                   sw1.setText("ON");
                   sw1.setBackgroundColor(Color.YELLOW);
                   sw2.setText("ON");
                   sw2.setBackgroundColor(Color.YELLOW);
                   btnall_on.setVisibility(View.INVISIBLE);
                   btnall_off.setVisibility(View.VISIBLE);
               }

               if (Integer.valueOf(result)== 1)
               {
                   sw1.setText("ON");
                   sw1.setBackgroundColor(Color.YELLOW);
                   sw2.setText("OFF");
                   sw2.setBackgroundColor(Color.GRAY);
                   btnall_on.setVisibility(View.VISIBLE);
                   btnall_off.setVisibility(View.VISIBLE);
               }

               if (Integer.valueOf(result)== 2)
               {
                   sw1.setText("OFF");
                   sw1.setBackgroundColor(Color.GRAY);
                   sw2.setText("ON");
                   sw2.setBackgroundColor(Color.YELLOW);
                   btnall_on.setVisibility(View.VISIBLE);
                   btnall_off.setVisibility(View.VISIBLE);
               }

               if (Integer.valueOf(result)== 3)
               {
                   sw1.setText("OFF");
                   sw1.setBackgroundColor(Color.GRAY);
                   sw2.setText("OFF");
                   sw2.setBackgroundColor(Color.GRAY);
                   btnall_on.setVisibility(View.VISIBLE);
                   btnall_off.setVisibility(View.INVISIBLE);
               }
           }

           public void onFailure(ParticleCloudException value) {
           }
       });

When I test on Android App, I always get the lightstate : 3

image

Plese kindly check and explain to me :

  • Can we minimize the length of the code, I mean the algorithm or formula I used is quite long and complicated

  • How about in the future, if I control over 3, 4 or event 10 devices, can I do like this, if so, it will be a very big lines of code

Thank you for your understanding !

Perhaps you can help me test some code that I recently wrote …

It is a Relay class, packaged as a header file so it is easy to add to my apps. If you need to support multiple relays, just create multiple instances of this class.

Relay.h


#ifndef __Relay__
#define __Relay__

/*********************************************************************************************************
 * CLASS: Relay < version 1.0 >
 *********************************************************************************************************
 * When an MPU pin controls power to the relay's coil, the Relay class can be used to simplify the
 * interface. When the MPU pin connects to a relay to sense it's position, the "Switch" class should be used.
 * Multiple instances of the Relay class can be used to manage multiple relays. 
 *			Relay state 0 = contacts OPEN / OFF
 *          Relay state 1 = contacts CLOSED / ON
 * NOTE: RELAY STATE references the state of the relay's contacts. Behind the scenes, the Relay class
 *       translates the relay state to and from the Particle device's pin state. This translation is
 *       based on whether the relay was configured as "normally open", or "normally closed".
 *       
 ********************************************************************************************************/

class Relay {

  private:
    int relayPin;
    bool normallyOpen;
    uint8_t relayState;
    
  public:
    static const bool NORMALLY_OPEN = true, NORMALLY_CLOSED = false;
    static const uint8_t OPEN = 0, CLOSED = 1, OFF = 0, ON = 1;
    
    void setup(int relayPin, bool normallyOpen) {                                       // setup()
        this->relayPin = relayPin;
        pinMode(relayPin, OUTPUT);            
        this->normallyOpen = normallyOpen;
        if (normallyOpen) this->relayState = OPEN; 
		else this->relayState = CLOSED;
    }
// -------------------------------------------------------------------------------------------------------     
	
	int getState(void) { return (int) this->relayState; }                               // getState()
// -------------------------------------------------------------------------------------------------------     

    int setState(int desiredState) {                                                    // setState()
		int desiredPinState;
        if (desiredState == OPEN) {
            if (this->normallyOpen) desiredPinState = LOW;
            else desiredPinState = HIGH;
        }
        else if (desiredState == CLOSED) {
            if (this->normallyOpen) desiredPinState = HIGH;
            else desiredPinState = LOW;
        }
		else return -1; // Error: Invalid desiredState
		if (digitalRead(relayPin) != desiredPinState) {
			digitalWrite (relayPin, desiredPinState);
			relayState = desiredState;
			return 0; // Success: Relay was toggled
		}
		this->relayState = desiredState;
		return 1; // Success: Relay was already set to the desiredState
    }
// -------------------------------------------------------------------------------------------------------     

};

#endif

This is the .ino that I did my initial testing with …


#include "Relay.h"
/************************************************************************************************************
 *                                            RelayDemo.ino
 ************************************************************************************************************
 *  This demo firmware uses two Particle Functions and the Relay class to control two relays
 *  The Particle Functions accept an integer as input 0 = OPEN, and 1 - CLOSED, toggle the relay to the
 *  specified state, and call Particle.publish to display the action taken or error detected.
 * 
 *  Note: For demo purposes, this code can run on a device with, or without, relays attached
 ***********************************************************************************************************/

Relay relay1, relay2;
const int OPEN = 0, CLOSED = 1, ERROR = -1;
void setup() {

    relay1.setup(D0, Relay::NORMALLY_OPEN);
    relay2.setup(D1, Relay::NORMALLY_CLOSED);
    Particle.function("setRelay1", setRelay1);
    Particle.function("setRelay2", setRelay2);
}

void loop() {
    
}

/************************************************************************************************************
 * These  Particle Functions can be triggered from the Particle Console, HTML, MQTT, timers, phone apps, etc.
 ***********************************************************************************************************/
 
int setRelay1(String s) {
    int desiredState = atoi(s);
    char buf[64];
    switch(relay1.setState(desiredState)) {
        case OPEN: snprintf(buf, sizeof(buf), "Relay1 is now %s", relay1.getState()? "CLOSED":"OPEN"); break;
        case CLOSED: snprintf(buf, sizeof(buf), "Relay1 is already %s", relay1.getState()? "CLOSED":"OPEN"); break;
        case ERROR: snprintf(buf, sizeof(buf), "Invalid Input"); break;
    }
    Particle.publish("Relay1", buf, PRIVATE);
    return relay1.getState();
}

int setRelay2(String s) {
    int desiredState = atoi(s);
    char buf[64];
    switch(relay2.setState(desiredState)) {
        case OPEN: snprintf(buf, sizeof(buf), "Relay2 is now %s", relay2.getState()? "CLOSED":"OPEN"); break;
        case CLOSED: snprintf(buf, sizeof(buf), "Relay2 is already %s", relay2.getState()? "CLOSED":"OPEN"); break;
        case ERROR: snprintf(buf, sizeof(buf), "Invalid Input"); break;
    }
    Particle.publish("Relay2", buf, PRIVATE);
    return relay2.getState();
}

I know the above class does not address your complete problem, but having a class makes it easy to scale … for example, you can create an array of relays, then send an array of settings to Android. I personally use HTML instead of Android, and I send the array in a JSON string. The JSON string includes the name of the light, and button text … so the status reads “ON” and the button reads “Turn Lamp Off”. In my case, I can add a light without touching the HTML because HTML justs sees a table with another “row” of data … and when the button is pressed, it just sends the row number to the Photon. The following browser screen capture shows a single relay (one row), but it is displayed in a table that could have any number of rows.

The Photon has a single Particle Function to toggle relays. When you click a button in the first row, The android sends a ‘1’, and the Particle knows to toggle the first relay in the relay array. The Particle would then have a ‘for’ loop to walk all of the relays to assemble the JSON string (or whatever) that contains the array that Android needs to recreate the table. You should be able to have Android repaint the table without the need to reboot the app.

browserImage

Hi @hitmanbaby2007 (and @Bear) ,

I wrote something similar some time ago, however I used Ionic for my app.
Like you, I had to set on/off relays and keep their status.
What I did at the time I think, is I looked at the relay library provided by Particle and tried to get inspiration from it. It’s a class like Rodney mentions, and it’s made by Particle. Looks neat and it’s already in the libraries available. This is the one:
https://build.particle.io/libs/RelayShield/0.0.6/tab/RelayShield.cpp
or the git repo:

If you wanted to check my project, it’s here:

Good luck with yours, guys!
Gustavo.

1 Like

one thing that you did that may not scale well is to have states defined that include both relays.
I would decouple them.
What I mean is, you defined a state machine (with status 0, 1, 2, 3) that involves relay1 and relay2 in it.
I would not tie them together and have status1 for relay 1 (states 0 and 1), status2 for relay2 (states 0 and 1) and so on. This, if you wrap it in a function and some global variables (or more elaborated, a class, but I'd leave that to the far future) then that is easier to scale up when would you have more relays.
Like mentioned by I think @Bear, you can encode the status of each relay in a bit in a cloud var, that that would be what your app would refresh at every start up and every now and then (and maybe, when the user pulls down the screen on the app to refresh the info).
Cheers,
Gustavo.
PS: I think @wgbartley did some bit processing that could help you in your app. His project is here:

Dear @gusgonnet and @Bear for your kind and specific answers

My idea from the beginning is imagine that, can we actually use Particle.variable only one time to post “status” of these relays and determine which is ON/OFF or both of them are in the same status. All I want so far is whenever I open the app it will automatically modify the button color/text based on actual relay status now

I’m appreciate for what you guys help me to realize my issue, but most of them is quite complicated for me, and somehow, I guess it actually have to define every individual status for every each of them

However, so far I can understand you guys meant that, we can wrap them all in one string or sequence (I dont know what to say, sorry for my knowledge), then seperate it later when I received in on AsyncTask getVariable() function

Dear @gusgonnet, I’m still wondering, am I wrong to think in this way, is it actually a bad formula, or my idea is good but not usually used in logical algorithm ? If you can understand what I mean, please point it out, it will be a very useful experience for me !

if (state1 == LOW && state2 == LOW) { status = 0; }

Thank you guys so much !

Particle.variable() doesn't post the status but only registers a global variable for remote requests.
You can manipulate the base variable any time in your code but only when the current state is requested the value will be provided as response to that request.

Not necessarily. There are plenty of possibilites.
The least elaborate would be - as suggested by @gusgonnet - a bitwise representation in one single status variable (e.g. 32 bits in a uint32_t)
More elaborate solutions could involve a collection of "relay" objects which can be "instructed" to do whatever necessary depending on the status you pass in.

Without a firm background in C++ I'd suggest you opt for the bit based aproach.
And since you probably won't be directly control your device from console or CLI, you shouldn't tie yourself to human readable commands but rather go the way most conventient for machines (device code and mobile app).
e.g. a command 1:0 could mean "relay 1 off", 2:1 "realay 2 on" and 3:0 "all (1&2) off".
When adding more and more switches you'd number them as 4, 8, 16, 32, ... to get a distinct binary position for each of them. That way you can create any combination of "target" relays to either swtich off or on.
You can also have an option (e.g. 5:-1 where 5 stands for binary 00000101) which could mean "toggle relays 1 & 3").

BTW, toggleSwitch() is a somewhat misleading function name. Toggling is usually understood as just inverting the state ignorant of current state. What your code does would better be called setSwitch() IMO.

Hi, you are not wrong and this is not a bad formula. What I'm trying to say is that is not convenient, for scaling or comprehension in the future, to tie state1 and state2 with a status=0.
I would have state1 tied with status1 and state2 tied with status2.
If you do that, you realise that in the end, you do not need status1 or status2 to give you the status of your relays, since the info they convey is contained already in state1 and state2.
Hope I did not lose you there :slight_smile:

My suggestion? Here:

  • remove the state variable from your code and mind, and go with state1, state2, stateX.
  • make state1, stateX as cloud variables (you have up to 20), then GET them from your app in your executeAsync task.

When you hit the case of having more than 20 relays, you'd be up to speed in your firmware and C and can think of a better solution to your problem.

Go @hitmanbaby2007 go!