Este exemplo baseia-se no nosso exemplo anterior de S-function e mostra como alimentar dados de um publicador ZeroMQ no Simulink via S-functions.
Recomendo não usar o cppzmq porque o Simulink tende a ter problemas com exceções em C++. Em vez disso, usamos a API em C do ZeroMQ.
Este exemplo em particular recebe um valor double de um publicador ZeroMQ (como uma string, tal como "-10.2345") e o envia para uma porta de saída do Simulink.
Configuração do Simulink
Recomendo usar o tempo de parada da simulação = inf aqui. Isso significa, no entanto, que os scopes não funcionam corretamente. Usando display
Implementação da S-function
Compile isto usando
build_zmq_sfunction.sh
mex CXXFLAGS="-fPIC -std=gnu++17" -lzmq zmq_value_input.cppzmq_value_input.cpp
#define S_FUNCTION_NAME zmq_value_input
#define S_FUNCTION_LEVEL 2
#include "simstruc.h"
#include <cmath> // Para geração de onda senoidal
#include <zmq.h> // Biblioteca ZeroMQ
#include <cstring> // Para memcpy
#include <thread>
#include <mutex>
#include <atomic>
#include <cmath>
#include <optional>
// Definir constantes
#define SAMPLING_RATE 10 // 1Hz
#define DEFAULT_VALUE 0.0
std::mutex zmq_mutex;
double last_received_value = nan("");
std::atomic<bool> zmq_running(true);
void zmq_receive_thread(void *context)
{
void *receiver = zmq_socket(context, ZMQ_SUB);
// Conectar ao publicador
if(zmq_connect(receiver, "tcp://127.0.0.1:5699") != 0) {
printf("Error connecting to ZeroMQ publisher: %s\n", zmq_strerror(zmq_errno()));
return;
}
// Inscrever-se em todas as mensagens
if (zmq_setsockopt(receiver, ZMQ_SUBSCRIBE, "", 0) != 0) {
printf("Error setting ZMQ_SUBSCRIBE: %s\n", zmq_strerror(zmq_errno()));
return;
}
while (zmq_running)
{
char buffer[256];
int bytes_received = zmq_recv(receiver, buffer, 256, 0);
if (bytes_received != -1)
{
std::string received(buffer, bytes_received);
// Você pode habilitar esta linha para depuração
//printf("Received: %s\n", received.c_str());
std::lock_guard<std::mutex> lock(zmq_mutex);
last_received_value = atof(buffer);
}
}
zmq_close(receiver);
}
// Function: mdlInitializeSizes ===============================================
// Abstract:
// Configurar os tamanhos dos vários vetores.
static void mdlInitializeSizes(SimStruct *S)
{
ssSetNumSFcnParams(S, 0); // Sem parâmetros
ssSetNumContStates(S, 0); // Sem estados contínuos
ssSetNumDiscStates(S, 0); // Sem estados discretos
// 1 porta de saída com 1 elemento
if (!ssSetNumOutputPorts(S, 1)) return;
ssSetOutputPortWidth(S, 0, 1); // Largura da porta de saída = 1 (valor da onda senoidal ou valor recebido)
ssSetOutputPortDataType(S, 0, SS_DOUBLE);
ssSetOutputPortComplexSignal(S, 0, COMPLEX_NO);
// Tempos de amostragemg++ -o zmq_push_sender zmq_push_sender.cpp -lzmq -std=gnu++17
ssSetNumPWork(S, 1); // Vetor de trabalho de ponteiros para armazenar o contexto ZeroMQ
ssSetNumModes(S, 0); // Vetor de modos
ssSetNumNonsampledZCs(S, 0); // Cruzamentos por zero
// Tornar o bloco S-function capaz de ser usado em um modelo gerado pelo Real-Time Workshop
ssSetOptions(S, 0);
}
// Function: mdlInitializeSampleTimes =========================================
// Abstract:
// Inicializar os tempos de amostragem para 1ms (1kHz)
static void mdlInitializeSampleTimes(SimStruct *S)
{
ssSetSampleTime(S, 0, 1.0 / SAMPLING_RATE); // Taxa de amostragem de 1kHz
ssSetOffsetTime(S, 0, 0.0);
}
// Function: mdlStart =========================================================
// Abstract:
// Inicializar o contexto ZeroMQ e iniciar a thread de recebimento.
#define MDL_START
static void mdlStart(SimStruct *S)
{
void *context = zmq_ctx_new();
ssSetPWorkValue(S, 0, context);
std::thread(zmq_receive_thread, context).detach();
}
// Function: mdlOutputs =======================================================
// Abstract:
// Enviar a onda senoidal ou o último valor recebido do ZeroMQ.
static void mdlOutputs(SimStruct *S, int_T tid)
{
real_T *y = ssGetOutputPortRealSignal(S, 0); // Ponteiro de saída
real_T time = ssGetT(S); // Obter o tempo de simulação atual
// Enviar o último valor recebido do ZeroMQ
std::lock_guard<std::mutex> lock(zmq_mutex);
y[0] = last_received_value == nan("") ? DEFAULT_VALUE : last_received_value;
//y[0] = isnan(last_received_value) ? DEFAULT_VALUE : last_received_value;
}
// Function: mdlTerminate =====================================================
// Abstract:
// Esta função é chamada no final da simulação para limpeza.
static void mdlTerminate(SimStruct *S)
{
zmq_running = false;
void *context = ssGetPWorkValue(S, 0);
if (context)
{
zmq_ctx_shutdown(context);
zmq_ctx_term(context);
}
}
// Trailer necessário da S-function
#ifdef MATLAB_MEX_FILE
#include "simulink.c" // Mecanismo de interface de arquivo MEX
#else
#include "cg_sfun.h" // Interface de geração de código
#endifProdutor de dados de teste
Este programa simples em C++ envia valores para o publicador ZeroMQ.
build_zmq_sender.sh
g++ -o zmq_push_sender zmq_push_sender.cpp -lzmq -std=gnu++17e execute em segundo plano.
zmq_push_sender.cpp
#include <zmq.hpp>
#include <iostream>
#include <thread>
#include <chrono>
#include <sstream>
#include <zmq_addon.hpp>
int main() {
// Preparar contexto e socket ZeroMQ
zmq::context_t context(4);
zmq::socket_t socket(context, zmq::socket_type::pub);
socket.bind("tcp://*:5699");
double value = 0.0;
while (true) {
// Criar a mensagem com o valor atual
std::ostringstream oss;
oss << value;
std::string value_str = oss.str();
zmq::message_t message(value_str.c_str(), value_str.size());
// Enviar a mensagem
// socket.send(zmq::str_buffer("AAA"), zmq::send_flags::none);
socket.send(message, zmq::send_flags::none);
printf("Sent: %s\n", value_str.c_str());
// Incrementar o valor e reiniciar se necessário
value += 1.0;
if (value >= 10.0) {
value = -10.0;
}
// Esperar 100ms
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
return 0;
}
Check out similar posts by category:
Matlab/Simulink C/C++
If this post helped you, please consider buying me a coffee or donating via PayPal to support research & publishing of new posts on TechOverflow
