Luteijn/Relay

From RevSpace
Jump to navigation Jump to search

Project "Microcontrolled relay board":

Project Luteijn/Relay
Status In progress
Contact Luteijn
Last Update 2018-01-25

My garage-door-opener can be controlled by pulling a particular DC 20V pin on the controller down. This is meant for a (key) switch to control operate the door or turn on the LED lamp. I've wired the pin through two relays. One pulls the pin low enough (using a 100 Ohm resistor to form a voltage divider with a pull-up inside the controller) to activate the lamp, the other shorts it to ground, which will activate the door actuator. The control is rather crude, basically a pulse will stop a moving door, or starts a non-moving door. Direction is reversed from whatever was the last direction., although this is not 100% guaranteed it seems. Normally not an issue as you'd see the direction of the door and can just operate the switch to compensate. Of course, the idea is that the relays can be operated from anywhere, to let people in when unable to open a door by hand. So it's useful to be able to 'see' if the door is open or closed. This is sensed via a reed relay and a magnet stuck to the door. if the door opens, the magnet travels away from the reed relay, changing the voltage over it, which is measurable by the relay-controller.

Typical Relay Board schematic: http://howtomechatronics.com/wp-content/uploads/2015/09/Relay-Module-Circuit-Schematics.png

I don't provide Vcc to the optocoupler, but use an 'enable' pin. Also, the idea is to not have a common ground. The relay board and a separate powersupply is of course way more expensive than the bare ATT45 I ended up using instead of an arduino, so if the relay borks and puts 30 VDC (or mains, if you're switching that) on the low voltage side, then it wouldn't matter too much that the Tiny is also fried. Still, especially during prototyping I'd rather keep things optically isolated. Now that it works, and I know the relay doesn't overload the Vcc, I've cheated and use one supply for both relay board and the Tiny.

The relay-controller runs on an ATT45, which talks over (software) serial to a host computer (GLInet router in my case) and from there to the rest of the world. Access control is handled on the host computer, for flexibility and to save space on the ATT45. All output is also rather terse to save space on the ATT45. For the same reason, the code has its static strings broken up into smaller units and I use #defines instead of 'const' variables.

As I was short on pins, I'm using the active low reset pin as an analogue input, making sure not to drop it too low to reset the Tiny. I found I needed a rather big Capacitor to decouple the reset line. Otherwise when one of the relays switched on, the Tiny would reset due to what I suspect was (E)MI. It might have also helped not to run the switched 30V right next to the long leads connecting the reed relay to the reset pin...

The reed contact that senses door state is normally kept closed by a small magnet. It connects the reset pin to ground via a pulldown resistor, there is also a pullup resistor, forming a voltage divider. Dimensioned so that Voltage goes from Vcc (5v from USB-serial dongle) when the reed switch (and the door) is open so just the pull-up is connected to about 4.2 volt when it is closed. Unfortunately it is a bit tricky to use a bias magnet to get the switch to be (reliably) open when the door is closed (and closed when it is not). So there's always some current flowing here, but as the total R the Vcc is over is about 50kΩ, we're talking about 100μA, which I think we can afford, even if the thing would be battery powered, as the tiny is using 10 (50) times that running at 1 (8) MHz. Probably could use even higher value resistors if needed to drop the sense current further.


/* relay-controller-tiny.ino */

#include <SoftwareSerial.h> // There is no uart on the ATTiny
 
#define versionstring "TRC1" // Tiny Relay Controller 1.

#define ledPin (0)    // pull low to close the relay controlling the LED lamp
#define doorPin (1)   // pull low to close the relay controlling the door
#define enablePin (2) // High to enable, Low to disable the optocouplers
#define RX (3)  // serial link to host
#define TX (4)  // serial link to host
#define sensorPin (A0) // on reset!
#define rate (19200) // 9600 might be better. 

#define crnl "\r\n" // help compiler optimize

bool ansi=false; // there's a bit of color use possible
bool enabled=false; // keeps track of the enable pin being high or low

unsigned char cutoff=244; //cutoff value between open and closed sensor, can be tweaked during runtime
bool sensor=false;  // door open or closed
bool prevsensor=false  // used to detect state changes.

signed char timeout=100;  // watchdog timeout value. note signed char so 127 is max
signed char timer=timeout; // current timer value, if it goes under 0, 'disable' optocouplers.


SoftwareSerial Serial(RX,TX); // control connection to host.

void setup() {  
  pinMode(enablePin,OUTPUT);
  digitalWrite(enablePin, LOW); // make sure optocouplers are/stay disabled asap.
  pinMode(doorPin,OUTPUT);
  digitalWrite(doorPin, HIGH); // active low, so enable current can be sunk, to light the led inside opto
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, HIGH); // active low, so enable current can be sunk, to light the led inside opto
  pinMode(sensorPin, INPUT); //will be using this as an analogue input so it can share the reset pin

  enabled=false; // initially we will not be in enable mode
  Serial.begin(rate); //start serial link
  delay(10);
  myversion(); // hello world!
  sensor=(analogRead(sensorPin)/4); // initialize door sensor value
  prompt(); // ready for input
}

void prompt() {
  normal(); // color off
  line(); // new line
  sensor?bred():bblue(); // color bold red when door open, blue when closed
  Serial.write(sensor?"O":"C"); // show O for open, C for closed
  enabled?byellow():bgreen(); // bold yellow when enabled, green when not
  Serial.write(enabled?"# ":"> "); // cisco style prompt to show enabled/disabled
  normal(); // normal color
}

void myversion(){ // simply writes version string to serial line, useful when reconnecting tty
 line();
 Serial.write(versionstring);
}

void pulse(int what,int time){ // pulse a pin for some ms
  digitalWrite(what,LOW);
  delay(time);
  digitalWrite(what,HIGH);
  return;
}
void disable() { // pull enable pin down to de-arm the optocouplers
  if (enabled) { // we call this every time the watchdog times out, just to be sure, but no need to do feedback when not enabled anyway
    line();
    Serial.write("D");
  }
  digitalWrite(enablePin,LOW); // always call, just to be on the safe side.
  if (enabled) {
    Serial.write("!");
  }
  enabled=false;
}

void enable() { // arms the torpedoes, eh optocouplers by supplying a Vcc 
  enabled=true;
  line();
  Serial.write("E");
  digitalWrite(enablePin,HIGH);
  Serial.write("!");
}

void line(){ // newline. might help to make this an inline function or macro instead.
  Serial.write(crnl);
}
// below are some functions to help print out ansi control sequences
void aesc(){ //<esc>[9 leads in all the color sequences
    Serial.write(27);
    Serial.write("[9");
}
void bblack(){
    if (!ansi) return;
    aesc();
    Serial.write("0m");
}
void bred(){
    if (!ansi) return;
    aesc();
    Serial.write("1m");
}
void bgreen(){
    if (!ansi) return;
    aesc();
    Serial.write("2m");
}
void byellow(){
    if (!ansi) return;
    aesc();
    Serial.write("3m");
}
void bblue(){
    if (!ansi) return;
    aesc();
    Serial.write("4m");
}
void bmagenta(){
    if (!ansi) return;
    aesc();
    Serial.write("5m");
}
void bcyan(){
    if (!ansi) return;
    aesc();
    Serial.write("6m");
}
void normal(){
    if (!ansi) return;
    aesc();
    Serial.write(";0m"); // needs the ; so the 9 is not directly prepended
}
   

void loop() {
    // put your main code here, to run repeatedly:
  char c;  
  if (Serial.available()){  // get a character from input if available
    timer=timeout;
    c=Serial.read();
  } else {
    c=' '; // nop command.
  }


  switch (c) { // command dispatcher
    case 'a': //toggle ansi colors
    ansi=ansi?0:1;
    break;
    case 's': //dump current sensor value
    case '=':
      line();
      Serial.write("=");
      Serial.print((analogRead(sensorPin)/4));
      Serial.write("!");
    break;
    case '-': // these adjust the cutoff value to distinguish between open and closed door
    if (enabled) { // need to enable to mess with cutoff values
      cutoff-=2;
    } //fall through, increase by one again, then print new value
    case '+': //increase cutoff (if enabled)
    if (enabled) { 
      cutoff++; 
      } // fall through, print new value
    case 'c': // dump current cutoff value
    line();
    Serial.write("C");
    Serial.print(cutoff);
    Serial.write("!");
    break;
    case 'd': // disable
    disable();
    break;
    case 'o': // operate door
    Serial.write(crnl);
    Serial.write("O");
    pulse(doorPin,1000); // 1 sec is more than enough, might lower this to save energy ;)
    Serial.write("!");
    break;
    case 'e': // enable - could add a small state machine to read in a kind of secret
    enable(); // but we just trust the upstream access control
    break;
    case 'l': // operates the relay for the led lamp.
    Serial.write(crnl);
    Serial.write("L");
    pulse(ledPin,1000);
    Serial.write("!");
    break;
    case 'v': // print version string again
    myversion();
    break;
    case ' ': // NOP
    // do not do anything if no command received
    break;
    default: // debug by echoing unknown command back; no 'help' screen as takes too much space
    line();
    Serial.write(c);
    Serial.write("?");
    break;
  }
  
  prevsensor=sensor; // save old sensor value
  sensor=(cutoff<(analogRead(sensorPin)/4)); // closed door, closed reed, pulls down. 
  if (sensor!=prevsensor) { // door state changed?
    prompt(); // print a prompt, which will show the changed state
  }
  
  if (--timer<0) { // decrease watchdog timer
    timer=timeout; // reset  timer
    if (enabled!=0) { // if disabled already then don't spam prompts
      c='~'; //will trigger prompt to show soon to be disabled mode
    }
    disable(); // always call disable, just in case pin glitched on
  } else { 
    delay(100); // 100 ms reaction time is fine, timeout=100 units of 100ms this way
  }
  
  if (c!=' ') { // prompt for new command 
    prompt();
  }
}


DONE:

  • control typical 'arduino' 2-relay board with NodeMCU.
  • control typical 'arduino' 2-relay board with ATTiny 45.
  • garage-door opener controller for NodeMCU as PoC - web frontend.
  • garage-door opener controller for ATT45, serial 'front'end.
  • made a box for mains->USB power brick to connect as separate power supply for coils, but since the TCO of just using the ATT and/or USB-serial converter as the 'fuse' compared to running another powerbrick seems lower, just connected the relay Vcc to the same Vcc from the USB-serial converter. NOTE: the enable pin feeds optocouplers, so the 'jumper' is open.

TODO:

  • invert logic on door open detection reed contact so open circuit when door closed?
  • add more reed contacts at different voltages to detect e.g. fully opened door, or checkpoints along the way. maybe some kind of ultrasound distance meter to detect how far the door is opened?