DEV Community

GemeOpen
GemeOpen

Posted on

Commissioning a 25 A MQTT contactor: from unboxing to Home Assistant to long-term maintenance

Commissioning a 25 A MQTT contactor: from unboxing to Home Assistant to long-term maintenance

A 10 A smart plug is easy to reason about. This article is about the other end of the range: a 25 A, 6000 W contactor — the kind of circuit that runs an immersion heater, a workshop compressor, or an EV charger. Getting it wrong is not "the lamp stays on", it's a trip and a phone call to an electrician.

I'll cover commissioning it onto your own broker, wiring it into Home Assistant, and the maintenance commands that matter once it's been in a cabinet for a year.

The device is the GemeOpen GSCW1M2P (Smart Contactor 25A-S2 Plus-WiFi). Disclosure up front: I work on these. The config below is what I run on my own bench, and the caveats are the ones our support threads actually generate.


What this device is, and what it isn't

Spec Value
Model GSCW1M2P
Rated current 25 A working
Max load 6000 W resistive
Input voltage 110–260 V AC, wide range
Channels 1
Standby draw under 4 W
Comms 2.4 GHz WiFi, ESP8266
Protocols MQTT, TCP, HTTP
Housing 50 × 110 × 32 mm, V0 flame-retardant PC/ABS
Rated life 5,000,000 mechanical / 50,000 electrical operations

What it is: a contactor you add to an existing distribution board, downstream of your normal overcurrent protection, so a high-power circuit becomes MQTT-controllable without replacing the consumer unit. That's the install pattern in the vendor's own documentation — added into the box, existing breaker stays.

What it is not: a breaker. It has no overcurrent or earth-leakage protection of its own. It switches a circuit; it does not protect one. Keep your MCB/RCBO upstream, and have a qualified electrician do the wiring. I'm not going to pretend this is a plug-in accessory — at 25 A the installation is the project.

Also note the load rating is specified for resistive loads. Motors and transformers have inrush and a worse power factor, so derate accordingly and check the manual before you put a compressor on it.


Commissioning

Step 1 — Get it on the network

Same provisioning flow as the rest of the range: power it up, hold the button about 6 seconds until the LED blinks, join the GemeOpen-xxxx hotspot, and set your 2.4 GHz SSID on the captive page (192.168.4.1). No app account.

2.4 GHz only. A 5 GHz-only network or an IoT VLAN with client isolation will break this.

Step 2 — Point it at your broker

Out of the box it talks to the vendor's test broker. You redirect it with one message, sent through that broker:

{
  "type": "custom",
  "protocol": "mqtt",
  "server": "192.168.1.10",
  "port": "1883",
  "clientId": "ca2b96693fe2",
  "username": "ha",
  "password": "yourpassword",
  "publish": "/workshop/cw1/report",
  "subcribe": "/workshop/cw1/cmd"
}
Enter fullscreen mode Exit fullscreen mode

Then power cycle it. Custom MQTT only takes effect after a restart — saving and walking away does nothing. This is the number one support question about this device, and I have hit it myself at 11pm, so: it's real.

Field-name trap, and it matters more on this device. The examples in this product's own documentation use subcribe — missing the "s" — because the documented firmware is 2.2.0. On firmware 2.3.8 and later the field is spelled subscribe. If your messages vanish into the void, that's the first thing to check. Ask the device what it's running:

{ "type": "info" }
Enter fullscreen mode Exit fullscreen mode

The reply includes version and type. Note the version — you'll want it later.

Step 3 — The setting that actually matters for a contactor

Before you wire anything to it, set the power-on default state:

{ "onState": 1, "type": "setting" }
Enter fullscreen mode Exit fullscreen mode

Three values:

  • 0 — memory: restores the state from before the power cut
  • 1 — off: comes back up de-energised
  • 2 — on: comes back up energised

For a 6 kW circuit, set onState: 1. A power cut at 14:00 should not mean your water heater or your compressor restarts at 14:02 while nobody is home. The docs flag onState: 2 as a safety risk for exactly this reason, and they're right. If you specifically want memory behaviour for, say, a lighting circuit, 0 is there — but not for a high-power load.

Step 4 — Lock it down

Two locks, and for a device sitting in a shared electrical closet both are worth turning on:

{ "keyLock": 1, "messageId": "1", "type": "setting" }
{ "type": "setting", "wifiLock": 1 }
Enter fullscreen mode Exit fullscreen mode

keyLock disables the physical button — the contactor can then only be driven over MQTT/TCP. wifiLock disables long-press re-provisioning, so nobody can walk up to the cabinet and re-point the device at their own network. Both are reversible over the protocol, which is the point: you keep control, they don't get a button.


The command surface

Everything is plain JSON on two topics. Here's the whole thing:

Command Payload you send What comes back
Switch on/off {"key":1,"type":"event"} Full state snapshot, no messageId echo
Set report interval {"timerEnable":1,"timerInterval":60,"type":"setting"} Echoes the two fields
Read live stats {"type":"statistic"} current, voltage, power, energy, key, mac
Read device info {"type":"info"} IP, MAC, firmware, SSID, signal, locks, timer config
Read protocol config {"type":"protocol"} server, port, clientId, topics
Power-on default {"onState":1,"type":"setting"} Full state snapshot
Key lock {"keyLock":1,"messageId":"1","type":"setting"} Full state snapshot + messageId
WiFi lock {"type":"setting","wifiLock":1} Full state snapshot
Custom MQTT {"type":"custom",...} Echoes the config
Custom TCP {"type":"custom","protocol":"tcp",...} Echoes the config
Clear energy {"system":"energy-clear","type":"setting"} {"commandName":"energy-clear","success":true,...}
Restart {"system":"restart","type":"setting"} Echoes
Factory reset {"type":"setting","system":"reset"} Echoes

Two parser gotchas that will bite you:

  1. The telemetry payload has no commandName. The periodic report is just {"current":15.027,"energy":25.047,"key":0,"mac":"ca2b96693fe2","messageId":"timing","power":2921.511,"voltage":226.024} — no command name, and messageId is the literal string "timing", not an echo of anything you sent. If your code dispatches on commandName, it will silently drop every telemetry message.
  2. messageId correlation is inconsistent. Command acks for switching and settings return a full state snapshot without echoing your messageId; energy-clear does echo it. Treat the report topic as a state stream, not as an RPC response channel.

First contact:

mosquitto_sub -h 192.168.1.10 -t '/workshop/cw1/report' -v

mosquitto_pub -h 192.168.1.10 -t '/workshop/cw1/cmd' \
  -m '{"key":1,"type":"event"}'
Enter fullscreen mode Exit fullscreen mode

One message, relay pulls in. That's the entire control protocol.


Application: an off-peak water heater, done properly

This is the use case I'd actually recommend for a 25 A contactor, because it's the one where "the cloud service shut down" stops being an inconvenience.

Circuit: a 3 kW immersion heater on its own RCBO, contactor in series, downstream. Set the reporting interval to something sane — 60 seconds is plenty and keeps the recorder database small:

{ "timerEnable": 1, "timerInterval": 60, "type": "setting" }
Enter fullscreen mode Exit fullscreen mode

Range is 5 to 86400 seconds. Home Assistant config — one switch, four sensors:

mqtt:
  switch:
    - name: "Immersion Heater"
      unique_id: gemeopen_immersion
      state_topic: "/workshop/cw1/report"
      command_topic: "/workshop/cw1/cmd"
      payload_on: '{"key":1,"type":"event"}'
      payload_off: '{"key":0,"type":"event"}'
      state_on: 1
      state_off: 0
      value_template: "{{ value_json.key }}"
      optimistic: false
      qos: 1

  sensor:
    - name: "Immersion Heater Power"
      unique_id: gemeopen_immersion_power
      state_topic: "/workshop/cw1/report"
      value_template: "{{ value_json.power if value_json.power is defined else this.state }}"
      unit_of_measurement: "W"
      device_class: power
      state_class: measurement

    - name: "Immersion Heater Current"
      unique_id: gemeopen_immersion_current
      state_topic: "/workshop/cw1/report"
      value_template: "{{ value_json.current if value_json.current is defined else this.state }}"
      unit_of_measurement: "A"
      device_class: current
      state_class: measurement

    - name: "Immersion Heater Energy"
      unique_id: gemeopen_immersion_energy
      state_topic: "/workshop/cw1/report"
      value_template: "{{ value_json.energy if value_json.energy is defined else this.state }}"
      unit_of_measurement: "kWh"
      device_class: energy
      state_class: total_increasing
Enter fullscreen mode Exit fullscreen mode

The is defined guard on every sensor is not optional here. The report topic carries both the switch acks (which have key but no electrical data) and the telemetry (which has electrical data but no commandName). Without the guard, HA logs a template warning on every toggle.

Now the automations. Off-peak window with a manual enable, plus a current guard:

automation:
  - alias: "Immersion heater on off-peak"
    trigger:
      - platform: time
        at: "00:30:00"
    condition:
      - condition: state
        entity_id: input_boolean.immersion_enabled
        state: "on"
    action:
      - service: switch.turn_on
        target:
          entity_id: switch.immersion_heater

  - alias: "Immersion heater off at peak"
    trigger:
      - platform: time
        at: "07:00:00"
    action:
      - service: switch.turn_off
        target:
          entity_id: switch.immersion_heater

  - alias: "Contactor current guard"
    trigger:
      - platform: numeric_state
        entity_id: sensor.immersion_heater_current
        above: 20
        for: "00:00:30"
    action:
      - service: switch.turn_off
        target:
          entity_id: switch.immersion_heater
      - service: notify.persistent_notification.create
        data:
          message: "Immersion circuit over 20 A for 30 s - contactor opened"
Enter fullscreen mode Exit fullscreen mode

That last automation is a convenience guard, not a protection function. It reacts in seconds; an MCB reacts in milliseconds. If your upstream protection is correctly rated, it should never fire. If it fires regularly, you have a wiring or load problem, not an automation problem.

Cumulative energy survives power loss — only an explicit clear resets it. Feed sensor.immersion_heater_energy into the Energy Dashboard and you get per-circuit cost tracking with nothing leaving the house.


Maintenance

The commands you'll actually use once it's been in a cabinet for a while.

Restart after any config change. Changing broker, topics, static IP or WiFi credentials requires a restart:

{ "system": "restart", "type": "setting" }
Enter fullscreen mode Exit fullscreen mode

The device reconnects to the network and your saved config is retained. The IP address may change — so put a DHCP reservation on it keyed to the MAC, or you'll be re-reading info-all every time it moves.

Clear the energy counter. Irreversible, and it only clears the counter:

{ "system": "energy-clear", "type": "setting" }
Enter fullscreen mode Exit fullscreen mode

Snapshot your long-term data before you run this if you care about history.

Factory reset — the one that costs you a site visit:

{ "type": "setting", "system": "reset" }
Enter fullscreen mode Exit fullscreen mode

This wipes WiFi credentials and custom MQTT. The device goes back to the vendor's test broker, which means the only ways back are: go through the vendor broker with a fresh custom message, or physically long-press it back into provisioning mode. If the device is inside a closed cabinet, factory reset is a ladder-and-screwdriver job. Don't run it casually from an automation.

Do the lifetime math before you deploy. 50,000 electrical operations is the rated figure. At 4 cycles a day that's roughly 34 years. At 100 cycles a day — a compressor cycling on pressure — it's about 16 months. If your application switches frequently, either pick a different device class or plan the replacement.

Budget the standby power. Under 4 W, always on. That's about 35 kWh a year — roughly £10 on a UK tariff, so factor it in when the circuit it controls only runs a few hours a day. For a water heater on a timer it pays for itself immediately; for something you switch twice a week it might not.

Check the firmware occasionally with {"type":"info"} and note the version. Two things have changed across versions that affect you: the subcribe/subscribe field spelling at 2.3.8, and TLS support (see below).

Thermals. 6000 W resistive in a 50 × 110 × 32 mm V0 housing is fine for the device, but the cabinet is your problem. If it's going in a sealed enclosure with other heat-generating gear, derate the load or add ventilation. A contactor that cooks itself is a fire risk, not a bug report.


Honest limitations

  • No MQTT over TLS yet. mqtts support is in development. Inside a trusted LAN, fine. Crossing the internet, tunnel it or use a VPN — and don't put credentials in a plaintext topic on a public broker.
  • No MQTT Discovery. Manual YAML. Tedious, but nothing gets renamed out from under you.
  • No gateway needed, and no app account. If you want one, this is the wrong product.
  • Won't join Mi Home, HiLink or Tuya — that's a design choice, not a gap. It targets your own broker and platforms like Home Assistant, ThingsBoard, or the major cloud IoT services.
  • The vendor console is a test tool, not a production cloud — their own docs say so. Don't build a commercial product on it. Run your own broker.
  • 6000 W is the resistive rating. Motors, transformers and anything with inrush need derating.

Where this fits

If you already run a broker, this is a boring, reliable component: it turns one high-power circuit into a JSON-controllable switch with metering attached, and the data goes to your disk and nowhere else. Commissioning takes about 40 minutes, most of which is the reboot requirement.

If you were hoping for a consumer product with an app, buy something else.

Full command reference, published openly with no sign-up:
https://www.smart-bird.cn/doc/product/GSCW/device/GSCW1M2P/command

Device page: https://www.smart-bird.cn/product/info/GSCW1M2P.html

Questions welcome in the comments — especially if you're running this on EMQX with ACLs, or driving it from Node-RED instead of Home Assistant.


Disclosure: written by the GemeOpen team — we build these devices. Every gotcha above comes from real support threads and my own bench, not a brainstorm. If something behaves differently on your unit, tell me and I'll find out why.

Top comments (0)