ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
es9038q2m_katana.cpp
Go to the documentation of this file.
1#include "es9038q2m_katana.h"
2
3#include "esphome/core/hal.h"
4#include "esphome/core/log.h"
5
6#include <cmath>
7
9
10ESPHOME_LOG_TAG(TAG, "es9038q2m_katana");
11
12namespace {
13
14constexpr uint8_t KATANA_CHIP_ID = 0x30;
15
16constexpr uint8_t KATANA_REG_CHIP_ID = 0x00;
17constexpr uint8_t KATANA_REG_RESET = 0x01;
18constexpr uint8_t KATANA_REG_VOLUME_1 = 0x02;
19constexpr uint8_t KATANA_REG_VOLUME_2 = 0x03;
20constexpr uint8_t KATANA_REG_MUTE = 0x04;
21constexpr uint8_t KATANA_REG_DSP_PROGRAM = 0x05;
22constexpr uint8_t KATANA_REG_DEEMPHASIS = 0x06;
23constexpr uint8_t KATANA_REG_DOP = 0x07;
24constexpr uint8_t KATANA_REG_FORMAT = 0x08;
25constexpr uint8_t KATANA_REG_COMMAND = 0x09;
26constexpr uint8_t KATANA_REG_MUTE_STREAM = 0x0A;
27
28constexpr float KATANA_VOLUME_MIDPOINT = 0.5f;
29constexpr float KATANA_VOLUME_MIDPOINT_STEPS = 50.0f; // -25 dB at 50% volume.
30// Cubic amplitude gain: -20 * log10(volume^3) / 0.5 attenuation steps.
31constexpr float KATANA_VOLUME_CUBIC_SCALE = 120.0f;
32constexpr uint8_t KATANA_VOLUME_FULL_ATTENUATION = 255;
33
34// The control logic ignores writes for this long after a reset.
35constexpr uint32_t RESET_SETTLE_DELAY_MS = 10;
36
37const LogString *filter_shape_to_str(FilterShape shape) {
38 switch (shape) {
40 return LOG_STR("linear_phase_fast");
42 return LOG_STR("linear_phase_slow");
44 return LOG_STR("minimum_phase_fast");
46 return LOG_STR("minimum_phase_slow");
48 return LOG_STR("apodizing");
50 return LOG_STR("hybrid");
51 default:
52 return LOG_STR("brick_wall");
53 }
54}
55
56const LogString *deemphasis_to_str(DeemphasisMode mode) {
57 switch (mode) {
59 return LOG_STR("32khz");
61 return LOG_STR("44_1khz");
63 return LOG_STR("48khz");
64 default:
65 return LOG_STR("bypass");
66 }
67}
68
69} // namespace
70
72 uint8_t chip_id = 0;
73 if (!this->read_byte(KATANA_REG_CHIP_ID, &chip_id)) {
74 this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
75 return;
76 }
77 if (chip_id != KATANA_CHIP_ID) {
78 ESP_LOGE(TAG, "Unexpected chip ID 0x%02X", chip_id);
79 this->mark_failed(LOG_STR("Unexpected chip ID"));
80 return;
81 }
82 if (!this->write_byte(KATANA_REG_RESET, 0x01)) {
83 this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
84 return;
85 }
86 delay(RESET_SETTLE_DELAY_MS);
87
88 // MUTE_STREAM is the gate the Linux driver closes between streams; ESPHome has no such
89 // notion, so it stays open and mute is only ever driven through MUTE.
90 if (!this->write_byte(KATANA_REG_FORMAT, this->format_) || !this->write_byte(KATANA_REG_COMMAND, 0x00) ||
91 !this->write_byte(KATANA_REG_DSP_PROGRAM, this->filter_shape_) ||
92 !this->write_byte(KATANA_REG_DEEMPHASIS, this->deemphasis_mode_) ||
93 !this->write_byte(KATANA_REG_DOP, this->dop_enabled_ ? 1 : 0) || !this->write_volume_() ||
94 !this->write_byte(KATANA_REG_MUTE, this->is_muted_ ? 1 : 0) || !this->write_byte(KATANA_REG_MUTE_STREAM, 0x00)) {
95 this->mark_failed(LOG_STR("Failed to configure the control logic"));
96 }
97}
98
100 ESP_LOGCONFIG(TAG,
101 "ES9038Q2M Katana:\n"
102 " Filter shape: %s\n"
103 " De-emphasis: %s\n"
104 " Format register: 0x%02X\n"
105 " DoP: %s",
106 LOG_STR_ARG(filter_shape_to_str(this->filter_shape_)),
107 LOG_STR_ARG(deemphasis_to_str(this->deemphasis_mode_)), this->format_, ONOFF(this->dop_enabled_));
108 LOG_I2C_DEVICE(this);
109 if (this->is_failed())
110 return;
111#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
112 for (uint8_t reg = KATANA_REG_CHIP_ID; reg <= KATANA_REG_MUTE_STREAM; reg++) {
113 uint8_t value = 0;
114 if (this->read_byte(reg, &value)) {
115 ESP_LOGV(TAG, " Register 0x%02X: 0x%02X", reg, value);
116 } else {
117 ESP_LOGV(TAG, " Register 0x%02X: read failed", reg);
118 }
119 }
120#endif
121}
122
123// Both channel registers follow ESPHome's single master volume.
125 return this->write_byte(KATANA_REG_VOLUME_1, this->volume_reg_) &&
126 this->write_byte(KATANA_REG_VOLUME_2, this->volume_reg_);
127}
128
129// Records the outcome of a runtime write; the cached state only changes on success.
131 if (ok) {
132 this->status_clear_warning();
133 } else {
134 this->status_set_warning(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
135 }
136 return ok;
137}
138
139bool ES9038Q2MKatana::set_volume(float volume) {
140 if (this->is_failed())
141 return false;
142 volume = clamp(volume, 0.0f, 1.0f);
143 const uint8_t previous = this->volume_reg_;
144 float attenuation = KATANA_VOLUME_FULL_ATTENUATION;
145 if (volume >= KATANA_VOLUME_MIDPOINT) {
146 attenuation = KATANA_VOLUME_MIDPOINT_STEPS * (1.0f - volume) / (1.0f - KATANA_VOLUME_MIDPOINT);
147 } else if (volume > 0.0f) {
148 attenuation = KATANA_VOLUME_MIDPOINT_STEPS - KATANA_VOLUME_CUBIC_SCALE * log10f(volume / KATANA_VOLUME_MIDPOINT);
149 }
150 this->volume_reg_ =
151 static_cast<uint8_t>(lroundf(clamp(attenuation, 0.0f, static_cast<float>(KATANA_VOLUME_FULL_ATTENUATION))));
152 if (this->check_write_(this->write_volume_()))
153 return true;
154 this->volume_reg_ = previous;
155 return false;
156}
157
159 if (this->volume_reg_ == KATANA_VOLUME_FULL_ATTENUATION)
160 return 0.0f;
161 if (this->volume_reg_ <= KATANA_VOLUME_MIDPOINT_STEPS)
162 return 1.0f - this->volume_reg_ * (1.0f - KATANA_VOLUME_MIDPOINT) / KATANA_VOLUME_MIDPOINT_STEPS;
163 return KATANA_VOLUME_MIDPOINT *
164 powf(10.0f, (KATANA_VOLUME_MIDPOINT_STEPS - this->volume_reg_) / KATANA_VOLUME_CUBIC_SCALE);
165}
166
167bool ES9038Q2MKatana::set_mute_state_(bool mute_state) {
168 if (this->is_failed())
169 return false;
170 if (!this->check_write_(this->write_byte(KATANA_REG_MUTE, mute_state ? 1 : 0)))
171 return false;
172 this->is_muted_ = mute_state;
173 return true;
174}
175
176} // namespace esphome::es9038q2m_katana
BedjetMode mode
BedJet operating mode.
void mark_failed()
Mark this component as failed.
bool is_failed() const
Definition component.h:274
void status_clear_warning()
Definition component.h:291
bool write_byte(uint8_t a_register, uint8_t data) const
Definition i2c.h:265
bool read_byte(uint8_t a_register, uint8_t *data)
Definition i2c.h:240
I2CRegister reg(uint8_t a_register)
calls the I2CRegister constructor
Definition i2c.h:152
ESPHOME_LOG_TAG(TAG, "es9038q2m_katana")
void HOT delay(uint32_t ms)
Definition hal.cpp:85
static void uint32_t