mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	Add dfplayer mini component (#655)
* Add dfplayer mini component * receiving some data * implemented many actions * lint * undo homeassistant_time.h * Update esphome/components/dfplayer/__init__.py Co-Authored-By: Otto Winter <otto@otto-winter.com> * Update esphome/components/dfplayer/dfplayer.cpp Co-Authored-By: Otto Winter <otto@otto-winter.com> * add set device. fixes * lint * Fixes and sync with docs * add test * lint * lint * lint
This commit is contained in:
		
				
					committed by
					
						 Otto Winter
						Otto Winter
					
				
			
			
				
	
			
			
			
						parent
						
							18426b71e4
						
					
				
				
					commit
					af15a4e710
				
			
							
								
								
									
										221
									
								
								esphome/components/dfplayer/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										221
									
								
								esphome/components/dfplayer/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,221 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome import automation | ||||||
|  | from esphome.const import CONF_ID, CONF_TRIGGER_ID | ||||||
|  | from esphome.components import uart | ||||||
|  |  | ||||||
|  | DEPENDENCIES = ['uart'] | ||||||
|  |  | ||||||
|  | dfplayer_ns = cg.esphome_ns.namespace('dfplayer') | ||||||
|  | DFPlayer = dfplayer_ns.class_('DFPlayer', cg.Component) | ||||||
|  | DFPlayerFinishedPlaybackTrigger = dfplayer_ns.class_('DFPlayerFinishedPlaybackTrigger', | ||||||
|  |                                                      automation.Trigger.template()) | ||||||
|  | DFPlayerIsPlayingCondition = dfplayer_ns.class_('DFPlayerIsPlayingCondition', automation.Condition) | ||||||
|  |  | ||||||
|  | MULTI_CONF = True | ||||||
|  | CONF_FOLDER = 'folder' | ||||||
|  | CONF_FILE = 'file' | ||||||
|  | CONF_LOOP = 'loop' | ||||||
|  | CONF_VOLUME = 'volume' | ||||||
|  | CONF_DEVICE = 'device' | ||||||
|  | CONF_EQ_PRESET = 'eq_preset' | ||||||
|  | CONF_ON_FINISHED_PLAYBACK = 'on_finished_playback' | ||||||
|  |  | ||||||
|  | EqPreset = dfplayer_ns.enum("EqPreset") | ||||||
|  | EQ_PRESET = { | ||||||
|  |     'NORMAL': EqPreset.NORMAL, | ||||||
|  |     'POP': EqPreset.POP, | ||||||
|  |     'ROCK': EqPreset.ROCK, | ||||||
|  |     'JAZZ': EqPreset.JAZZ, | ||||||
|  |     'CLASSIC': EqPreset.CLASSIC, | ||||||
|  |     'BASS': EqPreset.BASS, | ||||||
|  | } | ||||||
|  | Device = dfplayer_ns.enum("Device") | ||||||
|  | DEVICE = { | ||||||
|  |     'USB': Device.USB, | ||||||
|  |     'TF_CARD': Device.TF_CARD, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | NextAction = dfplayer_ns.class_('NextAction', automation.Action) | ||||||
|  | PreviousAction = dfplayer_ns.class_('PreviousAction', automation.Action) | ||||||
|  | PlayFileAction = dfplayer_ns.class_('PlayFileAction', automation.Action) | ||||||
|  | PlayFolderAction = dfplayer_ns.class_('PlayFolderAction', automation.Action) | ||||||
|  | SetVolumeAction = dfplayer_ns.class_('SetVolumeAction', automation.Action) | ||||||
|  | SetEqAction = dfplayer_ns.class_('SetEqAction', automation.Action) | ||||||
|  | SleepAction = dfplayer_ns.class_('SleepAction', automation.Action) | ||||||
|  | ResetAction = dfplayer_ns.class_('ResetAction', automation.Action) | ||||||
|  | StartAction = dfplayer_ns.class_('StartAction', automation.Action) | ||||||
|  | PauseAction = dfplayer_ns.class_('PauseAction', automation.Action) | ||||||
|  | StopAction = dfplayer_ns.class_('StopAction', automation.Action) | ||||||
|  | RandomAction = dfplayer_ns.class_('RandomAction', automation.Action) | ||||||
|  | SetDeviceAction = dfplayer_ns.class_('SetDeviceAction', automation.Action) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.All(cv.Schema({ | ||||||
|  |     cv.GenerateID(): cv.declare_id(DFPlayer), | ||||||
|  |     cv.Optional(CONF_ON_FINISHED_PLAYBACK): automation.validate_automation({ | ||||||
|  |         cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DFPlayerFinishedPlaybackTrigger), | ||||||
|  |     }), | ||||||
|  | }).extend(uart.UART_DEVICE_SCHEMA)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     yield cg.register_component(var, config) | ||||||
|  |     yield uart.register_uart_device(var, config) | ||||||
|  |  | ||||||
|  |     for conf in config.get(CONF_ON_FINISHED_PLAYBACK, []): | ||||||
|  |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||||
|  |         yield automation.build_automation(trigger, [], conf) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action('dfplayer.play_next', NextAction, cv.Schema({ | ||||||
|  |     cv.GenerateID(): cv.use_id(DFPlayer), | ||||||
|  | })) | ||||||
|  | def dfplayer_next_to_code(config, action_id, template_arg, args): | ||||||
|  |     var = cg.new_Pvariable(action_id, template_arg) | ||||||
|  |     yield cg.register_parented(var, config[CONF_ID]) | ||||||
|  |     yield var | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action('dfplayer.play_previous', PreviousAction, cv.Schema({ | ||||||
|  |     cv.GenerateID(): cv.use_id(DFPlayer), | ||||||
|  | })) | ||||||
|  | def dfplayer_previous_to_code(config, action_id, template_arg, args): | ||||||
|  |     var = cg.new_Pvariable(action_id, template_arg) | ||||||
|  |     yield cg.register_parented(var, config[CONF_ID]) | ||||||
|  |     yield var | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action('dfplayer.play', PlayFileAction, cv.maybe_simple_value({ | ||||||
|  |     cv.GenerateID(): cv.use_id(DFPlayer), | ||||||
|  |     cv.Required(CONF_FILE): cv.templatable(cv.int_), | ||||||
|  |     cv.Optional(CONF_LOOP): cv.templatable(cv.boolean), | ||||||
|  | }, key=CONF_FILE)) | ||||||
|  | def dfplayer_play_to_code(config, action_id, template_arg, args): | ||||||
|  |     var = cg.new_Pvariable(action_id, template_arg) | ||||||
|  |     yield cg.register_parented(var, config[CONF_ID]) | ||||||
|  |     template_ = yield cg.templatable(config[CONF_FILE], args, float) | ||||||
|  |     cg.add(var.set_file(template_)) | ||||||
|  |     if CONF_LOOP in config: | ||||||
|  |         template_ = yield cg.templatable(config[CONF_LOOP], args, float) | ||||||
|  |         cg.add(var.set_loop(template_)) | ||||||
|  |     yield var | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action('dfplayer.play_folder', PlayFolderAction, cv.Schema({ | ||||||
|  |     cv.GenerateID(): cv.use_id(DFPlayer), | ||||||
|  |     cv.Required(CONF_FOLDER): cv.templatable(cv.int_), | ||||||
|  |     cv.Optional(CONF_FILE): cv.templatable(cv.int_), | ||||||
|  |     cv.Optional(CONF_LOOP): cv.templatable(cv.boolean), | ||||||
|  | })) | ||||||
|  | def dfplayer_play_folder_to_code(config, action_id, template_arg, args): | ||||||
|  |     var = cg.new_Pvariable(action_id, template_arg) | ||||||
|  |     yield cg.register_parented(var, config[CONF_ID]) | ||||||
|  |     template_ = yield cg.templatable(config[CONF_FOLDER], args, float) | ||||||
|  |     cg.add(var.set_folder(template_)) | ||||||
|  |     if CONF_FILE in config: | ||||||
|  |         template_ = yield cg.templatable(config[CONF_FILE], args, float) | ||||||
|  |         cg.add(var.set_file(template_)) | ||||||
|  |     if CONF_LOOP in config: | ||||||
|  |         template_ = yield cg.templatable(config[CONF_LOOP], args, float) | ||||||
|  |         cg.add(var.set_loop(template_)) | ||||||
|  |     yield var | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action('dfplayer.set_device', SetDeviceAction, cv.maybe_simple_value({ | ||||||
|  |     cv.GenerateID(): cv.use_id(DFPlayer), | ||||||
|  |     cv.Required(CONF_DEVICE): cv.enum(DEVICE, upper=True), | ||||||
|  | }, key=CONF_DEVICE)) | ||||||
|  | def dfplayer_set_device_to_code(config, action_id, template_arg, args): | ||||||
|  |     var = cg.new_Pvariable(action_id, template_arg) | ||||||
|  |     yield cg.register_parented(var, config[CONF_ID]) | ||||||
|  |     template_ = yield cg.templatable(config[CONF_DEVICE], args, Device) | ||||||
|  |     cg.add(var.set_device(template_)) | ||||||
|  |     yield var | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action('dfplayer.set_volume', SetVolumeAction, cv.maybe_simple_value({ | ||||||
|  |     cv.GenerateID(): cv.use_id(DFPlayer), | ||||||
|  |     cv.Required(CONF_VOLUME): cv.templatable(cv.int_), | ||||||
|  | }, key=CONF_VOLUME)) | ||||||
|  | def dfplayer_set_volume_to_code(config, action_id, template_arg, args): | ||||||
|  |     var = cg.new_Pvariable(action_id, template_arg) | ||||||
|  |     yield cg.register_parented(var, config[CONF_ID]) | ||||||
|  |     template_ = yield cg.templatable(config[CONF_VOLUME], args, float) | ||||||
|  |     cg.add(var.set_volume(template_)) | ||||||
|  |     yield var | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action('dfplayer.set_eq', SetEqAction, cv.maybe_simple_value({ | ||||||
|  |     cv.GenerateID(): cv.use_id(DFPlayer), | ||||||
|  |     cv.Required(CONF_EQ_PRESET): cv.templatable(cv.enum(EQ_PRESET, upper=True)), | ||||||
|  | }, key=CONF_EQ_PRESET)) | ||||||
|  | def dfplayer_set_eq_to_code(config, action_id, template_arg, args): | ||||||
|  |     var = cg.new_Pvariable(action_id, template_arg) | ||||||
|  |     yield cg.register_parented(var, config[CONF_ID]) | ||||||
|  |     template_ = yield cg.templatable(config[CONF_EQ_PRESET], args, EqPreset) | ||||||
|  |     cg.add(var.set_eq(template_)) | ||||||
|  |     yield var | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action('dfplayer.sleep', SleepAction, cv.Schema({ | ||||||
|  |     cv.GenerateID(): cv.use_id(DFPlayer), | ||||||
|  | })) | ||||||
|  | def dfplayer_sleep_to_code(config, action_id, template_arg, args): | ||||||
|  |     var = cg.new_Pvariable(action_id, template_arg) | ||||||
|  |     yield cg.register_parented(var, config[CONF_ID]) | ||||||
|  |     yield var | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action('dfplayer.reset', ResetAction, cv.Schema({ | ||||||
|  |     cv.GenerateID(): cv.use_id(DFPlayer), | ||||||
|  | })) | ||||||
|  | def dfplayer_reset_to_code(config, action_id, template_arg, args): | ||||||
|  |     var = cg.new_Pvariable(action_id, template_arg) | ||||||
|  |     yield cg.register_parented(var, config[CONF_ID]) | ||||||
|  |     yield var | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action('dfplayer.start', StartAction, cv.Schema({ | ||||||
|  |     cv.GenerateID(): cv.use_id(DFPlayer), | ||||||
|  | })) | ||||||
|  | def dfplayer_start_to_code(config, action_id, template_arg, args): | ||||||
|  |     var = cg.new_Pvariable(action_id, template_arg) | ||||||
|  |     yield cg.register_parented(var, config[CONF_ID]) | ||||||
|  |     yield var | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action('dfplayer.pause', PauseAction, cv.Schema({ | ||||||
|  |     cv.GenerateID(): cv.use_id(DFPlayer), | ||||||
|  | })) | ||||||
|  | def dfplayer_pause_to_code(config, action_id, template_arg, args): | ||||||
|  |     var = cg.new_Pvariable(action_id, template_arg) | ||||||
|  |     yield cg.register_parented(var, config[CONF_ID]) | ||||||
|  |     yield var | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action('dfplayer.stop', StopAction, cv.Schema({ | ||||||
|  |     cv.GenerateID(): cv.use_id(DFPlayer), | ||||||
|  | })) | ||||||
|  | def dfplayer_stop_to_code(config, action_id, template_arg, args): | ||||||
|  |     var = cg.new_Pvariable(action_id, template_arg) | ||||||
|  |     yield cg.register_parented(var, config[CONF_ID]) | ||||||
|  |     yield var | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action('dfplayer.random', RandomAction, cv.Schema({ | ||||||
|  |     cv.GenerateID(): cv.use_id(DFPlayer), | ||||||
|  | })) | ||||||
|  | def dfplayer_random_to_code(config, action_id, template_arg, args): | ||||||
|  |     var = cg.new_Pvariable(action_id, template_arg) | ||||||
|  |     yield cg.register_parented(var, config[CONF_ID]) | ||||||
|  |     yield var | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_condition('dfplayer.is_playing', DFPlayerIsPlayingCondition, cv.Schema({ | ||||||
|  |     cv.GenerateID(): cv.use_id(DFPlayer), | ||||||
|  | })) | ||||||
|  | def dfplyaer_is_playing_to_code(config, condition_id, template_arg, args): | ||||||
|  |     var = cg.new_Pvariable(condition_id, template_arg) | ||||||
|  |     yield cg.register_parented(var, config[CONF_ID]) | ||||||
|  |     yield var | ||||||
							
								
								
									
										120
									
								
								esphome/components/dfplayer/dfplayer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								esphome/components/dfplayer/dfplayer.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,120 @@ | |||||||
|  | #include "dfplayer.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace dfplayer { | ||||||
|  |  | ||||||
|  | static const char* TAG = "dfplayer"; | ||||||
|  |  | ||||||
|  | void DFPlayer::play_folder(uint16_t folder, uint16_t file) { | ||||||
|  |   if (folder < 100 && file < 256) { | ||||||
|  |     this->send_cmd_(0x0F, (uint8_t) folder, (uint8_t) file); | ||||||
|  |   } else if (folder <= 10 && file <= 1000) { | ||||||
|  |     this->send_cmd_(0x14, (((uint16_t) folder) << 12) | file); | ||||||
|  |   } else { | ||||||
|  |     ESP_LOGE(TAG, "Cannot play folder %d file %d.", folder, file); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void DFPlayer::send_cmd_(uint8_t cmd, uint16_t argument) { | ||||||
|  |   uint8_t buffer[10]{0x7e, 0xff, 0x06, cmd, 0x01, (uint8_t)(argument >> 8), (uint8_t) argument, 0x00, 0x00, 0xef}; | ||||||
|  |   uint16_t checksum = 0; | ||||||
|  |   for (uint8_t i = 1; i < 7; i++) | ||||||
|  |     checksum += buffer[i]; | ||||||
|  |   checksum = -checksum; | ||||||
|  |   buffer[7] = checksum >> 8; | ||||||
|  |   buffer[8] = (uint8_t) checksum; | ||||||
|  |  | ||||||
|  |   this->sent_cmd_ = cmd; | ||||||
|  |  | ||||||
|  |   ESP_LOGD(TAG, "Send Command %#02x arg %#04x", cmd, argument); | ||||||
|  |   this->write_array(buffer, 10); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void DFPlayer::loop() { | ||||||
|  |   // Read message | ||||||
|  |   while (this->available()) { | ||||||
|  |     uint8_t byte; | ||||||
|  |     this->read_byte(&byte); | ||||||
|  |  | ||||||
|  |     if (this->read_pos_ == DFPLAYER_READ_BUFFER_LENGTH) | ||||||
|  |       this->read_pos_ = 0; | ||||||
|  |  | ||||||
|  |     switch (this->read_pos_) { | ||||||
|  |       case 0:  // Start mark | ||||||
|  |         if (byte != 0x7E) | ||||||
|  |           continue; | ||||||
|  |         break; | ||||||
|  |       case 1:  // Version | ||||||
|  |         if (byte != 0xFF) { | ||||||
|  |           ESP_LOGW(TAG, "Expected Version 0xFF, got %#02x", byte); | ||||||
|  |           this->read_pos_ = 0; | ||||||
|  |           continue; | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |       case 2:  // Buffer length | ||||||
|  |         if (byte != 0x06) { | ||||||
|  |           ESP_LOGW(TAG, "Expected Buffer length 0x06, got %#02x", byte); | ||||||
|  |           this->read_pos_ = 0; | ||||||
|  |           continue; | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |       case 9:  // End byte | ||||||
|  |         if (byte != 0xEF) { | ||||||
|  |           ESP_LOGW(TAG, "Expected end byte 0xEF, got %#02x", byte); | ||||||
|  |           this->read_pos_ = 0; | ||||||
|  |           continue; | ||||||
|  |         } | ||||||
|  |         // Parse valid received command | ||||||
|  |         uint8_t cmd = this->read_buffer_[3]; | ||||||
|  |         uint16_t argument = (this->read_buffer_[5] << 8) | this->read_buffer_[6]; | ||||||
|  |  | ||||||
|  |         ESP_LOGV(TAG, "Received message cmd: %#02x arg %#04x", cmd, argument); | ||||||
|  |  | ||||||
|  |         switch (cmd) { | ||||||
|  |           case 0x3A: | ||||||
|  |             if (argument == 1) { | ||||||
|  |               ESP_LOGI(TAG, "USB loaded"); | ||||||
|  |             } else if (argument == 2) | ||||||
|  |               ESP_LOGI(TAG, "TF Card loaded"); | ||||||
|  |             break; | ||||||
|  |           case 0x3B: | ||||||
|  |             if (argument == 1) { | ||||||
|  |               ESP_LOGI(TAG, "USB unloaded"); | ||||||
|  |             } else if (argument == 2) | ||||||
|  |               ESP_LOGI(TAG, "TF Card unloaded"); | ||||||
|  |             break; | ||||||
|  |           case 0x3F: | ||||||
|  |             if (argument == 1) { | ||||||
|  |               ESP_LOGI(TAG, "USB available"); | ||||||
|  |             } else if (argument == 2) { | ||||||
|  |               ESP_LOGI(TAG, "TF Card available"); | ||||||
|  |             } else if (argument == 3) { | ||||||
|  |               ESP_LOGI(TAG, "USB, TF Card available"); | ||||||
|  |             } | ||||||
|  |             break; | ||||||
|  |           case 0x41: | ||||||
|  |             ESP_LOGV(TAG, "Ack ok"); | ||||||
|  |             this->is_playing_ |= this->ack_set_is_playing_; | ||||||
|  |             this->is_playing_ &= !this->ack_reset_is_playing_; | ||||||
|  |             this->ack_set_is_playing_ = false; | ||||||
|  |             this->ack_reset_is_playing_ = false; | ||||||
|  |             break; | ||||||
|  |           case 0x3D:  // Playback finished | ||||||
|  |             this->is_playing_ = false; | ||||||
|  |             this->on_finished_playback_callback_.call(); | ||||||
|  |             break; | ||||||
|  |           default: | ||||||
|  |             ESP_LOGD(TAG, "Command %#02x arg %#04x", cmd, argument); | ||||||
|  |         } | ||||||
|  |         this->sent_cmd_ = 0; | ||||||
|  |         this->read_pos_ = 0; | ||||||
|  |         continue; | ||||||
|  |     } | ||||||
|  |     this->read_buffer_[this->read_pos_] = byte; | ||||||
|  |     this->read_pos_++; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace dfplayer | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										165
									
								
								esphome/components/dfplayer/dfplayer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										165
									
								
								esphome/components/dfplayer/dfplayer.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,165 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/components/uart/uart.h" | ||||||
|  | #include "esphome/core/automation.h" | ||||||
|  |  | ||||||
|  | const size_t DFPLAYER_READ_BUFFER_LENGTH = 25;  // two messages + some extra | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace dfplayer { | ||||||
|  |  | ||||||
|  | enum EqPreset { | ||||||
|  |   NORMAL = 0, | ||||||
|  |   POP = 1, | ||||||
|  |   ROCK = 2, | ||||||
|  |   JAZZ = 3, | ||||||
|  |   CLASSIC = 4, | ||||||
|  |   BASS = 5, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum Device { | ||||||
|  |   USB = 1, | ||||||
|  |   TF_CARD = 2, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class DFPlayer : public uart::UARTDevice, public Component { | ||||||
|  |  public: | ||||||
|  |   void loop() override; | ||||||
|  |  | ||||||
|  |   void next() { this->send_cmd_(0x01); } | ||||||
|  |   void previous() { this->send_cmd_(0x02); } | ||||||
|  |   void play_file(uint16_t file) { | ||||||
|  |     this->ack_set_is_playing_ = true; | ||||||
|  |     this->send_cmd_(0x03, file); | ||||||
|  |   } | ||||||
|  |   void play_file_loop(uint16_t file) { this->send_cmd_(0x08, file); } | ||||||
|  |   void play_folder(uint16_t folder, uint16_t file); | ||||||
|  |   void play_folder_loop(uint16_t folder) { this->send_cmd_(0x17, folder); } | ||||||
|  |   void volume_up() { this->send_cmd_(0x04); } | ||||||
|  |   void volume_down() { this->send_cmd_(0x05); } | ||||||
|  |   void set_device(Device device) { this->send_cmd_(0x09, device); } | ||||||
|  |   void set_volume(uint8_t volume) { this->send_cmd_(0x06, volume); } | ||||||
|  |   void set_eq(EqPreset preset) { this->send_cmd_(0x07, preset); } | ||||||
|  |   void sleep() { this->send_cmd_(0x0A); } | ||||||
|  |   void reset() { this->send_cmd_(0x0C); } | ||||||
|  |   void start() { this->send_cmd_(0x0D); } | ||||||
|  |   void pause() { | ||||||
|  |     this->ack_reset_is_playing_ = true; | ||||||
|  |     this->send_cmd_(0x0E); | ||||||
|  |   } | ||||||
|  |   void stop() { this->send_cmd_(0x16); } | ||||||
|  |   void random() { this->send_cmd_(0x18); } | ||||||
|  |  | ||||||
|  |   bool is_playing() { return is_playing_; } | ||||||
|  |  | ||||||
|  |   void add_on_finished_playback_callback(std::function<void()> callback) { | ||||||
|  |     this->on_finished_playback_callback_.add(std::move(callback)); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   void send_cmd_(uint8_t cmd, uint16_t argument = 0); | ||||||
|  |   void send_cmd_(uint8_t cmd, uint16_t high, uint16_t low) { | ||||||
|  |     this->send_cmd_(cmd, ((high & 0xFF) << 8) | (low & 0xFF)); | ||||||
|  |   } | ||||||
|  |   uint8_t sent_cmd_{0}; | ||||||
|  |  | ||||||
|  |   char read_buffer_[DFPLAYER_READ_BUFFER_LENGTH]; | ||||||
|  |   size_t read_pos_{0}; | ||||||
|  |  | ||||||
|  |   bool is_playing_{false}; | ||||||
|  |   bool ack_set_is_playing_{false}; | ||||||
|  |   bool ack_reset_is_playing_{false}; | ||||||
|  |  | ||||||
|  |   CallbackManager<void()> on_finished_playback_callback_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #define DFPLAYER_SIMPLE_ACTION(ACTION_CLASS, ACTION_METHOD) \ | ||||||
|  |   template<typename... Ts> class ACTION_CLASS : public Action<Ts...>, public Parented<DFPlayer> { \ | ||||||
|  |    public: \ | ||||||
|  |     void play(Ts... x) override { this->parent_->ACTION_METHOD(); } \ | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  | DFPLAYER_SIMPLE_ACTION(NextAction, next) | ||||||
|  | DFPLAYER_SIMPLE_ACTION(PreviousAction, previous) | ||||||
|  |  | ||||||
|  | template<typename... Ts> class PlayFileAction : public Action<Ts...>, public Parented<DFPlayer> { | ||||||
|  |  public: | ||||||
|  |   TEMPLATABLE_VALUE(uint16_t, file) | ||||||
|  |   TEMPLATABLE_VALUE(boolean, loop) | ||||||
|  |   void play(Ts... x) override { | ||||||
|  |     auto file = this->file_.value(x...); | ||||||
|  |     auto loop = this->loop_.value(x...); | ||||||
|  |     if (loop) { | ||||||
|  |       this->parent_->play_file_loop(file); | ||||||
|  |     } else { | ||||||
|  |       this->parent_->play_file(file); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | template<typename... Ts> class PlayFolderAction : public Action<Ts...>, public Parented<DFPlayer> { | ||||||
|  |  public: | ||||||
|  |   TEMPLATABLE_VALUE(uint16_t, folder) | ||||||
|  |   TEMPLATABLE_VALUE(uint16_t, file) | ||||||
|  |   TEMPLATABLE_VALUE(boolean, loop) | ||||||
|  |   void play(Ts... x) override { | ||||||
|  |     auto folder = this->folder_.value(x...); | ||||||
|  |     auto file = this->file_.value(x...); | ||||||
|  |     auto loop = this->loop_.value(x...); | ||||||
|  |     if (loop) { | ||||||
|  |       this->parent_->play_folder_loop(folder); | ||||||
|  |     } else { | ||||||
|  |       this->parent_->play_folder(folder, file); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | template<typename... Ts> class SetDeviceAction : public Action<Ts...>, public Parented<DFPlayer> { | ||||||
|  |  public: | ||||||
|  |   TEMPLATABLE_VALUE(Device, device) | ||||||
|  |   void play(Ts... x) override { | ||||||
|  |     auto device = this->device_.value(x...); | ||||||
|  |     this->parent_->set_device(device); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | template<typename... Ts> class SetVolumeAction : public Action<Ts...>, public Parented<DFPlayer> { | ||||||
|  |  public: | ||||||
|  |   TEMPLATABLE_VALUE(uint8_t, volume) | ||||||
|  |   void play(Ts... x) override { | ||||||
|  |     auto volume = this->volume_.value(x...); | ||||||
|  |     this->parent_->set_volume(volume); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | template<typename... Ts> class SetEqAction : public Action<Ts...>, public Parented<DFPlayer> { | ||||||
|  |  public: | ||||||
|  |   TEMPLATABLE_VALUE(EqPreset, eq) | ||||||
|  |   void play(Ts... x) override { | ||||||
|  |     auto eq = this->eq_.value(x...); | ||||||
|  |     this->parent_->set_eq(eq); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | DFPLAYER_SIMPLE_ACTION(SleepAction, sleep) | ||||||
|  | DFPLAYER_SIMPLE_ACTION(ResetAction, reset) | ||||||
|  | DFPLAYER_SIMPLE_ACTION(StartAction, start) | ||||||
|  | DFPLAYER_SIMPLE_ACTION(PauseAction, pause) | ||||||
|  | DFPLAYER_SIMPLE_ACTION(StopAction, stop) | ||||||
|  | DFPLAYER_SIMPLE_ACTION(RandomAction, random) | ||||||
|  |  | ||||||
|  | template<typename... Ts> class DFPlayerIsPlayingCondition : public Condition<Ts...>, public Parented<DFPlayer> { | ||||||
|  |  public: | ||||||
|  |   bool check(Ts... x) override { return this->parent_->is_playing(); } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class DFPlayerFinishedPlaybackTrigger : public Trigger<> { | ||||||
|  |  public: | ||||||
|  |   explicit DFPlayerFinishedPlaybackTrigger(DFPlayer *parent) { | ||||||
|  |     parent->add_on_finished_playback_callback([this]() { this->trigger(); }); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace dfplayer | ||||||
|  | }  // namespace esphome | ||||||
| @@ -60,6 +60,83 @@ api: | |||||||
|               - float_arr.size() |               - float_arr.size() | ||||||
|               - string_arr[0].c_str() |               - string_arr[0].c_str() | ||||||
|               - string_arr.size() |               - string_arr.size() | ||||||
|  |     - service: dfplayer_next | ||||||
|  |       then: | ||||||
|  |         - dfplayer.play_next: | ||||||
|  |     - service: dfplayer_previous | ||||||
|  |       then: | ||||||
|  |         - dfplayer.play_previous: | ||||||
|  |     - service: dfplayer_play | ||||||
|  |       variables: | ||||||
|  |         file: int | ||||||
|  |       then: | ||||||
|  |         - dfplayer.play: !lambda 'return file;' | ||||||
|  |     - service: dfplayer_play_loop | ||||||
|  |       variables: | ||||||
|  |         file: int | ||||||
|  |         loop_: bool | ||||||
|  |       then: | ||||||
|  |         - dfplayer.play: | ||||||
|  |             file: !lambda 'return file;' | ||||||
|  |             loop: !lambda 'return loop_;' | ||||||
|  |     - service: dfplayer_play_folder | ||||||
|  |       variables: | ||||||
|  |         folder: int | ||||||
|  |         file: int | ||||||
|  |       then: | ||||||
|  |         - dfplayer.play_folder: | ||||||
|  |             folder: !lambda 'return folder;' | ||||||
|  |             file: !lambda 'return file;' | ||||||
|  |  | ||||||
|  |     - service: dfplayer_play_loo_folder | ||||||
|  |       variables: | ||||||
|  |         folder: int | ||||||
|  |       then: | ||||||
|  |         - dfplayer.play_folder: | ||||||
|  |             folder: !lambda 'return folder;' | ||||||
|  |             loop: True | ||||||
|  |  | ||||||
|  |     - service: dfplayer_set_device | ||||||
|  |       variables: | ||||||
|  |         device: int | ||||||
|  |       then: | ||||||
|  |         - dfplayer.set_device: | ||||||
|  |             device: TF_CARD | ||||||
|  |  | ||||||
|  |     - service: dfplayer_set_volume | ||||||
|  |       variables: | ||||||
|  |         volume: int | ||||||
|  |       then: | ||||||
|  |         - dfplayer.set_volume: !lambda 'return volume;' | ||||||
|  |     - service: dfplayer_set_eq | ||||||
|  |       variables: | ||||||
|  |         preset: int | ||||||
|  |       then: | ||||||
|  |         - dfplayer.set_eq: !lambda 'return static_cast<dfplayer::EqPreset>(preset);' | ||||||
|  |  | ||||||
|  |     - service: dfplayer_sleep | ||||||
|  |       then: | ||||||
|  |         - dfplayer.sleep | ||||||
|  |  | ||||||
|  |     - service: dfplayer_reset | ||||||
|  |       then: | ||||||
|  |         - dfplayer.reset | ||||||
|  |  | ||||||
|  |     - service: dfplayer_start | ||||||
|  |       then: | ||||||
|  |         - dfplayer.start | ||||||
|  |  | ||||||
|  |     - service: dfplayer_pause | ||||||
|  |       then: | ||||||
|  |         - dfplayer.pause | ||||||
|  |  | ||||||
|  |     - service: dfplayer_stop | ||||||
|  |       then: | ||||||
|  |         - dfplayer.stop | ||||||
|  |  | ||||||
|  |     - service: dfplayer_random | ||||||
|  |       then: | ||||||
|  |         - dfplayer.random | ||||||
|  |  | ||||||
| wifi: | wifi: | ||||||
|   ssid: 'MySSID' |   ssid: 'MySSID' | ||||||
| @@ -532,3 +609,13 @@ sim800l: | |||||||
|     - sim800l.send_sms: |     - sim800l.send_sms: | ||||||
|         message: 'hello you' |         message: 'hello you' | ||||||
|         recipient: '+1234' |         recipient: '+1234' | ||||||
|  |  | ||||||
|  | dfplayer: | ||||||
|  |   on_finished_playback: | ||||||
|  |     then: | ||||||
|  |       if: | ||||||
|  |         condition: | ||||||
|  |           not: | ||||||
|  |             dfplayer.is_playing | ||||||
|  |         then: | ||||||
|  |           logger.log: 'Playback finished event' | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user