[PROJECT] DIY Hubduino MODBUS (RS485) controller for Autoslide

I am using Autoslide for my balcony sliding door. Old integration with HE requred 3 relays and 1 or 2 door sensors. This was working wery well for couple of years. Recently I discovered a hidden RS485 (MODBUS) connector and API was available on the Autoslide website.
Bingo!
I decided to build a custom Autoslide MODBUS controller using Hubduino framework. Poject is complete, debuged and tested. Now single small ESP32-based custom controller board will replace all relays and sensors.
NOTE.
This is DIY project. It requires some soldering skills and familiarity with Arduino IDE/Hubduino.
If anyone interested, I can post Hubduino Sketch and schematic.

2 Likes

I've been fighting modbus comms to my Autoslide on and off for a while now. I would love any and all advice for how you communicate with it and what your electrical hookups are like

At this point I'm just trying to get signs of life from it with a USB RS485 adapter and pymodbus and I can't get it to respond to ANYTHING

What firmware version is yours controller board?
My original board (about 3 years old) has a firmware ATM3.2
This one has few bugs but works at acceptable level.
MODBUS is unresponsive when door is in fully open state.
Status Reporting is correct in all other Door States.
Open/Close/OpenPet commands are working just fine.
However Mode Set commands are accepted but nothing is happening.
I applied some workaround for the broken Door Open status reporting
and make things working just fine at acceptable level.
I was not planning to change Mode electronically so, this is also
somewhat acceptable.

But here is the thing.
I called Autoslide support asking them all MODBUS related questions
and clearly explained what the problems are, They offered me a replacement
board for $150.00 So, I ordered this board hopping the MODBUS will be 100%
functional. Instead the MODBUS communication is completely broken!
The firmware is ATM3.9B
I immediately complained to Autoslide and they sent me one more board
with the same firmware and again completely broken MODBUS.
I wrote a huge angry letter to their support and now waiting for the response.
My main question is : Is MODBUS still supported with latest firmware or this
functionality is removed? And if MODBUS is supported why the controller is
unresponsive? (If MODBUS is still supported It could be a different speed but
I really want to here a response from the Autislide support.)

But after all, my original board is actually useful "as is" with all that bugs and
my workaround. If you interested in here is the working Hubduino Sketch:

//*******************************************************************************
//
//  Summary:  This is an Autoslide MODBUS Controller Arduino Sketch
//            for the direct integration with Hubitat Elevation (HE) hub 
//    
//  Change History:
//
//    Date        Who                   What
//    ----        ---                   ----
//    06-12-2025  Vitaliy Kholyavenko   Original Creation
//
//*******************************************************************************

// Serial Terminal Control
//#include <TerminalControl.h>

// WiFi Library
#include <SmartThingsESP32WiFi.h>

// WiFi Credentials
#include "credentials.h"

// MODBUS Library
#include <ModbusRTU.h>

///******************************************************************************************
// ESP32 Pins Definition
//******************************************************************************************

// On Board LED Pin
// This is System Heart Beat LED
#define HB_LED_PIN      2 

// Sensor Input Pin
#define SENSOR_IN_PIN  21 

// Switch Control Pin
#define SWITCH_OUT_PIN 22 

// UART-2 Pins
#define RXD2_PIN       16
#define TXD2_PIN       17

// MODBUS Pins 
#define DE_RE_PIN      26

//******************************************************************************************
// Global Variables
//******************************************************************************************
SmartThingsCallout_t messageCallout; // call out function forward decalaration

// ***** Timer-0 Parametewrs *****

// Declare HW Timer-0
hw_timer_t *Timer0 = NULL;

// Timer-0 Frequency in Hz
const unsigned int Timer0_Frequency  = 1000000;

// Timer-0 Interrupt Period in uS
const unsigned int Timer0_IRQ_Period = 10000;

// Heart Beat LED Period in uS
const unsigned int HB_LED_Period     = 1000000;
int HB_LED_Counter                   = 0;

// Sensor Control Parameters
volatile uint8_t debounceCounter     = 0;
volatile bool lastStableState        = HIGH;
volatile bool sensorState            = HIGH;
volatile bool sensorChanged          = false;

// Sensor Debounsing Timer in uS
const uint8_t sensorTimeout          = 150000;

// 1 Second Timer Flag
volatile bool Timer_Flag_1S          = false;

// ***** Modbus Parameters *****
ModbusRTU mb;
HardwareSerial& modbusSerial = Serial2;

#define MODBUS_SLAVE_ID                1

uint16_t writeRegAddr                = 0;
uint16_t writeData                   = 0;
uint16_t readRegAddr                 = 0;
uint16_t readData                    = 0;

volatile bool sendCommand            = false;
volatile bool writeDone              = false;
volatile bool readStatusFlag         = true;
volatile bool readDone               = false;
volatile bool readDataValid          = false;
volatile bool readModeFlag           = false;
volatile bool cmdRepeatEnable        = false;

// Autoslide Command Register Address
const uint16_t cmdWrRegAddr          = 0x0001;
const uint16_t modeWrRegAddr         = 0x0002;

// Autoslide Holding Registers Address
const uint16_t SensorActionRdRegAddr = 0x0001;
const uint16_t modeRdRegAddr         = 0x0002;
const uint16_t LockPositionRdRegAddr = 0x0004;

// Autoslide Response Mode Status
const uint16_t modeAuto              = 0x0000;
const uint16_t modeStacker           = 0x0001;
const uint16_t modeLocked            = 0x0002;
const uint16_t modePet               = 0x0003;

//Autoslide Sensor Action Status
const uint16_t sensorMaster          = 0x0001;
const uint16_t sensorPet             = 0x0003;
const uint16_t sensorStacker         = 0x0004;

// Autoslide Response Lock/Open/Close Status
const uint16_t statusClosedUnlocked  = 0x0000;
const uint16_t statusClosedLocked    = 0x0001;
const uint16_t statusOpened          = 0x0002;
      uint16_t statusCurrent         = 0xFFFF;

// Autoslide Button Commands
const uint16_t cmdChangeID           = 0x0000; // Echo back
const uint16_t cmdMasterBtn          = 0x0001; // Echo back
const uint16_t cmdPetBtn             = 0x0003;
const uint16_t cmdStackerBtn         = 0x0004;

// Autoslide Change Mode Commands
const uint16_t setModeAuto           = 0x0000;
const uint16_t setModeStaker         = 0x0001;
const uint16_t setModeLock           = 0x0002; // Echo back
const uint16_t setModePet            = 0x0003;

// Door Status States
enum {RESET,
      IDLE,
      doorClosing,
      doorClosed,
      doorOpening,
      doorOpened
     };

// Door Status State Machine State Variable
unsigned char doorState = RESET;

// Door Opening Timer
      uint8_t doorOpeningTimer   =  0;
const uint8_t doorOpeningTimeout = 85;

// Repeat Command
      uint8_t cmdRepeatCounter   =  0;
const uint8_t cmdrepeatPeriod    = 10;

//******************************************************************************************
// ESP32 WiFi Information
//******************************************************************************************

// Get Credentials from the credentials.h file
const char* str_ssid     = mySSID;
const char* str_password = myPASSWORD;

IPAddress ip       (192, 168, 20, 54); // Device IP Address //  <---You must edit this line!
IPAddress gateway  (192, 168,  20, 1); // Router Gateway    //  <---You must edit this line!
IPAddress subnet   (255, 255, 255, 0); // LAN Subnet Mask   //  <---You must edit this line!
IPAddress dnsserver(192, 168,  20, 1); // DNS Server        //  <---You must edit this line!
const unsigned int serverPort = 8090;  // Port to run the HTTP Server on

// Hubitat Hub TCP/IP Address
IPAddress hubIp   (192, 168, 20, 91);  // Hubitat Hub IP    //  <---You must edit this line!

// Hubitat Hub TCP/IP Address
const unsigned int hubPort = 39501;    // Hubitat Nub Port

//Create a SmartThings Ethernet ESP8266WiFi object
st::SmartThingsESP32WiFi smartthing(str_ssid, str_password, ip, gateway, subnet, dnsserver, serverPort, hubIp, hubPort, messageCallout);

//=======================================================================
// ESP32 Timer-0 ISR
//=======================================================================

void IRAM_ATTR Timer0_ISR()
{
  // Blink Heart Beat LED
  // (This is on-bord Blue LED)
  HeartBeatLED();

  // Check Sensor State
  checkSensorState();
}

//***********************************************************************
// Arduino Setup()
//***********************************************************************

void setup()
{
  // Heart Bit LED Pin
  pinMode(HB_LED_PIN, OUTPUT);
  digitalWrite(HB_LED_PIN, LOW);

  // Sensor In Pin
  pinMode(SENSOR_IN_PIN, INPUT_PULLUP);

  // Switch Control Pin
  pinMode(SWITCH_OUT_PIN, OUTPUT); 
  digitalWrite(SWITCH_OUT_PIN, LOW);

  // MODBUS Direction Control Pin
  pinMode(DE_RE_PIN, OUTPUT);
  digitalWrite(DE_RE_PIN, LOW);

  // Initialize Timers
  Timer0_Init();

  // Reset all Variables
  writeRegAddr     = 0;
  writeData        = 0;
  readData         = 0;
  statusCurrent    = 0xFFFF;
  cmdRepeatCounter = 0;

  // 1 Second Timer Flag
  Timer_Flag_1S   = false;

  // MODBUS Flags
  sendCommand     = false;
  writeDone       = false;
  readStatusFlag  = true;
  readDone        = false;
  readDataValid   = false;
  readModeFlag    = false;
  cmdRepeatEnable = false;

  doorState       = RESET;

  doorOpeningTimer = 0;

  // Heart Beat LED Counter
  HB_LED_Counter  = 0;

  // Sensor Control Parameters
  debounceCounter = 0;
  lastStableState = !digitalRead(SENSOR_IN_PIN);;
  sensorState     = HIGH;
  sensorChanged   = false;

  // Initialize UART-0 Port for the 
  // communication with PS (USB Serial)
  Serial.begin(115200);

  // Initialize MODBUS UART
  Serial2.begin(9600, SERIAL_8N1, RXD2_PIN, TXD2_PIN);
  mb.begin(&modbusSerial, DE_RE_PIN);
  mb.master();

  //Run the SmartThings init() routine to make sure the ThingShield is connected to the ST Hub
  smartthing.init();

  // By default Turn Switch 1 ON
  switch1_ON();

  // Controller Initialization is Complete
  Serial.println("Controller is Ready");
  Serial.println();
}

//******************************************************************************************
// Arduino Loop()
//******************************************************************************************

void loop()
{
  // Run SmartThing Logic
  smartthing.run();

  // Run MODBUS Function 
  // Always call mb.task() for non-blocking operation
  mb.task();

  // Allow Background Processes
  yield();

  // Check and Report Sensor State
  if (sensorChanged)
  {
    noInterrupts();
    bool state    = sensorState;
    sensorChanged = false;
    interrupts();

    if (state == LOW)
    {
      Serial.println("Sensor Closed");
      smartthing.send("contact2 closed");
    }
    else
    {
      Serial.println("Sensor Opened");
      smartthing.send("contact2 open");
    }
  }

  // Send a MODBUS Command if Command is Ready
  // and Slave is not Busy
  if ((true == sendCommand) && (false == mb.slave()))
  {
    if (true == mb.writeHreg(MODBUS_SLAVE_ID, writeRegAddr, writeData, writeCallback))
    {
      sendCommand = false;
      Serial.println("MODBUS Command was sent");
    }
  }

  //Periodic  Read MODBUS Status Register
  if ((true  == readStatusFlag) && (false == mb.slave()))
  {
    readStatusFlag = false;
    Serial.println();

    if (true == readModeFlag)
    // Read Mode Status Register
    {
      Serial.println("Read Mode Status");
      readRegAddr = modeRdRegAddr;
      mb.readHreg(MODBUS_SLAVE_ID, readRegAddr, &readData, 1, readCallback);
    }
    else
    // Read Lock and Position Status Register
    {
      Serial.println("Read Lock and Position Status");
      readRegAddr = LockPositionRdRegAddr;
      mb.readHreg(MODBUS_SLAVE_ID, readRegAddr, &readData, 1, readCallback);
    }
  }
  
  // Read Status was Successful
  if (true == readDone)
  {
    readDone   = false;

    // Display Status Register
    if (false == readModeFlag)
    {
      Serial.print("Lock and Position is : ");
      Serial.println(readData);

      doorStateSM(readData);
    }
    else
    {
      Serial.print("Mode is : ");
      Serial.println(readData);
    }
  }

  // Call Functions every 1 second
  if (true == Timer_Flag_1S)
  {
    Timer_Flag_1S = false;

    if (true == cmdRepeatEnable)
    {
      repeatCommand();
    }
    else
    {
      cmdRepeatCounter = 0;
    }
  }
}

//*****************************************************************************

// Timer-0 Init
void Timer0_Init(void)
{
  // Set Timer frequency
  Timer0 = timerBegin(Timer0_Frequency);
 
  // Attach onTimer function3to the Timer.
  timerAttachInterrupt(Timer0, &Timer0_ISR);
 
  // Set alarm to call Timer ISR function every Timer ISR Period (value in microseconds).
  // Repeat the alarm (third parameter) with unlimited count = 0 (fourth parameter).
  timerAlarm(Timer0, Timer0_IRQ_Period, true, 0);
}

//*****************************************************************************

// ***** Heart Beat LED, 1 Second Period *****
void HeartBeatLED()
{
  if (HB_LED_Counter < (HB_LED_Period / Timer0_IRQ_Period))
  {
    HB_LED_Counter++;
  }
  else
  {
    HB_LED_Counter = 0;
    digitalWrite(HB_LED_PIN, !digitalRead(HB_LED_PIN));

    // 1S Timer Flag
    Timer_Flag_1S  = true;
  }
}

//*****************************************************************************

// ***** Check Sensor State *****
void checkSensorState()
{
  bool rawState = digitalRead(SENSOR_IN_PIN);

  if (rawState != lastStableState)
  {
    debounceCounter++;

    if (debounceCounter >= (sensorTimeout / Timer0_IRQ_Period))
    {
      lastStableState = rawState;
      sensorState     = rawState;
      sensorChanged   = true;
      debounceCounter = 0;
    }
  }
  else
  {
    debounceCounter = 0;
  }
}

//*****************************************************************************

void switch1_ON()
{
  digitalWrite(SWITCH_OUT_PIN, LOW);
  smartthing.send("switch1 on");
}

// ----------------------------------------------------------------------

void switch1_OFF()
{
  digitalWrite(SWITCH_OUT_PIN, HIGH);
  smartthing.send("switch1 off");
}

//*****************************************************************************

// ***** Receive Command from Hubitat *****
void messageCallout(String command)
{
  Serial.print("Received command: '");
  Serial.print(command);
  Serial.println();
 
  cmdRepeatEnable = false;

  // Call HE Command Processor
  cmdFromHE(command);
}

// ----------------------------------------------------------------------

// Procesing Command from Hubitat
// and send a Command to the Bed
void cmdFromHE(String command)
{
  // Dismiss a "resfres" Message
  if (command.equals("refresh"))
  {
  }
  // Switch On/Off Commands
  else if (command.equals("switch1 on"))
  {
    switch1_ON();
  }
  else if (command.equals("switch1 off"))
  {
    switch1_OFF();
  }
  // Send Open Door Command
  else if (command.equals("openDoor"))
  {
    writeRegAddr = cmdWrRegAddr;
    writeData    = cmdMasterBtn;
    sendCommand  = true;
  }
  // Send Open Door Partially Command
  else if (command.equals("openDoorPartially"))
  {
    writeRegAddr    = cmdWrRegAddr;
    writeData       = cmdPetBtn;
    cmdRepeatEnable = true;
    sendCommand     = true;
  }
  // Send Close Door Command
  else if (command.equals("closeDoor"))
  {
    writeRegAddr = cmdWrRegAddr;
    writeData    = cmdMasterBtn;
    sendCommand  = true;
  }
  // Set Mode to Auto (Green)
  else if (command.equals("setModeAuto"))
  {
    writeRegAddr = modeWrRegAddr;
    writeData    = setModeAuto;
    sendCommand  = true;
  }
  // Set Mode to Pet (Yellow)
  else if (command.equals("setModePet"))
  {
    writeRegAddr = modeWrRegAddr;
    writeData    = setModePet;
    sendCommand  = true;
  }
  // Set Mode to Staker (Blue)
  else if (command.equals("setModeStaker"))
  {
    writeRegAddr = modeWrRegAddr;
    writeData    = setModeStaker;
    sendCommand  = true;
  }
  // Set Mode to Lock (Red)
  else if (command.equals("setModeLock"))
  {
    writeRegAddr = modeWrRegAddr;
    writeData    = setModeLock;
    sendCommand  = true;
  }
  // Received Illegal Command
  else
  {
    Serial.print("Illegal Command Received");
    Serial.println();
  }
}

//*****************************************************************************

// MODBUS Read Callback
bool readCallback(Modbus::ResultCode event, uint16_t, void*)
{
  if (event == Modbus::EX_SUCCESS)
  {
    // Display MODBUS Stats Data
    Serial.print("Read Success : ");
    Serial.println(readData);
    readDataValid = true;
  }
  else
  {
    // Display MODBUS Error Event
    Serial.print("Read Error: ");
    Serial.println(modbusResultToString(event));
    readDataValid = false;
    readData      = 0xFFFF;
  }

  readStatusFlag = true;
  readDone       = true;

  return true;
}

// ----------------------------------------------------------------------

// Check Door Position State SM
void doorStateSM(uint16_t statusData)
{
  Serial.print("SM State = ");
  Serial.print(doorState);
  Serial.print(" ; ");
  Serial.print("Current State = ");
  Serial.print(statusCurrent);
  Serial.print(" ; ");
  Serial.print("Reported State = ");
  Serial.println(statusData);
  Serial.println();

  switch (doorState)
  {
    // Reset/PowerOn
    case RESET:

      Serial.println("Waiting for the Valid Door Status ...");
      Serial.println();

      if (true == readDataValid)
      {
        doorOpeningTimer = 0;

          doorState = doorClosing;
      }

    break;

    // Waiting for Door Statys Change
    case IDLE:

      if (true == readDataValid)
      {
        if (statusCurrent != statusData)
        {
          doorOpeningTimer = 0;
          statusCurrent    = statusData;

          if ((statusData == statusClosedUnlocked) ||
              (statusData == statusClosedLocked))
          {
            doorState = doorClosed;
          }
          else if (statusData == statusOpened)
          {
            doorState = doorOpening;
          }
        }
      }

    break; 

    // Door is Closed
    case doorClosed:

      Serial.println("Report Status to HE : Door Closed");
      smartthing.send("contact1 closed");
      statusCurrent   = statusData;
      cmdRepeatEnable = false;

        doorState = IDLE;

    break;

    // Door is Opened
    case doorOpened:

      Serial.print("Timeout Counter = ");
      Serial.println(doorOpeningTimer);

      Serial.println("Report Status to HE : Door Opened");
      smartthing.send("contact1 open");
      statusCurrent = statusData;

        doorState = IDLE;

    break;

    // Delayed Door Opened
    // (Workaround for the MODBUS Reporting Bug)
    case doorOpening:

      doorOpeningTimer++;

      if ((false == readDataValid) ||
          (doorOpeningTimer >= doorOpeningTimeout))
      {
        doorState = doorOpened;
      }

    break;

    // Wait for Boor begin Closing
    case doorClosing:

      if ((true == readDataValid) &&
          ((statusData == statusClosedUnlocked) ||
           (statusData == statusClosedLocked)))
      {
        doorState = doorClosed;
      }

    break;

    // Processing Undefined State
    default:

      doorState = RESET;

    break;
  }  
}

// ----------------------------------------------------------------------

// MODBUS Write Callback
bool writeCallback(Modbus::ResultCode event, uint16_t, void*)
{
  if (event == Modbus::EX_SUCCESS)
  {
    Serial.println("Write Success : ");
    Serial.print(readData);
    writeDone = true;
  }
  else
  {
    // Display MODBUS Error Event
    Serial.print("Write Error: ");
    Serial.println(modbusResultToString(event));
  }

  return true;
}

// ----------------------------------------------------------------------

// MODBUS Helper for Readable Error Codes
const char* modbusResultToString(Modbus::ResultCode code)
{
  switch (code)
  {
    case Modbus::EX_SUCCESS           : return "Success";
    case Modbus::EX_ILLEGAL_FUNCTION  : return "Illegal Function";
    case Modbus::EX_ILLEGAL_ADDRESS   : return "Illegal Address";
    case Modbus::EX_ILLEGAL_VALUE     : return "Illegal Value";
    case Modbus::EX_SLAVE_FAILURE     : return "Slave Failure";
    case Modbus::EX_ACKNOWLEDGE       : return "Acknowledge";
    case Modbus::EX_TIMEOUT           : return "Timeout";
    
    default: return "Unknown";
  }
}  

// ----------------------------------------------------------------------

// Auto Repeat Command
void repeatCommand()
{
  if (cmdRepeatCounter < cmdrepeatPeriod)
  {
    cmdRepeatCounter++;
  }
  else
  {
    cmdRepeatCounter = 0;
    sendCommand      = true;
  }
}

//*****************************************************************************

The hardware is ESP32 Board plus RS485 Transceiver.

I got a response from the Autoslide support. Apparently not every board supports a MODBUS even the connector is physically present on the board. They shipped me a third board claiming it does support a MODBUS (hopefully bug-free). Board should be in on 07/05
Let me see how it will work. But anyway, my current board with few bugs definitely can be used with the applied workaround.

How did you check the firmware version?

I confirmed with autoslide before purchase that it supported Modbus, so I’ll be pretty annoyed if they try to tell me it doesn’t now. Their support robot is also trying to tell me that there are no known issues with Modbus, so I’m very glad to hear from you.

There is a paper label on top of the soldered add-on module next to the modbus connector:


This may not be a firmware version for the main cpu but at least something for reference.
The original board is ATM3.2 Two non-working are ATM3.9B
Third one which should/must support modbus will arrive on 07/05.
Let me see how good it will be.

I am glad, my original board worked with modbus but had a bug(s). I called tech support and specifically talked about modbus related issues. Regardless they sent me two non-working boards and third one is on the way. Here is their latest response:

I have attached another RMA form to this email please send back all boards that are not currently supporting your code with RMA forms in packaging,
Also I asked the warehouse team to ship you out an ATM2 Motherboard which should support your coding but please reach out if you need any other assistance.

I got a third board toady with ATM2 revision (seems to be older board version).
MODBUS is working much better. All commands are going through as expected. But "Door Opened" status reporting logic is very sick (even worth than my original board). "Door Opened" status is reported as soon as an OPEN command is send (BTW, regardless which way) to the Autoslide. In addition this ATM2 (older rev?) board has very different behavior for door opening which is very annoying. It opens fast partially and then moves slow to the completely open position.
Bottom line: my original board and this third replacement one are not perfect but I can use ether one. Now I have a hard time to decide which one to use.
Most likely I will use my original ATM3.2 board.

I have the 3.9B revision as well. While debugging I was able to switch it into Bluetooth mode (switch 6) and monitor the modbus-like commands coming from the Bluetooth module via RS485 to usb converter. I also found that the Bluetooth app seems to work just fine

If autoslide themselves is saying that the modbus doesn’t work, I may just sniff all of the commands from the Bluetooth module, and then de-solder the Bluetooth module and take its place

The Bluetooth commands are not quite modbus commands, but they are repeatable for each command, so I will just record them blindly and play them back as needed

I’m hoping AutoSlide can just resolve this, it seems crazy that what they publish as a first class feature isn’t actually working

So, do you see some sort of activity on the RS485 (MODBUS) connector while in the BT Mode? If "yes" it looks like they are added some sort of BT-to- RS485 module, This actually makes sense. What is a speed on the RS485 in the BT mode? Is it still 9600b/s or something different (presumably 115200b/s)?

This is interesting. If format is known then it is easy to reproduce all these commands with Hubduino.

If everything is on the RS485 port you don't have to de-solder a BT Module. RS85 is a multi-point bus/interface (120 ohm termination resistor may not be even needed on the plug-in device).

I have a question to you:
When is a "Door Opened" status is reported back?
On all boards I played with it is stupidly reported as soon as door started to move in open direction. However "Door Closed" status correctly reported AFTER door is completely closed. I expected the same for the "Door Opened" status but this is unfortunately not a case.

The Bluetooth is definitely using some Bluetooth to RS485 converter. It was running at 9600baud, and I was monitoring on the modbus connector.

I believe the commands were always the same, so without fully understanding the command format we could still just make a map of each command to the raw bytes, and just send those raw bytes to command it

I haven’t yet tested trying to send commands while in Bluetooth. I didn’t really consider trying because I was trying too hard to get modbus working properly and just using Bluetooth to prove my RS485 was correct. When I get back home next week I’ll do some tests to verify that having multiple masters on the 485 bus isn’t making a problem. Now that you mention it, I do agree it will probably work without removing the Bluetooth module

Thank you for the very valuable info.
So, the RS485 Connector is still alive but communication protocol is not a MODBUS compliant. This makes no sense but it is what it is. I will ask an Autoslide support to provide an updated API (whatever it is) on the RS485 port (so far they were responsive). Still my biggest question is: When "Door Opened" status is reported? If by any chance they fixed this and reporting is done at a right time (i.e. when door is actually fully opened (but not when OPEN command is sent out) I will ask them to send me again ATM3.9B board version. But if reporting is still at a wrong time it makes no sense to ger this ATM3.9B board. In this case I will use my current board (ATM3.2) "as is". My workaround is doing just a right job. But being experienced EE I prefer bug-free implementations. And this is/was a whole reason why I emailed Autoslide support in a first place.

PS.
I am stupid enough and too lazy. I should check what is going on on the RS485 port. But unfortunately I already sent back these two ATM3.9B boards.
Let me see what Autoslide response will be ...

I strongly suspect that in their software, “open = ! Closed”

In my opinion, I think this is okay. I would consider the door closed when it’s fully closed, but open anytime it is not fully closed. This is especially true for a door with a locking motor like I have.

I could understand your perspective though that it’s kind of pointless to have two different values to represent a single binary value of “closed” vs “not closed”. You bring up a good point that it would be more useful if implemented your way, but it’s not always true that the software engineer sees things the same way as the user. I also think having pet mode, where the door opens partially, makes things a little bit more ambiguous and hard to handle

Yes, you are correct. When door is not completely closed (and locked) it is open. But when door is still moving it is neither closed not open. Ideally they should provide a BUSY (i.e. MOVING) status in addition to the Opened/Closed status. For this reason I am thinking to add a Current Sensor to my controller. Since ESP32 has a number of ADCs simple low value resistor should do the job.