Naos v1.x: Difference between revisions

From Alteeve Wiki
Jump to navigation Jump to search
m (moved Naos v1.0 to Naos v1.x: Make it minor-version agnostic)
Line 37: Line 37:


<source lang="c">
<source lang="c">
#include <Ethernet.h> // Arduino's enthernet library.
#include <Ethernet.h> // Arduino's ethernet library.
#include <ctype.h> // Library for testing and character manipulation.
#include <ctype.h> // Library for testing and character manipulation.
#include <stdint.h> // Library for standard integer types (guarantees the size of an int).
#include <stdint.h> // Library for standard integer types (guarantees the size of an int).
Line 44: Line 44:
Author:
Author:
  - Digimer
  - Digimer
Version: 1.0.1
Version: 1.0.1
  - Release: 2010-01-23
  - Release: 2010-01-23
License:
- The GNU GPL v2.0
Thanks:
Thanks:
  - Hacklab.TO
  - Hacklab.TO:      The idea for this device was born there.
  - Christopher Olah; Name "Node Assassin"
  - Christopher Olah; Came up with the name "Node Assassin".
  - Mark Loit:        Taught me enough C to write ver. 1.0 of NaOS!
  - Mark Loit:        Taught me enough C to write version 1.0 of NaOS!
 
Bugs:
Bugs:
  - None known at this time.
  - 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 fences the requested node.
    - 1 releases the fence and lets the node boot.
- Example:
  - To fence Node 01, send:
    - 01:0
  - To release the fence and thus let the node boot, send:
    - 01:1
- 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.
*/
*/
// 00:X is a request for info where 'X' is the requested data. Currently '0' is only data type and returns the current state of all nodes.
// [01-99]:[0-1] Sends a command to set the state of node [01-99] to on [:1] or off [:0].


// MAC Address; Array of six bytes.
// MAC Address; Array of six bytes.
Line 67: Line 96:


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


// Setup the server.
// Setup the server.
Server server = Server(port);
Server server = Server(PORT);
 
// Setup my digital out pins.
// CONSTRAINT: Output pins must be ssigned sequentially
#define NODECOUNT    5
#define FIRSTNODEPIN 2


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


// Setup my digital out pins.
// Setup the Arduino on boot.
#define maxnode 5
#define firstnode 2
 
// Setup
void setup()
void setup()
{
{
Line 85: Line 116:
Ethernet.begin(mac, ip, dg, nm);
Ethernet.begin(mac, ip, dg, nm);
// Print what comes in over telnet.
// Print the serial port welcom message.
Serial.begin(9600);
Serial.begin(9600);
Serial.println("Node Assassin: 'Ariel' now listening for orders.");
Serial.println("Node Assassin: 'Ariel' now listening for orders.");
// Iterator
// Iterator to setup the digital pins to output and to set them
for (int i=firstnode; i<(firstnode+maxnode); i++)
// initially to LOW.
for (int pin = FIRSTNODEPIN; pin < (FIRSTNODEPIN+NODECOUNT); pin++)
{
{
// Set my pin 'i' to be output.
pinMode(pin, OUTPUT);
pinMode(i, OUTPUT);
digitalWrite(pin, LOW);
// and set it initially to be low.
digitalWrite(i, LOW);
}
}
Line 104: Line 134:
// And GO!
// And GO!
void loop()
void loop()
{
{
// Variables
// Variables
uint8_t node=0; // The device I am working on.
uint8_t node=0; // The node I will work on.
uint8_t state=0; // The (new?) state of the pin.
uint8_t state=0; // The (new?) state of the node.
char nodeASCII[3]; // ASCII representation of node number. This is
// '3' because of 'first char' + 'second char' + terminating <NUL>
char command[5]; // 4 chars "XX:Y" + <NUL>
int  index = 0; // Just an index to increment and reset in loops.
// Start the network library.
// Start the network library.
Line 113: Line 147:
if (client)
if (client)
{
{
char reading[7]; // 4 chars (XX:Y) + '\n' or '\r\n' or <NUL>' + double-increment below
// process the input in a line-based manner, allowing for 1 command per line
int i=0;
while ((-1 != (command[index] = client.read()) ) && (5 > index))
while (((reading[i]=client.read())!=-1)&&(i<5))
{
{
i++;
// 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.
}
}
// Spool off whatever is left in the buffer in case it was a string longer than 5.
// on a valid line the above while loop will exit with index == 4
if (client.available())
// 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)
{
{
Serial.println("Message too long. Format is 'XX:Y' where 'XX' is the xero-padded node number.");
server.write("Message too long. Format is 'XX:Y' where 'XX' is the xero-padded node number.\n");
while (-1 != client.read());
return;
return;
}
}
// Double increment to make sure my test works even when I don't get an '\n' or '\r\n'.
reading[i++]=0;
reading[i]=0;
// Parse the string.
// sanity check on length
// Format: XX:Y where XX is the node to work on and Y is the new state
if (4 > index)
// Make sure the string is formatted properly and print an error if not.
if (reading[2] != ':')
{
{
// Error
printMessage("Message too short. Format is 'XX:Y' where 'XX' is the zero-padded node number and Y is the state to set.\n");
printError(reading);
return;
return;
}
}
if (!isdigit(reading[0]) || !isdigit(reading[1]) || !isdigit(reading[3]))
// Spool off whatever is left in the buffer/line in case it was a string longer than 4.
if (5 == index)
{
{
// Error
char ch;
printError(reading);
printMessage("Message too long. Format is 'XX:Y' where 'XX' is the zero-padded node number and Y is the state to set.\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;
return;
}
}
// Next two check the termination
if ((reading[4] != 0) && (reading[4] != '\n') && (reading[4] != '\r'))
// <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])
{
{
// Extraneous terminated string.
// Error
printError(reading);
printError(command);
return;
return;
}
}
if ((reading[5] != 0) && (reading[5] != '\n') && (reading[5] != '\r'))
// Make sure the other characters are digits
if (!isdigit(command[0]) || !isdigit(command[1]) || !isdigit(command[3]))
{
{
// Extraneous terminated string.
// Error
printError(reading);
printError(command);
return;
return;
}
}
// Do the math to turn the node number into a binary value.
// No need to check for the terminator or newline at the end,
node=reading[0]-'0'; // First digit convertion (ie: '1' (0x31)-'0' (0x30) = 0x01 = "0000 0001 (dec. 1)").
// 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*=10; // Shift to the first base-10 position.
node+=reading[1]-'0'; // Now 'node' contains the binary version of the ASCII two-digit value read off of telnet.
node+=command[1]-'0'; // Now 'node' contains the binary version of the ASCII two-digit value read off of telnet.
// Do the math to turn the state number into a binary value.
// Do the math to turn the state number into a binary value.
state=reading[3]-'0'; // Now 'state' contains the binary version.
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.
// Check the node.
if (node > 5)
if (node > NODECOUNT)
{
{
// Node number can't be higher than '05' on this model.
// Node number can't be higher than NODECOUNT on this model.
Serial.println("This fence only supports up to '05' nodes.");
server.write("This fence only supports up to '05' nodes.\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("This fence only supports up to "); printMessage(nodeASCII); printMessage("nodes.\n");
return;
return;
}
}
// Check that the requested state is sane.
if (state > 1)
if (state > 1)
{
{
// Node number can't be higher than '05' on this model.
// Node number can't be higher than '1' on this model.
Serial.println("Invalid state received. Send 'XX:0' to kill a node, XX:1 to release a node");
printMessage("Invalid state received. Send 'XX:0' to kill a node, XX:1 to release a node\n");
server.write("Invalid state received. Send 'XX:0' to kill a node, XX:1 to release a node\n");
return;
return;
}
}
// Check is this is an info request.
// Check is this is an info request.
// By putting 0 first, I can never accidentally set 'node' to '0' with an accidental single-equal.
if (0 == node)
if (0 == node)
{
{
// Send states
// Send states
Serial.println("Node states: ");
printMessage("Node states: \n");
server.write("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("- Max Node: "); printMessage(nodeASCII); printMessage("\n");
// Make my maxnode an ASCII value so that I can print it by reversing the convertion to binary done earlier.
/*
char saynode[3]; // This is '3' because of 'first char' + 'second char' + terminating '0'.
Future optimization:
saynode[0]=(maxnode/10)+'0'; // Move from the 'tens' posiition into the '1' position and add '0' to get the ASCII value.
The division and modulus in the loop can be expensive
saynode[1]=(maxnode%10)+'0'; // The modulous returns my real one position.
processing wise, as the compiler cannot do the
saynode[2]=0; // Null terminated.
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.
*/
Serial.print("- Max Node: "); Serial.println(saynode);
for (int i=0; i<NODECOUNT; i++)
server.write("- Max Node: "); server.write(saynode); server.write("\n");
for (int i=0; i<maxnode; i++)
{
{
// 'i' is the current, printable node number.
// 'i' is the current, zero-based node number.
saynode[0]=((i+1)/10)+'0'; // The '+1' makes the node 1-based instead of 0-based.
nodeASCII[0]=((i+1)/10)+'0'; // The '+1' makes the node 1-based instead of 0-based.
saynode[1]=((i+1)%10)+'0'; // The modulous returns my real one position.
nodeASCII[1]=((i+1)%10)+'0'; // The modulous returns my real one position.
char saystate = digitalRead(i+firstnode); // i + pin offset.
state = digitalRead(i+FIRSTNODEPIN); // i + pin offset.
Serial.print("- Node "); Serial.print(saynode); Serial.print(": "); Serial.println((LOW == saystate) ? "Running" : "Fenced!" );
printMessage("- Node "); printMessage(nodeASCII); printMessage((LOW == state) ? ": Running\n" : ": Fenced!\n");
server.write("- Node "); server.write(saynode); server.write(": "); server.write((LOW == saystate) ? "Running\n" : "Fenced!\n");
}
}
Serial.println("End Message.");
printMessage("End Message.\n");
server.write("End Message.\n");
return;
return;
Line 219: Line 289:
node--;
node--;
// Set the pin based on whether 'state' is '0' or not.
// Set the pin based on whether 'state' is '0' or not.
digitalWrite(node+firstnode, (0 == state) ? HIGH : LOW);
digitalWrite(node+FIRSTNODEPIN, (0 == state) ? HIGH : LOW);
Serial.println((1 == state) ? "Now running." : "Now Fenced!");
printMessage("Node "); printMessage(command); printMessage((1 == state) ? ": Now running.\n" : ": Now Fenced!\n");
server.write((1 == state) ? "Now running.\n" : "Now Fenced!\n");
}
}
}
}


void printError(char *reading)
// The error handling function.
void printError(const char *message)
{
{
// Copy 'reading' so that we don't lose our pointer to it in memory.
char *source = reading;
// Loop through the string replacing '\n' or '\r' with a <NUL>.
while (*source)
{
// If this character is a newline, replace it.
if (('\n' == *source) || ('\r' == *source))
{
// Found it. Set it to 0 which will kill the newline.
*source=0;
// Exit out of the closest encompasing while or for loop.
break;
}
// Increment the source pointer for the next go-round.
source++;
}
// Print the message to the serial bus and the client.
// Print the message to the serial bus and the client.
// I know this is dirty but it represents the one line string.
// I know this is dirty but it represents the one line string.
Serial.print("Bad command: [" ); Serial.print(reading); Serial.println("]" );
printMessage("Bad command: [" ); printMessage(message); printMessage("]\n" );
server.write("Bad command: [" ); server.write(reading); server.write("]\n" );
}
 
void printMessage(const char *message)
{
// Print the message to the serial bus and the client.
Serial.print(message);
server.write(message);
}
}
</source>
</source>


{{na_footer}}
{{na_footer}}

Revision as of 23:17, 24 January 2010

 Node Assassin :: Naos v1.x


Release

  • Last update: Jan. 23, 2010
  • Tested Against: Arduino Alpha v0017
  • Naos Version: 1.0

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.

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).

/*
Author:
 - Digimer
 
Version: 1.0.1
 - Release: 2010-01-23

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 fences the requested node.
    - 1 releases the fence and lets the node boot.
 - Example:
   - To fence Node 01, send:
     - 01:0
   - To release the fence and thus let the node boot, send:
     - 01:1
 - 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.
*/

// MAC Address; Array of six bytes.
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEF };
// Arduino IP, netmask and gateway.
byte ip[] = { 192, 168, 1, 66 };
// Netmask defaults to 255.255.255.0.
byte nm[] = { 255, 255, 255, 0 };
// Default gateway defaults to IP with the last octal set to 1.
byte dg[] = { 192, 168, 1, 1 };

// 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: Output pins must be ssigned sequentially
#define NODECOUNT    5
#define FIRSTNODEPIN 2

// 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.println("Node Assassin: 'Ariel' now listening for orders.");
	
	// Iterator to setup the digital pins to output and to set them
	// initially to LOW.
	for (int pin = FIRSTNODEPIN; pin < (FIRSTNODEPIN+NODECOUNT); pin++)
	{
		pinMode(pin, OUTPUT);
		digitalWrite(pin, LOW);
	}
	
	// Start the server listening for connections.
	server.begin();
}

// And GO!
void loop()
{	
	// Variables
	uint8_t node=0;		// The node I will work on.
	uint8_t state=0;	// The (new?) state of the node.
	char nodeASCII[3];	// ASCII representation of node number. This is
				// '3' because of 'first char' + 'second char' + terminating <NUL>
	char command[5];	// 4 chars "XX:Y" + <NUL>
	int  index = 0;		// Just an index to increment and reset in loops.
	
	// 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("Message too short. Format is 'XX:Y' where 'XX' is the zero-padded node number and Y is the state to set.\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("Message too long. Format is 'XX:Y' where 'XX' is the zero-padded node number and Y is the state to set.\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.
		
		// 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 on this model.
			
			// 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("This fence only supports up to "); printMessage(nodeASCII); printMessage("nodes.\n");
			return;
		}
		
		// Check that the requested state is sane.
		if (state > 1)
		{
			// Node number can't be higher than '1' on this model.
			printMessage("Invalid state received. Send 'XX:0' to kill a node, XX:1 to release a node\n");
			return;
		}
		
		// Check is this is an info request.
		if (0 == node)
		{
			// 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("- Max Node: "); 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 i=0; i<NODECOUNT; i++)
			{
				// 'i' is the current, zero-based node number.
				nodeASCII[0]=((i+1)/10)+'0';	// The '+1' makes the node 1-based instead of 0-based.
				nodeASCII[1]=((i+1)%10)+'0';	// The modulous returns my real one position.
				
				state = digitalRead(i+FIRSTNODEPIN);	// i + pin offset.
				printMessage("- Node "); printMessage(nodeASCII); printMessage((LOW == state) ? ": Running\n" : ": Fenced!\n");
			}
			printMessage("End Message.\n");
			
			return;
		}
		
		// Subtract 1 from node to make it zero-based.
		node--;
		// Set the pin based on whether 'state' is '0' or not.
		digitalWrite(node+FIRSTNODEPIN, (0 == state) ? HIGH : LOW);
		printMessage("Node "); printMessage(command); printMessage((1 == state) ? ": Now running.\n" : ": Now Fenced!\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("Bad command: [" ); printMessage(message); printMessage("]\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.