Skip to content

MClimate: fix driver defects, add missing encoders, add PIR Mini - #285

Open
MClimate wants to merge 1 commit into
actility:mainfrom
MClimate:mclimate-vendor-update
Open

MClimate: fix driver defects, add missing encoders, add PIR Mini#285
MClimate wants to merge 1 commit into
actility:mainfrom
MClimate:mclimate-vendor-update

Conversation

@MClimate

Copy link
Copy Markdown

What this PR does

Brings the whole vendors/mclimate folder in line with MClimate's own source of truth
(the mclimate-payload-helper package, which drives our backend, our public client
scripts and our docs). The entries were originally imported from our documentation by
Actility, and had drifted.

  • Correctness: fixed wire-level decode/encode defects across 12 drivers.
  • Completeness: every device now has a working encodeDownlink + decodeDownlink
    (10 had none or an error stub) and full command-answer coverage.
  • New device: PIR Mini.
  • Hygiene: dead protocol references, images, metadata.

All 18 drivers pass the catalog harness (DRIVER_PATH=<driver> npx jest driver-examples.spec.js): 213 examples, 0 failures, ESLint clean.


Please review these deliberate output-schema changes

These rename decoded fields, so they are visible to existing ThingPark users. In every
case the new name matches the mclimate-payload-helper package and the decoder we
publish to customers, so the catalog was the outlier.

Driver Before After Why
ht-sensor temperature, humidity sensorTemperature, relativeHumidity (+ thermistorProperlyConnected, extThermistorTemperature) matches package + our public script; values were wrong before (see below)
co2-sensor co2, temperature, humidity, battery CO2, sensorTemperature, relativeHumidity, batteryVoltage same physical product as co2-notifier, which already used these names
co2-display ppm CO2 matches its own sibling co2-display-lite and the package
16a-dry-switch, 16a-switch-power-meter relayState: "ON"/"OFF" relayState: true/false the same field was already a boolean in each driver's own command-answer branch — one field, two types
16a-switch-power-meter energy_kWh, power_W, acVoltage_V, acCurrent_mA energy, power, acVoltage, acCurrent units belong in the model's sensors: ontology, not in field names
open-close-sensor sendEventLater (for cmd 0x1f) notificationBlindTime 0x1f is notification blind time on this device; sendEventLater is the Button's semantic
vicki proportionalAlgoParams, algoType, piMaxIntegratedError, effectiveMotorRange, internalAlgoTempState, and openWindowParams for cmd 0x46 proportionalAlgorithmParameters, temperatureControlAlgorithm (mapped string), maxAllowedIntegralValue, valveOpennessRangeInPercentage, internalAlgoTemporaryState (boolean), openWindowPrecisely package names; 0x46 previously collided with the real openWindowParams from 0x13 and silently overwrote it

Happy to keep any legacy name for backward compatibility if you prefer — say which.


Highest-impact bugs fixed (all were reachable with real payloads)

  1. ht-sensor temperature — the low byte was not zero-padded when building the hex
    string, so any reading whose raw low byte was < 0x10 decoded wildly wrong
    (e.g. −36 °C reported instead of +12 °C). Battery also used a legacy
    2 + nibble*0.1 formula instead of (raw*8+1600)/1000.
  2. fan-coil setFrostProtection transmitted 0x4F — the GET id — so asking the
    device to enable frost protection merely read the setting back. getFrostProtection
    sent 0x4D (get ECM relay). Two more GETs were unreachable behind duplicate case
    labels, and setWatchDogParams threw a TypeError.
  3. wireless-thermostat setTargetTemperature always emitted 0x2E + 2 bytes; the
    device expects 0x2E + 1 byte for integers and 0x50 + 2 bytes for decimals. The
    shipped example asserted the wrong frame, so tests passed.
  4. 16A Switch & Power Meter ignored the temperature sign bit (+133 °C instead of
    −5 °C
    ) and encoded setOvervoltageThresholds/setOverpowerThreshold one byte
    short (3680 W was emitted as a non-byte value).
  5. Keepalive strip lengthsco2-display stripped 8 bytes instead of 11,
    co2-display-lite 8 instead of 10, ht-pir-lite 7 instead of 6. Leaked bytes were
    re-parsed as commands, fabricating fields (keepAliveTime: null) or truncating the
    last argument of every command answer.
  6. vicki decodeDownlink was defined but never exported — the one red test in the
    catalog today. Plus reason reported 81 instead of 129 on the 0x81 keepalive,
    two status bits were missing, tempHysteresis lacked /10, and the open-window
    delta nibble decoded as NaN for values ≥ 10.
  7. flood-sensor had 4 of 5 message reasons — a fraud event (0x80) decoded with no
    reason at all — and treated any payload > 3 bytes as a long keepalive, so command
    answers became garbage.
  8. PIR-lite drivers offered PIR downlinks their firmware does not implement
    (0x3c, 0x48, 0x4a, 0x4c) while missing the real ones. Their shipped downlink
    example demonstrated a command the device would reject.

New device

PIR Mini (mclimate:pir-mini:1) — occupancy sensor with temperature, humidity and
light. Decoding verified field-for-field against our published API documentation, and
covers all three light-sensor states (reading / disabled / sensor error) plus the
0x3a/0x3b/0x40 occupancy and trigger events. 27 encodable commands.

Image supplied by MClimate and converted to the required 256×256 PNG.


Regional availability (EU868 fix)

Customer feedback said many MClimate devices could not be selected on EU868. Auditing
every model's deviceProfileIds against the ISM bands its profiles declare showed six
Class A models with no EU868 profile at all — only us915,au915 (RFGroup2) and
as923 (RFGroup4):

  • co2-display, co2-display-lite, co2-pir-lite, ht-pir-lite, multipurpose-button,
    wireless-thermostat

All six now also list mclimate_RFGroup1_1.0.3a_classA (eu868, plus cn779/in865/eu433/
kr920/ru864). All 18 models now cover EU868, and no model mixes LoRaWAN classes.

The 16A Dry Switch, 16A Switch & Power Meter and Fan Coil Thermostat are Class C devices
and already carried the EU868 Class C profile, so they needed no change.

The two 16A relays were also EU868-only; they now carry
mclimate_RFGroup2_1.0.3a_classC (us915, au915) and mclimate_RFGroup4_1.0.3a_classC
(as923). Every actively sold model now covers eu868 + us915/au915 + as923, each with
the LoRaWAN class its hardware actually uses (Class C for the two relays and the Fan Coil
Thermostat, Class A for everything else).


Discontinued product: AQI Sensor & Notifier

MClimate has stopped selling and supporting the Air Quality Sensor & Notifier (AQI).
models/aqi-sensor/model.yaml is therefore flagged deprecated: true, and its
exclusive profile MCLI/AQSORA.1.0.3a_ETSI carries distinction: (deprecated) per the
procedure in template/README.md#deprecation.

We have kept drivers/aqi-sensor/ on purpose: units already deployed on ThingPark
still send uplinks, and deleting the codec would leave those devices undecodable. The
deprecation flag stops new onboarding; please remove the model in a future release once
you are satisfied no active devices depend on it, and we will drop the driver and profile
in a follow-up.


Vendor entry

vendor.yaml now carries the full company record, and the display name is corrected from
Mclimate to MClimate (the brand is capitalised with a capital C).


Requests for Actility

  1. Duplicate model — co2-sensor and co2-notifier are the same product
    ("CO2 Sensor and Notifier"). They were imported twice, with different modelIds,
    different profiles and incompatible field names. Since modelId/protocolId are
    immutable we have not merged them; instead both now share an identical codec and
    consistent metadata. Please advise how to retire one (the catalog documents a
    deprecation path for device profiles but not for models). Our preference is to keep
    mclimate:co2-notifier:1 and deprecate mclimate:co2-sensor:1.
  2. LoRaWANCertified flags — currently true only for 16A Dry Switch,
    Multipurpose Button and Open/Close Sensor; everything else is false, including
    Vicki (which has an assigned lorawanDeviceProfileID 03380003). These were filled
    in bulk from the April 2026 LoRa Alliance list. We would like to reconcile them
    against the registry — could you confirm what your import produced?
  3. vendor.yaml — completed: corrected display name (MClimate), full company
    description, website, products page and public contact.

Not in this PR (tracked separately)

  • extractPoints.js per driver, to feed ThingPark X IoT Flow "points". Note that the
    examples Actility authored already carry points: blocks for six drivers, which the
    harness will start asserting as soon as an extractPoints.js exists — so this needs
    points added to every uplink example and is best done as its own change.
  • Upstream fixes in mclimate-payload-helper found during this work (these are in our
    package, not in the catalog): the AQI parser returns NaN after a refactor; the
    shared command reader mishandles two Wireless Thermostat responses (0x56 has no
    branch, 0x5e is misdecoded as device time); the HT Display Lite parser has the same
    lux zero-padding bug that ht-sensor had for temperature.

How to verify locally

cd tests && npm install
DRIVER_PATH=../vendors/mclimate/drivers/vicki npx jest driver-examples.spec.js

(Works on macOS too, despite the prerequisites note in
template/sample-vendor/drivers/README.md.)


🤖 Generated with Claude Code

Aligns vendors/mclimate with MClimate's own source of truth (the
mclimate-payload-helper package that drives our backend, public client
scripts and documentation). The entries were originally imported from our
docs by Actility and had drifted.

Correctness (12 drivers):
- ht-sensor: temperature low byte was not zero-padded, so readings whose
  raw low byte was < 0x10 decoded wildly wrong (-36 C instead of +12 C);
  battery used a legacy formula instead of (raw*8+1600)/1000; external
  thermistor bytes were ignored.
- fan-coil: setFrostProtection transmitted 0x4F (the GET id) so enabling
  frost protection only read it back; getFrostProtection sent 0x4D (get
  ECM relay); two GETs were unreachable behind duplicate case labels;
  setWatchDogParams threw; four response scalings were wrong (0x6d was
  100x off).
- wireless-thermostat: setTargetTemperature always emitted 0x2E + 2 bytes;
  the device expects 0x2E + 1 byte for integers and 0x50 + 2 bytes for
  decimals. The shipped example asserted the wrong frame.
- 16a-switch-power-meter: internal temperature ignored the sign bit
  (+133 C instead of -5 C); two thresholds were encoded a byte short.
- co2-display, co2-display-lite, ht-pir-lite: wrong keepalive strip
  lengths leaked payload bytes into command parsing.
- vicki: decodeDownlink was never exported (the one failing example in the
  catalog today), plus six decode fixes and four encoder fixes.
- flood-sensor: fraud events decoded without a reason; command answers
  were misread as keepalives.
- open-close-sensor: command 0x1f was mislabelled sendEventLater.

Completeness:
- Every device now has a working encodeDownlink and decodeDownlink; ten
  had none or an error stub. Commands are derived from the package command
  classes (t-valve 29, co2-display 46, co2-notifier 25, button 18, ...).
- Full command-answer coverage; decoders return {errors:[...]} per TS013
  instead of throwing or emitting NaN.
- PIR-lite drivers offered PIR commands their firmware does not implement;
  replaced with the documented set.
- Removed dead DataCake/Milesight/ChirpStack wrapper functions.
- Examples grew from 44 to 213 across 18 drivers; all pass.

New device:
- PIR Mini (mclimate:pir-mini:1), verified against our published API docs.

Regional availability:
- Six models had no EU868 profile at all; the two 16A relays were
  EU868-only. Every actively sold model now covers eu868, us915/au915 and
  as923 with the class its hardware uses.

Housekeeping:
- AQI Sensor & Notifier is discontinued: model flagged deprecated and its
  profile marked (deprecated). The driver is kept so units already in the
  field still decode.
- Removed dangling os1-mcli protocolIds from seven models.
- All device images normalised to 256x256; three empty sensor lists
  filled from the Actility ontology.
- vendor.yaml: corrected the display name to MClimate, added a full
  description, website, products page and contact.

Some decoded field names changed to match the package and our published
codecs (details in the pull request description).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants