esphome 2025.2.2__py3-none-any.whl → 2025.3.0b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. esphome/__main__.py +9 -1
  2. esphome/components/api/api_connection.cpp +426 -70
  3. esphome/components/api/api_connection.h +117 -25
  4. esphome/components/api/api_pb2.cpp +9 -0
  5. esphome/components/api/api_pb2.h +1 -0
  6. esphome/components/api/api_server.cpp +2 -2
  7. esphome/components/api/list_entities.cpp +76 -22
  8. esphome/components/api/list_entities.h +1 -0
  9. esphome/components/api/subscribe_state.h +2 -0
  10. esphome/components/bluetooth_proxy/bluetooth_proxy.h +8 -0
  11. esphome/components/bmp085/bmp085.cpp +1 -1
  12. esphome/components/chsc6x/__init__.py +2 -0
  13. esphome/components/chsc6x/chsc6x_touchscreen.cpp +47 -0
  14. esphome/components/chsc6x/chsc6x_touchscreen.h +34 -0
  15. esphome/components/chsc6x/touchscreen.py +33 -0
  16. esphome/components/climate/__init__.py +0 -1
  17. esphome/components/cst816/binary_sensor/__init__.py +2 -25
  18. esphome/components/cst816/touchscreen/cst816_touchscreen.cpp +3 -14
  19. esphome/components/cst816/touchscreen/cst816_touchscreen.h +0 -4
  20. esphome/components/esp32_ble_beacon/__init__.py +3 -1
  21. esphome/components/esp8266/gpio.py +1 -2
  22. esphome/components/font/__init__.py +185 -185
  23. esphome/components/font/font.cpp +4 -4
  24. esphome/components/font/font.h +1 -0
  25. esphome/components/haier/climate.py +11 -10
  26. esphome/components/hbridge/switch/hbridge_switch.cpp +2 -2
  27. esphome/components/heatpumpir/climate.py +2 -1
  28. esphome/components/heatpumpir/heatpumpir.cpp +1 -0
  29. esphome/components/heatpumpir/heatpumpir.h +1 -0
  30. esphome/components/i2c/__init__.py +6 -6
  31. esphome/components/i2c/i2c_bus_esp_idf.cpp +6 -2
  32. esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +1 -1
  33. esphome/components/ili9xxx/display.py +1 -0
  34. esphome/components/ili9xxx/ili9xxx_display.h +5 -0
  35. esphome/components/ili9xxx/ili9xxx_init.h +59 -0
  36. esphome/components/ld2450/__init__.py +51 -0
  37. esphome/components/ld2450/binary_sensor.py +47 -0
  38. esphome/components/ld2450/button/__init__.py +45 -0
  39. esphome/components/ld2450/button/reset_button.cpp +9 -0
  40. esphome/components/ld2450/button/reset_button.h +18 -0
  41. esphome/components/ld2450/button/restart_button.cpp +9 -0
  42. esphome/components/ld2450/button/restart_button.h +18 -0
  43. esphome/components/ld2450/ld2450.cpp +876 -0
  44. esphome/components/ld2450/ld2450.h +234 -0
  45. esphome/components/ld2450/number/__init__.py +121 -0
  46. esphome/components/ld2450/number/presence_timeout_number.cpp +12 -0
  47. esphome/components/ld2450/number/presence_timeout_number.h +18 -0
  48. esphome/components/ld2450/number/zone_coordinate_number.cpp +14 -0
  49. esphome/components/ld2450/number/zone_coordinate_number.h +19 -0
  50. esphome/components/ld2450/select/__init__.py +56 -0
  51. esphome/components/ld2450/select/baud_rate_select.cpp +12 -0
  52. esphome/components/ld2450/select/baud_rate_select.h +18 -0
  53. esphome/components/ld2450/select/zone_type_select.cpp +12 -0
  54. esphome/components/ld2450/select/zone_type_select.h +18 -0
  55. esphome/components/ld2450/sensor.py +156 -0
  56. esphome/components/ld2450/switch/__init__.py +45 -0
  57. esphome/components/ld2450/switch/bluetooth_switch.cpp +12 -0
  58. esphome/components/ld2450/switch/bluetooth_switch.h +18 -0
  59. esphome/components/ld2450/switch/multi_target_switch.cpp +12 -0
  60. esphome/components/ld2450/switch/multi_target_switch.h +18 -0
  61. esphome/components/ld2450/text_sensor.py +62 -0
  62. esphome/components/lvgl/defines.py +0 -2
  63. esphome/components/lvgl/font.cpp +1 -1
  64. esphome/components/lvgl/lvgl_esphome.cpp +27 -19
  65. esphome/components/lvgl/widgets/img.py +1 -3
  66. esphome/components/mcp2515/mcp2515.cpp +1 -0
  67. esphome/components/mlx90393/sensor.py +53 -33
  68. esphome/components/mlx90393/sensor_mlx90393.cpp +4 -0
  69. esphome/components/mlx90393/sensor_mlx90393.h +8 -3
  70. esphome/components/mqtt/__init__.py +2 -2
  71. esphome/components/msa3xx/__init__.py +189 -0
  72. esphome/components/msa3xx/binary_sensor.py +40 -0
  73. esphome/components/msa3xx/msa3xx.cpp +417 -0
  74. esphome/components/msa3xx/msa3xx.h +311 -0
  75. esphome/components/msa3xx/sensor.py +42 -0
  76. esphome/components/msa3xx/text_sensor.py +38 -0
  77. esphome/components/nfc/binary_sensor/__init__.py +4 -4
  78. esphome/components/opentherm/binary_sensor/__init__.py +4 -4
  79. esphome/components/opentherm/generate.py +6 -6
  80. esphome/components/opentherm/sensor/__init__.py +5 -6
  81. esphome/components/packages/__init__.py +35 -11
  82. esphome/components/pn532/binary_sensor.py +4 -4
  83. esphome/components/rc522/binary_sensor.py +4 -4
  84. esphome/components/socket/bsd_sockets_impl.cpp +1 -0
  85. esphome/components/socket/lwip_sockets_impl.cpp +1 -0
  86. esphome/components/socket/socket.h +3 -1
  87. esphome/components/ssd1306_base/__init__.py +7 -7
  88. esphome/components/thermostat/climate.py +1 -1
  89. esphome/components/tmp1075/tmp1075.cpp +7 -11
  90. esphome/components/tmp1075/tmp1075.h +1 -2
  91. esphome/components/tormatic/__init__.py +1 -0
  92. esphome/components/tormatic/cover.py +47 -0
  93. esphome/components/tormatic/tormatic_cover.cpp +355 -0
  94. esphome/components/tormatic/tormatic_cover.h +60 -0
  95. esphome/components/tormatic/tormatic_protocol.h +211 -0
  96. esphome/components/touchscreen/binary_sensor/__init__.py +3 -0
  97. esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.cpp +7 -1
  98. esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h +3 -1
  99. esphome/components/touchscreen/touchscreen.cpp +3 -4
  100. esphome/components/udp/udp_component.h +4 -1
  101. esphome/components/web_server/list_entities.cpp +70 -66
  102. esphome/components/web_server/list_entities.h +43 -22
  103. esphome/components/web_server/web_server.cpp +345 -68
  104. esphome/components/web_server/web_server.h +138 -6
  105. esphome/components/web_server_base/__init__.py +1 -1
  106. esphome/components/web_server_idf/__init__.py +2 -0
  107. esphome/components/web_server_idf/web_server_idf.cpp +177 -30
  108. esphome/components/web_server_idf/web_server_idf.h +53 -4
  109. esphome/config_validation.py +23 -125
  110. esphome/const.py +5 -1
  111. esphome/core/config.py +12 -4
  112. esphome/core/defines.h +1 -1
  113. esphome/core/helpers.h +5 -3
  114. esphome/core/time.cpp +1 -0
  115. esphome/cpp_generator.py +3 -3
  116. esphome/dashboard/core.py +30 -21
  117. esphome/dashboard/dns.py +7 -1
  118. esphome/dashboard/entries.py +83 -16
  119. esphome/dashboard/settings.py +0 -4
  120. esphome/dashboard/status/mdns.py +43 -14
  121. esphome/dashboard/status/mqtt.py +22 -9
  122. esphome/dashboard/status/ping.py +54 -10
  123. esphome/dashboard/web_server.py +56 -24
  124. esphome/storage_json.py +4 -0
  125. esphome/wizard.py +13 -17
  126. esphome/writer.py +1 -3
  127. esphome/yaml_util.py +36 -33
  128. esphome/zeroconf.py +9 -21
  129. {esphome-2025.2.2.dist-info → esphome-2025.3.0b1.dist-info}/METADATA +5 -5
  130. {esphome-2025.2.2.dist-info → esphome-2025.3.0b1.dist-info}/RECORD +134 -94
  131. esphome/components/cst816/binary_sensor/cst816_button.h +0 -27
  132. {esphome-2025.2.2.dist-info → esphome-2025.3.0b1.dist-info}/LICENSE +0 -0
  133. {esphome-2025.2.2.dist-info → esphome-2025.3.0b1.dist-info}/WHEEL +0 -0
  134. {esphome-2025.2.2.dist-info → esphome-2025.3.0b1.dist-info}/entry_points.txt +0 -0
  135. {esphome-2025.2.2.dist-info → esphome-2025.3.0b1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,189 @@
1
+ from esphome import automation
2
+ import esphome.codegen as cg
3
+ from esphome.components import i2c
4
+ import esphome.config_validation as cv
5
+ from esphome.const import (
6
+ CONF_CALIBRATION,
7
+ CONF_ID,
8
+ CONF_MIRROR_X,
9
+ CONF_MIRROR_Y,
10
+ CONF_OFFSET_X,
11
+ CONF_OFFSET_Y,
12
+ CONF_OFFSET_Z,
13
+ CONF_RANGE,
14
+ CONF_RESOLUTION,
15
+ CONF_SWAP_XY,
16
+ CONF_TRANSFORM,
17
+ CONF_TYPE,
18
+ )
19
+
20
+ CODEOWNERS = ["@latonita"]
21
+ DEPENDENCIES = ["i2c"]
22
+
23
+ MULTI_CONF = True
24
+
25
+ CONF_MSA3XX_ID = "msa3xx_id"
26
+
27
+ CONF_MIRROR_Z = "mirror_z"
28
+ CONF_ON_ACTIVE = "on_active"
29
+ CONF_ON_DOUBLE_TAP = "on_double_tap"
30
+ CONF_ON_FREEFALL = "on_freefall"
31
+ CONF_ON_ORIENTATION = "on_orientation"
32
+ CONF_ON_TAP = "on_tap"
33
+
34
+ MODEL_MSA301 = "MSA301"
35
+ MODEL_MSA311 = "MSA311"
36
+
37
+ msa3xx_ns = cg.esphome_ns.namespace("msa3xx")
38
+ MSA3xxComponent = msa3xx_ns.class_(
39
+ "MSA3xxComponent", cg.PollingComponent, i2c.I2CDevice
40
+ )
41
+
42
+ MSAModels = msa3xx_ns.enum("Model", True)
43
+ MSA_MODELS = {
44
+ MODEL_MSA301: MSAModels.MSA301,
45
+ MODEL_MSA311: MSAModels.MSA311,
46
+ }
47
+
48
+ MSARange = msa3xx_ns.enum("Range", True)
49
+ MSA_RANGES = {
50
+ "2G": MSARange.RANGE_2G,
51
+ "4G": MSARange.RANGE_4G,
52
+ "8G": MSARange.RANGE_8G,
53
+ "16G": MSARange.RANGE_16G,
54
+ }
55
+
56
+ MSAResolution = msa3xx_ns.enum("Resolution", True)
57
+ RESOLUTIONS_MSA301 = {
58
+ 14: MSAResolution.RES_14BIT,
59
+ 12: MSAResolution.RES_12BIT,
60
+ 10: MSAResolution.RES_10BIT,
61
+ 8: MSAResolution.RES_8BIT,
62
+ }
63
+
64
+ RESOLUTIONS_MSA311 = {
65
+ 12: MSAResolution.RES_12BIT,
66
+ }
67
+
68
+ _COMMON_SCHEMA = cv.Schema(
69
+ {
70
+ cv.GenerateID(): cv.declare_id(MSA3xxComponent),
71
+ cv.Optional(CONF_RANGE, default="2G"): cv.enum(MSA_RANGES, upper=True),
72
+ cv.Optional(CONF_CALIBRATION): cv.Schema(
73
+ {
74
+ cv.Optional(CONF_OFFSET_X, default=0): cv.float_range(
75
+ min=-4.5, max=4.5
76
+ ),
77
+ cv.Optional(CONF_OFFSET_Y, default=0): cv.float_range(
78
+ min=-4.5, max=4.5
79
+ ),
80
+ cv.Optional(CONF_OFFSET_Z, default=0): cv.float_range(
81
+ min=-4.5, max=4.5
82
+ ),
83
+ }
84
+ ),
85
+ cv.Optional(CONF_TRANSFORM): cv.Schema(
86
+ {
87
+ cv.Optional(CONF_MIRROR_X, default=False): cv.boolean,
88
+ cv.Optional(CONF_MIRROR_Y, default=False): cv.boolean,
89
+ cv.Optional(CONF_MIRROR_Z, default=False): cv.boolean,
90
+ cv.Optional(CONF_SWAP_XY, default=False): cv.boolean,
91
+ }
92
+ ),
93
+ cv.Optional(CONF_ON_ACTIVE): automation.validate_automation(single=True),
94
+ cv.Optional(CONF_ON_TAP): automation.validate_automation(single=True),
95
+ cv.Optional(CONF_ON_DOUBLE_TAP): automation.validate_automation(single=True),
96
+ cv.Optional(CONF_ON_FREEFALL): automation.validate_automation(single=True),
97
+ cv.Optional(CONF_ON_ORIENTATION): automation.validate_automation(single=True),
98
+ }
99
+ ).extend(cv.polling_component_schema("10s"))
100
+
101
+
102
+ CONFIG_SCHEMA = cv.typed_schema(
103
+ {
104
+ MODEL_MSA301: _COMMON_SCHEMA.extend(
105
+ {
106
+ cv.Optional(CONF_RESOLUTION, default=14): cv.enum(RESOLUTIONS_MSA301),
107
+ }
108
+ ).extend(i2c.i2c_device_schema(0x26)),
109
+ MODEL_MSA311: _COMMON_SCHEMA.extend(
110
+ {
111
+ cv.Optional(CONF_RESOLUTION, default=12): cv.enum(RESOLUTIONS_MSA311),
112
+ }
113
+ ).extend(i2c.i2c_device_schema(0x62)),
114
+ },
115
+ upper=True,
116
+ enum=MSA_MODELS,
117
+ )
118
+
119
+ MSA_SENSOR_SCHEMA = cv.Schema(
120
+ {
121
+ cv.GenerateID(CONF_MSA3XX_ID): cv.use_id(MSA3xxComponent),
122
+ }
123
+ )
124
+
125
+
126
+ async def to_code(config):
127
+ var = cg.new_Pvariable(config[CONF_ID])
128
+ await cg.register_component(var, config)
129
+ await i2c.register_i2c_device(var, config)
130
+
131
+ cg.add(var.set_model(config[CONF_TYPE]))
132
+ cg.add(var.set_range(MSA_RANGES[config[CONF_RANGE]]))
133
+ cg.add(var.set_resolution(RESOLUTIONS_MSA301[config[CONF_RESOLUTION]]))
134
+
135
+ if transform := config.get(CONF_TRANSFORM):
136
+ cg.add(
137
+ var.set_transform(
138
+ transform[CONF_MIRROR_X],
139
+ transform[CONF_MIRROR_Y],
140
+ transform[CONF_MIRROR_Z],
141
+ transform[CONF_SWAP_XY],
142
+ )
143
+ )
144
+
145
+ if calibration_config := config.get(CONF_CALIBRATION):
146
+ cg.add(
147
+ var.set_offset(
148
+ calibration_config[CONF_OFFSET_X],
149
+ calibration_config[CONF_OFFSET_Y],
150
+ calibration_config[CONF_OFFSET_Z],
151
+ )
152
+ )
153
+
154
+ # Triggers secton
155
+
156
+ if CONF_ON_ORIENTATION in config:
157
+ await automation.build_automation(
158
+ var.get_orientation_trigger(),
159
+ [],
160
+ config[CONF_ON_ORIENTATION],
161
+ )
162
+
163
+ if CONF_ON_TAP in config:
164
+ await automation.build_automation(
165
+ var.get_tap_trigger(),
166
+ [],
167
+ config[CONF_ON_TAP],
168
+ )
169
+
170
+ if CONF_ON_DOUBLE_TAP in config:
171
+ await automation.build_automation(
172
+ var.get_double_tap_trigger(),
173
+ [],
174
+ config[CONF_ON_DOUBLE_TAP],
175
+ )
176
+
177
+ if CONF_ON_ACTIVE in config:
178
+ await automation.build_automation(
179
+ var.get_active_trigger(),
180
+ [],
181
+ config[CONF_ON_ACTIVE],
182
+ )
183
+
184
+ if CONF_ON_FREEFALL in config:
185
+ await automation.build_automation(
186
+ var.get_freefall_trigger(),
187
+ [],
188
+ config[CONF_ON_FREEFALL],
189
+ )
@@ -0,0 +1,40 @@
1
+ import esphome.codegen as cg
2
+ from esphome.components import binary_sensor
3
+ import esphome.config_validation as cv
4
+ from esphome.const import CONF_ACTIVE, CONF_NAME, DEVICE_CLASS_VIBRATION, ICON_VIBRATE
5
+
6
+ from . import CONF_MSA3XX_ID, MSA_SENSOR_SCHEMA
7
+
8
+ CODEOWNERS = ["@latonita"]
9
+ DEPENDENCIES = ["msa3xx"]
10
+
11
+ CONF_TAP = "tap"
12
+ CONF_DOUBLE_TAP = "double_tap"
13
+
14
+ ICON_TAP = "mdi:gesture-tap"
15
+ ICON_DOUBLE_TAP = "mdi:gesture-double-tap"
16
+
17
+ EVENT_SENSORS = (CONF_TAP, CONF_DOUBLE_TAP, CONF_ACTIVE)
18
+ ICONS = (ICON_TAP, ICON_DOUBLE_TAP, ICON_VIBRATE)
19
+
20
+ CONFIG_SCHEMA = MSA_SENSOR_SCHEMA.extend(
21
+ {
22
+ cv.Optional(event): cv.maybe_simple_value(
23
+ binary_sensor.binary_sensor_schema(
24
+ device_class=DEVICE_CLASS_VIBRATION,
25
+ icon=icon,
26
+ ),
27
+ key=CONF_NAME,
28
+ )
29
+ for event, icon in zip(EVENT_SENSORS, ICONS)
30
+ }
31
+ )
32
+
33
+
34
+ async def to_code(config):
35
+ hub = await cg.get_variable(config[CONF_MSA3XX_ID])
36
+
37
+ for sensor in EVENT_SENSORS:
38
+ if sensor in config:
39
+ sens = await binary_sensor.new_binary_sensor(config[sensor])
40
+ cg.add(getattr(hub, f"set_{sensor}_binary_sensor")(sens))
@@ -0,0 +1,417 @@
1
+ #include "msa3xx.h"
2
+ #include "esphome/core/log.h"
3
+ #include "esphome/core/hal.h"
4
+ #include "esphome/core/helpers.h"
5
+
6
+ namespace esphome {
7
+ namespace msa3xx {
8
+
9
+ static const char *const TAG = "msa3xx";
10
+
11
+ const uint8_t MSA_3XX_PART_ID = 0x13;
12
+
13
+ const float GRAVITY_EARTH = 9.80665f;
14
+ const float LSB_COEFF = 1000.0f / (GRAVITY_EARTH * 3.9); // LSB to 1 LSB = 3.9mg = 0.0039g
15
+ const float G_OFFSET_MIN = -4.5f; // -127...127 LSB = +- 0.4953g = +- 4.857 m/s^2 => +- 4.5 for the safe
16
+ const float G_OFFSET_MAX = 4.5f; // -127...127 LSB = +- 0.4953g = +- 4.857 m/s^2 => +- 4.5 for the safe
17
+
18
+ const uint8_t RESOLUTION[] = {14, 12, 10, 8};
19
+
20
+ const uint32_t TAP_COOLDOWN_MS = 500;
21
+ const uint32_t DOUBLE_TAP_COOLDOWN_MS = 500;
22
+ const uint32_t ACTIVITY_COOLDOWN_MS = 500;
23
+
24
+ const char *model_to_string(Model model) {
25
+ switch (model) {
26
+ case Model::MSA301:
27
+ return "MSA301";
28
+ case Model::MSA311:
29
+ return "MSA311";
30
+ default:
31
+ return "Unknown";
32
+ }
33
+ }
34
+
35
+ const char *power_mode_to_string(PowerMode power_mode) {
36
+ switch (power_mode) {
37
+ case PowerMode::NORMAL:
38
+ return "Normal";
39
+ case PowerMode::LOW_POWER:
40
+ return "Low Power";
41
+ case PowerMode::SUSPEND:
42
+ return "Suspend";
43
+ default:
44
+ return "Unknown";
45
+ }
46
+ }
47
+
48
+ const char *res_to_string(Resolution resolution) {
49
+ switch (resolution) {
50
+ case Resolution::RES_14BIT:
51
+ return "14-bit";
52
+ case Resolution::RES_12BIT:
53
+ return "12-bit";
54
+ case Resolution::RES_10BIT:
55
+ return "10-bit";
56
+ case Resolution::RES_8BIT:
57
+ return "8-bit";
58
+ default:
59
+ return "Unknown";
60
+ }
61
+ }
62
+
63
+ const char *range_to_string(Range range) {
64
+ switch (range) {
65
+ case Range::RANGE_2G:
66
+ return "±2g";
67
+ case Range::RANGE_4G:
68
+ return "±4g";
69
+ case Range::RANGE_8G:
70
+ return "±8g";
71
+ case Range::RANGE_16G:
72
+ return "±16g";
73
+ default:
74
+ return "Unknown";
75
+ }
76
+ }
77
+
78
+ const char *bandwidth_to_string(Bandwidth bandwidth) {
79
+ switch (bandwidth) {
80
+ case Bandwidth::BW_1_95HZ:
81
+ return "1.95 Hz";
82
+ case Bandwidth::BW_3_9HZ:
83
+ return "3.9 Hz";
84
+ case Bandwidth::BW_7_81HZ:
85
+ return "7.81 Hz";
86
+ case Bandwidth::BW_15_63HZ:
87
+ return "15.63 Hz";
88
+ case Bandwidth::BW_31_25HZ:
89
+ return "31.25 Hz";
90
+ case Bandwidth::BW_62_5HZ:
91
+ return "62.5 Hz";
92
+ case Bandwidth::BW_125HZ:
93
+ return "125 Hz";
94
+ case Bandwidth::BW_250HZ:
95
+ return "250 Hz";
96
+ case Bandwidth::BW_500HZ:
97
+ return "500 Hz";
98
+ default:
99
+ return "Unknown";
100
+ }
101
+ }
102
+
103
+ const char *orientation_xy_to_string(OrientationXY orientation) {
104
+ switch (orientation) {
105
+ case OrientationXY::PORTRAIT_UPRIGHT:
106
+ return "Portrait Upright";
107
+ case OrientationXY::PORTRAIT_UPSIDE_DOWN:
108
+ return "Portrait Upside Down";
109
+ case OrientationXY::LANDSCAPE_LEFT:
110
+ return "Landscape Left";
111
+ case OrientationXY::LANDSCAPE_RIGHT:
112
+ return "Landscape Right";
113
+ default:
114
+ return "Unknown";
115
+ }
116
+ }
117
+
118
+ const char *orientation_z_to_string(bool orientation) { return orientation ? "Downwards looking" : "Upwards looking"; }
119
+
120
+ void MSA3xxComponent::setup() {
121
+ ESP_LOGCONFIG(TAG, "Setting up MSA3xx...");
122
+
123
+ uint8_t part_id{0xff};
124
+ if (!this->read_byte(static_cast<uint8_t>(RegisterMap::PART_ID), &part_id) || (part_id != MSA_3XX_PART_ID)) {
125
+ ESP_LOGE(TAG, "Part ID is wrong or missing. Got 0x%02X", part_id);
126
+ this->mark_failed();
127
+ return;
128
+ }
129
+
130
+ // Resolution LSB/g
131
+ // Range : MSA301 : MSA311
132
+ // S2g : 1024 (2^10) : 4096 (2^12)
133
+ // S4g : 512 (2^9) : 2048 (2^11)
134
+ // S8g : 256 (2^8) : 1024 (2^10)
135
+ // S16g : 128 (2^7) : 512 (2^9)
136
+ if (this->model_ == Model::MSA301) {
137
+ this->device_params_.accel_data_width = 14;
138
+ this->device_params_.scale_factor_exp = static_cast<uint8_t>(this->range_) - 12;
139
+ } else if (this->model_ == Model::MSA311) {
140
+ this->device_params_.accel_data_width = 12;
141
+ this->device_params_.scale_factor_exp = static_cast<uint8_t>(this->range_) - 10;
142
+ } else {
143
+ ESP_LOGE(TAG, "Unknown model");
144
+ this->mark_failed();
145
+ return;
146
+ }
147
+
148
+ this->setup_odr_(this->data_rate_);
149
+ this->setup_power_mode_bandwidth_(this->power_mode_, this->bandwidth_);
150
+ this->setup_range_resolution_(this->range_, this->resolution_); // 2g...16g, 14...8 bit
151
+ this->setup_offset_(this->offset_x_, this->offset_y_, this->offset_z_); // calibration offsets
152
+ this->write_byte(static_cast<uint8_t>(RegisterMap::TAP_DURATION), 0b11000100); // set tap duration 250ms
153
+ this->write_byte(static_cast<uint8_t>(RegisterMap::SWAP_POLARITY), this->swap_.raw); // set axes polarity
154
+ this->write_byte(static_cast<uint8_t>(RegisterMap::INT_SET_0), 0b01110111); // enable all interrupts
155
+ this->write_byte(static_cast<uint8_t>(RegisterMap::INT_SET_1), 0b00011000); // including orientation
156
+ }
157
+
158
+ void MSA3xxComponent::dump_config() {
159
+ ESP_LOGCONFIG(TAG, "MSA3xx:");
160
+ LOG_I2C_DEVICE(this);
161
+ if (this->is_failed()) {
162
+ ESP_LOGE(TAG, "Communication with MSA3xx failed!");
163
+ }
164
+ ESP_LOGCONFIG(TAG, " Model: %s", model_to_string(this->model_));
165
+ ESP_LOGCONFIG(TAG, " Power Mode: %s", power_mode_to_string(this->power_mode_));
166
+ ESP_LOGCONFIG(TAG, " Bandwidth: %s", bandwidth_to_string(this->bandwidth_));
167
+ ESP_LOGCONFIG(TAG, " Range: %s", range_to_string(this->range_));
168
+ ESP_LOGCONFIG(TAG, " Resolution: %s", res_to_string(this->resolution_));
169
+ ESP_LOGCONFIG(TAG, " Offsets: {%.3f m/s², %.3f m/s², %.3f m/s²}", this->offset_x_, this->offset_y_, this->offset_z_);
170
+ ESP_LOGCONFIG(TAG, " Transform: {mirror_x=%s, mirror_y=%s, mirror_z=%s, swap_xy=%s}", YESNO(this->swap_.x_polarity),
171
+ YESNO(this->swap_.y_polarity), YESNO(this->swap_.z_polarity), YESNO(this->swap_.x_y_swap));
172
+ LOG_UPDATE_INTERVAL(this);
173
+
174
+ #ifdef USE_BINARY_SENSOR
175
+ LOG_BINARY_SENSOR(" ", "Tap", this->tap_binary_sensor_);
176
+ LOG_BINARY_SENSOR(" ", "Double Tap", this->double_tap_binary_sensor_);
177
+ LOG_BINARY_SENSOR(" ", "Active", this->active_binary_sensor_);
178
+ #endif
179
+
180
+ #ifdef USE_SENSOR
181
+ LOG_SENSOR(" ", "Acceleration X", this->acceleration_x_sensor_);
182
+ LOG_SENSOR(" ", "Acceleration Y", this->acceleration_y_sensor_);
183
+ LOG_SENSOR(" ", "Acceleration Z", this->acceleration_z_sensor_);
184
+ #endif
185
+
186
+ #ifdef USE_TEXT_SENSOR
187
+ LOG_TEXT_SENSOR(" ", "Orientation XY", this->orientation_xy_text_sensor_);
188
+ LOG_TEXT_SENSOR(" ", "Orientation Z", this->orientation_z_text_sensor_);
189
+ #endif
190
+ }
191
+
192
+ bool MSA3xxComponent::read_data_() {
193
+ uint8_t accel_data[6];
194
+ if (!this->read_bytes(static_cast<uint8_t>(RegisterMap::ACC_X_LSB), accel_data, 6)) {
195
+ return false;
196
+ }
197
+
198
+ auto raw_to_x_bit = [](uint16_t lsb, uint16_t msb, uint8_t data_bits) -> uint16_t {
199
+ return ((msb << 8) | lsb) >> (16 - data_bits);
200
+ };
201
+
202
+ auto lpf = [](float new_value, float old_value, float alpha = 0.5f) {
203
+ return alpha * new_value + (1.0f - alpha) * old_value;
204
+ };
205
+
206
+ this->data_.lsb_x =
207
+ this->twos_complement_(raw_to_x_bit(accel_data[0], accel_data[1], this->device_params_.accel_data_width),
208
+ this->device_params_.accel_data_width);
209
+ this->data_.lsb_y =
210
+ this->twos_complement_(raw_to_x_bit(accel_data[2], accel_data[3], this->device_params_.accel_data_width),
211
+ this->device_params_.accel_data_width);
212
+ this->data_.lsb_z =
213
+ this->twos_complement_(raw_to_x_bit(accel_data[4], accel_data[5], this->device_params_.accel_data_width),
214
+ this->device_params_.accel_data_width);
215
+
216
+ this->data_.x = lpf(ldexp(this->data_.lsb_x, this->device_params_.scale_factor_exp) * GRAVITY_EARTH, this->data_.x);
217
+ this->data_.y = lpf(ldexp(this->data_.lsb_y, this->device_params_.scale_factor_exp) * GRAVITY_EARTH, this->data_.y);
218
+ this->data_.z = lpf(ldexp(this->data_.lsb_z, this->device_params_.scale_factor_exp) * GRAVITY_EARTH, this->data_.z);
219
+
220
+ return true;
221
+ }
222
+
223
+ bool MSA3xxComponent::read_motion_status_() {
224
+ if (!this->read_byte(static_cast<uint8_t>(RegisterMap::MOTION_INTERRUPT), &this->status_.motion_int.raw)) {
225
+ return false;
226
+ }
227
+
228
+ if (!this->read_byte(static_cast<uint8_t>(RegisterMap::ORIENTATION_STATUS), &this->status_.orientation.raw)) {
229
+ return false;
230
+ }
231
+
232
+ return true;
233
+ }
234
+
235
+ void MSA3xxComponent::loop() {
236
+ if (!this->is_ready()) {
237
+ return;
238
+ }
239
+
240
+ RegMotionInterrupt old_motion_int = this->status_.motion_int;
241
+
242
+ if (!this->read_data_() || !this->read_motion_status_()) {
243
+ this->status_set_warning();
244
+ return;
245
+ }
246
+
247
+ this->process_motions_(old_motion_int);
248
+ }
249
+
250
+ void MSA3xxComponent::update() {
251
+ ESP_LOGV(TAG, "Updating MSA3xx...");
252
+
253
+ if (!this->is_ready()) {
254
+ ESP_LOGV(TAG, "Component MSA3xx not ready for update");
255
+ return;
256
+ }
257
+ ESP_LOGV(TAG, "Acceleration: {x = %+1.3f m/s², y = %+1.3f m/s², z = %+1.3f m/s²}; ", this->data_.x, this->data_.y,
258
+ this->data_.z);
259
+
260
+ ESP_LOGV(TAG, "Orientation: {XY = %s, Z = %s}", orientation_xy_to_string(this->status_.orientation.orient_xy),
261
+ orientation_z_to_string(this->status_.orientation.orient_z));
262
+
263
+ #ifdef USE_SENSOR
264
+ if (this->acceleration_x_sensor_ != nullptr)
265
+ this->acceleration_x_sensor_->publish_state(this->data_.x);
266
+ if (this->acceleration_y_sensor_ != nullptr)
267
+ this->acceleration_y_sensor_->publish_state(this->data_.y);
268
+ if (this->acceleration_z_sensor_ != nullptr)
269
+ this->acceleration_z_sensor_->publish_state(this->data_.z);
270
+ #endif
271
+
272
+ #ifdef USE_TEXT_SENSOR
273
+ if (this->orientation_xy_text_sensor_ != nullptr &&
274
+ (this->status_.orientation.orient_xy != this->status_.orientation_old.orient_xy ||
275
+ this->status_.never_published)) {
276
+ this->orientation_xy_text_sensor_->publish_state(orientation_xy_to_string(this->status_.orientation.orient_xy));
277
+ }
278
+ if (this->orientation_z_text_sensor_ != nullptr &&
279
+ (this->status_.orientation.orient_z != this->status_.orientation_old.orient_z || this->status_.never_published)) {
280
+ this->orientation_z_text_sensor_->publish_state(orientation_z_to_string(this->status_.orientation.orient_z));
281
+ }
282
+ this->status_.orientation_old = this->status_.orientation;
283
+ #endif
284
+
285
+ this->status_.never_published = false;
286
+ this->status_clear_warning();
287
+ }
288
+ float MSA3xxComponent::get_setup_priority() const { return setup_priority::DATA; }
289
+
290
+ void MSA3xxComponent::set_offset(float offset_x, float offset_y, float offset_z) {
291
+ this->offset_x_ = offset_x;
292
+ this->offset_y_ = offset_y;
293
+ this->offset_z_ = offset_z;
294
+ }
295
+
296
+ void MSA3xxComponent::set_transform(bool mirror_x, bool mirror_y, bool mirror_z, bool swap_xy) {
297
+ this->swap_.x_polarity = mirror_x;
298
+ this->swap_.y_polarity = mirror_y;
299
+ this->swap_.z_polarity = mirror_z;
300
+ this->swap_.x_y_swap = swap_xy;
301
+ }
302
+
303
+ void MSA3xxComponent::setup_odr_(DataRate rate) {
304
+ RegOutputDataRate reg_odr;
305
+ auto reg = this->read_byte(static_cast<uint8_t>(RegisterMap::ODR));
306
+ if (reg.has_value()) {
307
+ reg_odr.raw = reg.value();
308
+ } else {
309
+ reg_odr.raw = 0x0F; // defaut from datasheet
310
+ }
311
+
312
+ reg_odr.x_axis_disable = false;
313
+ reg_odr.y_axis_disable = false;
314
+ reg_odr.z_axis_disable = false;
315
+ reg_odr.odr = rate;
316
+
317
+ this->write_byte(static_cast<uint8_t>(RegisterMap::ODR), reg_odr.raw);
318
+ }
319
+
320
+ void MSA3xxComponent::setup_power_mode_bandwidth_(PowerMode power_mode, Bandwidth bandwidth) {
321
+ // 0x11 POWER_MODE_BANDWIDTH
322
+ auto reg = this->read_byte(static_cast<uint8_t>(RegisterMap::POWER_MODE_BANDWIDTH));
323
+
324
+ RegPowerModeBandwidth power_mode_bandwidth;
325
+ if (reg.has_value()) {
326
+ power_mode_bandwidth.raw = reg.value();
327
+ } else {
328
+ power_mode_bandwidth.raw = 0xde; // defaut from datasheet
329
+ }
330
+
331
+ power_mode_bandwidth.power_mode = power_mode;
332
+ power_mode_bandwidth.low_power_bandwidth = bandwidth;
333
+
334
+ this->write_byte(static_cast<uint8_t>(RegisterMap::POWER_MODE_BANDWIDTH), power_mode_bandwidth.raw);
335
+ }
336
+
337
+ void MSA3xxComponent::setup_range_resolution_(Range range, Resolution resolution) {
338
+ RegRangeResolution reg;
339
+ reg.raw = this->read_byte(static_cast<uint8_t>(RegisterMap::RANGE_RESOLUTION)).value_or(0x00);
340
+ reg.range = range;
341
+ if (this->model_ == Model::MSA301) {
342
+ reg.resolution = resolution;
343
+ }
344
+ this->write_byte(static_cast<uint8_t>(RegisterMap::RANGE_RESOLUTION), reg.raw);
345
+ }
346
+
347
+ void MSA3xxComponent::setup_offset_(float offset_x, float offset_y, float offset_z) {
348
+ uint8_t offset[3];
349
+
350
+ auto offset_g_to_lsb = [](float accel) -> int8_t {
351
+ float acccel_clamped = clamp(accel, G_OFFSET_MIN, G_OFFSET_MAX);
352
+ return static_cast<int8_t>(acccel_clamped * LSB_COEFF);
353
+ };
354
+
355
+ offset[0] = offset_g_to_lsb(offset_x);
356
+ offset[1] = offset_g_to_lsb(offset_y);
357
+ offset[2] = offset_g_to_lsb(offset_z);
358
+
359
+ ESP_LOGV(TAG, "Offset (%.3f, %.3f, %.3f)=>LSB(%d, %d, %d)", offset_x, offset_y, offset_z, offset[0], offset[1],
360
+ offset[2]);
361
+
362
+ this->write_bytes(static_cast<uint8_t>(RegisterMap::OFFSET_COMP_X), (uint8_t *) &offset, 3);
363
+ }
364
+
365
+ int64_t MSA3xxComponent::twos_complement_(uint64_t value, uint8_t bits) {
366
+ if (value > (1ULL << (bits - 1))) {
367
+ return (int64_t) (value - (1ULL << bits));
368
+ } else {
369
+ return (int64_t) value;
370
+ }
371
+ }
372
+
373
+ void binary_event_debounce(bool state, bool old_state, uint32_t now, uint32_t &last_ms, Trigger<> &trigger,
374
+ uint32_t cooldown_ms, void *bs, const char *desc) {
375
+ if (state && now - last_ms > cooldown_ms) {
376
+ ESP_LOGV(TAG, "%s detected", desc);
377
+ trigger.trigger();
378
+ last_ms = now;
379
+ #ifdef USE_BINARY_SENSOR
380
+ if (bs != nullptr) {
381
+ static_cast<binary_sensor::BinarySensor *>(bs)->publish_state(true);
382
+ }
383
+ #endif
384
+ } else if (!state && now - last_ms > cooldown_ms && bs != nullptr) {
385
+ #ifdef USE_BINARY_SENSOR
386
+ static_cast<binary_sensor::BinarySensor *>(bs)->publish_state(false);
387
+ #endif
388
+ }
389
+ }
390
+
391
+ #ifdef USE_BINARY_SENSOR
392
+ #define BS_OPTIONAL_PTR(x) ((void *) (x))
393
+ #else
394
+ #define BS_OPTIONAL_PTR(x) (nullptr)
395
+ #endif
396
+
397
+ void MSA3xxComponent::process_motions_(RegMotionInterrupt old) {
398
+ uint32_t now = millis();
399
+
400
+ binary_event_debounce(this->status_.motion_int.single_tap_interrupt, old.single_tap_interrupt, now,
401
+ this->status_.last_tap_ms, this->tap_trigger_, TAP_COOLDOWN_MS,
402
+ BS_OPTIONAL_PTR(this->tap_binary_sensor_), "Tap");
403
+ binary_event_debounce(this->status_.motion_int.double_tap_interrupt, old.double_tap_interrupt, now,
404
+ this->status_.last_double_tap_ms, this->double_tap_trigger_, DOUBLE_TAP_COOLDOWN_MS,
405
+ BS_OPTIONAL_PTR(this->double_tap_binary_sensor_), "Double Tap");
406
+ binary_event_debounce(this->status_.motion_int.active_interrupt, old.active_interrupt, now,
407
+ this->status_.last_action_ms, this->active_trigger_, ACTIVITY_COOLDOWN_MS,
408
+ BS_OPTIONAL_PTR(this->active_binary_sensor_), "Activity");
409
+
410
+ if (this->status_.motion_int.orientation_interrupt) {
411
+ ESP_LOGVV(TAG, "Orientation changed");
412
+ this->orientation_trigger_.trigger();
413
+ }
414
+ }
415
+
416
+ } // namespace msa3xx
417
+ } // namespace esphome