Rollease Acmeda Pulse 2 Hub Users?

any examples pls?

only as mentioned above - the Bond Bridge. I think there have been attempts to use RFX-TRX, not sure if successfully.

1 Like

Did you have to send the hub back to them in order to do the RF Firmware update? I had already sent back my unit to them last year as part of their proactive recall of the hub memory issues, but they obviously didn’t touch the RF Firmware on my unit because its still at B10.

For What it’s worth I have 2 Hubs and 2 Bond Bridges.
I would love to rely on Hubs exclusively but they go offline from time to time. Sometimes it’s Automate home assistant integration, and sometimes the hubs.
I find bond bridges incredibly reliable. So all my Automations use Bond Bridge. But I use Hubs for Data on batteries, accurate blind positioning, etc.
In addition I power the hubs via zigbee smart switches, If a number of shade sensors go offline an automation runs to restart the Pulse 2 and then reload the integration

I also got sent new B17 hubs and they didnt ask for the old B10 hub back.

So the other day the two B17 RF edition hubs were going offline repeatedly. so i sniffed the traffic. Turns out my hubs are talking to AWS IOTs service in US East Coast and im in New Zealand so pinging the captured DNS endpoint name results in about 200ms delay. I sent a support email to Rollease and have had radio silence.

So i cracked open the shell on the spare B10 i have been left with.

TLDR;
I have a working ESPHome Config for it.
Chip is ESP32-D0WD-V3 (revision 3)
Working LAN8720A from SMSC
Working I2C (2 devices, * PCA9536: 0x41, ATECC508A @ 0/0x58 secure element)
Working tri colour leds driving by IO 0,1,2 on the PCA9536
Working uart to the STM32L051 micro that is connected to the RF chip
Buttons mapped to GPIO36 (external) GPIO39 Internal button
Winbond 4MB flash (just works)

Quering the STM via the uart interface:

09:11:19	[I]	[RF_A:201]	
TX: !000v?;
09:11:19	[D]	[RF_A:287]	
!BR1vB10;

So im getting back the Bridge Version B10 from the STM32.
A i had to re pair all my blinds so the stored IDs do not match the current pairing ID’s on the B17 hubs.

09:14:26	[I]	[RF_A:201]	
TX: !USZo;
09:14:27	[D]	[RF_A:287]	
!USZEnp;

So the STM32 returns the ID with Enp, Error not present.

From reading its all down now to the pairing of the shades.
Ill be testing with my office blind to see if i can get it paired whit the B10 hub.

Other observations on the board:
The last test pad by the Ethernet port is the enable programing mode when you reset with en pin which is exposed and labeled on a test pad also.
The tx rx pads a also clearly labeled.
The factory firmware is mongoose OS (AWS IOT Framework)

Tracing out the other tx rx pads they connect to the STM32 so dumping the flash from this and replacing it with the B17 firmware would also be viable. Would have to extract it from one of the B17 hubs. this requires a different tool to the esptool used to dump the original firmware

#dump the original rom when in download mode
esptool.exe --port COM6 read_flash 0x00000 0x400000 dump.bin
#write the dump when in download mode ie. restore the original firmware
esptool.exe --chip esp32 --port COM6 write_flash 0x0000 dump.bin

To flash the esphome image use the manual download legacy OTA format and use ESPHome-Flasher (1.4.0) to upload the image, once you have the image on you can use wirelessly if you make changes. the version of the tool doesn’t matter i would susspect.

Using the Pulse LINQ is a good way to discover all the commands the STM32 will accept.

esphome:
  name: pulse2hubdev
  friendly_name: pulse2hubdev
  on_boot:
    priority: -100
    then:
      - switch.turn_on: green

esp32:
  board: esp32dev
  framework:
    type: arduino

# Logging
logger:
  level: DEBUG
  logs:
    ethernet: INFO
    i2c: INFO
    text_sensor: INFO
    sensor: INFO
    uart: DEBUG
  baud_rate: 0  # disables default serial logging so UART pins are free

ethernet:
  type: LAN8720
  mdc_pin: GPIO16 
  mdio_pin: GPIO23 
  clk:
    pin: GPIO0
    mode: CLK_EXT_IN
  phy_addr: 1
  power_pin: GPIO2

# wifi:
#   networks:
#     - ssid: !secret wifi_ssid2
#       password: !secret wifi_password2
   
api:
  encryption:
    key: "W2NcdU3qMfp7UQbgS832RIP+DWCqQbjgZeVztbov2CY="

ota:
  - platform: esphome
    password: "cb2a0f65d905cf5e8509ac73ab55a7ee"

web_server:
  port: 80

i2c:
  id: i2c_bus
  sda: GPIO14
  scl: GPIO4
  frequency: 100kHz
  scan: false

pca9554:
  - id: 'pca9554a_device'
    i2c_id: i2c_bus
    address: 0x41

switch:
  - platform: gpio
    name: "Red Led"
    id: red
    pin:
      pca9554: pca9554a_device
      number: 0
      mode:
        output: true
      inverted: true
        
  - platform: gpio
    name: "Blue Led"
    id: blue
    pin:
      pca9554: pca9554a_device
      number: 1
      mode:
        output: true
      inverted: true
  - platform: gpio
    name: "Green Led"
    id: green
    pin:
      pca9554: pca9554a_device
      number: 2
      mode:
        output: true
      inverted: true
  - platform: gpio
    name: "Switch4"
    pin:
      pca9554: pca9554a_device
      number: 4
      mode:
        output: true
      inverted: false

sensor:
  - platform: uptime
    name: "Uptime"

text_sensor:
  - platform: version
    name: "ESPHome Version"
  - platform: ethernet_info
    ip_address:
      name: "ETH IP"
    mac_address:
      name: "ETH MAC"
    dns_address:
      name: "ETH DNS"



binary_sensor:
  - platform: gpio
    name: "GPIO36"
    pin:
      number: GPIO36
      mode: { input: true }
      inverted: true
  - platform: gpio
    name: "Status Line (GPIO39)"
    pin:
      number: GPIO39
      mode:
        input: true
      inverted: true        # flip to true if the logic is active-low

uart:
  - id: rf_a
    rx_pin: GPIO13   # STM32 TX -> ESP RX
    tx_pin: GPIO12   # STM32 RX <- ESP TX  (strap pin is safer as TX)
    baud_rate: 115200
    data_bits: 8
    parity: NONE
    stop_bits: 1
    rx_buffer_size: 4096

globals:
  - id: ascii_rf
    type: std::string
  - id: raw_rf
    type: std::vector<uint8_t>
  - id: last_rx_ms
    type: uint32_t
    initial_value: '0'

script:
  - id: rf_send_ascii_semicolon
    mode: restart
    parameters:
      s: std::string
    then:
      - lambda: |-
          std::string msg = s;
          if (msg.empty() || msg[0] != '!') msg.insert(0, "!");
          if (msg.back() != ';') msg.push_back(';');
          id(rf_a)->write_str(msg.c_str());   // <-- convert to const char*
          ESP_LOGI("RF_A","TX: %s", msg.c_str());

text:
  - platform: template
    id: blind_id
    name: "Dooya Blind ID"
    mode: TEXT
    initial_value: "KV8"
    optimistic: true
    min_length: 1
    max_length: 8


button:
  - platform: template
    name: "Bridge: GLOBALVERSION (!000v?;)"
    on_press:
      - script.execute: { id: rf_send_ascii_semicolon, s: "!000v?;" }

  - platform: template
    name: "Bridge: GLOBALPOSITION (!000r?;)"
    on_press:
      - script.execute: { id: rf_send_ascii_semicolon, s: "!000r?;" }

  - platform: template
    name: "Bridge: DAT (flush/request)"
    on_press:
      - script.execute: { id: rf_send_ascii_semicolon, s: "!DAT;" }

  - platform: template
    name: "Blind Open (!ID o;)"
    on_press:
      - lambda: |-
          std::string b = id(blind_id).state.c_str();
          id(rf_send_ascii_semicolon).execute("!" + b + "o;");

  - platform: template
    name: "Blind Close (!ID c;)"
    on_press:
      - lambda: |-
          std::string b = id(blind_id).state.c_str();
          id(rf_send_ascii_semicolon).execute("!" + b + "c;");

  - platform: template
    name: "Blind Stop (!ID s;)"
    on_press:
      - lambda: |-
          std::string b = id(blind_id).state.c_str();
          id(rf_send_ascii_semicolon).execute("!" + b + "s;");

  - platform: template
    name: "Blind Possition (!ID pP?;)"
    on_press:
      - lambda: |-
          std::string b = id(blind_id).state.c_str();
          id(rf_send_ascii_semicolon).execute("!" + b + "pP?;");
  - platform: template
    name: "Blind Pair (!000&;)"
    on_press:
      - lambda: |-
          id(rf_send_ascii_semicolon).execute("!000&;");

interval:
  - interval: 20ms
    then:
      - lambda: |-
          auto &u = *id(rf_a);
          bool got = false;
          uint8_t c;
          while (u.available() && u.read_byte(&c)) {
            got = true;
            id(last_rx_ms) = millis();

            // Build pretty ASCII (printable -> char, else '.')
            char shown = (c >= 32 && c <= 126) ? (char)c : '.';
            if (shown != '\r' && shown != '\n') id(ascii_rf).push_back(shown);
            id(raw_rf).push_back(c);

            // Flush a frame on ';' (Dooya frames) OR on newline
            if (c == ';' || c == '\n') {
              // Log ASCII frame
              if (!id(ascii_rf).empty()) {
                ESP_LOGD("RF_A", "%s", id(ascii_rf).c_str());
              } else {
                ESP_LOGD("RF_A", "<blank frame>");
              }

              // Also show hex of the same frame
              char line[3*256 + 1]; size_t n = id(raw_rf).size(); if (n > 256) n = 256;
              char *p = line;
              for (size_t i = 0; i < n; i++) { sprintf(p, "%02X ", id(raw_rf)[i]); p += 3; }
              *p = 0;
              ESP_LOGV("RF_A", "[HEX] %s", line);

              id(raw_rf).clear();
              id(ascii_rf).clear();
            }
          }

          // Safety: if bytes stopped mid-frame, dump what we have after 40 ms
          if (!got && !id(raw_rf).empty() && (millis() - id(last_rx_ms) > 40)) {
            char line[3*256 + 1]; size_t n = id(raw_rf).size(); if (n > 256) n = 256;
            char *p = line;
            for (size_t i = 0; i < n; i++) { sprintf(p, "%02X ", id(raw_rf)[i]); p += 3; }
            *p = 0;
            ESP_LOGD("RF_A", "[RAW timeout] %s", line);
            id(raw_rf).clear();
            id(ascii_rf).clear();
          }



Update have it working and pairing an existing blind to the bridge running ESPHome

[14:48:16.621][D][button:023]: 'Bridge: GLOBALVERSION (!000v?;)' Pressed.
[14:48:16.626][I][RF_A:221]: TX: !000v?;
[14:48:16.659][D][RF_A:303]: !BR1vB10;
[14:48:16.663][V][RF_A:313]: [HEX] 21 42 52 31 76 42 31 30 3B 
[14:48:26.669][D][button:023]: 'Blind Pair (!000&;)' Pressed.
[14:48:26.673][I][RF_A:221]: TX: !000&;
[14:48:27.904][D][RF_A:303]: !JJ8Enl;
[14:48:27.907][V][RF_A:313]: [HEX] 21 4A 4A 38 45 6E 6C 3B 
[14:58:06.744][D][button:023]: 'Blind Pair (!000&;)' Pressed.
[14:58:06.748][I][RF_A:221]: TX: !000&;
[14:58:07.977][D][RF_A:303]: !DGZEnl;
[14:58:07.981][V][RF_A:313]: [HEX] 21 44 47 5A 45 6E 6C 3B 
[14:58:17.258][D][button:023]: 'Blind Pair (!000&;)' Pressed.
[14:58:17.262][I][RF_A:221]: TX: !000&;
[14:58:17.588][D][RF_A:303]: !KV8A,RAD;
[14:58:17.592][V][RF_A:313]: [HEX] 21 4B 56 38 41 2C 52 41 44 3B 
[14:58:18.460][D][RF_A:303]: !KV8vU22,RAD;
[14:58:18.464][V][RF_A:313]: [HEX] 21 4B 56 38 76 55 32 32 2C 52 41 44 3B 
[14:58:20.301][D][RF_A:303]: !KV8r099b180,RAD;
[14:58:20.304][V][RF_A:313]: [HEX] 21 4B 56 38 72 30 39 39 62 31 38 30 2C 52 41 44 3B 
[14:58:41.577][D][RF_A:303]: !KV8r092b000,RAD;
[14:58:41.581][V][RF_A:313]: [HEX] 21 4B 56 38 72 30 39 32 62 30 30 30 2C 52 41 44 3B 
[14:58:44.321][D][RF_A:303]: !KV8r100b180,RAD;
[14:58:44.325][V][RF_A:313]: [HEX] 21 4B 56 38 72 31 30 30 62 31 38 30 2C 52 41 44 3B 
[14:58:48.557][D][RF_A:303]: !KV8r087b000,RAD;
[14:58:48.561][V][RF_A:313]: [HEX] 21 4B 56 38 72 30 38 37 62 30 30 30 2C 52 41 44 3B 
[14:58:54.311][D][RF_A:303]: !KV8r100b180,RAD;
[14:58:54.315][V][RF_A:313]: [HEX] 21 4B 56 38 72 31 30 30 62 31 38 30 2C 52 41 44 3B 
[14:59:50.101][D][text:050]: 'Dooya Blind ID' - Setting text value: KV8
[14:59:50.104][D][text:016]: 'Dooya Blind ID': Sending state KV8
[14:59:50.189][D][button:023]: 'Blind Open (!ID o;)' Pressed.
[14:59:50.193][I][RF_A:221]: TX: !KV8o;
[14:59:50.380][D][RF_A:303]: !KV8o,RAD;
[14:59:50.384][V][RF_A:313]: [HEX] 21 4B 56 38 6F 2C 52 41 44 3B 
[14:59:55.208][D][button:023]: 'Blind Stop (!ID s;)' Pressed.
[14:59:55.212][I][RF_A:221]: TX: !KV8s;
[14:59:55.382][D][RF_A:303]: !KV8s,RAB;
[14:59:55.385][V][RF_A:313]: [HEX] 21 4B 56 38 73 2C 52 41 42 3B 
[14:59:56.240][D][RF_A:303]: !KV8r075b000,RAC;
[14:59:56.244][V][RF_A:313]: [HEX] 21 4B 56 38 72 30 37 35 62 30 30 30 2C 52 41 43 3B 
[15:00:17.819][D][button:023]: 'Blind Close (!ID c;)' Pressed.
[15:00:17.823][I][RF_A:221]: TX: !KV8c;
[15:00:18.015][D][RF_A:303]: !KV8c,RAB;
[15:00:18.018][V][RF_A:313]: [HEX] 21 4B 56 38 63 2C 52 41 42 3B 
[15:00:23.821][D][RF_A:303]: !KV8r100b180,RAA;
[15:00:23.825][V][RF_A:313]: [HEX] 21 4B 56 38 72 31 30 30 62 31 38 30 2C 52 41 41 3B 
[15:01:22.179][D][RF_A:303]: !KV8r094b000,RAC;
[15:01:22.183][V][RF_A:313]: [HEX] 21 4B 56 38 72 30 39 34 62 30 30 30 2C 52 41 43 3B 
[15:01:29.853][D][RF_A:303]: !KV8r100b180,RAC;
[15:01:29.857][V][RF_A:313]: [HEX] 21 4B 56 38 72 31 30 30 62 31 38 30 2C 52 41 43 3B 

Using the pairing command i captured on the original firmware '!000v?; and using the p2 button on the remote for the shade it paired with the RF bridge

Two way comms, open close and Running (RAB) status and percentage.

TX: !KV8c; close pressed
!KV8c,RAB; running
!KV8r094b000,RAC; 94%
!KV8r100b180,RAC; 100% (using the remote and seeing feedback in the esp log)

With a bit more effort these Pulse2Hubs can be turned into an offline blind controller which does not rely on AWS or the cloud and improve reliability no end.