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/coolix/* @glmnet | ||||
| esphome/components/cover/* @esphome/core | ||||
| esphome/components/cs5460a/* @balrog-kun | ||||
| esphome/components/ct_clamp/* @jesserockz | ||||
| esphome/components/debug/* @OttoWinter | ||||
| 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 | ||||
|     address: 99 | ||||
|     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: | ||||
|   setup_mode: False | ||||
|   | ||||
		Reference in New Issue
	
	Block a user