Garage Door

Hacked Merlin garage door opener

Hacked Merlin garage door opener. Consisting of

  • MySensors Actuator circuit for controlling open/close and light
  • 2 x Binary Sensor (for Open and Closed status)
  • Powered by the Merlin door opener with LM2596 buck to drop 16v down to 5v
  • Includes home temperature backbone for sensors in the garage, attic, outdoor and in the gas heater cavity (more for single sensor convenience and power source reuse than anything else)
  • Automatically opens the garage when I return home on my scooter via OwnTracks, and notifying OpenHAB via MQTT when I travel through certain GPS located regions.
    • OwnTracks connects to my private MQTT broker by port forwarding through my router & publishes the way point triggers.
    • These publications are received by OpenHAB with the mqttitude binding and ‘latch’ switches – see OpenHAB Items below.
  • Se-cured/Signed communication with the Gateway, so no one else can send a MySensors signal to control it

The RFP30N06LE Mosfets you can see there are complete overkill for the switching circuits. I could have used relays, but was conscious of the limited power supply I was tapping into. I should have used a basic transistor, or even better, an optocoupler, but that’s all I had at the time.

It’s possibly due to this lack of isolation that the light flashes on the opener unit after moving, when controller via this circuit. I’ll possibly revisit this in the future.

The resistors there are the same as on the wired opener. One for the motor, one for the light. The original idea was to flash the light as a pre-warning when controlling the opener remotely.

OpenHab Items

Switch PresenceBrendan_PhoneMqttHome        "Brendan at Home"           (gPresence)       {mqttitude="mosquitto:owntracks/brendan/0/event:Home"}
Switch PresenceBrendan_PhoneMqttWgtn       	"Brendan at Wellington"   	(gPresence)      	{mqttitude="mosquitto:owntracks/brendan/0/event:Wellington"}

String Garage_Door_Closed       "Garage Door Fully Down[%s]"    <door>          (All, gGarage)          {mqtt="<[mosquitto:mygateway1-out/46/2/1/0/16:state:MAP(]"}
String Garage_Door_Opened       "Garage Door Fully Up[%s]"      <door>          (gGarage)               {mqtt="<[mosquitto:mygateway1-out/46/4/1/0/16:state:MAP(]"}
Switch Garage_Door_Switch       "Garage Door Switch"            <garage>        (gGarage)               {mqtt=">[mosquitto:mygateway1-in/46/1/0/0/2:command:on:1]", autoupdate="false"}
String Garage_Door_Switch_Push                                                                          {mqtt=">[mosquitto:mygateway1-in/46/1/0/0/2:command:on:1]"}<br>
Switch Garage_Light_Switch      "Garage Light Switch"           <light>         (gGarage)               {mqtt=">[mosquitto:mygateway1-in/46/2/0/0/2:command:on:1]", autoupdate="false"}
Switch Garage_Door_Timer                                                                                {expire="30m,command=OFF"}

OpenHAB Sitemap

Frame label="Garage" {
        Text item=Garage_Door_Closed
        Group item=gGarage
        Text item=Garage_Temperature

Frame label="Presence"{
        Group item=gPresence

OpenHAB Rules

var DateTime brendanAtWgtn

rule "Brendan at Wgtn "
//Doesn't matter whether I'm entering or existing Wgtn, point is, I was there
        Item PresenceBrendan_PhoneMqttWgtn received update
        brendanAtWgtn = now
        logWarn("BrendanAtWgtn", "Setting to now")

rule "Brendan at Home"
//This time though, only trigger as I enter home region
        Item PresenceBrendan_PhoneMqttHome changed to ON
        logWarn("BrendanAtHome", "PhoneMqttHome changed to ON")
//and if I was recently in Wgtn, then open the door if it's not already.
        if((brendanAtWgtnn != null && brendanAtWgtn.plusMinutes(90).isAfter(now))){

                if(Garage_Door_Closed.state == "Yes"){
                        logWarn("BrendanAtHome", "Do Open Garage")
                        sendCommand(Garage_Door_Switch, ON)
                        logWarn("BrendanAtHome", "Brendan home, but garage is already open")
//Clear the Wgtn state so we don't cause false openings while we come and go around home
                brendanAtWgtn = null

var Timer garageDoorTimer = null
var Boolean garageDoorStillOpen = false

rule "Garage Door Opening"
        Item Garage_Door_Closed received update
        if(Garage_Door_Closed.state == "No"){
                Notify_Info.postUpdate("Garage door is opening")
                Notify_Info.postUpdate("Garage door has closed")


rule "Garage Door Closing"
        Item Garage_Door_Opened received update
        if(Garage_Door_Opened.state == "No"{
                Notify_Info.postUpdate("Garage door is closing")
                Notify_Info.postUpdate("Garage door has opened")

rule "Garage Door Timer"
        Item Garage_Door_Timer received command OFF
        Notify_Info.postUpdate("The garage door is still open")

Arduino code

// Enable debug prints to serial monitor
//#define MY_DEBUG 

// Enable and select radio type attached
#define MY_RADIO_NRF24
//#define MY_RADIO_RFM69

#define MY_NODE_ID 46


#define MY_SIGNING_SOFT //!< Software signing


#include <SPI.h>
#include <MySensors.h>  

#include <DallasTemperature.h>
#include <OneWire.h>

#define COMPARE_TEMP 1 // Send temperature only if changed? 1 = Yes 0 = No

#define ONE_WIRE_BUS 3 // Pin where dallase sensor is connected 
#define MAX_ATTACHED_DS18B20 16
OneWire oneWire(ONE_WIRE_BUS); // Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
DallasTemperature sensors(&oneWire); // Pass the oneWire reference to Dallas Temperature. 
float lastTemperature[MAX_ATTACHED_DS18B20];
int numSensors=0;
boolean receivedConfig = false;
boolean metric = true; 
// Initialize temperature message
MyMessage msg(0,V_TEMP);

// From Binary switch
#include <Bounce2.h>
#define DOOR_FULLY_CLOSED_PIN  2  // Arduino Digital I/O pin for button/reed switch
#define DOOR_FULLY_OPEN_PIN  4  // Arduino Digital I/O pin for button/reed switch
int oldFullyClosedValue=-1;
int oldFullyOpenValue=-1;
// Change to V_LIGHT if you use S_LIGHT in presentation below
MyMessage binfullyclosedmsg(DOOR_FULLY_CLOSED_CHILD_ID,V_TRIPPED);
MyMessage binfullyopenmsg(DOOR_FULLY_OPEN_CHILD_ID,V_TRIPPED);

Bounce debouncerClosed = Bounce(); 
Bounce debouncerOpened = Bounce(); 

//from RelayActuator
#define RELAY_1  5  // Arduino Digital I/O pin number for first relay (second on pin+1 etc)
#define NUMBER_OF_RELAYS 2 // Total number of attached relays
#define RELAY_ON 1  // GPIO value to write to turn on attached relay
#define RELAY_OFF 0 // GPIO value to write to turn off attached relay

//From Pressure
#include <Adafruit_BMP085.h>

#define BARO_CHILD 0
#define TEMP_CHILD 1

const float ALTITUDE = 20; // <-- adapt this value to your own location's altitude.

// Sleep time between reads (in seconds). Do not change this value as the forecast algorithm needs a sample every minute.
const unsigned long SLEEP_TIME = 60000; 

float dP_dt;
MyMessage tempMsg(TEMP_CHILD, V_TEMP);

//const unsigned long SLEEP_TIME = 60000; 
unsigned long timeOfLastTempCheck = SLEEP_TIME * -1;

void before() { 
 // Startup up the OneWire library
  for (int sensor=1, pin=RELAY_1; sensor<=NUMBER_OF_RELAYS;sensor++, pin++) {
    // Then set relay pins in output mode
    pinMode(pin, OUTPUT);   
    // Set relay to last known state (using eeprom storage) 
    digitalWrite(pin, RELAY_OFF);

void setup()  
 //  requestTemperatures() will not block current thread

// From Binary switch
 // Setup the buttons
  // Activate internal pull-up


  metric = getControllerConfig().isMetric;

char* string2char(String command){
        char *p = const_cast<char*>(command.c_str());
        return p;

void presentation() {
  // Send the sketch version information to the gateway and Controller
  sendSketchInfo("Garage Door and Sensors", "1.1");

  // Fetch the number of attached temperature sensors  
  numSensors = sensors.getDeviceCount();
  // Present all sensors to controller
  for (int i=0; i<numSensors && i<MAX_ATTACHED_DS18B20; i++) {   
     present(i, S_TEMP, string2char(String("Temperature: " + String(i))));

// From Binary switch
  // Register binary input sensor to gw (they will be created as child devices)
  // You can use S_DOOR, S_MOTION or S_LIGHT here depending on your usage. 
  // If S_LIGHT is used, remember to update variable type you send in. See "msg" above.
  present(DOOR_FULLY_CLOSED_CHILD_ID, S_DOOR, "Door location 1");  
  present(DOOR_FULLY_OPEN_CHILD_ID, S_DOOR, "Door location 2");  

   for (int sensor=1, pin=RELAY_1; sensor<=NUMBER_OF_RELAYS;sensor++, pin++) {
    // Register all sensors to gw (they will be created as child devices)
    present(sensor, S_LIGHT, string2char(String("Relay: " + String(sensor))));


void loop() {     
  if(millis() - timeOfLastTempCheck > SLEEP_TIME){
    timeOfLastTempCheck = millis();
 //   checkpressure();

void receive(const MyMessage &message) {

  // We only expect one type of message from controller. But we better check anyway.
  if (message.type==V_LIGHT) {

       digitalWrite(message.sensor-1+RELAY_1, RELAY_ON);
       digitalWrite(message.sensor-1+RELAY_1, RELAY_OFF);


void checkTemperature(){

  // Fetch temperatures from Dallas sensors

  // query conversion time and sleep until conversion completed
  int16_t conversionTime = sensors.millisToWaitForConversion(sensors.getResolution());
  // sleep() call can be replaced by wait() call if node need to process incoming messages (or if node is repeater)

  // Read temperatures and send them to controller 
  for (int i=0; i<numSensors && i<MAX_ATTACHED_DS18B20; i++) {
    // Fetch and round temperature to one decimal
    float temperature = static_cast<float>(static_cast<int>((getControllerConfig().isMetric?sensors.getTempCByIndex(i):sensors.getTempFByIndex(i)) * 10.)) / 10.;
    // Only send data if temperature has changed and no error
    #if COMPARE_TEMP == 1
    if (lastTemperature[i] != temperature && temperature != -127.00 && temperature != 85.00) {
    if (temperature != -127.00 && temperature != 85.00) {
      // Send in the new temperature
      // Save new temperatures for next compare

void checkSwitchs(){


 int value =;  
 if (value != oldFullyClosedValue) {
     // Send in the new value
     send(binfullyclosedmsg.set(value==HIGH ? 1 : 0));
     oldFullyClosedValue = value;

 value =;
 if (value != oldFullyOpenValue) {
     // Send in the new value
     send(binfullyopenmsg.set(value==HIGH ? 1 : 0));
     oldFullyOpenValue = value;

Last modified April 10, 2023: more content (59f5057)