Over the past few weeks I’ve been working on a small project that allows me to control electrical relays from an Arduino over the network from Android and C clients. It’s been delayed slightly from finals, holidays and power outages thanks to snow storms, but below is a demo of the first complete version.

Looking for the source code or instructions on how to set up your own?

The whole project aims to be as simple as possible. As can be seen in the demo video above, the hardware setup consists of an Arduino Uno, Arduino ethernet shield, PowerSwitch Tail II relay, two wires connecting the relay to the Arduino, and power and ethernet for the Arduino.

The protocol for communicating with the server is extremely short. In fact, it consists of two commands, one of which has further options.

  • GET operation: Just send a g to the server and it responds with the state of pins 2-9 in the form 2-0;3-1;4-0; etc. where the first number is the pin and the second number is whether the pin is on or off.
  • SET operation: Turns a relay on/off. Of the form s-[pin]-[cmd] where [pin] is the pin to perform the command on and the command is either: 0 for off, 1 for on, or t for toggle.

The code for the GET operation is simply:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int op_get(EthernetClient client) {
   // Create a string with the status of each pin
   char status[34];
   char append[5];
   status[0] = '\0';

   for(int i=2; i<=9; i++) {
      sprintf(append, "%c-%c;", i+48, (digitalRead(i) == HIGH) ? '1' : '0');
      strncat(status, append, 4);
   }

   // Add a final newline and move the nul
   status[32] = '\n';
   status[33] = '\0';

   // Send the status string to the client
   client.print(status);

   return SUCCESS;
}

Just loop through the pins, check the state using digitalRead(), append each pin to the final string to send to the client and then send the data to the client.

The SET operation is just as simple.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Convert pin to an int
pin -= 48;

switch(cmd) {
  // Turn relay off
  case '0':
     digitalWrite(pin, LOW);
     client.println(OK);
     break;
  // Turn relay on
  case '1':
     digitalWrite(pin, HIGH);
     client.println(OK);
     break;
  // Toggle relay state
  case 't':
  case 'T':
     (digitalRead(pin) == HIGH) ? digitalWrite(pin, LOW) : digitalWrite(pin, HIGH);
     client.println(OK);
     break;
  // Unexpected data from client
  default:
     abort_client(client);
     return FAILURE;
}

Omitting some of code that reads and parses the data sent to the server (it’s boring), we first convert the pin to an int by just subtracting 48 since the data from the client is a string so a single character from that is a char and the max allowed pin is 9 so it’s always guaranteed to be one decimal long.

After this it’s just a switch to determine what command to perform using the digitalWrite() function. If a toggle command, we first check the state of the pin and then set it to the opposite state. The reply to the client is just either OK or ERR.

Full code for the Arduino server is below, but the most recent version will be on GitHub at the link at the top of this post.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
// RelayRemote
// Shane Tully (shane@shanetully.com)
// shanetully.com
// https://github.com/shanet/RelayRemote
//
// Copyright (C) 2012 Shane Tully
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include <SPI.h>
#include <Ethernet.h>

#define PORT 2424
#define OK   "OK"
#define ERR  "ERR"

#define SUCCESS 0
#define FAILURE -1

// IMPORTANT: The IP AND MAC MUST BE CHANGED to something unique for each Arduino.
// The gateway will probably need changed as well.
byte ip[]      = {10, 10, 10, 31};
byte mac[]     = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEE};
byte gateway[] = {10, 10, 10, 1};
byte subnet[]  = {255, 255, 255, 0};

EthernetServer server = EthernetServer(PORT);

void setup() {
   // Set the relay pins as output
   for(int i=2; i<=9; i++) {
      pinMode(i, OUTPUT);
   }

   // Start the server
   Ethernet.begin(mac, ip, gateway, subnet);
   server.begin();
}

void loop() {
   // The client should be sending one of two commands.
   // GET: Of the form "g". Tells us to send back the status of each pin
   // SET: Of the form "s-[pin]-[state]". Tells us to set [pin] to [state]
   //      Pin should be any pin in [2,9]
   //      State is either 0 (off), 1 (on), or t (toggle)
   char op;

   // Get a client from the server
   EthernetClient client = server.available();

   if(client) {
      if(client.available()) {
         // Read the operation
         op = client.read();

         switch(op) {
            // Get status operation
            case 'g':
               op_get(client);
               break;
            // Set pin operation
            case 's':
               if(op_set(client) == FAILURE) return;
               break;
            default:
               abort_client(client);
               return;
         }
      }

      // We're done with this client. Disconnect it.
      client.stop();
   }
}

int op_set(EthernetClient client) {
   char pin;
   char cmd;

   // Read and ignore the hypen seperator
   if(client.read() != '-') {
      abort_client(client);
      return FAILURE;
   }

   // Read the pin
   if((pin = client.read()) == -1) {
      abort_client(client);
      return FAILURE;
   }

   // Check that the pin is in the valid range
   if(pin-48 < 2 || pin-48 > 9) {
      abort_client(client);
      return FAILURE;
   }

   // Read and ignore the hypen separator
   if(client.read() != '-') {
      abort_client(client);
      return FAILURE;
   }

   // Read the command to perform
   if((cmd = client.read()) == -1) {
      abort_client(client);
      return FAILURE;
   }

   // Convert pin to an int
   pin -= 48;

   switch(cmd) {
      // Turn relay off
      case '0':
         digitalWrite(pin, LOW);
         client.println(OK);
         break;
      // Turn relay on
      case '1':
         digitalWrite(pin, HIGH);
         client.println(OK);
         break;
      // Toggle relay state
      case 't':
      case 'T':
         (digitalRead(pin) == HIGH) ? digitalWrite(pin, LOW) : digitalWrite(pin, HIGH);
         client.println(OK);
         break;
      // Unexpected data from client
      default:
         abort_client(client);
         return FAILURE;
   }

   return SUCCESS;
}

int op_get(EthernetClient client) {
   // Create a string with the status of each pin
   char status[34];
   char append[5];
   status[0] = '\0';

   for(int i=2; i<=9; i++) {
      sprintf(append, "%c-%c;", i+48, (digitalRead(i) == HIGH) ? '1' : '0');
      strncat(status, append, 4);
   }

   // Add a final newline and move the nul
   status[32] = '\n';
   status[33] = '\0';

   // Send the status string to the client
   client.print(status);

   return SUCCESS;
}

void abort_client(EthernetClient client) {
   client.println(ERR);
   client.stop();
}