From 1029202848f4c2987f0f056abf2d81e210ed0c54 Mon Sep 17 00:00:00 2001
From: functionpointer <suspendfunction@gmail.com>
Date: Thu, 27 Feb 2025 19:28:12 +0100
Subject: [PATCH] [mlx90393] Fix inverted gain and resolution. Expose
 temperature_compensation and hallconf. (#7635)

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
---
 esphome/components/mlx90393/sensor.py         | 86 ++++++++++++-------
 .../components/mlx90393/sensor_mlx90393.cpp   |  4 +
 esphome/components/mlx90393/sensor_mlx90393.h | 11 ++-
 tests/components/mlx90393/common.yaml         |  7 +-
 .../mlx90393/test.esp32-s3-ard.yaml           |  5 ++
 .../mlx90393/test.esp32-s3-idf.yaml           |  5 ++
 6 files changed, 80 insertions(+), 38 deletions(-)
 create mode 100644 tests/components/mlx90393/test.esp32-s3-ard.yaml
 create mode 100644 tests/components/mlx90393/test.esp32-s3-idf.yaml

diff --git a/esphome/components/mlx90393/sensor.py b/esphome/components/mlx90393/sensor.py
index fe01d8ebfc..cb9cb84aae 100644
--- a/esphome/components/mlx90393/sensor.py
+++ b/esphome/components/mlx90393/sensor.py
@@ -1,20 +1,21 @@
+from esphome import pins
 import esphome.codegen as cg
-import esphome.config_validation as cv
 from esphome.components import i2c, sensor
+import esphome.config_validation as cv
 from esphome.const import (
+    CONF_FILTER,
+    CONF_GAIN,
     CONF_ID,
-    UNIT_MICROTESLA,
-    UNIT_CELSIUS,
-    STATE_CLASS_MEASUREMENT,
+    CONF_OVERSAMPLING,
+    CONF_RESOLUTION,
+    CONF_TEMPERATURE,
+    CONF_TEMPERATURE_COMPENSATION,
     ICON_MAGNET,
     ICON_THERMOMETER,
-    CONF_GAIN,
-    CONF_RESOLUTION,
-    CONF_OVERSAMPLING,
-    CONF_FILTER,
-    CONF_TEMPERATURE,
+    STATE_CLASS_MEASUREMENT,
+    UNIT_CELSIUS,
+    UNIT_MICROTESLA,
 )
-from esphome import pins
 
 CODEOWNERS = ["@functionpointer"]
 DEPENDENCIES = ["i2c"]
@@ -26,30 +27,46 @@ MLX90393Component = mlx90393_ns.class_(
 )
 
 GAIN = {
-    "1X": 7,
-    "1_33X": 6,
-    "1_67X": 5,
-    "2X": 4,
-    "2_5X": 3,
-    "3X": 2,
-    "4X": 1,
-    "5X": 0,
+    "1X": 0,
+    "1_25X": 1,
+    "1_67X": 2,
+    "2X": 3,
+    "2_5X": 4,
+    "3X": 5,
+    "3_75X": 6,
+    "5X": 7,
 }
 
 RESOLUTION = {
-    "16BIT": 0,
-    "17BIT": 1,
-    "18BIT": 2,
-    "19BIT": 3,
+    "DIV_8": 3,
+    "DIV_4": 2,
+    "DIV_2": 1,
+    "DIV_1": 0,
 }
 
 CONF_X_AXIS = "x_axis"
 CONF_Y_AXIS = "y_axis"
 CONF_Z_AXIS = "z_axis"
 CONF_DRDY_PIN = "drdy_pin"
+CONF_HALLCONF = "hallconf"
 
 
-def mlx90393_axis_schema(default_resolution: str):
+def _validate(config):
+    if config[CONF_TEMPERATURE_COMPENSATION]:
+        for axis in [CONF_X_AXIS, CONF_Y_AXIS, CONF_Z_AXIS]:
+            if axis not in config:
+                continue
+            if (res := config[axis][CONF_RESOLUTION]) in [
+                "DIV_8",
+                "DIV_4",
+            ]:
+                raise cv.Invalid(
+                    f"{axis}: {CONF_RESOLUTION} cannot be {res} with {CONF_TEMPERATURE_COMPENSATION} enabled"
+                )
+    return config
+
+
+def mlx90393_axis_schema():
     return sensor.sensor_schema(
         unit_of_measurement=UNIT_MICROTESLA,
         accuracy_decimals=0,
@@ -58,7 +75,7 @@ def mlx90393_axis_schema(default_resolution: str):
     ).extend(
         cv.Schema(
             {
-                cv.Optional(CONF_RESOLUTION, default=default_resolution): cv.enum(
+                cv.Optional(CONF_RESOLUTION, default="DIV_4"): cv.enum(
                     RESOLUTION, upper=True, space="_"
                 )
             }
@@ -66,19 +83,19 @@ def mlx90393_axis_schema(default_resolution: str):
     )
 
 
-CONFIG_SCHEMA = (
+CONFIG_SCHEMA = cv.All(
     cv.Schema(
         {
             cv.GenerateID(): cv.declare_id(MLX90393Component),
-            cv.Optional(CONF_GAIN, default="2_5X"): cv.enum(
-                GAIN, upper=True, space="_"
-            ),
+            cv.Optional(CONF_GAIN, default="1X"): cv.enum(GAIN, upper=True, space="_"),
             cv.Optional(CONF_DRDY_PIN): pins.gpio_input_pin_schema,
-            cv.Optional(CONF_OVERSAMPLING, default=2): cv.int_range(min=0, max=3),
+            cv.Optional(CONF_OVERSAMPLING, default=0): cv.int_range(min=0, max=3),
             cv.Optional(CONF_FILTER, default=6): cv.int_range(min=0, max=7),
-            cv.Optional(CONF_X_AXIS): mlx90393_axis_schema("19BIT"),
-            cv.Optional(CONF_Y_AXIS): mlx90393_axis_schema("19BIT"),
-            cv.Optional(CONF_Z_AXIS): mlx90393_axis_schema("16BIT"),
+            cv.Optional(CONF_X_AXIS): mlx90393_axis_schema(),
+            cv.Optional(CONF_Y_AXIS): mlx90393_axis_schema(),
+            cv.Optional(CONF_Z_AXIS): mlx90393_axis_schema(),
+            cv.Optional(CONF_TEMPERATURE_COMPENSATION, default=False): bool,
+            cv.Optional(CONF_HALLCONF, default=0xC): cv.one_of(0xC, 0x0),
             cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
                 unit_of_measurement=UNIT_CELSIUS,
                 accuracy_decimals=1,
@@ -96,7 +113,8 @@ CONFIG_SCHEMA = (
         },
     )
     .extend(cv.polling_component_schema("60s"))
-    .extend(i2c.i2c_device_schema(0x0C))
+    .extend(i2c.i2c_device_schema(0x0C)),
+    _validate,
 )
 
 
@@ -111,6 +129,8 @@ async def to_code(config):
     cg.add(var.set_gain(GAIN[config[CONF_GAIN]]))
     cg.add(var.set_oversampling(config[CONF_OVERSAMPLING]))
     cg.add(var.set_filter(config[CONF_FILTER]))
+    cg.add(var.set_temperature_compensation(config[CONF_TEMPERATURE_COMPENSATION]))
+    cg.add(var.set_hallconf(config[CONF_HALLCONF]))
 
     if CONF_X_AXIS in config:
         sens = await sensor.new_sensor(config[CONF_X_AXIS])
diff --git a/esphome/components/mlx90393/sensor_mlx90393.cpp b/esphome/components/mlx90393/sensor_mlx90393.cpp
index d4431a7334..e86080fe9c 100644
--- a/esphome/components/mlx90393/sensor_mlx90393.cpp
+++ b/esphome/components/mlx90393/sensor_mlx90393.cpp
@@ -43,6 +43,10 @@ void MLX90393Cls::setup() {
   this->mlx_.setDigitalFiltering(this->filter_);
 
   this->mlx_.setTemperatureOverSampling(this->temperature_oversampling_);
+
+  this->mlx_.setTemperatureCompensation(this->temperature_compensation_);
+
+  this->mlx_.setHallConf(this->hallconf_);
 }
 
 void MLX90393Cls::dump_config() {
diff --git a/esphome/components/mlx90393/sensor_mlx90393.h b/esphome/components/mlx90393/sensor_mlx90393.h
index 8dfb7e6a13..479891a76c 100644
--- a/esphome/components/mlx90393/sensor_mlx90393.h
+++ b/esphome/components/mlx90393/sensor_mlx90393.h
@@ -29,7 +29,10 @@ class MLX90393Cls : public PollingComponent, public i2c::I2CDevice, public MLX90
   void set_resolution(uint8_t xyz, uint8_t res) { resolutions_[xyz] = res; }
   void set_filter(uint8_t filter) { filter_ = filter; }
   void set_gain(uint8_t gain_sel) { gain_ = gain_sel; }
-
+  void set_temperature_compensation(bool temperature_compensation) {
+    temperature_compensation_ = temperature_compensation;
+  }
+  void set_hallconf(uint8_t hallconf) { hallconf_ = hallconf; }
   // overrides for MLX library
 
   // disable lint because it keeps suggesting const uint8_t *response.
@@ -49,9 +52,11 @@ class MLX90393Cls : public PollingComponent, public i2c::I2CDevice, public MLX90
   sensor::Sensor *t_sensor_{nullptr};
   uint8_t gain_;
   uint8_t oversampling_;
-  uint8_t temperature_oversampling_ = 0;
+  uint8_t temperature_oversampling_{0};
   uint8_t filter_;
-  uint8_t resolutions_[3] = {0};
+  uint8_t resolutions_[3]{0};
+  bool temperature_compensation_{false};
+  uint8_t hallconf_{0xC};
   GPIOPin *drdy_pin_{nullptr};
 };
 
diff --git a/tests/components/mlx90393/common.yaml b/tests/components/mlx90393/common.yaml
index a7ab0867cc..0b074f9be3 100644
--- a/tests/components/mlx90393/common.yaml
+++ b/tests/components/mlx90393/common.yaml
@@ -7,14 +7,17 @@ sensor:
   - platform: mlx90393
     oversampling: 1
     filter: 0
-    gain: 3X
+    gain: 1X
+    temperature_compensation: true
     x_axis:
       name: mlxxaxis
+      resolution: DIV_2
     y_axis:
       name: mlxyaxis
+      resolution: DIV_1
     z_axis:
       name: mlxzaxis
-      resolution: 17BIT
+      resolution: DIV_2
     temperature:
       name: mlxtemp
       oversampling: 2
diff --git a/tests/components/mlx90393/test.esp32-s3-ard.yaml b/tests/components/mlx90393/test.esp32-s3-ard.yaml
new file mode 100644
index 0000000000..ee2c29ca4e
--- /dev/null
+++ b/tests/components/mlx90393/test.esp32-s3-ard.yaml
@@ -0,0 +1,5 @@
+substitutions:
+  scl_pin: GPIO5
+  sda_pin: GPIO4
+
+<<: !include common.yaml
diff --git a/tests/components/mlx90393/test.esp32-s3-idf.yaml b/tests/components/mlx90393/test.esp32-s3-idf.yaml
new file mode 100644
index 0000000000..ee2c29ca4e
--- /dev/null
+++ b/tests/components/mlx90393/test.esp32-s3-idf.yaml
@@ -0,0 +1,5 @@
+substitutions:
+  scl_pin: GPIO5
+  sda_pin: GPIO4
+
+<<: !include common.yaml