mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	Add CS5460A power-meter component (#1474)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
		
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							bb759d52c8
						
					
				
				
					commit
					4d586b1446
				
			| @@ -29,6 +29,7 @@ esphome/components/climate/* @esphome/core | |||||||
| esphome/components/climate_ir/* @glmnet | esphome/components/climate_ir/* @glmnet | ||||||
| esphome/components/coolix/* @glmnet | esphome/components/coolix/* @glmnet | ||||||
| esphome/components/cover/* @esphome/core | esphome/components/cover/* @esphome/core | ||||||
|  | esphome/components/cs5460a/* @balrog-kun | ||||||
| esphome/components/ct_clamp/* @jesserockz | esphome/components/ct_clamp/* @jesserockz | ||||||
| esphome/components/debug/* @OttoWinter | esphome/components/debug/* @OttoWinter | ||||||
| esphome/components/dfplayer/* @glmnet | esphome/components/dfplayer/* @glmnet | ||||||
|   | |||||||
							
								
								
									
										0
									
								
								esphome/components/cs5460a/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/cs5460a/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										342
									
								
								esphome/components/cs5460a/cs5460a.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										342
									
								
								esphome/components/cs5460a/cs5460a.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,342 @@ | |||||||
|  | #include "cs5460a.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace cs5460a { | ||||||
|  |  | ||||||
|  | static const char *TAG = "cs5460a"; | ||||||
|  |  | ||||||
|  | void CS5460AComponent::write_register_(enum CS5460ARegister addr, uint32_t value) { | ||||||
|  |   this->write_byte(CMD_WRITE | (addr << 1)); | ||||||
|  |   this->write_byte(value >> 16); | ||||||
|  |   this->write_byte(value >> 8); | ||||||
|  |   this->write_byte(value >> 0); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | uint32_t CS5460AComponent::read_register_(uint8_t addr) { | ||||||
|  |   uint32_t value; | ||||||
|  |  | ||||||
|  |   this->write_byte(CMD_READ | (addr << 1)); | ||||||
|  |   value = (uint32_t) this->transfer_byte(CMD_SYNC0) << 16; | ||||||
|  |   value |= (uint32_t) this->transfer_byte(CMD_SYNC0) << 8; | ||||||
|  |   value |= this->transfer_byte(CMD_SYNC0) << 0; | ||||||
|  |  | ||||||
|  |   return value; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool CS5460AComponent::softreset_() { | ||||||
|  |   uint32_t pc = ((uint8_t) phase_offset_ & 0x3f) | (phase_offset_ < 0 ? 0x40 : 0); | ||||||
|  |   uint32_t config = (1 << 0) |                    /* K = 0b0001 */ | ||||||
|  |                     (current_hpf_ ? 1 << 5 : 0) | /* IHPF */ | ||||||
|  |                     (voltage_hpf_ ? 1 << 6 : 0) | /* VHPF */ | ||||||
|  |                     (pga_gain_ << 16) |           /* Gi */ | ||||||
|  |                     (pc << 17);                   /* PC */ | ||||||
|  |   int cnt = 0; | ||||||
|  |  | ||||||
|  |   /* Serial resynchronization */ | ||||||
|  |   this->write_byte(CMD_SYNC1); | ||||||
|  |   this->write_byte(CMD_SYNC1); | ||||||
|  |   this->write_byte(CMD_SYNC1); | ||||||
|  |   this->write_byte(CMD_SYNC0); | ||||||
|  |  | ||||||
|  |   /* Reset */ | ||||||
|  |   this->write_register_(REG_CONFIG, 1 << 7); | ||||||
|  |   delay(10); | ||||||
|  |   while (cnt++ < 50 && (this->read_register_(REG_CONFIG) & 0x81) != 0x000001) | ||||||
|  |     ; | ||||||
|  |   if (cnt > 50) | ||||||
|  |     return false; | ||||||
|  |  | ||||||
|  |   this->write_register_(REG_CONFIG, config); | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void CS5460AComponent::setup() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "Setting up CS5460A..."); | ||||||
|  |  | ||||||
|  |   float current_full_scale = (pga_gain_ == CS5460A_PGA_GAIN_10X) ? 0.25 : 0.10; | ||||||
|  |   float voltage_full_scale = 0.25; | ||||||
|  |   current_multiplier_ = current_full_scale / (fabsf(current_gain_) * 0x1000000); | ||||||
|  |   voltage_multiplier_ = voltage_full_scale / (voltage_gain_ * 0x1000000); | ||||||
|  |  | ||||||
|  |   /* | ||||||
|  |    * Calculate power from the Energy register because the Power register | ||||||
|  |    * stores instantaneous power which varies a lot in each AC cycle, | ||||||
|  |    * while the Energy value is accumulated over the "computation cycle" | ||||||
|  |    * which should be an integer number of AC cycles. | ||||||
|  |    */ | ||||||
|  |   power_multiplier_ = | ||||||
|  |       (current_full_scale * voltage_full_scale * 4096) / (current_gain_ * voltage_gain_ * samples_ * 0x800000); | ||||||
|  |  | ||||||
|  |   pulse_freq_ = | ||||||
|  |       (current_full_scale * voltage_full_scale) / (fabsf(current_gain_) * voltage_gain_ * pulse_energy_wh_ * 3600); | ||||||
|  |  | ||||||
|  |   hw_init_(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void CS5460AComponent::hw_init_() { | ||||||
|  |   this->spi_setup(); | ||||||
|  |   this->enable(); | ||||||
|  |  | ||||||
|  |   if (!this->softreset_()) { | ||||||
|  |     this->disable(); | ||||||
|  |     ESP_LOGE(TAG, "CS5460A reset failed!"); | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   uint32_t status = this->read_register_(REG_STATUS); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Version: %x", (status >> 6) & 7); | ||||||
|  |  | ||||||
|  |   this->write_register_(REG_CYCLE_COUNT, samples_); | ||||||
|  |   this->write_register_(REG_PULSE_RATE, lroundf(pulse_freq_ * 32.0f)); | ||||||
|  |  | ||||||
|  |   /* Use one of the power saving features (assuming external oscillator), reset other CONTROL bits, | ||||||
|  |    * sometimes softreset_() is not enough */ | ||||||
|  |   this->write_register_(REG_CONTROL, 0x000004); | ||||||
|  |  | ||||||
|  |   this->restart_(); | ||||||
|  |   this->disable(); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Init ok"); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Doesn't reset the register values etc., just restarts the "computation cycle" */ | ||||||
|  | void CS5460AComponent::restart_() { | ||||||
|  |   int cnt; | ||||||
|  |  | ||||||
|  |   this->enable(); | ||||||
|  |   /* Stop running conversion, wake up if needed */ | ||||||
|  |   this->write_byte(CMD_POWER_UP); | ||||||
|  |   /* Start continuous conversion */ | ||||||
|  |   this->write_byte(CMD_START_CONT); | ||||||
|  |   this->disable(); | ||||||
|  |  | ||||||
|  |   this->started_(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void CS5460AComponent::started_() { | ||||||
|  |   /* | ||||||
|  |    * Try to guess when the next batch of results is going to be ready and | ||||||
|  |    * schedule next STATUS check some time before that moment.  This assumes | ||||||
|  |    * two things: | ||||||
|  |    *   * a new "computation cycle" started just now.  If it started some | ||||||
|  |    *     time ago we may be a late next time, but hopefully less late in each | ||||||
|  |    *     iteration -- that's why we schedule the next check in some 0.8 of | ||||||
|  |    *     the time we actually expect the next reading ready. | ||||||
|  |    *   * MCLK rate is 4.096MHz and K == 1.  If there's a CS5460A module in | ||||||
|  |    *     use with a different clock this will need to be parametrised. | ||||||
|  |    */ | ||||||
|  |   expect_data_ts_ = millis() + samples_ * 1024 / 4096; | ||||||
|  |  | ||||||
|  |   schedule_next_check_(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void CS5460AComponent::schedule_next_check_() { | ||||||
|  |   int32_t time_left = expect_data_ts_ - millis(); | ||||||
|  |  | ||||||
|  |   /* First try at 0.8 of the actual expected time (if it's in the future) */ | ||||||
|  |   if (time_left > 0) | ||||||
|  |     time_left -= time_left / 5; | ||||||
|  |  | ||||||
|  |   if (time_left > -500) { | ||||||
|  |     /* But not sooner than in 30ms from now */ | ||||||
|  |     if (time_left < 30) | ||||||
|  |       time_left = 30; | ||||||
|  |   } else { | ||||||
|  |     /* | ||||||
|  |      * If the measurement is more than 0.5s overdue start worrying.  The | ||||||
|  |      * device may be stuck because of an overcurrent error or similar, | ||||||
|  |      * from now on just retry every 1s.  After 15s try a reset, if it | ||||||
|  |      * fails we give up and mark the component "failed". | ||||||
|  |      */ | ||||||
|  |     if (time_left > -15000) { | ||||||
|  |       time_left = 1000; | ||||||
|  |       this->status_momentary_warning("warning", 1000); | ||||||
|  |     } else { | ||||||
|  |       ESP_LOGCONFIG(TAG, "Device officially stuck, resetting"); | ||||||
|  |       this->cancel_timeout("status-check"); | ||||||
|  |       this->hw_init_(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->set_timeout("status-check", time_left, [this]() { | ||||||
|  |     if (!this->check_status_()) | ||||||
|  |       this->schedule_next_check_(); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool CS5460AComponent::check_status_() { | ||||||
|  |   this->enable(); | ||||||
|  |   uint32_t status = this->read_register_(REG_STATUS); | ||||||
|  |  | ||||||
|  |   if (!(status & 0xcbf83c)) { | ||||||
|  |     this->disable(); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   uint32_t clear = 1 << 20; | ||||||
|  |  | ||||||
|  |   /* TODO: Report if IC=0 but only once as it can't be cleared */ | ||||||
|  |  | ||||||
|  |   if (status & (1 << 2)) { | ||||||
|  |     clear |= 1 << 2; | ||||||
|  |     ESP_LOGE(TAG, "Low supply detected"); | ||||||
|  |     this->status_momentary_warning("warning", 500); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (status & (1 << 3)) { | ||||||
|  |     clear |= 1 << 3; | ||||||
|  |     ESP_LOGE(TAG, "Modulator oscillation on current channel"); | ||||||
|  |     this->status_momentary_warning("warning", 500); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (status & (1 << 4)) { | ||||||
|  |     clear |= 1 << 4; | ||||||
|  |     ESP_LOGE(TAG, "Modulator oscillation on voltage channel"); | ||||||
|  |     this->status_momentary_warning("warning", 500); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (status & (1 << 5)) { | ||||||
|  |     clear |= 1 << 5; | ||||||
|  |     ESP_LOGE(TAG, "Watch-dog timeout"); | ||||||
|  |     this->status_momentary_warning("warning", 500); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (status & (1 << 11)) { | ||||||
|  |     clear |= 1 << 11; | ||||||
|  |     ESP_LOGE(TAG, "EOUT Energy Accumulation Register out of range"); | ||||||
|  |     this->status_momentary_warning("warning", 500); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (status & (1 << 12)) { | ||||||
|  |     clear |= 1 << 12; | ||||||
|  |     ESP_LOGE(TAG, "Energy out of range"); | ||||||
|  |     this->status_momentary_warning("warning", 500); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (status & (1 << 13)) { | ||||||
|  |     clear |= 1 << 13; | ||||||
|  |     ESP_LOGE(TAG, "RMS voltage out of range"); | ||||||
|  |     this->status_momentary_warning("warning", 500); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (status & (1 << 14)) { | ||||||
|  |     clear |= 1 << 14; | ||||||
|  |     ESP_LOGE(TAG, "RMS current out of range"); | ||||||
|  |     this->status_momentary_warning("warning", 500); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (status & (1 << 15)) { | ||||||
|  |     clear |= 1 << 15; | ||||||
|  |     ESP_LOGE(TAG, "Power calculation out of range"); | ||||||
|  |     this->status_momentary_warning("warning", 500); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (status & (1 << 16)) { | ||||||
|  |     clear |= 1 << 16; | ||||||
|  |     ESP_LOGE(TAG, "Voltage out of range"); | ||||||
|  |     this->status_momentary_warning("warning", 500); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (status & (1 << 17)) { | ||||||
|  |     clear |= 1 << 17; | ||||||
|  |     ESP_LOGE(TAG, "Current out of range"); | ||||||
|  |     this->status_momentary_warning("warning", 500); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (status & (1 << 19)) { | ||||||
|  |     clear |= 1 << 19; | ||||||
|  |     ESP_LOGE(TAG, "Divide overflowed"); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (status & (1 << 22)) { | ||||||
|  |     bool dir = status & (1 << 21); | ||||||
|  |     if (current_gain_ < 0) | ||||||
|  |       dir = !dir; | ||||||
|  |     ESP_LOGI(TAG, "Energy counter %s pulse", dir ? "negative" : "positive"); | ||||||
|  |     clear |= 1 << 22; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   uint32_t raw_current = 0; /* Calm the validators */ | ||||||
|  |   uint32_t raw_voltage = 0; | ||||||
|  |   uint32_t raw_energy = 0; | ||||||
|  |  | ||||||
|  |   if (status & (1 << 23)) { | ||||||
|  |     clear |= 1 << 23; | ||||||
|  |  | ||||||
|  |     if (current_sensor_ != nullptr) | ||||||
|  |       raw_current = this->read_register_(REG_IRMS); | ||||||
|  |  | ||||||
|  |     if (voltage_sensor_ != nullptr) | ||||||
|  |       raw_voltage = this->read_register_(REG_VRMS); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (status & ((1 << 23) | (1 << 5))) { | ||||||
|  |     /* Read to clear the WDT bit */ | ||||||
|  |     raw_energy = this->read_register_(REG_E); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->write_register_(REG_STATUS, clear); | ||||||
|  |   this->disable(); | ||||||
|  |  | ||||||
|  |   /* | ||||||
|  |    * Schedule the next STATUS check assuming that DRDY was asserted very | ||||||
|  |    * recently, then publish the new values.  Do this last for reentrancy in | ||||||
|  |    * case the publish triggers a restart() or for whatever reason needs to | ||||||
|  |    * cancel the timeout set in schedule_next_check_(), or needs to use SPI. | ||||||
|  |    * If the current or power values haven't changed one bit it may be that | ||||||
|  |    * the chip somehow forgot to update the registers -- seen happening very | ||||||
|  |    * rarely.  In that case don't publish them because the user may have | ||||||
|  |    * the input connected to a multiplexer and may have switched channels | ||||||
|  |    * since the previous reading and we'd be publishing the stale value for | ||||||
|  |    * the new channel.  If the value *was* updated it's very unlikely that | ||||||
|  |    * it wouldn't have changed, especially power/energy which are affected | ||||||
|  |    * by the noise on both the current and value channels (in case of energy, | ||||||
|  |    * accumulated over many conversion cycles.) | ||||||
|  |    */ | ||||||
|  |   if (status & (1 << 23)) { | ||||||
|  |     this->started_(); | ||||||
|  |  | ||||||
|  |     if (current_sensor_ != nullptr && raw_current != prev_raw_current_) { | ||||||
|  |       current_sensor_->publish_state(raw_current * current_multiplier_); | ||||||
|  |       prev_raw_current_ = raw_current; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (voltage_sensor_ != nullptr) | ||||||
|  |       voltage_sensor_->publish_state(raw_voltage * voltage_multiplier_); | ||||||
|  |  | ||||||
|  |     if (power_sensor_ != nullptr && raw_energy != prev_raw_energy_) { | ||||||
|  |       int32_t raw = (int32_t)(raw_energy << 8) >> 8; /* Sign-extend */ | ||||||
|  |       power_sensor_->publish_state(raw * power_multiplier_); | ||||||
|  |       prev_raw_energy_ = raw_energy; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void CS5460AComponent::dump_config() { | ||||||
|  |   uint32_t state = this->get_component_state(); | ||||||
|  |  | ||||||
|  |   ESP_LOGCONFIG(TAG, "CS5460A:"); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Init status: %s", | ||||||
|  |                 state == COMPONENT_STATE_LOOP ? "OK" : (state == COMPONENT_STATE_FAILED ? "failed" : "other")); | ||||||
|  |   LOG_PIN("  CS Pin: ", cs_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Samples / cycle: %u", samples_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Phase offset: %i", phase_offset_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  PGA Gain: %s", pga_gain_ == CS5460A_PGA_GAIN_50X ? "50x" : "10x"); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Current gain: %.5f", current_gain_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Voltage gain: %.5f", voltage_gain_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Current HPF: %s", current_hpf_ ? "enabled" : "disabled"); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Voltage HPF: %s", voltage_hpf_ ? "enabled" : "disabled"); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Pulse energy: %.2f Wh", pulse_energy_wh_); | ||||||
|  |   LOG_SENSOR("  ", "Voltage", voltage_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Current", current_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Power", power_sensor_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace cs5460a | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										123
									
								
								esphome/components/cs5460a/cs5460a.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								esphome/components/cs5460a/cs5460a.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,123 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/automation.h" | ||||||
|  | #include "esphome/components/sensor/sensor.h" | ||||||
|  | #include "esphome/components/spi/spi.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace cs5460a { | ||||||
|  |  | ||||||
|  | enum CS5460ACommand { | ||||||
|  |   CMD_SYNC0 = 0xfe, | ||||||
|  |   CMD_SYNC1 = 0xff, | ||||||
|  |   CMD_START_SINGLE = 0xe0, | ||||||
|  |   CMD_START_CONT = 0xe8, | ||||||
|  |   CMD_POWER_UP = 0xa0, | ||||||
|  |   CMD_POWER_STANDBY = 0x88, | ||||||
|  |   CMD_POWER_SLEEP = 0x90, | ||||||
|  |   CMD_CALIBRATION = 0xc0, | ||||||
|  |   CMD_READ = 0x00, | ||||||
|  |   CMD_WRITE = 0x40, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum CS5460ARegister { | ||||||
|  |   REG_CONFIG = 0x00, | ||||||
|  |   REG_IDCOFF = 0x01, | ||||||
|  |   REG_IGN = 0x02, | ||||||
|  |   REG_VDCOFF = 0x03, | ||||||
|  |   REG_VGN = 0x04, | ||||||
|  |   REG_CYCLE_COUNT = 0x05, | ||||||
|  |   REG_PULSE_RATE = 0x06, | ||||||
|  |   REG_I = 0x07, | ||||||
|  |   REG_V = 0x08, | ||||||
|  |   REG_P = 0x09, | ||||||
|  |   REG_E = 0x0a, | ||||||
|  |   REG_IRMS = 0x0b, | ||||||
|  |   REG_VRMS = 0x0c, | ||||||
|  |   REG_TBC = 0x0d, | ||||||
|  |   REG_POFF = 0x0e, | ||||||
|  |   REG_STATUS = 0x0f, | ||||||
|  |   REG_IACOFF = 0x10, | ||||||
|  |   REG_VACOFF = 0x11, | ||||||
|  |   REG_MASK = 0x1a, | ||||||
|  |   REG_CONTROL = 0x1c, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** Enum listing the current channel aplifiergain settings for the CS5460A. | ||||||
|  |  */ | ||||||
|  | enum CS5460APGAGain { | ||||||
|  |   CS5460A_PGA_GAIN_10X = 0b0, | ||||||
|  |   CS5460A_PGA_GAIN_50X = 0b1, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class CS5460AComponent : public Component, | ||||||
|  |                          public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, | ||||||
|  |                                                spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_1MHZ> { | ||||||
|  |  public: | ||||||
|  |   void set_samples(uint32_t samples) { samples_ = samples; } | ||||||
|  |   void set_phase_offset(int8_t phase_offset) { phase_offset_ = phase_offset; } | ||||||
|  |   void set_pga_gain(CS5460APGAGain pga_gain) { pga_gain_ = pga_gain; } | ||||||
|  |   void set_gains(float current_gain, float voltage_gain) { | ||||||
|  |     current_gain_ = current_gain; | ||||||
|  |     voltage_gain_ = voltage_gain; | ||||||
|  |   } | ||||||
|  |   void set_hpf_enable(bool current_hpf, bool voltage_hpf) { | ||||||
|  |     current_hpf_ = current_hpf; | ||||||
|  |     voltage_hpf_ = voltage_hpf; | ||||||
|  |   } | ||||||
|  |   void set_pulse_energy_wh(float pulse_energy_wh) { pulse_energy_wh_ = pulse_energy_wh; } | ||||||
|  |   void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; } | ||||||
|  |   void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; } | ||||||
|  |   void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; } | ||||||
|  |  | ||||||
|  |   void restart() { restart_(); } | ||||||
|  |  | ||||||
|  |   void setup() override; | ||||||
|  |   void loop() override {} | ||||||
|  |   float get_setup_priority() const override { return setup_priority::DATA; } | ||||||
|  |   void dump_config() override; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   uint32_t samples_; | ||||||
|  |   int8_t phase_offset_; | ||||||
|  |   CS5460APGAGain pga_gain_; | ||||||
|  |   float current_gain_; | ||||||
|  |   float voltage_gain_; | ||||||
|  |   bool current_hpf_; | ||||||
|  |   bool voltage_hpf_; | ||||||
|  |   float pulse_energy_wh_; | ||||||
|  |   sensor::Sensor *current_sensor_{nullptr}; | ||||||
|  |   sensor::Sensor *voltage_sensor_{nullptr}; | ||||||
|  |   sensor::Sensor *power_sensor_{nullptr}; | ||||||
|  |  | ||||||
|  |   void write_register_(enum CS5460ARegister addr, uint32_t value); | ||||||
|  |   uint32_t read_register_(uint8_t addr); | ||||||
|  |   bool softreset_(); | ||||||
|  |   void hw_init_(); | ||||||
|  |   void restart_(); | ||||||
|  |   void started_(); | ||||||
|  |   void schedule_next_check_(); | ||||||
|  |   bool check_status_(); | ||||||
|  |  | ||||||
|  |   float current_multiplier_; | ||||||
|  |   float voltage_multiplier_; | ||||||
|  |   float power_multiplier_; | ||||||
|  |   float pulse_freq_; | ||||||
|  |   uint32_t expect_data_ts_; | ||||||
|  |   uint32_t prev_raw_current_{0}; | ||||||
|  |   uint32_t prev_raw_energy_{0}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | template<typename... Ts> class CS5460ARestartAction : public Action<Ts...> { | ||||||
|  |  public: | ||||||
|  |   CS5460ARestartAction(CS5460AComponent *cs5460a) : cs5460a_(cs5460a) {} | ||||||
|  |  | ||||||
|  |   void play(Ts... x) override { cs5460a_->restart(); } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   CS5460AComponent *cs5460a_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace cs5460a | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										136
									
								
								esphome/components/cs5460a/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								esphome/components/cs5460a/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,136 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import spi, sensor | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_CURRENT, | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_POWER, | ||||||
|  |     CONF_VOLTAGE, | ||||||
|  |     UNIT_VOLT, | ||||||
|  |     UNIT_AMPERE, | ||||||
|  |     UNIT_WATT, | ||||||
|  |     ICON_EMPTY, | ||||||
|  |     DEVICE_CLASS_POWER, | ||||||
|  |     DEVICE_CLASS_CURRENT, | ||||||
|  |     DEVICE_CLASS_VOLTAGE, | ||||||
|  | ) | ||||||
|  | from esphome import automation | ||||||
|  | from esphome.automation import maybe_simple_id | ||||||
|  |  | ||||||
|  | CODEOWNERS = ["@balrog-kun"] | ||||||
|  | DEPENDENCIES = ["spi"] | ||||||
|  |  | ||||||
|  | cs5460a_ns = cg.esphome_ns.namespace("cs5460a") | ||||||
|  | CS5460APGAGain = cs5460a_ns.enum("CS5460APGAGain") | ||||||
|  | PGA_GAIN_OPTIONS = { | ||||||
|  |     "10X": CS5460APGAGain.CS5460A_PGA_GAIN_10X, | ||||||
|  |     "50X": CS5460APGAGain.CS5460A_PGA_GAIN_50X, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | CS5460AComponent = cs5460a_ns.class_("CS5460AComponent", spi.SPIDevice, cg.Component) | ||||||
|  | CS5460ARestartAction = cs5460a_ns.class_("CS5460ARestartAction", automation.Action) | ||||||
|  |  | ||||||
|  | CONF_SAMPLES = "samples" | ||||||
|  | CONF_PHASE_OFFSET = "phase_offset" | ||||||
|  | CONF_PGA_GAIN = "pga_gain" | ||||||
|  | CONF_CURRENT_GAIN = "current_gain" | ||||||
|  | CONF_VOLTAGE_GAIN = "voltage_gain" | ||||||
|  | CONF_CURRENT_HPF = "current_hpf" | ||||||
|  | CONF_VOLTAGE_HPF = "voltage_hpf" | ||||||
|  | CONF_PULSE_ENERGY = "pulse_energy" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def validate_config(config): | ||||||
|  |     current_gain = abs(config[CONF_CURRENT_GAIN]) * ( | ||||||
|  |         1.0 if config[CONF_PGA_GAIN] == "10X" else 5.0 | ||||||
|  |     ) | ||||||
|  |     voltage_gain = config[CONF_VOLTAGE_GAIN] | ||||||
|  |     pulse_energy = config[CONF_PULSE_ENERGY] | ||||||
|  |  | ||||||
|  |     if current_gain == 0.0 or voltage_gain == 0.0: | ||||||
|  |         raise cv.Invalid("The gains can't be zero") | ||||||
|  |  | ||||||
|  |     max_energy = (0.25 * 0.25 / 3600 / (2 ** -4)) / (voltage_gain * current_gain) | ||||||
|  |     min_energy = (0.25 * 0.25 / 3600 / (2 ** 18)) / (voltage_gain * current_gain) | ||||||
|  |     mech_min_energy = (0.25 * 0.25 / 3600 / 7.8) / (voltage_gain * current_gain) | ||||||
|  |     if pulse_energy < min_energy or pulse_energy > max_energy: | ||||||
|  |         raise cv.Invalid( | ||||||
|  |             "For given current&voltage gains, the pulse energy must be between " | ||||||
|  |             f"{min_energy} Wh and {max_energy} Wh and in mechanical counter mode " | ||||||
|  |             f"between {mech_min_energy} Wh and {max_energy} Wh" | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     return config | ||||||
|  |  | ||||||
|  |  | ||||||
|  | validate_energy = cv.float_with_unit("energy", "(Wh|WH|wh)?", optional_unit=True) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.All( | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.declare_id(CS5460AComponent), | ||||||
|  |             cv.Optional(CONF_SAMPLES, default=4000): cv.int_range(min=1, max=0xFFFFFF), | ||||||
|  |             cv.Optional(CONF_PHASE_OFFSET, default=0): cv.int_range(min=-64, max=63), | ||||||
|  |             cv.Optional(CONF_PGA_GAIN, default="10X"): cv.enum( | ||||||
|  |                 PGA_GAIN_OPTIONS, upper=True | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_CURRENT_GAIN, default=0.001): cv.negative_one_to_one_float, | ||||||
|  |             cv.Optional(CONF_VOLTAGE_GAIN, default=0.001): cv.zero_to_one_float, | ||||||
|  |             cv.Optional(CONF_CURRENT_HPF, default=True): cv.boolean, | ||||||
|  |             cv.Optional(CONF_VOLTAGE_HPF, default=True): cv.boolean, | ||||||
|  |             cv.Optional(CONF_PULSE_ENERGY, default=10.0): validate_energy, | ||||||
|  |             cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( | ||||||
|  |                 UNIT_VOLT, ICON_EMPTY, 0, DEVICE_CLASS_VOLTAGE | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_CURRENT): sensor.sensor_schema( | ||||||
|  |                 UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_POWER): sensor.sensor_schema( | ||||||
|  |                 UNIT_WATT, ICON_EMPTY, 0, DEVICE_CLASS_POWER | ||||||
|  |             ), | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(cv.COMPONENT_SCHEMA) | ||||||
|  |     .extend(spi.spi_device_schema(cs_pin_required=False)), | ||||||
|  |     validate_config, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |     await spi.register_spi_device(var, config) | ||||||
|  |  | ||||||
|  |     cg.add(var.set_samples(config[CONF_SAMPLES])) | ||||||
|  |     cg.add(var.set_phase_offset(config[CONF_PHASE_OFFSET])) | ||||||
|  |     cg.add(var.set_pga_gain(config[CONF_PGA_GAIN])) | ||||||
|  |     cg.add(var.set_gains(config[CONF_CURRENT_GAIN], config[CONF_VOLTAGE_GAIN])) | ||||||
|  |     cg.add(var.set_hpf_enable(config[CONF_CURRENT_HPF], config[CONF_VOLTAGE_HPF])) | ||||||
|  |     cg.add(var.set_pulse_energy_wh(config[CONF_PULSE_ENERGY])) | ||||||
|  |  | ||||||
|  |     if CONF_VOLTAGE in config: | ||||||
|  |         conf = config[CONF_VOLTAGE] | ||||||
|  |         sens = await sensor.new_sensor(conf) | ||||||
|  |         cg.add(var.set_voltage_sensor(sens)) | ||||||
|  |     if CONF_CURRENT in config: | ||||||
|  |         conf = config[CONF_CURRENT] | ||||||
|  |         sens = await sensor.new_sensor(conf) | ||||||
|  |         cg.add(var.set_current_sensor(sens)) | ||||||
|  |     if CONF_POWER in config: | ||||||
|  |         conf = config[CONF_POWER] | ||||||
|  |         sens = await sensor.new_sensor(conf) | ||||||
|  |         cg.add(var.set_power_sensor(sens)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action( | ||||||
|  |     "cs5460a.restart", | ||||||
|  |     CS5460ARestartAction, | ||||||
|  |     maybe_simple_id( | ||||||
|  |         { | ||||||
|  |             cv.Required(CONF_ID): cv.use_id(CS5460AComponent), | ||||||
|  |         } | ||||||
|  |     ), | ||||||
|  | ) | ||||||
|  | async def restart_action_to_code(config, action_id, template_arg, args): | ||||||
|  |     paren = await cg.get_variable(config[CONF_ID]) | ||||||
|  |     return cg.new_Pvariable(action_id, template_arg, paren) | ||||||
| @@ -914,6 +914,28 @@ sensor: | |||||||
|     id: ph_ezo |     id: ph_ezo | ||||||
|     address: 99 |     address: 99 | ||||||
|     unit_of_measurement: 'pH' |     unit_of_measurement: 'pH' | ||||||
|  |   - platform: cs5460a | ||||||
|  |     id: cs5460a1 | ||||||
|  |     current: | ||||||
|  |       name: "Socket current" | ||||||
|  |     voltage: | ||||||
|  |       name: "Mains voltage" | ||||||
|  |     power: | ||||||
|  |       name: "Socket power" | ||||||
|  |       on_value: | ||||||
|  |         then: | ||||||
|  |           cs5460a.restart: cs5460a1 | ||||||
|  |     samples: 1600 | ||||||
|  |     pga_gain: 10X | ||||||
|  |     current_gain: 0.01 | ||||||
|  |     voltage_gain: 0.000573 | ||||||
|  |     current_hpf: on | ||||||
|  |     voltage_hpf: on | ||||||
|  |     phase_offset: 20 | ||||||
|  |     pulse_energy: 0.01 kWh | ||||||
|  |     cs_pin: | ||||||
|  |       mcp23xxx: mcp23017_hub | ||||||
|  |       number: 14 | ||||||
|  |  | ||||||
| esp32_touch: | esp32_touch: | ||||||
|   setup_mode: False |   setup_mode: False | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user