Playlist Optimierung (2/3): Entfernung Queues

Hallo zusammen,
Ich dachte ich mache ein neues Thema für den 2. PR zu der Playlist Optimierung auf. Ziel ist es Smart Pointer einsetzten zu können, was mit FreeRTOS Queues nicht möglich ist, da diese „raw copy“ verwenden (siehe hier und hier). Dadurch möchte ich diese ersetzten, da wir sie mit einer Tiefe von 1 als „Message Box“ einsetzten.

Hierfür habe ich mir ein MessageBox System ausgelesen (angelehnt an PolyM) überlegt. Hier wird ein Smart Pointer von einer Basisklasse verwendet um von dieser abgeleiteten Nachrichten (mit oder ohne Payload) zu verteilen.

Statt einer Queue wird es ein Briefkasten geben mit drei Objekten:

  1. Der Nachricht als ein std::unique_ptr<Msg>
  2. Einem std::mutex zum Synchronisieren des Zugriffes
  3. Einem Binary Semaphore zur Signalisierung einer neuen Nachricht

Klassenstruktur

Damit das System auch für andere Arten von Nachrichten (zB Event von dem RFID-Treibern oder Nachrichten an das LED System) verwendet werden kann, gibt es 3 statt 2 Schichten der Abstraktion (AudioMsg ist notwendig um die richtige Spezialisierung der Template Klasse AudioDataMsg auszuwählen zu können).

Verteilung der Nachrichten

image

Die Verteilung der Nachricht würde hier durch eine zentrale Funktion erfolgen. Dieser leitet die Nachricht abhängig von MsgType an die Module weiter, die es in den lokalen Briefkasten ablegen und dem Thread signalisieren, dass eine neue Nachricht da ist.

Codeblock für das Verteilen einer Nachricht:
Message.cpp

void Message_Send(Msg &&msg) {
	// message distribution hub
	switch (msg.type()) {
		case Msg::AudioPlayerMsg:
			AudioPlayer_SignalMessage(std::move(msg));
			break;

		// not implemented
		case Msg::LedMsg:
			Led_SignalMessage(std::move(msg));
			break;

		default:
			Log_Printf(LOGLEVEL_ERROR, "Message with the type %d is not implemented", std::to_underlying(msg.type()));
			break;
	}
}

AudioPlayer.cpp

// our inbox for command & control messages to the AudioPlayer Task
static std::unique_ptr<Msg> newMsg;
static std::mutex msgGuard;
static SemaphoreHandle_t msgReceived = xSemaphoreCreateBinary();

[...]

void AudioPlayer_SignalMessage(Msg &&msg) {
	{ // Don't remove because of the lifetime of the mutex
		std::lock_guard<std::mutex> lock(msgGuard);
		newMsg = msg.move();
		log_n("Pushing new msg: cmd: %d, data: %d", ((AudioMsg &) *newMsg).event(), ((AudioMsg &) *newMsg).data());
	}
	xSemaphoreGive(msgReceived);
}

Codeblock für die Auswertung:
AudioPlayer.cpp

		// check for new messages
		auto timeout = (gPlayProperties.playMode == NO_PLAYLIST || gPlayProperties.pausePlay) ? portMAX_DELAY : pdMS_TO_TICKS(1); // if we are idle, we sleep forever
		auto retValue = xSemaphoreTake(msgReceived, timeout);
		if (retValue) {
			// we message received
			std::unique_ptr<Msg> msg;
			{ // Don't remove because of the lifetime of the mutex
				std::lock_guard<std::mutex> lock(msgGuard);
				msg = std::move(newMsg);
			}
			auto &audioMsg = (AudioMsg &) (*msg);
			switch (audioMsg.event()) {
				case AudioMsg::TrackCommand: {
					// save the trackCommand and execute it later
					uint8_t command = audioMsg.data;
				} break;

				case AudioMsg::VolumeCommand: {
					// change the volume of the audio 
					uint8_t volume = audioMsg.data;
				} break;

				case AudioMsg::PlaylistCommand: {
					// got a new playlist, cast Msg --> AudioDataMsg
					AudioDataMsg<std::unique_ptr<Playlist>> &playlistMsg = (AudioDataMsg<std::unique_ptr<Playlist>> &) *msg;
					playlist = std::move(playlistMsg.playload());
				} break;

				case AudioMsg::TTSCommand: {
					AudioDataMsg<pstring> &ttsMsg = (AudioDataMsg<pstring> &) *msg;
					speakIt = playlistMsg.playload();
				} break;

				default:
					Log_Printf(LOGLEVEL_ERROR, "Unknown audio event %d", std::to_underlying(audioMsg.event()));
					break;
			}
		}

Konklusion

Das Message System ist aktuell ein Proof of Concept, sie funktioniert für den AudioPlayer um trackCommands, Lautstärkeänderungen und Playlists zu übertragen. Was ich noch implementieren möchte ist TTS (Text To Speech) und die Kapselung der Variablen der Playlist (zB currentTrack, playMode, repeat Track/Playlist, …).

Der Code ist hier zu finden: GitHub - laszloh/ESPuino at feature/playlist-queue-removal

Wenn Interesse an dem System besteht, könnten andere Teile des Systems auch auf dieses Nachrichtensystem umgestellt werden (die Led.cpp wäre prädestiniert dafür, da sie stark Event getriggert ist).

3 „Gefällt mir“
  1. Wie ist das mit Timeouts? Also den Task für LED halten wir ja an, wenn ein Upload läuft.

  2. Was sich auch als Empfänger anböte ist MQTT. Muss das dafür in einem Task laufen?

Zu 1:
Das macht kein Problem, da wir die Tasks per vTaskSuspend anhalten, werden sie von FreeRTOS hart aus ihrem aktuellen Status (Running/Ready/Blocked) in Suspended gebracht. Sobald sie wieder aktiviert werden, gehen sie dann von Suspend zurück zu ihrem aktiven Zustand (Blocked wenn zB die Semaphore nicht da ist und wir für immer blockieren), bzw Ready/Running wenn eine neue Nachricht da ist oder das Timeout ausgelaufen ist.

edit: Der MessageBox ist ein suspendierter Thread auch egal, die Nachrichten werden einfach überschrieben, wenn sie niemand abholt :slight_smile:

Zu 2:
Ja, das geht. In einem eignen Task muss es nicht laufen, da muss nur darauf geachtet werden, dass wir die loopTask nicht für immer blockieren (d.h. 0 als Timeout für die xSemaphoreTake von der MessageBox).

1 „Gefällt mir“