WS7 digital input module: Zigbee protocol details
Zigbee protocol description of the JetHome WS7 device for integration with Zigbee coordinators.
See also
Three-channel Zigbee Discrete Input Module JetHome WS7 - device description, installation and operation.
Compatibility of JetHome Zigbee devices with various ecosystems - Zigbee stacks compatibility table.
Table of Contents
Device identification
Field |
Value |
|---|---|
Vendor |
|
Model |
|
|
|
|
|
Profile ID |
|
Device ID (on all endpoints) |
|
HW Version ( |
|
Stack Version ( |
|
ZCL Version ( |
|
Power Source ( |
|
Zigbee device type |
End Device (sleepy) |
Touchlink |
unsupported |
Green Power |
unsupported |
Important
Matching in the driver is only on the pair manufacturerName + modelIdentifier. Device ID 0x00FF is too generic to rely on.
Full set of cluster attributes genBasic (0x0000) on EP1
Attr ID |
Name |
Type |
Access |
Value |
|---|---|---|---|---|
|
ZCLVersion |
|
R |
|
|
ApplicationVersion |
|
R |
current |
|
StackVersion |
|
R |
|
|
HWVersion |
|
R |
|
|
ManufacturerName |
|
R |
|
|
ModelIdentifier |
|
R |
|
|
DateCode |
|
R |
|
|
PowerSource |
|
R |
|
|
PhysicalEnvironment |
|
R/W |
unspecified |
|
SWBuildID |
|
R |
consistent with |
|
ClusterRevision |
|
R |
|
Attention
On the ZCL spec sheet SWBuildID (0x4000) - octet string. On WS7 it is declared as uint8. Decoder needs to consider uint8 type when reading.
Note
The server command Reset to Factory Defaults is supported.
Endpoint map
WS7 declares three endpoints physically corresponding to three symmetric discrete inputs.
Endpoint |
Alias |
Destination |
|---|---|---|
|
|
Main: Basic, PowerCfg, DeviceTempCfg, Groups, MultistateInput for input #1; OUT - OnOff, OTA |
|
|
MultistateInput for input #2; OUT - OnOff |
|
|
MultistateInput for input #3; OUT - OnOff |
Note
All three endpoints use Profile = 0x0104, DeviceID = 0x00FF.
Note
The device physically has a service button for commissioning, factory reset, and switching the mode of inputs. This button does not send ZCL messages over the network - the coordinator will not receive events from it.
Clusters
Endpoint 1 (main)
Server (Input)
Cluster ID |
Name |
Destination |
|---|---|---|
|
Basic |
standard identification attributes (see WS7 digital input module: Zigbee protocol details) |
|
Power Configuration |
battery condition, reportable |
|
Device Temperature Configuration |
reportable is declared, but value is always 0 - measurement is not implemented |
|
Groups |
standard |
|
Multistate Input (Basic) |
input event #1 |
Client (Output) - the device sends commands outward
Cluster ID |
Name |
When |
|---|---|---|
|
On/Off |
always |
|
OTA Upgrade |
always |
Important
Cluster Identify (0x0003) missing - not declared in Simple Descriptor. Do not use Identify / IdentifyQuery.
Endpoint 2 / Endpoint 3
Symmetrical, differing only in the binding to the physical input.
Side |
Cluster ID |
Name |
|---|---|---|
Server (In) |
|
Multistate Input (Basic) - events of input No. 2 (EP2) / input No. 3 (EP3) |
Client (Out) |
|
On/Off |
Summary diagram of directions
Coordinator WS7
│ │
│ ◄── 0x0006 OnOff (Toggle/On/Off) от EP1/EP2/EP3 ────────┤ (только при наличии binding)
│ ◄── 0x0012 MultistateInput.Report от EP1/EP2/EP3 ───────┤ (всегда unicast на 0x0000:1)
│ ◄── 0x0001 PowerCfg.Report от EP1 ──────────────────────┤ (раз в час)
│ ──► 0x0000 Basic Read attrs ────────────────────────────┤
│ ◄──► 0x0019 OTA Upgrade ───────────────────────────────┤
▼ ▼
Input logic: two modes of operation
CLICK and HOLD modes
Each of the three inputs operates in one of the two modes common to all three inputs:
Mode |
Default |
Which events are recognized |
|---|---|---|
CLICK |
yes |
single and double click |
HOLD |
— |
pressing edge and releasing edge |
Note
The current mode is stored in the device’s non-volatile memory and survives a factory reset. After factory reset, the mode returns to CLICK.
Mode switching
Switching - double click on the service button of the device. Confirmation:
CLICK→ green LED flashes 1 time;HOLD→ the green LED flashes 2 times.
Attention
** No mode change event is sent over the network.** The coordinator will not be notified of the mode change. The current mode can be recognized only indirectly - by the type of the first events received.
Decoding genMultistateInput.PresentValue (attr 0x0055)
The attribute uint16, takes only four values:
Value |
Semantics |
In which mode comes |
Trigger |
|---|---|---|---|
|
release |
HOLD |
releasing (opening) the input |
|
single |
CLICK |
single click |
|
double |
CLICK |
double click |
|
hold |
HOLD |
pressing (shorting) the input |
The channel (input #1 / #2 / #3) is defined by the endpoint field of the incoming ZCL frame (1 → in1, 2 → in2, 3 → in3).
Parallel channel - outgoing OnOff (cluster 0x0006)
In addition to the Multistate Input report, for some events the device additionally sends a command to genOnOff. The addressing route is binding → groups → fallback:
Binding (
addrMode = AddrNotPresent) is the primary path. The device uses its own binding table.Groups - the same command (
AddrGroup) is additionally sent for each group the endpoint is a member of.Coordinator (fallback) -
afAddr16Bit,shortAddr = 0x0000,endpoint = 1. But this fallback only receives Multistate Input report; OnOff command is NOT sent to the coordinator via fallback path.
Matching events and commands
Event |
Mode |
OnOff command ( |
Multistate ( |
|---|---|---|---|
single |
CLICK |
Toggle (cmd |
|
double |
CLICK |
(not sending) |
|
hold (press) |
HOLD |
On (cmd |
|
release |
HOLD |
Off (cmd |
|
Important
For the coordinator to receive OnOff commands from the device, must be ZDO Bind cluster 0x0006 for each of EP1 / EP2 / EP3 to the coordinator. Without binding, OnOff commands will not reach the coordinator.
Multistate Input comes to the coordinator always, without binding - the device sends it by unicast to 0x0000:1.
It is not possible to control the device via OnOff
The device ignores incoming OnOff commands from the coordinator (callback is empty). Any attempt to control via On / Off / Toggle to WS7 is bypassed. OnOff on WS7 is a sender-only channel.
Processing timings
Parameter |
Value |
|---|---|
Contact debouncing |
5 ms |
Long press (for inputs) |
2000 ms. |
Double-click window |
200 ms |
Long press of the service button |
5000 ms |
Power Configuration / Battery
Attributes on EP1, cluster 0x0001
Attr ID |
Name |
Type |
Access |
Semantics |
|---|---|---|---|---|
|
BatteryVoltage |
|
R + Reportable |
tenths of a volt ( |
|
BatteryPercentageRemaining |
|
R + Reportable |
ZCL format: 1 = 0.5 %, |
Note
The attributes batteryAlarmState (0x003E), batteryAlarmMask, batteryRatedVoltage, batterySize not declared - the device does not send them and does not respond to read them.
Driver-side decoding
voltage_V = batteryVoltage / 10.0 # uint8 30 → 3.0 V
voltage_mV = batteryVoltage * 100 # → 3000 mV
percent = batteryPercentageRemaining / 2.0 # 200 → 100 %
This matches the standard behavior of Battery 1 from ZCL r8. WS7 doesn’t have a “don’t divide percentage by 2” override - division is mandatory.
The internal formula for the percentage
The device converts the voltage to a percentage linearly over the range 2.0 V … 3.0 V:
если V >= 2.0 V:
pct = clamp((V - 2.0) * 100 / 1.0, 0, 100)
иначе:
pct = 0
That is voltage = 2.0 V → 0 %, voltage = 3.0 V → 100 %. Over the network it is already pct * 2 to BatteryPercentageRemaining.
Report period
Trigger |
Period |
|---|---|
Regular timer |
60 minutes (fixed) |
After starting the device |
once right after init |
Short click of the service button on the network |
when pressed (also OTA query) |
After rejoin |
5 seconds after reconnection |
Warning
Configure Reporting from the coordinator does not affect the battery report frequency. The period (60 minutes) is set internally and is not reconfigurable. The command Configure Reporting is received by the stack without error, but the actual period remains the same. It is possible to send Configure Reporting (for compatibility with the standard interview procedure), but it is not possible to expect the period to change.
Note
The report always contains both attributes (0x0020 + 0x0021) one frame Report Attributes from EP1 to 0x0000:1.
Device Temperature Configuration (cluster 0x0002, EP1)
Attr ID |
Name |
Type |
Access |
Value |
|---|---|---|---|---|
|
CurrentTemperature |
|
R + Reportable |
Always 0 |
The attribute is declared, but the chip measurement is not implemented - the value never changes.
Tip
Driver: can be ignored or shown as “not implemented”. Do not pull in e.temperature() - this will mislead the user.
OTA Upgrade (cluster 0x0019, EP1, client side)
Attributes (all R | Cli)
Attr ID |
Name |
Type |
Initial value |
|---|---|---|---|
|
UpgradeServerID |
|
|
|
FileOffset |
|
|
|
CurrentFileVersion |
|
current version ( |
|
CurrentZigBeeStackVersion |
|
|
|
DownloadedFileVersion |
|
|
|
DownloadedZigBeeStackVersion |
|
|
|
ImageUpgradeStatus |
|
|
|
ManufacturerID |
|
|
|
ImageTypeID |
|
|
|
MinimumBlockPeriod |
|
|
|
ClusterRevision |
|
|
OTA request triggers
The device itself initiates an OTA check (Query Next Image Request):
right after a successful match;
when the service button is clicked briefly if the device is online.
Parameters for the coordinator’s OTA server
Parameter |
Value |
|---|---|
|
|
|
|
Minimum new |
|
Update Information API |
fw.jethome.com/api/devices/ws7/info - Returns JSON with a list of available versions and image URLs |
Mirror (used in zigbee-OTA) |
OTA stream - standard ZCL: Query Next Image Request → Query Next Image Response → Image Block Request / Image Block Response → Upgrade End Request → Upgrade End Response. There are no Vendor extensions.
Pairing, factory reset, rejoin
WS7 is an End Device, so it requires a router/coordinator parent in the radius.
Pairing (logging on to the network)
Step |
User Action |
Device behavior |
|---|---|---|
1 |
The coordinator is being transferred to |
— |
2 |
Hold down the service button for ≥ 5 seconds outside the network |
Red LED blinks for 30 seconds. NWK Steering is started. |
3 |
— |
The device searches for a network, makes a connection, and stops blinking if successful. |
4 |
— |
Starts periodic battery metering (60 min). |
Important
There is no option “permit_join → device joins automatically”. Pairing requires a physical long-press of 5 seconds.
Factory reset - two ways
Long network hold - service button ≥ 5 seconds: red LED 1 second, clearing NV data, local leave, program reboot.
Power-on hold - if the button is pressed during power-on: red LED ≈ 1 second, clearing NV data.
Note
After factory reset, the input mode is reset to CLICK (default value).
Rejoin
When a parent device is lost, WS7 puts a rejoin event and tries to restore the network. Upon success:
5 seconds later a fresh battery report comes in;
pending input events are replayed;
battery metering is rescheduled to the standard 60-minute cycle.
What the coordinator driver is supposed to do
Minimum package of actions after the interview:
Read Basic attributes on EP1:
0x0004(ManufacturerName),0x0005(ModelIdentifier),0x0003(HWVersion),0x0001(ApplicationVersion),0x4000(SWBuildID),0x0006(DateCode).Run ZDO Bind for outgoing device clusters so that the user receives all events:
src=device, srcEP=1, cluster=0x0006, dst=coord, dstEP=1 src=device, srcEP=2, cluster=0x0006, dst=coord, dstEP=1 src=device, srcEP=3, cluster=0x0006, dst=coord, dstEP=1 src=device, srcEP=1, cluster=0x0001, dst=coord, dstEP=1
Optionally - send
Configure ReportingforgenPowerCfg(batteryVoltage,batteryPercentageRemaining). The command will be accepted, but frequency will not change - the real period is set in the device (60 minutes).Do not try to do
Configure ReportingforgenMultistateInputorgenOnOff- reports go outside the reporting manager.Do not attempt to control the device via incoming OnOff - commands are ignored.
Do not use Identify (
0x0003) - no cluster.Multistate Input accept as
attributeReport/readResponseon cluster0x0012, attr0x0055, without bind (comes unicast to0x0000:1).
Event decoding (recommended model)
Canonical table
EP |
|
Mode |
OnOff (if bind) |
Name |
|---|---|---|---|---|
1 |
|
CLICK |
Toggle |
|
1 |
|
CLICK |
— |
|
1 |
|
HOLD |
On |
|
1 |
|
HOLD |
Off |
|
2 |
|
CLICK |
Toggle |
|
2 |
|
CLICK |
— |
|
2 |
|
HOLD |
On |
|
2 |
|
HOLD |
Off |
|
3 |
|
CLICK |
Toggle |
|
3 |
|
CLICK |
— |
|
3 |
|
HOLD |
On |
|
3 |
|
HOLD |
Off |
|
Decoder pseudo-code
on attribute_report(cluster, attr, value, endpoint):
if cluster == 0x0001: # genPowerCfg
if attr == 0x0021 and value < 255:
publish("battery", value / 2.0) # %
if attr == 0x0020 and value < 255:
publish("voltage", value * 100) # mV
return
if cluster == 0x0012 and attr == 0x0055: # genMultistateInput
action_map = {0: "release", 1: "single", 2: "double", 4: "hold"}
if value not in action_map:
return # WS7 других значений не шлёт
ep_map = {1: "in1", 2: "in2", 3: "in3"}
publish("action", f"{action_map[value]}_{ep_map[endpoint]}")
return
on command_received(cluster, command, endpoint):
if cluster == 0x0006: # genOnOff (через bind)
ep_map = {1: "in1", 2: "in2", 3: "in3"}
cmd_map = {0x00: "off", 0x01: "on", 0x02: "toggle"}
publish("onoff_command", f"{cmd_map[command]}_{ep_map[endpoint]}")
Test cases for driver validation
Identification.
manufacturerName="JetHome",modelIdentifier="WS7",hwVersion=2, profile0x0104, deviceID0x00FF. Matching only on the pairmanufacturerName+modelIdentifier.EP card. Active EP will return
[1, 2, 3]. Simple Descriptor EP1 contains in:[0x0000, 0x0001, 0x0002, 0x0004, 0x0012], out:[0x0006, 0x0019]. EP2 / EP3: in[0x0012], out[0x0006].Identify does not respond. Cluster
0x0003commands go unanswered (orDefault Response: UNSUP_CLUSTER_COMMAND).Battery init. Immediately after pairing comes
Report Attributesfrom EP1 to cluster0x0001with both attributes (0x0020and0x0021) in one frame.Battery period. The next battery report is in ~60 minutes.
Configure Reportingtemp does not change.CLICK / single on EP=1. Single click:
Report Attributescluster0x0012attr0x0055value=1from EP=1; plus (if bind to0x0006)Togglefrom EP=1. Decoder outputssingle_in1.CLICK / double on EP=2. Double click:
presentValue=2from EP=2; OnOff does not come. Decoder outputsdouble_in2.HOLD / press+release on EP=3. Press →
presentValue=4+ (bind)Onfrom EP=3 →hold_in3. Release →presentValue=0+ (bind)Offfrom EP=3 →release_in3.Triple click is not possible. Three consecutive clicks (with < 200ms interval between the 2nd and 3rd) will give only one event
double- the third click is ignored.Switch mode CLICK ↔ HOLD. Double-click the service button: green LED 1 or 2 times. No event is delivered over the network. After reboot the mode is saved.
Factory reset → CLICK. After long-pressing 5 sec or holding at boot, the mode is reset to
CLICK.Incoming OnOff is ignored. Send
On/Off/Togglefrom coordinator to EP1 cluster0x0006. No response from the device.OTA Query. On a short click of the service button, the device sends
Query Next Image RequestfrommanufacturerCode=0xF123,imageType=0xF001.Pairing. Long-press 5 sec off-net → NWK Steering → join (if coordinator in
permit_join).Rejoin. After loss of parent: device reports rejoin, 5 sec after recovery comes fresh battery report + pending input events.
Z2m converter template
import * as fz from "../converters/fromZigbee";
import * as exposes from "../lib/exposes";
import * as reporting from "../lib/reporting";
import type {DefinitionWithExtend, Fz} from "../lib/types";
import * as utils from "../lib/utils";
const e = exposes.presets;
const ws7 = {
fz: {
action: {
cluster: "genMultistateInput",
type: ["attributeReport", "readResponse"],
convert: (model, msg, publish, options, meta) => {
const map: Record<number, string> = {
0: "release", 1: "single", 2: "double", 4: "hold",
};
const action = map[msg.data.presentValue];
if (!action) return;
return {action: utils.postfixWithEndpointName(action, msg, model, meta)};
},
} satisfies Fz.Converter<"genMultistateInput", undefined, ["attributeReport", "readResponse"]>,
},
};
export const definitions: DefinitionWithExtend[] = [
{
fingerprint: [{modelID: "WS7", manufacturerName: "JetHome"}],
model: "WS7",
vendor: "JetHome",
description: "3-channel battery discrete input module (CLICK / HOLD modes)",
fromZigbee: [fz.battery, fz.command_toggle, fz.command_on, fz.command_off, ws7.fz.action],
toZigbee: [],
ota: true,
meta: {multiEndpoint: true},
endpoint: () => ({in1: 1, in2: 2, in3: 3}),
exposes: [
e.battery(),
e.battery_voltage(),
e.action([
"release_in1", "single_in1", "double_in1", "hold_in1",
"release_in2", "single_in2", "double_in2", "hold_in2",
"release_in3", "single_in3", "double_in3", "hold_in3",
]),
],
configure: async (device, coordinatorEndpoint) => {
const ep1 = device.getEndpoint(1);
await reporting.bind(ep1, coordinatorEndpoint, ["genPowerCfg", "genOnOff"]);
await reporting.batteryVoltage(ep1);
await reporting.batteryPercentageRemaining(ep1);
const ep2 = device.getEndpoint(2);
await reporting.bind(ep2, coordinatorEndpoint, ["genOnOff"]);
const ep3 = device.getEndpoint(3);
await reporting.bind(ep3, coordinatorEndpoint, ["genOnOff"]);
},
},
];
ZHA quirk template
class JetHomeWS7(CustomDevice):
signature = {
MODELS_INFO: [("JetHome", "WS7")],
ENDPOINTS: {
1: {
PROFILE_ID: 0x0104,
DEVICE_TYPE: 0x00FF,
INPUT_CLUSTERS: [0x0000, 0x0001, 0x0002, 0x0004, 0x0012],
OUTPUT_CLUSTERS: [0x0006, 0x0019],
},
2: {
PROFILE_ID: 0x0104,
DEVICE_TYPE: 0x00FF,
INPUT_CLUSTERS: [0x0012],
OUTPUT_CLUSTERS: [0x0006],
},
3: {
PROFILE_ID: 0x0104,
DEVICE_TYPE: 0x00FF,
INPUT_CLUSTERS: [0x0012],
OUTPUT_CLUSTERS: [0x0006],
},
},
}
replacement = signature
In the MultistateInputCluster.attribute_updated handler, decode presentValue ( {0, 1, 2, 4} only ) to zha_event (button_<ep>_<action>).
References
Manufacturer: jethome.com
Update Information API (JSON): fw.jethome.com/api/devices/ws7/info
OTA Index (Z2M): github.com/Koenkk/zigbee-OTA
ZCL Standard: ZigBee Cluster Library Specification, ZCL r8 - Clusters
0x0000Basic,0x0001Power Configuration,0x0002Device Temperature Configuration,0x0004Groups,0x0006On/Off,0x0012Multistate Input (Basic),0x0019OTA Upgrade.