Naos v1.x: Difference between revisions

From Alteeve Wiki
Jump to navigation Jump to search
Line 48: Line 48:
  - Digimer
  - Digimer
   
   
Version: 1.1.4
Version: 1.1.4.1
  - Release: Apr. 05, 2010
  - Release: Apr. 06, 2010


License:
License:
Line 97: Line 97:


Changes:
Changes:
- v1.1.4.1
  - Fixed the numbering of Nodes in '00:0' status requests.
  - v1.1.4
  - v1.1.4
   - Changed the version number to correspond to the matching supported Node
   - Changed the version number to correspond to the matching supported Node
Line 151: Line 153:
//      internal tracking number.
//      internal tracking number.
char serialNumber[7]="PR0002";
char serialNumber[7]="PR0002";
char osVersion[7]="v1.1.4";
char osVersion[7]="v1.1.4.1";
char buildDate[11]="2010-04-03";
char buildDate[11]="2010-04-03";


Line 380: Line 382:
{
{
// 'i' is the current, zero-based node number.
// 'i' is the current, zero-based node number.
nodeASCII[0]=((node+1)/10)+'0'; // The '+1' makes the node 1-based instead of 0-based.
nodeASCII[0]=(node/10)+'0';
nodeASCII[1]=((node+1)%10)+'0'; // The modulous returns my real one position.
// The modulous returns my real one position.
nodeASCII[1]=(node%10)+'0';
// Make this a bit more readable.
// Make this a bit more readable.

Revision as of 00:48, 7 April 2010

 Node Assassin :: Naos v1.x


Release

  • Last update: Apr. 05, 2010
  • Tested Against: Arduino Alpha v0018 x86
  • Naos Version: 1.1.4

Notes

WARNING

USE THIS CODE AT YOUR OWN RISK!

It's the default warning for everything related to this project, but it's worth repeating here. This code come with no guarantee in any way, shape or form. This has only been tested on my board, the Arduino Duemilanove with the ATmega320 chip connected to the Wiznet W5100 ethernet shield. I can't guarantee it will work on any other hardware, but I would love to hear from you if you do try it elsewhere!

Changes To Make BEFORE Uploading

Before you upload this, be sure to set an IP address and MAC address that suits your network.

  • Change the network address to fit your network. The defaults are:
    • IP Addr: 192.168.1.66
    • Netmask: 255.255.255.0
    • Gateway: 192.168.1.1
  • In the sketch below, alter the byte mac[] value to a value unique on you network. To be honest, the default is probably ok.

Use

Copy this code into the 'Arduino alpha' loader, test it and then upload it to your board.

Thanks

The majority of this first version was made possible thanks the generous patience of Mark Loit at hacklab.to who spent the day giving me a crash course in C and helping me diagnose my first circuit. He also gets the credit for the project's tag line.

Code

#include <Ethernet.h>	// Arduino's ethernet library.
#include <ctype.h>	// Library for testing and character manipulation.
#include <stdint.h>	// Library for standard integer types (guarantees the size of an int).
#include <stdlib.h>	// Library for things like 'sizeof()' and 'itoa()'.

/*
Author:
 - Digimer
 
Version: 1.1.4.1
 - Release: Apr. 06, 2010

License:
 - The GNU GPL v2.0

Thanks:
 - Hacklab.TO:       The idea for this device was born there.
 - Christopher Olah; Came up with the name "Node Assassin".
 - Mark Loit:        Taught me enough C to write version 1.0 of NaOS!

Bugs:
 - None known at this time.

Protocol:
 - Telnet (or similar) to the IP and Port set below.
  - To query the state of the nodes, send:
    - 00:0
  - The integer after the '00:' is reserved for future queries.
 - To set the state of a node, send:
  - XX:Y
  - XX is the zero-padded node ID number; 01, 02, 03, 04 or 05
  - Y  is the state to set
    - 0 releases the fence and lets the node boot.
    - 1 fences the requested node.
    - 2 Fence for one second. Useful for rebooting a node or for when a port
        is connected to a node's power button to boot or gracefully power down
        a node (via ACPI).
    - 3 Fence for five seconds. Only useful when connected to a power button.
        This allows the Node Assassin to force a frozen server to power off.
 - Example:
   - To fence Node 01, send:
     - 01:1
   - To release the fence and thus let the node boot, send:
     - 01:2
 - Sending any other non-standard command will generate an error message and no
   action will be taken.
  
Note:
 - This device implements NO security. You MUST install in on a private, secure
   intranet or similar back channel. Installing it on the same LAN as the
   storage devices is advised. 
 - Changing this file will have no effect until the program is recompiled and
   uploaded to the Node Assassin.
   
To Do:
 - Make naming the device and setting it's network settings configurable.

Changes:
 - v1.1.4.1
   - Fixed the numbering of Nodes in '00:0' status requests.
 - v1.1.4
   - Changed the version number to correspond to the matching supported Node
     Assassin hardware version.
   - Added the concept of "nodes" which are groups of two digital outputs plus
     one analog input treated as a digital input.
   - Changed the states. Most critically, 0 now releases the fence and 1 fences
     the node. Further, "fencing" is no longer simply closing the switch. A
     fence triggers a sequence of switch open and closing to accomplish the
     fence.
   - Added support for node feed detection and added errors and warnings to
     state calls that are based on the feed values.

 - v1.0.4
   - Set all output to send a final 'EOM' (End Of Message) on a new line after
     all output for the fence agent to know when data has finished returning.
   - Prefixed all error messages with 'ERR: '.
 - v1.0.3
   - Added the '00:1' query message which returns the Node Assassin's details
     and identification.
   - Added the '##:2' and '##:3' options.
 - v1.0.2
   - First release.
*/

// MAC Address; Array of six bytes.
// If you've been assigned a MAC address by AN!, or if you have your own block
// of MAC addresses, enter it here.
// NOTE! If you do not have a MAC address, choose one that starts with h02.
//       This will mark it as a locally administered MAC address. For example,
//       use "{ 0x02, 0x00, 0x00, 0xFF, 0xF0, 0xAA }".
byte mac[] = { 0x00, 0x09, 0x30, 0xFF, 0xF0, 0x8A };

// Arduino IP, netmask and gateway.
byte ip[] = { 192, 168, 1, 66 };
// byte ip[] = { 192, 168, 111, 66 };
// byte ip[] = { 10, 255, 0, 66 };

// Netmask defaults to 255.255.255.0.
byte nm[] = { 255, 255, 255, 0 };
// byte nm[] = { 255, 255, 0, 0 };

// Default gateway defaults to IP with the last octal set to 1.
byte dg[] = { 192, 168, 1, 1 };
// byte dg[] = { 192, 168, 111, 1 };
// byte dg[] = { 10, 255, 255, 254 };

// The user-set name of the node, up to sixteen characters long.
char nodeName[16]="Motoko";

// The serial number.
// NOTE! Only set a serial number starting with 'NA####' if you were assigned
//       one by AN!. Otherwise, use 'PR####' where the digit section is your
//       internal tracking number.
char serialNumber[7]="PR0002";
char osVersion[7]="v1.1.4.1";
char buildDate[11]="2010-04-03";

// This is the port that I will listen on.
#define PORT 238

// Setup the server.
Server server = Server(PORT);

// Setup my digital out pins.
// CONSTRAINT: Input and Output pins must be ssigned sequentially
#define NODECOUNT      4
// The digital pins are output
#define FIRSTOUTPIN    2
// The analog pins are treated as digital inputs, so I count them from 14 - 19.
#define FIRSTINPIN     14

// My function prototypes.
void printError(const char *message);
void printMessage(const char *message);

// Setup the Arduino on boot.
void setup()
{
	// Setup the IP info.
	Ethernet.begin(mac, ip, dg, nm);
	
	// Print the serial port welcom message.
	Serial.begin(9600);
	Serial.print("Node Assassin: ["); Serial.print(nodeName); Serial.println("] starting.");
	
	// Iterator to setup the digital pins to output and to set them
	// initially to LOW.
	for (int pin = FIRSTOUTPIN; pin < (FIRSTOUTPIN+(NODECOUNT*2)); pin++)
	{
		Serial.print("Set - pin: ["); Serial.print(pin); Serial.println("] to OUTPUT/LOW.");
		pinMode(pin, OUTPUT);
		digitalWrite(pin, LOW);
	}
	
	// Iterator to setup the analog pins as digital inputs.
	for (int pin = FIRSTINPIN; pin < (FIRSTINPIN+NODECOUNT); pin++)
	{
		Serial.print("Set - pin: ["); Serial.print(pin); Serial.println("] to INPUT.");
		pinMode(pin, INPUT);
	}
	Serial.println("Ready!");
	
	// Start the server listening for connections.
	server.begin();
}

// And GO!
void loop()
{	
	// Variables
	uint8_t node=0;		// The node that will work on.
	uint8_t state=0;	// The state of the node.
	int node_power_pin=0;	// This will contain the actual pin mapped to
				// the requested node's power pin (digital pin).
	uint8_t np_state;	// This will contain the power state.
	int node_reset_pin=0;	// This will contain the actual pin mapped to
				// the requested node's reset pin (digital pin).
	uint8_t nr_state;	// This will contain the reset state.
	int node_feed_pin=0;	// This will contain the actual pin mapped to
				// the requested node's feed pin (analog pin).
	uint8_t nf_state;	// This will contain the feed state.
	char nodeASCII[3];	// ASCII representation of node number. This is
				// '3' because of 'first char' + 'second char' + terminating <NUL>
	char command[5];	// 5 chars "XX:Y" + <NUL>
	int  index = 0;		// Just an index to increment and reset in loops.
	char macString[6];	// MAC address.
	
	// Start the network library.
	Client client=server.available();
	if (client)
	{
		// process the input in a line-based manner, allowing for 1
		// command per line
		while ((-1 != (command[index] = client.read()) ) && (5 > index))
		{
			// exit at the end of line
			if (('\n' == command[index]) || ('\r' == command[index]) )
			{
				break; // EOL found, break out of the while loop.
			}
			index++; // advance the index.
		}
		// on a valid line the above while loop will exit with index == 4
		
		// If there is no message, nothing to do but exit. 
		// Coding note: By putting 0 first, I can never accidentally
		// set the variable to '0' with an accidental single-equal.
		if (0 == index)
		{
			return;
		}
		
		// sanity check on length
		if (4 > index)
		{
// 			printMessage("ERROR: Message too short. Format is 'XX:Y' where 'XX' is the zero-padded node number and Y is the state to set.\n");
			printMessage("ERROR: Message too short.\n");
			printMessage("EOM\n");
			return;
		}
		
		// Spool off whatever is left in the buffer/line in case it was a string longer than 4.
		if (5 == index)
		{
			char ch;
// 			printMessage("ERR: Message too long. Format is 'XX:Y' where 'XX' is the zero-padded node number and Y is the state to set.\n");
			printMessage("ERROR: Message too long.\n");
			printMessage("EOM\n");
			while (-1 != (ch = client.read()) )
			{
				// exit at the end of line
				if( ('\n' == ch) || ('\r' == ch) )
				{
					break; // break out of the while loop
				}
			}
			return;
		}
		
		// <NUL> terminate the string
		command[index] = 0;
		
		// Parse the string; Error if anything isn't right.
		// Make sure we have a colon in the right location
		if (':' != command[2])
		{
			// Error
			printError(command);
			return;
		}
		// Make sure the other characters are digits
		if (!isdigit(command[0]) || !isdigit(command[1]) || !isdigit(command[3]))
		{
			// Error
			printError(command);
			return;
		}
		
		// No need to check for the terminator or newline at the end,
		// that was taken care of in the read loop.
		// Do the math to turn the ASCII node number into a binary
		// value.
		node=command[0]-'0';	// First digit convertion (ie: '1' (0x31)-'0' (0x30) = 0x01 = "0000 0001 (dec. 1)").
		node*=10;		// Shift to the first base-10 position.
		node+=command[1]-'0';	// Now 'node' contains the binary version of the ASCII two-digit value read off of telnet.
		
		// Convert the "node" value to the actual power and reset pins.
		// Because each node consumes to digital output pins, I need to
		// double the 'node' value and then add the digital out offset.
		// This will give me the reset pin directly, and then subtract
		// by one to get the power button pin. The Feed pin is simple
		// the node's value plus the input offset.
		node_power_pin=(((node*2)-1)+(FIRSTOUTPIN-1));
		node_reset_pin=((node*2)+(FIRSTOUTPIN-1));
		node_feed_pin=(node+(FIRSTINPIN-1));
		
		// Do the math to turn the state number into a binary value.
		state=command[3]-'0';	// Now 'state' contains the binary version.
		
		// copy the ASCII node name for the response messages [so we don't have to convert it back later]
		nodeASCII[0] = command[0];
		nodeASCII[1] = command[1];
		nodeASCII[2] = 0; // <NUL> terminate it
		
		// Check the node.
		if (node > NODECOUNT)
		{
			// Node number can't be higher than NODECOUNT.
			
			// Make my NODECOUNT an ASCII value so that I can print it by reversing the convertion to binary done earlier.
			// the below 2 lines will be converted by the compiler, so there is no run-time penalty for the math here
			nodeASCII[0]=(NODECOUNT/10)+'0';	// Move from the 'tens' posiition into the '1' position and add '0' to get the ASCII value.
			nodeASCII[1]=(NODECOUNT%10)+'0';	// The modulous returns my real one position.
			// nodeASCII was <NUL> terminated earlier at 3, so no need to do it again here
			
// 			printMessage("ERROR: This fence supports up to "); printMessage(nodeASCII); printMessage("nodes.\n");
			printMessage("ERROR: Max node value: ["); printMessage(nodeASCII); printMessage("]\n");
			printMessage("EOM\n");
			return;
		}
		
		// Check that the requested state is sane.
		if (state > 3)
		{
			// Node state can't be higher than '1' on this model.
// 			printMessage("ERR: Invalid state received. Send '##:0' to release a node's fence, ##:1 to fence a node, ##:2 to press the power button for one second and ##:3 to press the power button for five seconds, forcing the node to power down.\n");
			printMessage("ERROR: Invalid state.\n");
			printMessage("EOM\n");
			return;
		}
		
		// Check is this is an info request.
		if (0 == node)
		{
			// If the message request is '0', return states.
			// If the message request is '1', return NA info.
			
			if (0 == state)
			{
				// Send states
				printMessage("Node states: \n");
				
				// Make my NODECOUNT an ASCII value so that I can print it by reversing the convertion to binary done earlier.
				// the below 2 lines will be converted by the compiler, so there is no run-time penalty for the math here
				nodeASCII[0]=(NODECOUNT/10)+'0';	// Move from the 'tens' posiition into the '1' position and add '0' to get the ASCII value.
				nodeASCII[1]=(NODECOUNT%10)+'0';	// The modulous returns my real one position.
				// nodeASCII was <NUL> terminated earlier at 3, so no need to do it again here
				
				printMessage("- Node Count: "); printMessage(nodeASCII); printMessage("\n");
				
				/*
				Future optimization:
				The division and modulus in the loop can be expensive
				processing wise, as the compiler cannot do the
				calculation at compile time. As we are simply
				itteratively looping and incrementing, we can increment
				the ASCII value directly, removing the need for any
				division or modulus operations.
				*/
				
				for (int node=1; node<=NODECOUNT; node++)
				{
					// 'i' is the current, zero-based node number.
					nodeASCII[0]=(node/10)+'0';
					// The modulous returns my real one position.
					nodeASCII[1]=(node%10)+'0';
					
					// Make this a bit more readable.
					node_power_pin=(((node*2)-1)+(FIRSTOUTPIN-1));
					node_reset_pin=((node*2)+(FIRSTOUTPIN-1));
					node_feed_pin=(node+(FIRSTINPIN-1));
					
					np_state = digitalRead(node_power_pin);
					nr_state = digitalRead(node_reset_pin);
					nf_state = digitalRead(node_feed_pin);
					printMessage("- Node "); printMessage(nodeASCII); printMessage(": ");
					printMessage("P"); printMessage((LOW == np_state) ? "0, " : "1, ");
					printMessage("R"); printMessage((LOW == nr_state) ? "0, " : "1, ");
					printMessage("F"); printMessage((LOW == nf_state) ? "0\n" : "1\n");
					Serial.print(" P.Pin: "); Serial.println(node_power_pin);
					Serial.print(" R.Pin: "); Serial.println(node_reset_pin);
					Serial.print(" F.Pin: "); Serial.println(node_feed_pin);
				}
				printMessage("EOM\n");
			}
			else if (1 == state)
			{
				/* Setup some strings. */
				// MAC address.
				printMessage("Node info: \n");
				char macASCII[18]; // Enough room for 6 bytes
						   // of hex [12 digits], colon
						   // seperators (:) and the
						   // NULL terminator
				char ipASCII[16];  // IP address with '.'s.
				char nmASCII[16];  // Now the netmask
				char dgASCII[16];  // and the default gateway.
				int j=0;
				for(int i=0; i < (sizeof(mac) / sizeof(mac[0])); i++)
				{
					// If I have a value, that is, is '1'
					// or higher, start by inserting a 
					// period (.) to seperate the octets.
					// This method avoids a preceeding '.'.
					if (i)
					{
						macASCII[j++] = ':';
					}
					// We're dealing with two characters,
					// so if this value is less than 16
					// (0000 1111), add a leading '0'.
					if (mac[i]<16)
					{
						macASCII[j] = '0';
						itoa(mac[i], &macASCII[(j)+1], 16);
					}
					else
					{
						itoa(mac[i], &macASCII[j], 16);
					}
					j+=2;
				}
				
				// IP, netmask and default gateway.
				j=0;
				for (int i=0; i < (sizeof(ip) / sizeof(ip[0])); i++)
				{
					// If I have a value, that is, is '1'
					// or higher, start by inserting a 
					// period (.) to seperate the octets.
					// This method avoids a preceeding '.'.
					if (i)
					{
						ipASCII[j++] = '.';
					}
					// Convert the integer to an ASCII.
					itoa(ip[i], &ipASCII[j++], 10);
					// Increment 'j' one or two places,
					// depending on the value of 'j'.
					if(ip[i]>9) j++;
					if(ip[i]>99) j++;
				}
				j=0;
				for (int i=0; i < (sizeof(nm) / sizeof(nm[0])); i++)
				{
					// If I have a value, that is, is '1'
					// or higher, start by inserting a 
					// period (.) to seperate the octets.
					// This method avoids a preceeding '.'.
					if (i)
					{
						nmASCII[j++] = '.';
					}
					// Convert the integer to an ASCII.
					itoa(nm[i], &nmASCII[j++], 10);
					// Increment 'j' one or two places,
					// depending on the value of 'j'.
					if(nm[i]>9) j++;
					if(nm[i]>99) j++;
				}
				j=0;
				for (int i=0; i < (sizeof(ip) / sizeof(ip[0])); i++)
				{
					// If I have a value, that is, is '1'
					// or higher, start by inserting a 
					// period (.) to seperate the octets.
					// This method avoids a preceeding '.'.
					if (i)
					{
						dgASCII[j++] = '.';
					}
					// Convert the integer to an ASCII.
					itoa(dg[i], &dgASCII[j++], 10);
					// Increment 'j' one or two places,
					// depending on the value of 'j'.
					if(dg[i]>9) j++;
					if(dg[i]>99) j++;
				}
				// Make my NODECOUNT an ASCII value so that I can print it by reversing the convertion to binary done earlier.
				// the below 2 lines will be converted by the compiler, so there is no run-time penalty for the math here
				nodeASCII[0]=(NODECOUNT/10)+'0';	// Move from the 'tens' posiition into the '1' position and add '0' to get the ASCII value.
				nodeASCII[1]=(NODECOUNT%10)+'0';	// The modulous returns my real one position.
				
				// Print the info.
				printMessage("- Node Name: ..... "); printMessage(nodeName); printMessage("\n");
				printMessage("- Port Count: .... "); printMessage(nodeASCII); printMessage("\n");
				printMessage("- NAOS Version: .. "); printMessage(osVersion); printMessage("\n");
				printMessage("- Serial Number: . "); printMessage(serialNumber); printMessage("\n");
				printMessage("- Build Date: .... "); printMessage(buildDate); printMessage("\n");
				printMessage("- MAC address: ... "); printMessage(macASCII); printMessage("\n");
				printMessage("- IP address: .... "); printMessage(ipASCII); printMessage("\n");
				printMessage("- Subnet Mask: ... "); printMessage(nmASCII); printMessage("\n");
				printMessage("- Default Gateway: "); printMessage(dgASCII); printMessage("\n");
				printMessage("EOM\n");
			}
			else
			{
				// Unrecognized message request.
// 				printMessage("ERR: Unknown message request ID. '00:0' returns node states, '00:1' returns Node Assassin information.\n");
				printMessage("ERROR: Unknown message request.\n");
				printMessage("EOM\n");
			}
			
			return;
		}
		
		// Make the node number printable.
		nodeASCII[0]=((node+1)/10)+'0';	// The '+1' makes the node 1-based instead of 0-based.
		nodeASCII[1]=((node+1)%10)+'0';	// The modulous returns my real one position.
		// Subtract 1 from node to make it zero-based.
		node--;
		// Set the pin based on whether 'state' is '0' or not.
		if (0 == state)
		{
			/* Release the fence (does nothing if not fenced). */
			printMessage("Releasing "); printMessage(nodeASCII); printMessage("\n");
			
			// Get prior states.
			np_state = digitalRead(node_power_pin);
			nr_state = digitalRead(node_reset_pin);
			// Release power.
			digitalWrite(node_power_pin, LOW);
			printMessage(" - Power "); printMessage((LOW == np_state) ? "wasn't fenced.\n" : "released.\n");
			// Release reset.
			digitalWrite(node_reset_pin, LOW);
			printMessage(" - Reset "); printMessage((LOW == nr_state) ? "wasn't fenced.\n" : "released.\n");
			// Wait one second and then check that the pin states are actually LOW.
			delay(1000);
			np_state = digitalRead(node_power_pin);
			nr_state = digitalRead(node_reset_pin);
			printMessage(" - Status: "); printMessage(((HIGH == np_state) || (HIGH == nr_state)) ? "ERROR! Fence failed to release.\n" : "SUCCESS!\n");
			printMessage("EOM\n");
		}
		else if (1 == state)
		{
			/* Fence the node. */
			printMessage("Fencing node "); printMessage(nodeASCII); printMessage(":\n");
			
			// Get prior states.
			np_state = digitalRead(node_power_pin);
			nr_state = digitalRead(node_reset_pin);
			
			// If either power or reset where HIGH already, release
			// them and wait one second. This will effectively
			// allow the fence to be "re-run".
			if (HIGH == np_state)
			{
// 				printMessage(" - Power was already fenced, releasing for one second in order to re-apply fence.");
				printMessage(" - Power was fenced, releasing.");
				digitalWrite(node_power_pin, LOW);
			}
			if (HIGH == nr_state)
			{
// 				printMessage(" - Reset was already fenced, releasing for one second in order to re-apply fence.");
				printMessage(" - Reset was fenced, releasing.");
				digitalWrite(node_reset_pin, LOW);
			}
			// If either were HIGH, sleep for one second.
			if ((HIGH == np_state) || (HIGH == nr_state))
			{
				delay(1000);
			}
			
			// Fence the reset for one second.
			digitalWrite(node_reset_pin, HIGH);
// 			printMessage(" - Reset fenced for one second.\n");
			printMessage(" - Reset fenced.\n");
			delay(1000);
			
			// Release reset.
			digitalWrite(node_reset_pin, LOW);
// 			printMessage(" - Reset released to allow power fencing in one second.\n");
			printMessage(" - Reset released.\n");
			delay(1000);
			
			// Fence power
			digitalWrite(node_power_pin, HIGH);
// 			printMessage(" - Power fenced. Will wait five seconds to ensure power-down.\n");
			printMessage(" - Power fenced.\n");
			delay(5000);
			
			// Check that the power is off and wait another 25 seconds if it isn't.
			int fence_ok=1;
			nf_state = digitalRead(node_feed_pin);
			if (HIGH == nf_state)
			{
// 				printMessage(" - WARNING! Node power still detected. Waiting an additional 25 seconds.\n");
				printMessage(" - WARNING: Node still on, waiting.\n");
				delay(25000);
				nf_state = digitalRead(node_feed_pin);
				if (HIGH == nf_state)
				{
// 					printMessage(" - ERROR! Node power still detected after 30 seconds. FENCE FAILED!\n");
					printMessage(" - ERROR! Node still on. FENCE FAILED!\n");
					fence_ok=0;
				}
			}
			
			// Proceed if fence succeeded.
			if (1 == fence_ok)
			{
// 				printMessage(" - SUCCESS! Fence succeeded.\n");
				printMessage(" - SUCCESS!\n");
				// Re-fence the reset switch.
				digitalWrite(node_reset_pin, HIGH);
// 				printMessage(" - Re-applying reset fence to lock out node's front-panel switches.\n");
				printMessage(" - Node's front-panel switches locked.\n");
			}
			printMessage("EOM\n");
		}
		else if (2 == state)
		{
			// Hit the power switch for one second. First check the
			// current state so that I can properly report what is
			// happening.
			nf_state = digitalRead(node_feed_pin);
			if (HIGH == nf_state)
			{
				// Was on, so report that we are shutting down.
// 				printMessage("Initiating ACPI power down of node "); printMessage(nodeASCII); printMessage(":\n");
				printMessage("Initiating ACPI power down\n");
			}
			else
			{
				// Was off, so report that we are booting.
// 				printMessage("Initiating boot up of node "); printMessage(command); printMessage(":\n");
				printMessage("Booting node\n");
			}
			
			// Get prior states. If either are HIGH, Error out and
			// do nothing.
			np_state = digitalRead(node_power_pin);
			nr_state = digitalRead(node_reset_pin);
			if ((HIGH == np_state) || (HIGH == nr_state))
			{
// 				printMessage(" - ERROR! Node is fenced. Release the fence and try again.\n");
				printMessage(" - ERROR! Node is already fenced.\n");
			}
			else
			{
				digitalWrite(node_power_pin, HIGH);
// 				printMessage(" - Power button closed, waiting one second.\n");
				printMessage(" - Power button closed.\n");
				delay(1000);
				digitalWrite(node_power_pin, LOW);
				printMessage(" - Power button opened.\n");
			}
			printMessage("EOM\n");
		}
		else if (3 == state)
		{
			// Hit the power switch for five seconds. Make sure the
			// power feed is low, and if not, wait another 25
			// seconds and check again.
			printMessage("Forcing power down of node "); printMessage(nodeASCII); printMessage(":\n");
			
			// If the node is off, then there is nothing to do.
			nf_state = digitalRead(node_feed_pin);
			if (LOW == nf_state)
			{
				// Node is off, so no need to proceed.
// 				printMessage(" - WARNING! Node is already off, no need to proceed.\n");
				printMessage(" - WARNING! Node is already off.\n");
			}
			else
			{
				// Fence power
				digitalWrite(node_power_pin, HIGH);
// 				printMessage(" - Power switch pressed. Will wait five seconds to ensure power-down.\n");
				printMessage(" - Forcing node off.\n");
				delay(5000);
				
				// Check that the power is off and wait another 25 seconds if it isn't.
				int fence_ok=1;
				nf_state = digitalRead(node_feed_pin);
				if (HIGH == nf_state)
				{
// 					printMessage(" - WARNING! Node power still detected. Waiting an additional 25 seconds.\n");
					printMessage(" - WARNING! Node still on, waiting.\n");
					delay(25000);
					nf_state = digitalRead(node_feed_pin);
					if (HIGH == nf_state)
					{
// 						printMessage(" - ERROR! Node power still detected after 30 seconds. FORCED SHUTDOWN FAILED!\n");
						printMessage(" - ERROR! Node still on. FORCED SHUTDOWN FAILED!\n");
						fence_ok=0;
					}
				}
				
				// Proceed if fence succeeded.
				if (1 == fence_ok)
				{
// 					printMessage(" - SUCCESS! Node is now off.\n");
					printMessage(" - SUCCESS!\n");
					digitalWrite(node_power_pin, LOW);
// 					printMessage(" - Power fence released, Node ready for normal operation.\n");
					printMessage(" - Fence released.\n");
				}
			}
			printMessage("EOM\n"); 
		}
	}
}

// The error handling function.
void printError(const char *message)
{
	// Print the message to the serial bus and the client.
	// I know this is dirty but it represents the one line string.
	printMessage("ERR: Bad command: [" ); printMessage(message); printMessage("]\n" );
	printMessage("EOM\n");
}

void printMessage(const char *message)
{
	// Print the message to the serial bus and the client.
	Serial.print(message);
	server.write(message);
}

 

Input, advice, complaints and meanderings all welcome!
Digimer digimer@alteeve.ca https://alteeve.ca/w legal stuff:  
All info is provided "As-Is". Do not use anything here unless you are willing and able to take resposibility for your own actions. © 1997-2013
Naming credits go to Christopher Olah!
In memory of Kettle, Tonia, Josh, Leah and Harvey. In special memory of Hannah, Jack and Riley.