Summary
Remotely controlling a PC to turn it on and off from software control on another PC. This is useful in driver and other development testing.
Introduction
I'm working heavily on C# Open Source Operating System (Cosmos). While VMware is great, its even better to test on real hardware. To do this I wanted to make the deployment automatic, but this required some custom work. Booting could be done using PXE, and debugging through a null modem serial cable. But who wants to manually turn on and off the PC for every build?
To solve this problem I needed to find a way to turn the slave PC on and off using software on my main PC. A few alternatives were first considered.
WOL (Wake On LAN)
A magic packet can be sent across the network containing the MAC address of the PC to turn it on. However WOL has no facility to turn off or reset.
USB Power Controllers
Amazon has a bunch of USB and Ethernet based power switches that turn on and off a power socket. PCs can be programmed to restore after a power loss. However these tended to be very expensive, and the lower end ones had horrible comments about quality. These would also not provide perfect control for what I needed.
Custom Solution
I found a blog that discussed several attempts by an end user to do exactly what I was doing by using the lines of a serial port. Its actually more complicated than one would think because isolation is required using at minimum opto couplers. The schematic required some very specific parts however that were hard to come by.
Relay Controllers
I found several ready to go relay controllers for purchase. I evaluated many of them and finally settled on the CanaKit UK1104. It costs $59.95 plus shipping. It supports 4 relays, and also has input sensors which I can use to detect the PC state. This is actually important, because toggling the power switch is used to both turn on and off the PC, so I needed to know if I was about to turn it on, or off.
To use the relays, you also need a +9V or +12V power supply. I have hooked many 12V applications to the computer PSU, but since the device is actually used to turn the PC on, and there are no always on +12V lines on an ATX computer PSU, the additional power supply is needed.
Choosing a Motherboard
Everything else in the PC is standard and many are used parts I got from a computer repair facility. In my case I needed a few specific items on the motherboard:
- On board Ethernet with PXE boot. Most boards have this.
- Serial port, or serial header.
- Inexpensive.
- On board video
After some evaluation I settled on the Intel D425KT. Its a Mini-ITX board with on board serial, and a second serial available via headers. It has an on board Atom CPU and costs about $50 wholesale, or $60-$75 retail.
Building the PC
First, I built the PC and made sure it worked normally. Next step was to add the relay board and make some custom changes to the wiring. Assembled the PC looks like this.
Now lets examine the front panel header of the motherboard. Every motherboard's pin locations are different so its important to look in your manual for proper connections. Wrong connections can short out your board.
The connections we are interested in are in green and red. To make the connections easy, find an old case and get the power light and power switch. They will have connectors that can be used to ensure shorts do not occur. Sometimes they wont match exactly but they can be separated and leads can be extracted with a small knife and rearranged.
(Note, this is not the D425KT in this photo)
First we need to connect up the power switch to 6 and 8. Cut the power cable and wire it to COM and NO on one of the relays. I used relay 4. COM is ground, and NO is shorted to COM when the relay is on. NC is shorted to COM when the relay is off, but we don't need that in our application. Since it is a switch, there is no polarity and it doesn't matter which one pins 6 and 8 are connected to, so long as one is connected to COM and one to NO. The relays are screw down terminals, so no soldering is required, just a small screwdriver. I also spliced back the original physical switch and connected it directly to the relay to form the splice so I can still manually operate the PC.
Next we need to connect the Power LED. This is a +5 line that is powered when the PC is on. Its used to connect the power light, but since its +5V we can use it to sense if the PC is on or off. To do this cut the power line and attach the +5 line to channel 1 on the controller card. Only the +5 line is used, the ground remains only to the motherboard. On the line, the coloured one is used for positive and the plastic connector is usually marked as well.
If one is not coloured and the other white, they may be red and black instead. Red is always positive, and black is always ground. Less common in these wires is the stripe system because they are smaller.
If they are both white, one will have a stripe. The one with the stripe is positive.
I also spliced the light back in this as well so I have a visible indicator as well. To attach to the board which has pins for sensors, I cut off the plastic connector from the hard drive light and used it. Not all the channels are equal and some use different voltage levels. For our use probably any would work (TTL or Schottky), but Schottky is a bit better for our use so I chose channel 1.
So after we are done it should look something like this.
Or the real life version.
Finally since the PC will be booted over the network and use the serial for debugging, there are important connections on the back as well.
Software
Now we are ready to go! The relay controller installs as a USB to serial, which means it can be addressed with a terminal program or very easily with any programming language by using a serial port.
The relay controller operates on series of text commands listed in the manual. To test it we can now open a terminal program and issue some commands. This is TeraTerm, a free terminal program.
First we check the state of Channel 1. Its 0, this means the computer is off.
Then we have to toggle the switch since power switches are push and release. That is we simulate it being pushed and released by turning on the relay and turning it off. In software make sure you delay about 500ms between on and off for the motherboard to recognize the press. Remember, we are simulating a human finger.
Finally we check channel 1 again, and we can see the computer is now on.
One of the other relays could be hooked to the reset pins, but its not needed. To reset the PC, simply turn it off, then back on. To turn it off is the same as it is to turn it on, just "push" the button.
Video
I have also posted a video of this system in action.
Source
The source code used in this video can be extracted easily from the Cosmos project source. Here is the relevant code as used in Cosmos (C# Open Source Operating System).
<pre wrap="true">using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Threading;
using System.IO.Ports;
using Cosmos.Build.Common;
namespace Cosmos.Debug.VSDebugEngine.Host {
public class Slave : Base {
string mPortName;
SerialPort mPort;
Thread mPowerStateThread;
public Slave(NameValueCollection aParams, bool aUseGDB)
: base(aParams, aUseGDB) {
var xPort = mParams[BuildProperties.SlavePortString];
if (xPort == "None") {
throw new Exception("No slave port is set.");
}
var xParts = xPort.Split(' ');
mPortName = xParts[1];
}
string WaitForPrompt() {
var xSB = new StringBuilder();
char xLastChar = ' ';
char xChar = ' ';
while (true) {
xLastChar = xChar;
xChar = (char)mPort.ReadChar();
xSB.Append(xChar);
if (xChar == ':' && xLastChar == ':') {
break;
}
}
xSB.Length = xSB.Length - 2;
return xSB.ToString();
}
void TogglePowerSwitch() {
Send("REL4.ON");
Thread.Sleep(500);
Send("REL4.OFF");
}
bool IsOn() {
var xResult = Send("CH1.GET").Split('\n');
return xResult[1][0] == '1';
}
string Send(string aData) {
mPort.Write(aData + "\r\n");
return WaitForPrompt();
}
void WaitPowerState(bool aOn) {
int xCount = 0;
while (IsOn() == !aOn) {
Thread.Sleep(250);
xCount++;
if (xCount == 20) {
throw new Exception("Slave did not respond to power command.");
}
}
}
public override void Start() {
mPort = new SerialPort(mPortName);
mPort.Open();
Send("");
Send("CH1.SETMODE(2)");
if (IsOn()) {
TogglePowerSwitch();
WaitPowerState(false);
Thread.Sleep(1000);
}
TogglePowerSwitch();
WaitPowerState(true);
if (OnShutDown != null) {
mPowerStateThread = new Thread(delegate() {
while (true) {
Thread.Sleep(1000);
if (!IsOn()) {
mPort.Close();
OnShutDown(this, EventArgs.Empty);
break;
}
}
});
mPowerStateThread.Start();
}
}
public override void Stop() {
if (mPowerStateThread != null) {
mPowerStateThread.Abort();
mPowerStateThread.Join();
}
if (IsOn()) {
TogglePowerSwitch();
WaitPowerState(false);
}
mPort.Close();
}
}
}