Este ejemplo se basa en nuestro ejemplo anterior de S-function y muestra cómo alimentar datos desde un publicador ZeroMQ a Simulink vía S-functions.
Recomiendo no usar cppzmq porque Simulink tiende a tener problemas con las excepciones de C++. En su lugar, usamos la API C de ZeroMQ.
Este ejemplo en particular recibe un valor double desde un publicador ZeroMQ (como una cadena, tal como "-10.2345") y lo envía a un puerto de salida de Simulink.
Configuración de Simulink
Recomiendo usar simulation stop time = inf aquí. Esto significa, sin embargo, que los scopes no funcionan correctamente. Usando display
Implementación de la S-function
Compila esto 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> // For sine wave generation
#include <zmq.h> // ZeroMQ library
#include <cstring> // For 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 al 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;
}
// Suscribirse a todos los mensajes
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);
// Puedes habilitar esta línea para depuración
//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 tamaños de los varios vectores.
static void mdlInitializeSizes(SimStruct *S)
{
ssSetNumSFcnParams(S, 0); // No parameters
ssSetNumContStates(S, 0); // No continuous states
ssSetNumDiscStates(S, 0); // No discrete states
// 1 puerto de salida con 1 elemento
if (!ssSetNumOutputPorts(S, 1)) return;
ssSetOutputPortWidth(S, 0, 1); // Output port width = 1 (sine wave value or received value)
ssSetOutputPortDataType(S, 0, SS_DOUBLE);
ssSetOutputPortComplexSignal(S, 0, COMPLEX_NO);
// Sample timesg++ -o zmq_push_sender zmq_push_sender.cpp -lzmq -std=gnu++17
ssSetNumPWork(S, 1); // Pointer work vector to store ZeroMQ context
ssSetNumModes(S, 0); // Mode vector
ssSetNumNonsampledZCs(S, 0); // Zero crossings
// Hacer el bloque S-function capaz de ser usado en un modelo generado por Real-Time Workshop
ssSetOptions(S, 0);
}
// Function: mdlInitializeSampleTimes =========================================
// Abstract:
// Inicializar los sample times a 1ms (1kHz)
static void mdlInitializeSampleTimes(SimStruct *S)
{
ssSetSampleTime(S, 0, 1.0 / SAMPLING_RATE); // 1kHz sampling rate
ssSetOffsetTime(S, 0, 0.0);
}
// Function: mdlStart =========================================================
// Abstract:
// Inicializar el contexto ZeroMQ e iniciar el hilo de recepción.
#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:
// Emitir la onda senoidal o el último valor recibido de ZeroMQ.
static void mdlOutputs(SimStruct *S, int_T tid)
{
real_T *y = ssGetOutputPortRealSignal(S, 0); // Output pointer
real_T time = ssGetT(S); // Get the current simulation time
// Emitir el último valor recibido de 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 función es llamada al final de la simulación para limpieza.
static void mdlTerminate(SimStruct *S)
{
zmq_running = false;
void *context = ssGetPWorkValue(S, 0);
if (context)
{
zmq_ctx_shutdown(context);
zmq_ctx_term(context);
}
}
// Trailer requerido de S-function
#ifdef MATLAB_MEX_FILE
#include "simulink.c" // MEX-file interface mechanism
#else
#include "cg_sfun.h" // Code generation interface
#endifProductor de datos de prueba
Este simple programa C++ envía valores al publicador ZeroMQ.
build_zmq_sender.sh
g++ -o zmq_push_sender zmq_push_sender.cpp -lzmq -std=gnu++17y ejecútalo en 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 y 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) {
// Crear el mensaje con el valor actual
std::ostringstream oss;
oss << value;
std::string value_str = oss.str();
zmq::message_t message(value_str.c_str(), value_str.size());
// Enviar el mensaje
// 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 el valor y resetear si es necesario
value += 1.0;
if (value >= 10.0) {
value = -10.0;
}
// Esperar 100ms
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
return 0;
}
Echa un vistazo a artículos similares por categoría:
Matlab/Simulink C/C++
Si este artículo te ha ayudado, considera invitarme a un café o hacer una donación vía PayPal para apoyar la investigación y publicación de nuevos artículos en TechOverflow
