Wake-on-motion sensor for PC

Jun 10, 2019

TL;DR

  • You’ll need:
    • an Arduino Pro Micro or equivalent
    • three resistors: 2x150-200R and 1x10k
    • a PIR sensor
    • two optocouplers (I used PC817)
    • interface to your PC’s power board (I completely replaced it, so 1xFFC 6PINx1mm and a connector)
  • Here’s the schematic: schematic
  • The code to program the Arduino is at the bottom of this page or on github.

Background

I have an old HP Pavilion dv5 laptop that I’m using as a scanner for my software defined radio (SDR) setup. It’s pretty old (Core2Duo), but I’ve upgraded it to reasonable performance by giving it 8Gb RAM + 500Gb SSD, so it does the job.

It’s in pretty bad shape, and pretty much all the external components, including the power button board (see below), have broken or gone missing. For a very long time, I was turning it on by shorting two pins on the motherboard.

So I bought a 6-pin FFC (the flat black cable connecting the button above to the motherboard) as well as a connector board (like below) with the idea of giving it a proper power button. But then I thought, why not upgrade it with a motion sensor, so that it:

  • turns the screen on in power saving mode; and
  • wakes the computer up when in sleep mode.

FFC

Turning on the screen

So this is relatively simple, and you don’t need a complicated interface to your PC. What you do is you import a library to your Arduino that will make your PC believe the duino is a keyboard, and then you send a dead keystroke whenever the motion sensor detects movement.

There are ready-to-use sensor boards that can interface directly with the Arduino.

The following link details how to do it by sending a Ctrl keystroke. But there is a better option. Ctrl might still do something if you are running specific software, so we’ll send a key that doesn’t exist: F13. Support for F13 and advanced function keys is present in almost all OSes, but most keyboards don’t have them.

FYI here are the F13-F24 codes (you shouldn’ need to define them if using the most recent versions of Keyboard.h)

#define KEYCODE_F13 0x68
#define KEYCODE_F14 0x69
#define KEYCODE_F15 0x6A
#define KEYCODE_F16 0x6B
#define KEYCODE_F17 0x6C
#define KEYCODE_F18 0x6D
#define KEYCODE_F19 0x6E
#define KEYCODE_F20 0x6F
#define KEYCODE_F21 0x70
#define KEYCODE_F22 0x71
#define KEYCODE_F23 0x72
#define KEYCODE_F24 0x73

Waking from sleep

So this is trickier, because on my computer, I couldn’t add the Arduino to the list of peripherals allowed to wake the computer from sleep. But, since I had access to the power button board, I could use the info from there to detect the wake state of my computer.

Voltage between pins 5 and 6 was oscillating between 0 and 3.3V every second in sleep mode, remaining at 0V when fully awake and +3.3V when off.

So, if we “listen” to port 5 long enough, we can catch a rising edge to +3.3V. Furthermore, if we wait long enough and don’t see a rising edge, it must mean we are awake.

If we look at the circuit again:

schematic

  • To close the circuit using the Arduino, we use an optocoupler (IC2) plugged into D8. An optocoupler is basically a phototransistor glued to a LED, so when you put some current into the LED, it lights up and closes the phototransistor, allowing current to pass through while isolating the two parts of the circuit. You want to protect your LED from frying under high current, so we add a protection resistor R2.
  • To measure voltage from the power button board, we also use an optocoupler (IC1) plugged into D9, except this time it’s the power button board that powers the LED in the coupler through R3. Because the pins of the Arduino are floating (they don’t have a 0V reference voltage), we add a pull-down resistor R4 to the mass. This ensures that D9=0V at rest. When the LED pin is up, IC1 closes the circuit and 5V flows from RAW to D9, setting D9 to high, with minimal current flowing through R4.

Then, we just have to write a rudimentary state machine to wait for a rising edge, upload it to the Arduino and that’s it (note that the pins in the code below are not the same as on the schematic - consider it an exercise):

#include <Keyboard.h>

#define KEY_F13  0xF0 // 0x68 + 0x88

int ledPin = 17;     // this is the virtual port of a LED on the Arduino board, not a real pin
int powerInPin = 10; // connect IC1 to this pin   
int sensorInPin = 9; // connect the PIR sensor to this pin
int optoPin = 8;     // connect IC2 to this pin
int val = 0;      
int sens = 0;
int ACT=0;           // stores the state of the machine
int MSR=2;           // stores measured LED pin state
int CYW=1;           // counter
int CYLIMIT=100;     // max wait

void setup() {
  pinMode(ledPin, OUTPUT);  
  pinMode(powerInPin, INPUT);    
  pinMode(sensorInPin, INPUT);
  pinMode(optoPin, OUTPUT);
  Serial.begin(9600);
  ACT=0;
  MSR=2;
  CYW=0;
  CYLIMIT=200;
}

void loop() {
  sens = digitalRead(sensorInPin);
  val = digitalRead(powerInPin);   // read the input pin

  // just for show: output power to ledPin and sensor data to TXLED
  digitalWrite(ledPin, val);
  //Serial.println("Power status");
  //Serial.println(val);
  
  if(sens == HIGH){
    TXLED1; 
    //Serial.println("Sensor in high state");
    //Serial.println(ACT);
  }else{
    TXLED0;
    //Serial.println("Sensor in low state");
    //Serial.println(ACT);
  }

  if ( sens == HIGH && ACT == 0) {
    USBDevice.wakeupHost();
    Keyboard.press( KEY_F13 );
    Keyboard.release( KEY_F13 );
    Serial.println("Detected rising edge");
    ACT=1;
    MSR = val;
  }

  if(ACT == 1  && CYW < CYLIMIT){
    if(MSR != val){ // we have a change in the power line: we are in sleep mode
      digitalWrite(optoPin, HIGH);
      delay(600);
      digitalWrite(optoPin, LOW);
      Serial.println("Pray you never see this: we are in sleep mode");
      ACT=0;
      CYW=1;
      MSR=2;
    }  else { 
      Serial.println("Increasing CYW:");
      CYW=CYW+1;
      Serial.println(CYW);
    }
  }

  if(ACT == 1 && CYW >= CYLIMIT){ // we have overwaited,the computer must be ON. Do nothing, just reset.
    Serial.println("The computer is ON. Resetting.");
    ACT=0;
    CYW=1;
    MSR=2;
  }
  

  delay(5);
}