13static constexpr uint16_t SERVICE_NOTIFY_UUID = 0xFFF0;
14static constexpr uint16_t CHAR_NOTIFY_UUID = 0xFFF1;
15static constexpr uint16_t SERVICE_WRITE_UUID = 0xFFD0;
16static constexpr uint16_t CHAR_WRITE_UUID = 0xFFD1;
17static constexpr uint16_t CHAR_INIT_UUID = 0xFFD4;
19static constexpr uint8_t MODBUS_DEVICE_ID = 0x20;
20static constexpr uint8_t MODBUS_READ_HOLDING = 0x03;
21static constexpr uint16_t REG_MAIN = 4000;
22static constexpr uint16_t REG_MAIN_WORDS = 32;
23static constexpr uint16_t REG_LOAD = 4408;
24static constexpr uint16_t REG_LOAD_WORDS = 6;
27static constexpr uint32_t CYCLE_TIMEOUT_MS = 3000;
29static constexpr uint32_t LOAD_READ_DELAY_MS = 300;
31static constexpr uint32_t CYCLE_TIMEOUT_ID = 0;
32static constexpr uint32_t LOAD_READ_ID = 1;
41 ESP_LOGCONFIG(TAG,
"Renogy Inverter BLE:");
42 LOG_UPDATE_INTERVAL(
this);
43 LOG_SENSOR(
" ",
"AC input voltage", this->ac_input_voltage_sensor_);
44 LOG_SENSOR(
" ",
"AC output voltage", this->ac_output_voltage_sensor_);
45 LOG_SENSOR(
" ",
"AC output current", this->ac_output_current_sensor_);
46 LOG_SENSOR(
" ",
"AC output frequency", this->ac_output_frequency_sensor_);
47 LOG_SENSOR(
" ",
"Input frequency", this->input_frequency_sensor_);
48 LOG_SENSOR(
" ",
"Battery voltage", this->battery_voltage_sensor_);
49 LOG_SENSOR(
" ",
"Temperature", this->temperature_sensor_);
50 LOG_SENSOR(
" ",
"Load current", this->load_current_sensor_);
51 LOG_SENSOR(
" ",
"Load active power", this->load_active_power_sensor_);
52 LOG_SENSOR(
" ",
"Load apparent power", this->load_apparent_power_sensor_);
63 std::array<uint8_t, 8> req{MODBUS_DEVICE_ID,
65 static_cast<uint8_t
>(start_register >> 8),
66 static_cast<uint8_t
>(start_register),
67 static_cast<uint8_t
>(word_count >> 8),
68 static_cast<uint8_t
>(word_count)};
69 const uint16_t crc =
crc16(req.data(), 6);
75 esp_ble_gattc_write_char(this->
parent_->
get_gattc_if(), this->parent_->get_conn_id(), this->write_handle_,
76 req.size(), req.data(), ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
78 ESP_LOGW(TAG,
"write_char (reg %u) failed, status=%d", start_register,
status);
84 this->
set_timeout(CYCLE_TIMEOUT_ID, CYCLE_TIMEOUT_MS, [
this]() {
85 ESP_LOGW(TAG,
"Read cycle timed out (state=%u); resetting",
static_cast<uint8_t
>(this->
state_));
96 this->init_handle_, ESP_GATT_AUTH_REQ_NONE);
98 ESP_LOGW(TAG,
"init read_char failed, status=%d",
status);
103void RenogyInverterBle::update() {
104 if (this->
node_state != espbt::ClientState::ESTABLISHED) {
105 ESP_LOGD(TAG,
"Not connected yet; skipping poll");
109 ESP_LOGD(TAG,
"Previous read cycle still in progress; skipping");
118 ESP_LOGW(TAG,
"CRC mismatch (state=%u, len=%u)",
static_cast<uint8_t
>(this->
state_), len);
123 if ((this->
frame_[1] & 0x80) != 0) {
124 ESP_LOGW(TAG,
"Modbus exception 0x%02X (state=%u)", this->
frame_[2],
static_cast<uint8_t
>(this->
state_));
130 const uint16_t expected_words = this->
state_ ==
State::MAIN ? REG_MAIN_WORDS : REG_LOAD_WORDS;
131 if (!awaiting_reply || this->
frame_[0] != MODBUS_DEVICE_ID || this->
frame_[1] != MODBUS_READ_HOLDING ||
132 this->
frame_[2] != 2 * expected_words) {
133 ESP_LOGW(TAG,
"Unexpected response %02X %02X %02X (state=%u)", this->
frame_[0], this->
frame_[1], this->
frame_[2],
134 static_cast<uint8_t
>(this->
state_));
141 publish(this->ac_input_voltage_sensor_, reg16(0) * 0.1f);
142 publish(this->ac_output_voltage_sensor_, reg16(2) * 0.1f);
143 publish(this->ac_output_current_sensor_, reg16(3) * 0.01f);
144 publish(this->ac_output_frequency_sensor_, reg16(4) * 0.01f);
145 publish(this->battery_voltage_sensor_, reg16(5) * 0.1f);
146 publish(this->temperature_sensor_, reg16(6) * 0.1f);
147 publish(this->input_frequency_sensor_, reg16(9) * 0.01f);
149 this->
set_timeout(LOAD_READ_ID, LOAD_READ_DELAY_MS, [
this]() {
155 publish(this->load_current_sensor_, reg16(0) * 0.01f);
156 publish(this->load_active_power_sensor_,
static_cast<float>(reg16(1)));
157 publish(this->load_apparent_power_sensor_,
static_cast<float>(reg16(2)));
161void RenogyInverterBle::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
162 esp_ble_gattc_cb_param_t *param) {
164 case ESP_GATTC_DISCONNECT_EVT: {
169 case ESP_GATTC_SEARCH_CMPL_EVT: {
171 espbt::ESPBTUUID::from_uint16(CHAR_NOTIFY_UUID));
173 espbt::ESPBTUUID::from_uint16(CHAR_WRITE_UUID));
175 espbt::ESPBTUUID::from_uint16(CHAR_INIT_UUID));
176 if (notify_chr ==
nullptr || write_chr ==
nullptr) {
177 ESP_LOGE(TAG,
"Required characteristics not found; not a Renogy inverter?");
182 this->
init_handle_ = (init_chr !=
nullptr) ? init_chr->handle : 0;
186 if (status != ESP_OK) {
187 ESP_LOGW(TAG,
"register_for_notify failed, status=%d", status);
191 case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
193 if (param->reg_for_notify.handle != this->notify_handle_) {
196 if (param->reg_for_notify.status != ESP_GATT_OK) {
197 ESP_LOGW(TAG,
"Notification registration failed, status=%d", param->reg_for_notify.status);
200 this->
node_state = espbt::ClientState::ESTABLISHED;
201 ESP_LOGI(TAG,
"Connected; will poll the inverter every update interval");
204 case ESP_GATTC_READ_CHAR_EVT: {
208 if (param->read.status != ESP_GATT_OK) {
209 ESP_LOGW(TAG,
"init read failed, status=%d", param->read.status);
218 case ESP_GATTC_NOTIFY_EVT: {
219 if (param->notify.handle != this->notify_handle_) {
222 const uint16_t value_len = param->notify.value_len;
223 if (this->
frame_len_ + value_len > MAX_FRAME) {
224 ESP_LOGW(TAG,
"Frame overflow; resetting");
235 const uint16_t expected_len = ((this->
frame_[1] & 0x80) != 0) ? 5 : 3 + this->
frame_[2] + 2;
uint16_t crc16(const uint8_t *data, uint16_t len, uint16_t crc, uint16_t reverse_poly, bool refin, bool refout)
Calculate a CRC-16 checksum of data with size len.