1563 lines
62 KiB
C++
1563 lines
62 KiB
C++
#include "Atomic.h"
|
|
#include "Logger.h"
|
|
#include "BasicTCPSocket.h"
|
|
#include "ClassRegistryItem.h"
|
|
#include "ConfigurationDatabase.h"
|
|
#include "DataSourceI.h"
|
|
#include "DebugBrokerWrapper.h"
|
|
#include "GAM.h"
|
|
#include "GlobalObjectsDatabase.h"
|
|
#include "HighResolutionTimer.h"
|
|
#include "ObjectRegistryDatabase.h"
|
|
#include "ReferenceT.h"
|
|
#include "Sleep.h"
|
|
#include "StreamString.h"
|
|
#include "Threads.h"
|
|
#include "TimeoutType.h"
|
|
#include "TypeConversion.h"
|
|
#include "WebDebugService.h"
|
|
#include "WebUI.h"
|
|
|
|
namespace MARTe {
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// File-scope helpers (same as in DebugService.cpp)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
static void WDS_EscapeJson(const char8 *src, StreamString &dst) {
|
|
if (src == NULL_PTR(const char8 *)) return;
|
|
while (*src != '\0') {
|
|
if (*src == '"') dst += "\\\"";
|
|
else if (*src == '\\') dst += "\\\\";
|
|
else if (*src == '\n') dst += "\\n";
|
|
else if (*src == '\r') dst += "\\r";
|
|
else if (*src == '\t') dst += "\\t";
|
|
else dst += *src;
|
|
src++;
|
|
}
|
|
}
|
|
|
|
static bool WDS_SuffixMatch(const char8 *target, const char8 *pattern) {
|
|
uint32 tLen = StringHelper::Length(target);
|
|
uint32 pLen = StringHelper::Length(pattern);
|
|
if (pLen > tLen) return false;
|
|
const char8 *suffix = target + (tLen - pLen);
|
|
if (StringHelper::Compare(suffix, pattern) == 0) {
|
|
if (tLen == pLen || *(suffix - 1) == '.') return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool WDS_FindPath(ReferenceContainer *c, const Object *target,
|
|
StreamString &path) {
|
|
if (c == NULL_PTR(ReferenceContainer *)) return false;
|
|
uint32 n = c->Size();
|
|
for (uint32 i = 0u; i < n; i++) {
|
|
Reference ref = c->Get(i);
|
|
if (!ref.IsValid()) continue;
|
|
if (ref.operator->() == target) { path = ref->GetName(); return true; }
|
|
ReferenceContainer *sub = dynamic_cast<ReferenceContainer *>(ref.operator->());
|
|
if (sub != NULL_PTR(ReferenceContainer *)) {
|
|
if (WDS_FindPath(sub, target, path)) {
|
|
StreamString full;
|
|
full.Printf("%s.%s", ref->GetName(), path.Buffer());
|
|
path = full;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void WDS_BuildCDB(ReferenceContainer *container, ConfigurationDatabase &cdb) {
|
|
if (container == NULL_PTR(ReferenceContainer *)) return;
|
|
uint32 n = container->Size();
|
|
for (uint32 i = 0u; i < n; i++) {
|
|
Reference child = container->Get(i);
|
|
if (!child.IsValid()) continue;
|
|
const char8 *name = child->GetName();
|
|
if (name == NULL_PTR(const char8 *)) continue;
|
|
bool created = cdb.CreateRelative(name);
|
|
if (!created) { if (!cdb.MoveRelative(name)) continue; }
|
|
const char8 *cn = child->GetClassProperties()->GetName();
|
|
if (cn != NULL_PTR(const char8 *)) (void)cdb.Write("Class", cn);
|
|
{
|
|
ConfigurationDatabase exp;
|
|
if (child->ExportData(exp)) {
|
|
exp.MoveToRoot();
|
|
uint32 ne = exp.GetNumberOfChildren();
|
|
for (uint32 j = 0u; j < ne; j++) {
|
|
const char8 *ek = exp.GetChildName(j);
|
|
if (StringHelper::Compare(ek, "Class") == 0 ||
|
|
StringHelper::Compare(ek, "Name") == 0 ||
|
|
StringHelper::Compare(ek, "IsContainer") == 0) continue;
|
|
if (exp.MoveRelative(ek)) { exp.MoveToAncestor(1u); continue; }
|
|
AnyType at = exp.GetType(ek);
|
|
if (at.GetDataPointer() != NULL_PTR(void *)) {
|
|
char8 buf[256] = { '\0' };
|
|
AnyType dst(CharString, 0u, buf);
|
|
dst.SetNumberOfElements(0u, 256u);
|
|
if (TypeConvert(dst, at)) (void)cdb.Write(ek, buf);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ReferenceContainer *rc = dynamic_cast<ReferenceContainer *>(child.operator->());
|
|
if (rc != NULL_PTR(ReferenceContainer *)) WDS_BuildCDB(rc, cdb);
|
|
(void)cdb.MoveToAncestor(1u);
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// DebugServiceI out-of-line definitions (each transport .so is self-contained)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
DebugServiceI *DebugServiceI::instance = NULL_PTR(DebugServiceI *);
|
|
|
|
bool DebugServiceI::GetFullObjectName(const Object &obj, StreamString &fullPath) {
|
|
fullPath = "";
|
|
if (WDS_FindPath(ObjectRegistryDatabase::Instance(), &obj, fullPath)) {
|
|
return true;
|
|
}
|
|
const char8 *name = obj.GetName();
|
|
if (name != NULL_PTR(const char8 *))
|
|
fullPath = name;
|
|
return true;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// CLASS_REGISTER
|
|
// ---------------------------------------------------------------------------
|
|
|
|
CLASS_REGISTER(WebDebugService, "1.0")
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Constructor / Destructor
|
|
// ---------------------------------------------------------------------------
|
|
|
|
WebDebugService::WebDebugService()
|
|
: binderHttp(this, WdsBinder::Http),
|
|
binderStream(this, WdsBinder::Stream),
|
|
httpService(binderHttp),
|
|
streamerService(binderStream) {
|
|
httpPort = 8090u;
|
|
isPaused = false;
|
|
stepRemaining = 0u;
|
|
streamerSeq = 0u;
|
|
streamerT0Ns = 0u;
|
|
manualConfigSet = false;
|
|
logHistoryWriteIdx = 0u;
|
|
logHistoryFill = 0u;
|
|
memset(logHistory, 0, sizeof(logHistory));
|
|
for (uint32 i = 0u; i < MAX_SSE_CLIENTS; i++) {
|
|
sseClients[i] = NULL_PTR(BasicTCPSocket *);
|
|
}
|
|
}
|
|
|
|
WebDebugService::~WebDebugService() {
|
|
DebugServiceI::SetInstance(NULL_PTR(DebugServiceI *));
|
|
httpService.Stop();
|
|
streamerService.Stop();
|
|
tcpServer.Close();
|
|
sseMutex.FastLock();
|
|
for (uint32 i = 0u; i < MAX_SSE_CLIENTS; i++) {
|
|
if (sseClients[i] != NULL_PTR(BasicTCPSocket *)) {
|
|
sseClients[i]->Close();
|
|
delete sseClients[i];
|
|
sseClients[i] = NULL_PTR(BasicTCPSocket *);
|
|
}
|
|
}
|
|
sseMutex.FastUnLock();
|
|
mutex.FastLock();
|
|
for (uint32 i = 0u; i < signals.Size(); i++) {
|
|
if (signals[i] != NULL_PTR(DebugSignalInfo *)) {
|
|
delete signals[i];
|
|
}
|
|
}
|
|
mutex.FastUnLock();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Initialise
|
|
// ---------------------------------------------------------------------------
|
|
|
|
bool WebDebugService::Initialise(StructuredDataI &data) {
|
|
if (!ReferenceContainer::Initialise(data)) return false;
|
|
|
|
uint32 port = 8090u;
|
|
(void)data.Read("HttpPort", port);
|
|
httpPort = (uint16)port;
|
|
|
|
if (!traceBuffer.Init(4 * 1024 * 1024)) return false;
|
|
|
|
DebugServiceI::SetInstance(this);
|
|
|
|
PatchRegistry();
|
|
|
|
// Initialize Logger early so REPORT_ERROR calls from application startup
|
|
// are captured in the ring buffer before the Streamer thread begins.
|
|
(void)Logger::Instance();
|
|
REPORT_ERROR(ErrorManagement::Information, "WebDebugService initialised on port %d", (int)httpPort);
|
|
|
|
// Record time origin for relative SSE timestamps (ms since service init).
|
|
streamerT0Ns = (uint64)((float64)HighResolutionTimer::Counter() *
|
|
HighResolutionTimer::Period() * 1.0e9);
|
|
|
|
ConfigurationDatabase threadData;
|
|
threadData.Write("Timeout", (uint32)1000);
|
|
httpService.Initialise(threadData);
|
|
streamerService.Initialise(threadData);
|
|
|
|
if (!tcpServer.Open()) return false;
|
|
if (!tcpServer.Listen(httpPort)) return false;
|
|
|
|
if (httpService.Start() != ErrorManagement::NoError) return false;
|
|
if (streamerService.Start() != ErrorManagement::NoError) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// PatchRegistry (identical logic to DebugService)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
static void WDS_PatchItem(const char8 *originalName, ObjectBuilder *debugBuilder) {
|
|
ClassRegistryItem *item = ClassRegistryDatabase::Instance()->Find(originalName);
|
|
if (item != NULL_PTR(ClassRegistryItem *)) {
|
|
item->SetObjectBuilder(debugBuilder);
|
|
}
|
|
}
|
|
|
|
void WebDebugService::PatchRegistry() {
|
|
WDS_PatchItem("MemoryMapInputBroker",
|
|
new DebugMemoryMapInputBrokerBuilder());
|
|
WDS_PatchItem("MemoryMapOutputBroker",
|
|
new DebugMemoryMapOutputBrokerBuilder());
|
|
WDS_PatchItem("MemoryMapSynchronisedInputBroker",
|
|
new DebugMemoryMapSynchronisedInputBrokerBuilder());
|
|
WDS_PatchItem("MemoryMapSynchronisedOutputBroker",
|
|
new DebugMemoryMapSynchronisedOutputBrokerBuilder());
|
|
WDS_PatchItem("MemoryMapInterpolatedInputBroker",
|
|
new DebugMemoryMapInterpolatedInputBrokerBuilder());
|
|
WDS_PatchItem("MemoryMapMultiBufferInputBroker",
|
|
new DebugMemoryMapMultiBufferInputBrokerBuilder());
|
|
WDS_PatchItem("MemoryMapMultiBufferOutputBroker",
|
|
new DebugMemoryMapMultiBufferOutputBrokerBuilder());
|
|
WDS_PatchItem("MemoryMapSynchronisedMultiBufferInputBroker",
|
|
new DebugMemoryMapSynchronisedMultiBufferInputBrokerBuilder());
|
|
WDS_PatchItem("MemoryMapSynchronisedMultiBufferOutputBroker",
|
|
new DebugMemoryMapSynchronisedMultiBufferOutputBrokerBuilder());
|
|
WDS_PatchItem("MemoryMapAsyncOutputBroker",
|
|
new DebugMemoryMapAsyncOutputBrokerBuilder());
|
|
WDS_PatchItem("MemoryMapAsyncTriggerOutputBroker",
|
|
new DebugMemoryMapAsyncTriggerOutputBrokerBuilder());
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// DebugServiceI RT-path
|
|
// ---------------------------------------------------------------------------
|
|
|
|
DebugSignalInfo *WebDebugService::RegisterSignal(void *memoryAddress,
|
|
TypeDescriptor type,
|
|
const char8 *name,
|
|
uint8 numberOfDimensions,
|
|
uint32 numberOfElements) {
|
|
mutex.FastLock();
|
|
DebugSignalInfo *res = NULL_PTR(DebugSignalInfo *);
|
|
uint32 sigIdx = 0xFFFFFFFFu;
|
|
for (uint32 i = 0u; i < signals.Size(); i++) {
|
|
if (signals[i]->memoryAddress == memoryAddress) {
|
|
res = signals[i]; sigIdx = i; break;
|
|
}
|
|
}
|
|
if (res == NULL_PTR(DebugSignalInfo *)) {
|
|
sigIdx = signals.Size();
|
|
res = new DebugSignalInfo();
|
|
res->memoryAddress = memoryAddress;
|
|
res->type = type;
|
|
res->name = name;
|
|
res->numberOfDimensions = numberOfDimensions;
|
|
res->numberOfElements = numberOfElements;
|
|
res->isTracing = false;
|
|
res->isForcing = false;
|
|
res->internalID = sigIdx;
|
|
res->decimationFactor = 1u;
|
|
res->decimationCounter = 0u;
|
|
res->breakOp = BREAK_OFF;
|
|
res->breakThreshold = 0.0;
|
|
signals.Push(res);
|
|
}
|
|
if (sigIdx != 0xFFFFFFFFu) {
|
|
bool found = false;
|
|
for (uint32 i = 0u; i < aliases.Size(); i++) {
|
|
if (aliases[i].name == name) { found = true; break; }
|
|
}
|
|
if (!found) {
|
|
SignalAlias a;
|
|
a.name = name;
|
|
a.signalIndex = sigIdx;
|
|
aliases.Push(a);
|
|
}
|
|
}
|
|
mutex.FastUnLock();
|
|
return res;
|
|
}
|
|
|
|
void WebDebugService::ProcessSignal(DebugSignalInfo *signalInfo,
|
|
uint32 size, uint64 timestamp) {
|
|
if (signalInfo == NULL_PTR(DebugSignalInfo *)) return;
|
|
if (signalInfo->isForcing) {
|
|
memcpy(signalInfo->memoryAddress, signalInfo->forcedValue, size);
|
|
}
|
|
if (signalInfo->isTracing) {
|
|
Atomic::Add((volatile int32 *)&signalInfo->decimationCounter, 1);
|
|
if ((uint32)signalInfo->decimationCounter >= signalInfo->decimationFactor) {
|
|
int32 old = Atomic::Exchange((volatile int32 *)&signalInfo->decimationCounter, 0);
|
|
if ((uint32)old >= signalInfo->decimationFactor) {
|
|
tracePushMutex.FastLock();
|
|
traceBuffer.Push(signalInfo->internalID, timestamp,
|
|
(uint8 *)signalInfo->memoryAddress, size);
|
|
tracePushMutex.FastUnLock();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void WebDebugService::RegisterBroker(DebugSignalInfo **signalPointers,
|
|
uint32 numSignals, MemoryMapBroker *broker,
|
|
volatile bool *anyActiveFlag,
|
|
Vec<uint32> *activeIndices,
|
|
Vec<uint32> *activeSizes,
|
|
FastPollingMutexSem *activeMutex,
|
|
volatile bool *anyBreakFlag,
|
|
Vec<uint32> *breakIndices,
|
|
const char8 *gamName, bool isOutput) {
|
|
mutex.FastLock();
|
|
BrokerInfo b;
|
|
b.signalPointers = signalPointers;
|
|
b.numSignals = numSignals;
|
|
b.broker = broker;
|
|
b.anyActiveFlag = anyActiveFlag;
|
|
b.activeIndices = activeIndices;
|
|
b.activeSizes = activeSizes;
|
|
b.activeMutex = activeMutex;
|
|
b.anyBreakFlag = anyBreakFlag;
|
|
b.breakIndices = breakIndices;
|
|
b.isOutput = isOutput;
|
|
if (gamName != NULL_PTR(const char8 *)) b.gamName = gamName;
|
|
brokers.Push(b);
|
|
mutex.FastUnLock();
|
|
}
|
|
|
|
void WebDebugService::ConsumeStepIfNeeded(const char8 *gamName,
|
|
const char8 *threadName) {
|
|
if (stepRemaining == 0u) return;
|
|
mutex.FastLock();
|
|
if (stepThreadFilter.Size() > 0u &&
|
|
(threadName == NULL_PTR(const char8 *) || stepThreadFilter != threadName)) {
|
|
mutex.FastUnLock();
|
|
return;
|
|
}
|
|
if (stepRemaining > 0u) {
|
|
stepRemaining--;
|
|
if (stepRemaining == 0u) {
|
|
isPaused = true;
|
|
pausedAtGam = (gamName != NULL_PTR(const char8 *)) ? gamName : "";
|
|
}
|
|
}
|
|
mutex.FastUnLock();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// DebugServiceI control-path
|
|
// ---------------------------------------------------------------------------
|
|
|
|
uint32 WebDebugService::ForceSignal(const char8 *name, const char8 *valueStr) {
|
|
mutex.FastLock();
|
|
uint32 count = 0u;
|
|
for (uint32 i = 0u; i < aliases.Size(); i++) {
|
|
if (aliases[i].name == name || WDS_SuffixMatch(aliases[i].name.Buffer(), name)) {
|
|
DebugSignalInfo *s = signals[aliases[i].signalIndex];
|
|
uint32 elemBytes = (uint32)(s->type.numberOfBits) / 8u;
|
|
uint32 totalBytes = elemBytes * s->numberOfElements;
|
|
if (elemBytes == 0u || totalBytes > (uint32)sizeof(s->forcedValue)) continue;
|
|
s->isForcing = true;
|
|
AnyType dest(s->type, 0u, s->forcedValue);
|
|
AnyType source(CharString, 0u, valueStr);
|
|
(void)TypeConvert(dest, source);
|
|
count++;
|
|
}
|
|
}
|
|
UpdateBrokersActiveStatus();
|
|
mutex.FastUnLock();
|
|
return count;
|
|
}
|
|
|
|
uint32 WebDebugService::UnforceSignal(const char8 *name) {
|
|
mutex.FastLock();
|
|
uint32 count = 0u;
|
|
for (uint32 i = 0u; i < aliases.Size(); i++) {
|
|
if (aliases[i].name == name || WDS_SuffixMatch(aliases[i].name.Buffer(), name)) {
|
|
signals[aliases[i].signalIndex]->isForcing = false;
|
|
count++;
|
|
}
|
|
}
|
|
UpdateBrokersActiveStatus();
|
|
mutex.FastUnLock();
|
|
return count;
|
|
}
|
|
|
|
uint32 WebDebugService::TraceSignal(const char8 *name, bool enable, uint32 decimation) {
|
|
mutex.FastLock();
|
|
uint32 count = 0u;
|
|
for (uint32 i = 0u; i < aliases.Size(); i++) {
|
|
if (aliases[i].name == name || WDS_SuffixMatch(aliases[i].name.Buffer(), name)) {
|
|
DebugSignalInfo *s = signals[aliases[i].signalIndex];
|
|
s->isTracing = enable;
|
|
s->decimationFactor = decimation;
|
|
s->decimationCounter = 0u;
|
|
count++;
|
|
}
|
|
}
|
|
UpdateBrokersActiveStatus();
|
|
mutex.FastUnLock();
|
|
return count;
|
|
}
|
|
|
|
uint32 WebDebugService::SetBreak(const char8 *name, uint8 op, float64 threshold) {
|
|
mutex.FastLock();
|
|
uint32 count = 0u;
|
|
for (uint32 i = 0u; i < aliases.Size(); i++) {
|
|
if (aliases[i].name == name || WDS_SuffixMatch(aliases[i].name.Buffer(), name)) {
|
|
DebugSignalInfo *s = signals[aliases[i].signalIndex];
|
|
s->breakThreshold = threshold;
|
|
s->breakOp = op;
|
|
count++;
|
|
}
|
|
}
|
|
if (count > 0u) UpdateBrokersBreakStatus();
|
|
mutex.FastUnLock();
|
|
return count;
|
|
}
|
|
|
|
uint32 WebDebugService::ClearBreak(const char8 *name) {
|
|
mutex.FastLock();
|
|
uint32 count = 0u;
|
|
for (uint32 i = 0u; i < aliases.Size(); i++) {
|
|
if (aliases[i].name == name || WDS_SuffixMatch(aliases[i].name.Buffer(), name)) {
|
|
signals[aliases[i].signalIndex]->breakOp = BREAK_OFF;
|
|
count++;
|
|
}
|
|
}
|
|
if (count > 0u) UpdateBrokersBreakStatus();
|
|
mutex.FastUnLock();
|
|
return count;
|
|
}
|
|
|
|
bool WebDebugService::IsInstrumented(const char8 *fullPath,
|
|
bool &traceable, bool &forcable) {
|
|
mutex.FastLock();
|
|
bool found = false;
|
|
for (uint32 i = 0u; i < aliases.Size(); i++) {
|
|
if (aliases[i].name == fullPath ||
|
|
WDS_SuffixMatch(aliases[i].name.Buffer(), fullPath)) {
|
|
found = true; break;
|
|
}
|
|
}
|
|
mutex.FastUnLock();
|
|
traceable = found;
|
|
forcable = found;
|
|
return found;
|
|
}
|
|
|
|
uint32 WebDebugService::RegisterMonitorSignal(const char8 *path, uint32 periodMs) {
|
|
mutex.FastLock();
|
|
for (uint32 j = 0u; j < monitoredSignals.Size(); j++) {
|
|
if (monitoredSignals[j].path == path) {
|
|
monitoredSignals[j].periodMs = periodMs;
|
|
mutex.FastUnLock();
|
|
return 1u;
|
|
}
|
|
}
|
|
|
|
StreamString fullPath = path;
|
|
fullPath.Seek(0u);
|
|
char8 term;
|
|
Vec<StreamString> parts;
|
|
StreamString token;
|
|
while (fullPath.GetToken(token, ".", term)) { parts.Push(token); token = ""; }
|
|
|
|
uint32 count = 0u;
|
|
if (parts.Size() >= 2u) {
|
|
StreamString signalName = parts[parts.Size() - 1u];
|
|
StreamString dsPath;
|
|
for (uint32 i = 0u; i < parts.Size() - 1u; i++) {
|
|
dsPath += parts[i];
|
|
if (i < parts.Size() - 2u) dsPath += ".";
|
|
}
|
|
ReferenceT<DataSourceI> ds = ObjectRegistryDatabase::Instance()->Find(dsPath.Buffer());
|
|
if (ds.IsValid()) {
|
|
uint32 idx = 0u;
|
|
if (ds->GetSignalIndex(idx, signalName.Buffer())) {
|
|
MonitoredSignal m;
|
|
m.dataSource = ds;
|
|
m.signalIdx = idx;
|
|
m.path = path;
|
|
m.periodMs = periodMs;
|
|
m.lastPollTime = 0u;
|
|
m.size = 0u;
|
|
(void)ds->GetSignalByteSize(idx, m.size);
|
|
if (m.size == 0u) m.size = 4u;
|
|
m.internalID = 0x80000000u | monitoredSignals.Size();
|
|
for (uint32 i = 0u; i < aliases.Size(); i++) {
|
|
if (aliases[i].name == path || WDS_SuffixMatch(aliases[i].name.Buffer(), path)) {
|
|
m.internalID = signals[aliases[i].signalIndex]->internalID;
|
|
break;
|
|
}
|
|
}
|
|
monitoredSignals.Push(m);
|
|
count = 1u;
|
|
}
|
|
}
|
|
}
|
|
mutex.FastUnLock();
|
|
return count;
|
|
}
|
|
|
|
uint32 WebDebugService::UnmonitorSignal(const char8 *path) {
|
|
mutex.FastLock();
|
|
uint32 count = 0u;
|
|
for (uint32 i = 0u; i < monitoredSignals.Size(); i++) {
|
|
if (monitoredSignals[i].path == path ||
|
|
WDS_SuffixMatch(monitoredSignals[i].path.Buffer(), path)) {
|
|
(void)monitoredSignals.Remove(i); i--; count++;
|
|
}
|
|
}
|
|
mutex.FastUnLock();
|
|
return count;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// EmbeddedServiceMethodBinderI — framework dispatches here; we never reach it
|
|
// ---------------------------------------------------------------------------
|
|
ErrorManagement::ErrorType WebDebugService::Execute(ExecutionInfo &info) {
|
|
(void)info;
|
|
return ErrorManagement::FatalError;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Config helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
void WebDebugService::SetFullConfig(ConfigurationDatabase &config) {
|
|
config.MoveToRoot();
|
|
config.Copy(fullConfig);
|
|
manualConfigSet = true;
|
|
}
|
|
|
|
void WebDebugService::RebuildConfigFromRegistry() {
|
|
fullConfig = ConfigurationDatabase();
|
|
WDS_BuildCDB(ObjectRegistryDatabase::Instance(), fullConfig);
|
|
fullConfig.MoveToRoot();
|
|
}
|
|
|
|
void WebDebugService::JsonifyDatabase(ConfigurationDatabase &db,
|
|
StreamString &json) {
|
|
json += "{";
|
|
uint32 n = db.GetNumberOfChildren();
|
|
bool first = true;
|
|
for (uint32 i = 0u; i < n; i++) {
|
|
const char8 *key = db.GetChildName(i);
|
|
if (key == NULL_PTR(const char8 *)) continue;
|
|
if (!first) json += ", ";
|
|
first = false;
|
|
json += "\"";
|
|
WDS_EscapeJson(key, json);
|
|
json += "\": ";
|
|
if (db.MoveRelative(key)) {
|
|
JsonifyDatabase(db, json);
|
|
(void)db.MoveToAncestor(1u);
|
|
} else {
|
|
AnyType at = db.GetType(key);
|
|
if (at.GetDataPointer() != NULL_PTR(void *)) {
|
|
char8 buf[512] = { '\0' };
|
|
AnyType dst(CharString, 0u, buf);
|
|
dst.SetNumberOfElements(0u, 512u);
|
|
if (TypeConvert(dst, at)) {
|
|
json += "\"";
|
|
WDS_EscapeJson(buf, json);
|
|
json += "\"";
|
|
} else {
|
|
json += "null";
|
|
}
|
|
} else {
|
|
json += "null";
|
|
}
|
|
}
|
|
}
|
|
json += "}";
|
|
}
|
|
|
|
void WebDebugService::EnrichWithConfig(const char8 *path, StreamString &json) {
|
|
if (path == NULL_PTR(const char8 *)) return;
|
|
if (!manualConfigSet) RebuildConfigFromRegistry();
|
|
fullConfig.MoveToRoot();
|
|
const char8 *current = path;
|
|
bool ok = true;
|
|
while (ok) {
|
|
const char8 *dot = StringHelper::SearchString(current, ".");
|
|
StreamString part;
|
|
if (dot != NULL_PTR(const char8 *)) {
|
|
uint32 len = (uint32)(dot - current);
|
|
(void)part.Write(current, len);
|
|
current = dot + 1;
|
|
} else {
|
|
part = current; ok = false;
|
|
}
|
|
bool found = false;
|
|
if (fullConfig.MoveRelative(part.Buffer())) { found = true; }
|
|
if (!found) {
|
|
StreamString pref; pref.Printf("+%s", part.Buffer());
|
|
if (fullConfig.MoveRelative(pref.Buffer())) found = true;
|
|
}
|
|
if (!found) return;
|
|
}
|
|
uint32 nc = fullConfig.GetNumberOfChildren();
|
|
for (uint32 i = 0u; i < nc; i++) {
|
|
const char8 *ek = fullConfig.GetChildName(i);
|
|
if (fullConfig.MoveRelative(ek)) {
|
|
fullConfig.MoveToAncestor(1u); continue;
|
|
}
|
|
AnyType at = fullConfig.GetType(ek);
|
|
if (at.GetDataPointer() != NULL_PTR(void *)) {
|
|
char8 buf[512] = { '\0' };
|
|
AnyType dst(CharString, 0u, buf);
|
|
dst.SetNumberOfElements(0u, 512u);
|
|
if (TypeConvert(dst, at)) {
|
|
json += ", \"";
|
|
WDS_EscapeJson(ek, json);
|
|
json += "\": \"";
|
|
WDS_EscapeJson(buf, json);
|
|
json += "\"";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Broker index maintenance
|
|
// ---------------------------------------------------------------------------
|
|
|
|
void WebDebugService::UpdateBrokersActiveStatus() {
|
|
for (uint32 i = 0u; i < brokers.Size(); i++) {
|
|
if (brokers[i].signalPointers == NULL_PTR(DebugSignalInfo **)) continue;
|
|
uint32 count = 0u;
|
|
Vec<uint32> tempInd, tempSizes;
|
|
for (uint32 j = 0u; j < brokers[i].numSignals; j++) {
|
|
DebugSignalInfo *s = brokers[i].signalPointers[j];
|
|
if (s != NULL_PTR(DebugSignalInfo *) && (s->isTracing || s->isForcing)) {
|
|
tempInd.Push(j);
|
|
tempSizes.Push((brokers[i].broker != NULL_PTR(MemoryMapBroker *))
|
|
? brokers[i].broker->GetCopyByteSize(j) : 4u);
|
|
count++;
|
|
}
|
|
}
|
|
if (brokers[i].activeMutex) brokers[i].activeMutex->FastLock();
|
|
if (brokers[i].activeIndices) brokers[i].activeIndices->Swap(tempInd);
|
|
if (brokers[i].activeSizes) brokers[i].activeSizes->Swap(tempSizes);
|
|
if (brokers[i].anyActiveFlag) *(brokers[i].anyActiveFlag) = (count > 0u);
|
|
if (brokers[i].activeMutex) brokers[i].activeMutex->FastUnLock();
|
|
}
|
|
}
|
|
|
|
void WebDebugService::UpdateBrokersBreakStatus() {
|
|
for (uint32 i = 0u; i < brokers.Size(); i++) {
|
|
if (brokers[i].signalPointers == NULL_PTR(DebugSignalInfo **)) continue;
|
|
Vec<uint32> tempBreak;
|
|
uint32 count = 0u;
|
|
for (uint32 j = 0u; j < brokers[i].numSignals; j++) {
|
|
DebugSignalInfo *s = brokers[i].signalPointers[j];
|
|
if (s != NULL_PTR(DebugSignalInfo *) && s->breakOp != BREAK_OFF) {
|
|
tempBreak.Push(j); count++;
|
|
}
|
|
}
|
|
if (brokers[i].activeMutex) brokers[i].activeMutex->FastLock();
|
|
if (brokers[i].breakIndices) brokers[i].breakIndices->Swap(tempBreak);
|
|
if (brokers[i].anyBreakFlag) *(brokers[i].anyBreakFlag) = (count > 0u);
|
|
if (brokers[i].activeMutex) brokers[i].activeMutex->FastUnLock();
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Step control
|
|
// ---------------------------------------------------------------------------
|
|
|
|
void WebDebugService::Step(uint32 n, const char8 *threadName) {
|
|
mutex.FastLock();
|
|
stepRemaining = n;
|
|
isPaused = false;
|
|
stepThreadFilter = (threadName != NULL_PTR(const char8 *)) ? threadName : "";
|
|
mutex.FastUnLock();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Command handler
|
|
// ---------------------------------------------------------------------------
|
|
|
|
void WebDebugService::GetStepStatus(StreamString &out) {
|
|
mutex.FastLock();
|
|
bool paused = isPaused;
|
|
uint32 rem = stepRemaining;
|
|
StreamString gam = pausedAtGam;
|
|
StreamString thr = stepThreadFilter;
|
|
mutex.FastUnLock();
|
|
out.Printf("{\"Paused\":%s,\"PausedAtGam\":\"%s\","
|
|
"\"StepRemaining\":%u,\"StepThread\":\"%s\"}\nOK STEP_STATUS\n",
|
|
paused ? "true" : "false", gam.Buffer(), rem, thr.Buffer());
|
|
}
|
|
|
|
void WebDebugService::GetSignalValue(const char8 *name, StreamString &out) {
|
|
mutex.FastLock();
|
|
DebugSignalInfo *sig = NULL_PTR(DebugSignalInfo *);
|
|
for (uint32 i = 0u; i < aliases.Size(); i++) {
|
|
if (aliases[i].name == name || WDS_SuffixMatch(aliases[i].name.Buffer(), name)) {
|
|
sig = signals[aliases[i].signalIndex]; break;
|
|
}
|
|
}
|
|
if (sig == NULL_PTR(DebugSignalInfo *)) {
|
|
mutex.FastUnLock();
|
|
out.Printf("{\"Error\":\"Signal not found: %s\"}\nOK VALUE\n", name);
|
|
return;
|
|
}
|
|
TypeDescriptor td = sig->type;
|
|
uint32 nElem = sig->numberOfElements;
|
|
uint32 bySz = (td.numberOfBits > 0u) ? (td.numberOfBits / 8u) : 1u;
|
|
bool trunc = (nElem > GET_VALUE_MAX_ELEMENTS);
|
|
if (trunc) nElem = GET_VALUE_MAX_ELEMENTS;
|
|
uint32 total = bySz * nElem;
|
|
if (total > 1024u) { total = 1024u; nElem = total / bySz; }
|
|
uint8 lb[1024]; memset(lb, 0, sizeof(lb));
|
|
if (sig->memoryAddress != NULL_PTR(void *)) memcpy(lb, sig->memoryAddress, total);
|
|
mutex.FastUnLock();
|
|
|
|
StreamString valueStr;
|
|
if (nElem > 1u) {
|
|
for (uint32 i = 0u; i < nElem; i++) {
|
|
char8 eb[128] = { '\0' };
|
|
AnyType se(td, 0u, (void *)(lb + i * bySz));
|
|
AnyType ds(CharString, 0u, eb);
|
|
ds.SetNumberOfElements(0u, 128u);
|
|
(void)TypeConvert(ds, se);
|
|
if (i > 0u) valueStr += ", ";
|
|
valueStr += eb;
|
|
}
|
|
} else {
|
|
char8 eb[256] = { '\0' };
|
|
AnyType se(td, 0u, (void *)lb);
|
|
AnyType ds(CharString, 0u, eb);
|
|
ds.SetNumberOfElements(0u, 256u);
|
|
(void)TypeConvert(ds, se);
|
|
valueStr = eb;
|
|
}
|
|
out += "{\"Name\":\"";
|
|
out += name;
|
|
out += "\",\"Value\":\"";
|
|
const char8 *vp = valueStr.Buffer();
|
|
while (vp != NULL_PTR(const char8 *) && *vp != '\0') {
|
|
if (*vp == '"') out += "\\\"";
|
|
else if (*vp == '\\') out += "\\\\";
|
|
else { char8 tmp[2] = { *vp, '\0' }; out += tmp; }
|
|
vp++;
|
|
}
|
|
out.Printf("\",\"Elements\":%u,\"Truncated\":%s}\nOK VALUE\n",
|
|
nElem, trunc ? "true" : "false");
|
|
}
|
|
|
|
void WebDebugService::ServeConfig(StreamString &out) {
|
|
if (!manualConfigSet) RebuildConfigFromRegistry();
|
|
fullConfig.MoveToRoot();
|
|
JsonifyDatabase(fullConfig, out);
|
|
out += "\nOK CONFIG\n";
|
|
}
|
|
|
|
void WebDebugService::InfoNode(const char8 *path, StreamString &out) {
|
|
Reference ref = ObjectRegistryDatabase::Instance()->Find(path);
|
|
out += "{";
|
|
if (ref.IsValid()) {
|
|
out += "\"Name\":\""; WDS_EscapeJson(ref->GetName(), out);
|
|
out += "\",\"Class\":\""; WDS_EscapeJson(ref->GetClassProperties()->GetName(), out);
|
|
out += "\"";
|
|
ConfigurationDatabase db;
|
|
if (ref->ExportData(db)) {
|
|
out += ",\"Config\":{";
|
|
db.MoveToRoot();
|
|
uint32 nc = db.GetNumberOfChildren();
|
|
for (uint32 i = 0u; i < nc; i++) {
|
|
const char8 *cn = db.GetChildName(i);
|
|
AnyType at = db.GetType(cn);
|
|
char8 buf[1024]; AnyType st(CharString, 0u, buf); st.SetNumberOfElements(0, 1024);
|
|
if (TypeConvert(st, at)) {
|
|
out += "\""; WDS_EscapeJson(cn, out); out += "\":\"";
|
|
WDS_EscapeJson(buf, out); out += "\"";
|
|
if (i < nc - 1u) out += ",";
|
|
}
|
|
}
|
|
out += "}";
|
|
}
|
|
EnrichWithConfig(path, out);
|
|
} else {
|
|
StreamString enrichAlias;
|
|
mutex.FastLock();
|
|
bool found = false;
|
|
for (uint32 i = 0u; i < aliases.Size(); i++) {
|
|
if (aliases[i].name == path || WDS_SuffixMatch(aliases[i].name.Buffer(), path)) {
|
|
DebugSignalInfo *s = signals[aliases[i].signalIndex];
|
|
const char8 *tn = TypeDescriptor::GetTypeNameFromTypeDescriptor(s->type);
|
|
out.Printf("\"Name\":\"%s\",\"Class\":\"Signal\",\"Type\":\"%s\",\"ID\":%u",
|
|
s->name.Buffer(), tn ? tn : "Unknown", s->internalID);
|
|
enrichAlias = aliases[i].name;
|
|
found = true; break;
|
|
}
|
|
}
|
|
mutex.FastUnLock();
|
|
if (found) EnrichWithConfig(enrichAlias.Buffer(), out);
|
|
else out += "\"Error\":\"Object not found\"";
|
|
}
|
|
out += "}\nOK INFO\n";
|
|
}
|
|
|
|
void WebDebugService::ListNodes(const char8 *path, StreamString &out) {
|
|
Reference ref =
|
|
(path == NULL_PTR(const char8 *) || StringHelper::Length(path) == 0 ||
|
|
StringHelper::Compare(path, "/") == 0)
|
|
? ObjectRegistryDatabase::Instance()
|
|
: ObjectRegistryDatabase::Instance()->Find(path);
|
|
out.Printf("Nodes under %s:\n", path ? path : "/");
|
|
if (ref.IsValid()) {
|
|
ReferenceContainer *rc = dynamic_cast<ReferenceContainer *>(ref.operator->());
|
|
if (rc != NULL_PTR(ReferenceContainer *)) {
|
|
uint32 n = rc->Size();
|
|
for (uint32 i = 0u; i < n; i++) {
|
|
Reference c = rc->Get(i);
|
|
if (c.IsValid()) {
|
|
out.Printf(" %s [%s]\n", c->GetName(),
|
|
c->GetClassProperties()->GetName());
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
out += " (not found)\n";
|
|
}
|
|
out += "OK LS\n";
|
|
}
|
|
|
|
uint32 WebDebugService::ExportTree(ReferenceContainer *container,
|
|
StreamString &json, const char8 *pathPrefix) {
|
|
if (container == NULL_PTR(ReferenceContainer *)) return 0u;
|
|
uint32 size = container->Size();
|
|
uint32 valid = 0u;
|
|
for (uint32 i = 0u; i < size; i++) {
|
|
Reference child = container->Get(i);
|
|
if (!child.IsValid()) continue;
|
|
if (valid > 0u) json += ",\n";
|
|
const char8 *cname = child->GetName();
|
|
if (cname == NULL_PTR(const char8 *)) cname = "unnamed";
|
|
StreamString cp;
|
|
if (pathPrefix != NULL_PTR(const char8 *)) cp.Printf("%s.%s", pathPrefix, cname);
|
|
else cp = cname;
|
|
StreamString nj;
|
|
nj += "{\"Name\":\""; WDS_EscapeJson(cname, nj);
|
|
nj += "\",\"Class\":\""; WDS_EscapeJson(child->GetClassProperties()->GetName(), nj);
|
|
nj += "\"";
|
|
ReferenceContainer *inner = dynamic_cast<ReferenceContainer *>(child.operator->());
|
|
DataSourceI *ds = dynamic_cast<DataSourceI *>(child.operator->());
|
|
GAM *gam = dynamic_cast<GAM *>(child.operator->());
|
|
if (inner != NULL_PTR(ReferenceContainer *) || ds != NULL_PTR(DataSourceI *) || gam != NULL_PTR(GAM *)) {
|
|
nj += ",\"Children\":[\n";
|
|
uint32 sc = 0u;
|
|
if (inner != NULL_PTR(ReferenceContainer *)) sc += ExportTree(inner, nj, cp.Buffer());
|
|
if (ds != NULL_PTR(DataSourceI *)) {
|
|
uint32 ns = ds->GetNumberOfSignals();
|
|
for (uint32 j = 0u; j < ns; j++) {
|
|
if (sc > 0u) { nj += ",\n"; } sc++;
|
|
StreamString sn; (void)ds->GetSignalName(j, sn);
|
|
const char8 *st = TypeDescriptor::GetTypeNameFromTypeDescriptor(ds->GetSignalType(j));
|
|
uint8 d = 0u; (void)ds->GetSignalNumberOfDimensions(j, d);
|
|
uint32 el = 0u; (void)ds->GetSignalNumberOfElements(j, el);
|
|
StreamString sfp; sfp.Printf("%s.%s", cp.Buffer(), sn.Buffer());
|
|
bool tr = false, fo = false; (void)IsInstrumented(sfp.Buffer(), tr, fo);
|
|
nj += "{\"Name\":\""; WDS_EscapeJson(sn.Buffer(), nj);
|
|
nj += "\",\"Class\":\"Signal\",\"Type\":\""; WDS_EscapeJson(st ? st : "Unknown", nj);
|
|
nj.Printf("\",\"Dimensions\":%u,\"Elements\":%u,\"IsTraceable\":%s,\"IsForcable\":%s}",
|
|
d, el, tr ? "true" : "false", fo ? "true" : "false");
|
|
}
|
|
}
|
|
if (gam != NULL_PTR(GAM *)) {
|
|
uint32 nIn = gam->GetNumberOfInputSignals();
|
|
for (uint32 j = 0u; j < nIn; j++) {
|
|
if (sc > 0u) { nj += ",\n"; } sc++;
|
|
StreamString sn; (void)gam->GetSignalName(InputSignals, j, sn);
|
|
const char8 *st = TypeDescriptor::GetTypeNameFromTypeDescriptor(gam->GetSignalType(InputSignals, j));
|
|
uint32 d = 0u; (void)gam->GetSignalNumberOfDimensions(InputSignals, j, d);
|
|
uint32 el = 0u; (void)gam->GetSignalNumberOfElements(InputSignals, j, el);
|
|
StreamString sfp; sfp.Printf("%s.In.%s", cp.Buffer(), sn.Buffer());
|
|
bool tr = false, fo = false; (void)IsInstrumented(sfp.Buffer(), tr, fo);
|
|
nj += "{\"Name\":\"In."; WDS_EscapeJson(sn.Buffer(), nj);
|
|
nj += "\",\"Class\":\"InputSignal\",\"Type\":\""; WDS_EscapeJson(st ? st : "Unknown", nj);
|
|
nj.Printf("\",\"Dimensions\":%u,\"Elements\":%u,\"IsTraceable\":%s,\"IsForcable\":%s}",
|
|
d, el, tr ? "true" : "false", fo ? "true" : "false");
|
|
}
|
|
uint32 nOut = gam->GetNumberOfOutputSignals();
|
|
for (uint32 j = 0u; j < nOut; j++) {
|
|
if (sc > 0u) { nj += ",\n"; } sc++;
|
|
StreamString sn; (void)gam->GetSignalName(OutputSignals, j, sn);
|
|
const char8 *st = TypeDescriptor::GetTypeNameFromTypeDescriptor(gam->GetSignalType(OutputSignals, j));
|
|
uint32 d = 0u; (void)gam->GetSignalNumberOfDimensions(OutputSignals, j, d);
|
|
uint32 el = 0u; (void)gam->GetSignalNumberOfElements(OutputSignals, j, el);
|
|
StreamString sfp; sfp.Printf("%s.Out.%s", cp.Buffer(), sn.Buffer());
|
|
bool tr = false, fo = false; (void)IsInstrumented(sfp.Buffer(), tr, fo);
|
|
nj += "{\"Name\":\"Out."; WDS_EscapeJson(sn.Buffer(), nj);
|
|
nj += "\",\"Class\":\"OutputSignal\",\"Type\":\""; WDS_EscapeJson(st ? st : "Unknown", nj);
|
|
nj.Printf("\",\"Dimensions\":%u,\"Elements\":%u,\"IsTraceable\":%s,\"IsForcable\":%s}",
|
|
d, el, tr ? "true" : "false", fo ? "true" : "false");
|
|
}
|
|
}
|
|
nj += "\n]";
|
|
}
|
|
nj += "}";
|
|
json += nj;
|
|
valid++;
|
|
}
|
|
return valid;
|
|
}
|
|
|
|
void WebDebugService::Discover(StreamString &out) {
|
|
out += "{\"Signals\":[\n";
|
|
mutex.FastLock();
|
|
uint32 total = 0u;
|
|
for (uint32 i = 0u; i < aliases.Size(); i++) {
|
|
if (total > 0u) out += ",\n";
|
|
DebugSignalInfo *sig = signals[aliases[i].signalIndex];
|
|
const char8 *tn = TypeDescriptor::GetTypeNameFromTypeDescriptor(sig->type);
|
|
out.Printf(" {\"name\":\"%s\",\"id\":%u,\"type\":\"%s\","
|
|
"\"dimensions\":%u,\"elements\":%u",
|
|
aliases[i].name.Buffer(), sig->internalID,
|
|
tn ? tn : "Unknown", sig->numberOfDimensions, sig->numberOfElements);
|
|
EnrichWithConfig(aliases[i].name.Buffer(), out);
|
|
out += "}";
|
|
total++;
|
|
}
|
|
for (uint32 i = 0u; i < monitoredSignals.Size(); i++) {
|
|
bool found = false;
|
|
for (uint32 j = 0u; j < aliases.Size(); j++) {
|
|
if (aliases[j].name == monitoredSignals[i].path) { found = true; break; }
|
|
}
|
|
if (!found) {
|
|
if (total > 0u) out += ",\n";
|
|
const char8 *tn = TypeDescriptor::GetTypeNameFromTypeDescriptor(
|
|
monitoredSignals[i].dataSource->GetSignalType(monitoredSignals[i].signalIdx));
|
|
uint8 dims = 0u; uint32 elems = 1u;
|
|
(void)monitoredSignals[i].dataSource->GetSignalNumberOfDimensions(monitoredSignals[i].signalIdx, dims);
|
|
(void)monitoredSignals[i].dataSource->GetSignalNumberOfElements(monitoredSignals[i].signalIdx, elems);
|
|
out.Printf(" {\"name\":\"%s\",\"id\":%u,\"type\":\"%s\","
|
|
"\"dimensions\":%u,\"elements\":%u",
|
|
monitoredSignals[i].path.Buffer(), monitoredSignals[i].internalID,
|
|
tn ? tn : "Unknown", dims, elems);
|
|
EnrichWithConfig(monitoredSignals[i].path.Buffer(), out);
|
|
out += "}";
|
|
total++;
|
|
}
|
|
}
|
|
mutex.FastUnLock();
|
|
out += "\n]}\nOK DISCOVER\n";
|
|
}
|
|
|
|
void WebDebugService::HandleCommand(const StreamString &cmdIn, StreamString &out) {
|
|
StreamString cmd = cmdIn;
|
|
StreamString token;
|
|
cmd.Seek(0u);
|
|
char8 term;
|
|
const char8 *delims = " \r\n";
|
|
if (!cmd.GetToken(token, delims, term)) return;
|
|
|
|
if (token == "FORCE") {
|
|
StreamString name, val;
|
|
if (cmd.GetToken(name, delims, term) && cmd.GetToken(val, delims, term)) {
|
|
uint32 c = ForceSignal(name.Buffer(), val.Buffer());
|
|
out.Printf("OK FORCE %u\n", c);
|
|
}
|
|
} else if (token == "UNFORCE") {
|
|
StreamString name;
|
|
if (cmd.GetToken(name, delims, term)) {
|
|
uint32 c = UnforceSignal(name.Buffer());
|
|
out.Printf("OK UNFORCE %u\n", c);
|
|
}
|
|
} else if (token == "TRACE") {
|
|
StreamString name, state, decim;
|
|
if (cmd.GetToken(name, delims, term) && cmd.GetToken(state, delims, term)) {
|
|
bool en = (state == "1");
|
|
uint32 d = 1u;
|
|
if (cmd.GetToken(decim, delims, term)) {
|
|
AnyType dv(UnsignedInteger32Bit, 0u, &d);
|
|
AnyType ds(CharString, 0u, decim.Buffer());
|
|
(void)TypeConvert(dv, ds);
|
|
}
|
|
uint32 c = TraceSignal(name.Buffer(), en, d);
|
|
out.Printf("OK TRACE %u\n", c);
|
|
}
|
|
} else if (token == "BREAK") {
|
|
StreamString name, opStr;
|
|
if (cmd.GetToken(name, delims, term) && cmd.GetToken(opStr, delims, term)) {
|
|
uint32 c = 0u;
|
|
if (opStr == "OFF") {
|
|
c = ClearBreak(name.Buffer());
|
|
} else {
|
|
StreamString thrStr;
|
|
if (cmd.GetToken(thrStr, delims, term)) {
|
|
uint8 op = BREAK_OFF;
|
|
if (opStr == ">") op = BREAK_GT;
|
|
else if (opStr == "<") op = BREAK_LT;
|
|
else if (opStr == "==") op = BREAK_EQ;
|
|
else if (opStr == ">=") op = BREAK_GEQ;
|
|
else if (opStr == "<=") op = BREAK_LEQ;
|
|
else if (opStr == "!=") op = BREAK_NEQ;
|
|
if (op != BREAK_OFF) {
|
|
float64 thr = 0.0;
|
|
AnyType tv(Float64Bit, 0u, &thr);
|
|
AnyType ts(CharString, 0u, thrStr.Buffer());
|
|
(void)TypeConvert(tv, ts);
|
|
c = SetBreak(name.Buffer(), op, thr);
|
|
}
|
|
}
|
|
}
|
|
out.Printf("OK BREAK %u\n", c);
|
|
}
|
|
} else if (token == "PAUSE") {
|
|
SetPaused(true);
|
|
out += "OK\n";
|
|
// broadcast status event
|
|
StreamString ev;
|
|
ev.Printf("{\"type\":\"status\",\"paused\":true,\"gam\":\"%s\",\"remaining\":%u}",
|
|
pausedAtGam.Buffer(), (uint32)stepRemaining);
|
|
BroadcastSse("message", ev);
|
|
} else if (token == "RESUME") {
|
|
SetPaused(false);
|
|
out += "OK\n";
|
|
StreamString ev;
|
|
ev += "{\"type\":\"status\",\"paused\":false,\"gam\":\"\",\"remaining\":0}";
|
|
BroadcastSse("message", ev);
|
|
} else if (token == "STEP") {
|
|
StreamString nStr;
|
|
uint32 n = 1u;
|
|
if (cmd.GetToken(nStr, delims, term)) {
|
|
AnyType nv(UnsignedInteger32Bit, 0u, &n);
|
|
AnyType ns(CharString, 0u, nStr.Buffer());
|
|
(void)TypeConvert(nv, ns);
|
|
}
|
|
StreamString thr;
|
|
const char8 *ta = NULL_PTR(const char8 *);
|
|
if (cmd.GetToken(thr, delims, term) && thr.Size() > 0u) ta = thr.Buffer();
|
|
Step(n, ta);
|
|
out.Printf("OK STEP %u\n", n);
|
|
} else if (token == "STEP_STATUS") {
|
|
GetStepStatus(out);
|
|
} else if (token == "VALUE") {
|
|
StreamString sn;
|
|
if (cmd.GetToken(sn, delims, term)) GetSignalValue(sn.Buffer(), out);
|
|
else out += "{\"Error\":\"Missing signal name\"}\nOK VALUE\n";
|
|
} else if (token == "DISCOVER") {
|
|
Discover(out);
|
|
} else if (token == "TREE") {
|
|
out += "{\"Name\":\"Root\",\"Class\":\"ObjectRegistryDatabase\","
|
|
"\"Children\":[\n";
|
|
(void)ExportTree(ObjectRegistryDatabase::Instance(), out, NULL_PTR(const char8 *));
|
|
out += "\n]}\nOK TREE\n";
|
|
} else if (token == "INFO") {
|
|
StreamString path;
|
|
if (cmd.GetToken(path, delims, term)) InfoNode(path.Buffer(), out);
|
|
} else if (token == "LS") {
|
|
StreamString path;
|
|
if (cmd.GetToken(path, delims, term)) ListNodes(path.Buffer(), out);
|
|
else ListNodes(NULL_PTR(const char8 *), out);
|
|
} else if (token == "CONFIG") {
|
|
ServeConfig(out);
|
|
} else if (token == "MONITOR") {
|
|
StreamString sub, name, period;
|
|
if (cmd.GetToken(sub, delims, term) && sub == "SIGNAL" &&
|
|
cmd.GetToken(name, delims, term) && cmd.GetToken(period, delims, term)) {
|
|
uint32 p = 100u;
|
|
AnyType pv(UnsignedInteger32Bit, 0u, &p);
|
|
AnyType ps(CharString, 0u, period.Buffer());
|
|
(void)TypeConvert(pv, ps);
|
|
uint32 c = RegisterMonitorSignal(name.Buffer(), p);
|
|
out.Printf("OK MONITOR %u\n", c);
|
|
}
|
|
} else if (token == "UNMONITOR") {
|
|
StreamString sub, name;
|
|
if (cmd.GetToken(sub, delims, term) && sub == "SIGNAL" &&
|
|
cmd.GetToken(name, delims, term)) {
|
|
uint32 c = UnmonitorSignal(name.Buffer());
|
|
out.Printf("OK UNMONITOR %u\n", c);
|
|
}
|
|
} else if (token == "SERVICE_INFO") {
|
|
out.Printf("OK SERVICE_INFO HTTP:%u STATE:%s\n",
|
|
(uint32)httpPort, isPaused ? "PAUSED" : "RUNNING");
|
|
}
|
|
// MSG command handled separately — needs access to ORD; not yet supported here
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// SSE broadcast
|
|
// ---------------------------------------------------------------------------
|
|
|
|
void WebDebugService::BroadcastSse(const char8 *eventType, const StreamString &data) {
|
|
StreamString pkt;
|
|
pkt.Printf("event: %s\ndata: ", eventType);
|
|
pkt += data;
|
|
pkt += "\n\n";
|
|
sseMutex.FastLock();
|
|
for (uint32 i = 0u; i < MAX_SSE_CLIENTS; i++) {
|
|
if (sseClients[i] != NULL_PTR(BasicTCPSocket *)) {
|
|
uint32 sz = pkt.Size();
|
|
if (!sseClients[i]->Write(pkt.Buffer(), sz)) {
|
|
sseClients[i]->Close();
|
|
delete sseClients[i];
|
|
sseClients[i] = NULL_PTR(BasicTCPSocket *);
|
|
}
|
|
}
|
|
}
|
|
sseMutex.FastUnLock();
|
|
}
|
|
|
|
void WebDebugService::RemoveDeadSseClients() {
|
|
sseMutex.FastLock();
|
|
for (uint32 i = 0u; i < MAX_SSE_CLIENTS; i++) {
|
|
if (sseClients[i] != NULL_PTR(BasicTCPSocket *) &&
|
|
!sseClients[i]->IsConnected()) {
|
|
sseClients[i]->Close();
|
|
delete sseClients[i];
|
|
sseClients[i] = NULL_PTR(BasicTCPSocket *);
|
|
}
|
|
}
|
|
sseMutex.FastUnLock();
|
|
}
|
|
|
|
void WebDebugService::UpgradeToSse(BasicTCPSocket *client) {
|
|
// Send SSE upgrade headers
|
|
const char8 *hdr =
|
|
"HTTP/1.1 200 OK\r\n"
|
|
"Content-Type: text/event-stream\r\n"
|
|
"Cache-Control: no-cache\r\n"
|
|
"Connection: keep-alive\r\n"
|
|
"Access-Control-Allow-Origin: *\r\n"
|
|
"\r\n";
|
|
uint32 sz = StringHelper::Length(hdr);
|
|
(void)client->Write(hdr, sz);
|
|
|
|
// Send an initial status event so the browser knows the connection is alive
|
|
mutex.FastLock();
|
|
bool paused = isPaused;
|
|
StreamString gam = pausedAtGam;
|
|
uint32 rem = stepRemaining;
|
|
mutex.FastUnLock();
|
|
StreamString initEv;
|
|
initEv.Printf("{\"type\":\"status\",\"paused\":%s,\"gam\":\"%s\",\"remaining\":%u}",
|
|
paused ? "true" : "false", gam.Buffer(), rem);
|
|
StreamString initPkt;
|
|
initPkt += "event: message\ndata: ";
|
|
initPkt += initEv;
|
|
initPkt += "\n\n";
|
|
uint32 isz = initPkt.Size();
|
|
(void)client->Write(initPkt.Buffer(), isz);
|
|
|
|
// Replay log history so the browser sees messages emitted before it connected
|
|
logHistoryMutex.FastLock();
|
|
{
|
|
uint32 startIdx = (logHistoryFill >= LOG_HISTORY_SIZE)
|
|
? logHistoryWriteIdx : 0u;
|
|
uint32 count = logHistoryFill;
|
|
for (uint32 i = 0u; i < count; i++) {
|
|
uint32 idx = (startIdx + i) % LOG_HISTORY_SIZE;
|
|
if (logHistory[idx][0] != '\0') {
|
|
StreamString histPkt;
|
|
histPkt += "event: message\ndata: ";
|
|
histPkt += logHistory[idx];
|
|
histPkt += "\n\n";
|
|
uint32 hsz = histPkt.Size();
|
|
(void)client->Write(histPkt.Buffer(), hsz);
|
|
}
|
|
}
|
|
}
|
|
logHistoryMutex.FastUnLock();
|
|
|
|
// Register in the SSE client slot array
|
|
sseMutex.FastLock();
|
|
bool registered = false;
|
|
for (uint32 i = 0u; i < MAX_SSE_CLIENTS; i++) {
|
|
if (sseClients[i] == NULL_PTR(BasicTCPSocket *)) {
|
|
sseClients[i] = client;
|
|
registered = true;
|
|
break;
|
|
}
|
|
}
|
|
sseMutex.FastUnLock();
|
|
if (!registered) {
|
|
// No free slot: close immediately
|
|
client->Close();
|
|
delete client;
|
|
}
|
|
// Do NOT close or delete client here — Streamer owns it now
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// HTTP request parsing
|
|
// ---------------------------------------------------------------------------
|
|
|
|
bool WebDebugService::ParseRequest(BasicTCPSocket *client,
|
|
StreamString &method, StreamString &path,
|
|
StreamString &body) {
|
|
// Read until we see the end of HTTP headers (\r\n\r\n)
|
|
static const uint32 MAX_HEADER = 8192u;
|
|
char8 buf[MAX_HEADER + 1];
|
|
uint32 total = 0u;
|
|
uint32 headerEnd = 0u;
|
|
bool foundEnd = false;
|
|
|
|
while (total < MAX_HEADER && !foundEnd) {
|
|
uint32 sz = MAX_HEADER - total;
|
|
if (!client->Read(buf + total, sz) || sz == 0u) break;
|
|
total += sz;
|
|
buf[total] = '\0';
|
|
// scan for \r\n\r\n
|
|
for (uint32 i = 0u; i + 3u < total; i++) {
|
|
if (buf[i] == '\r' && buf[i+1] == '\n' && buf[i+2] == '\r' && buf[i+3] == '\n') {
|
|
headerEnd = i + 4u;
|
|
foundEnd = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!foundEnd) return false;
|
|
|
|
// Parse request line
|
|
const char8 *line = buf;
|
|
const char8 *sp1 = StringHelper::SearchChar(line, ' ');
|
|
if (sp1 == NULL_PTR(const char8 *)) return false;
|
|
method = "";
|
|
{ uint32 mLen = (uint32)(sp1 - line); (void)method.Write(line, mLen); }
|
|
const char8 *sp2 = StringHelper::SearchChar(sp1 + 1, ' ');
|
|
if (sp2 == NULL_PTR(const char8 *)) return false;
|
|
path = "";
|
|
{ uint32 pLen = (uint32)(sp2 - sp1 - 1); (void)path.Write(sp1 + 1, pLen); }
|
|
|
|
// Extract Content-Length for POST body
|
|
uint32 contentLength = 0u;
|
|
const char8 *cl = StringHelper::SearchString(buf, "Content-Length:");
|
|
if (cl != NULL_PTR(const char8 *)) {
|
|
cl += 15u;
|
|
while (*cl == ' ') cl++;
|
|
AnyType cv(UnsignedInteger32Bit, 0u, &contentLength);
|
|
AnyType cs(CharString, 0u, cl);
|
|
(void)TypeConvert(cv, cs);
|
|
}
|
|
|
|
// Read body
|
|
body = "";
|
|
if (contentLength > 0u) {
|
|
uint32 alreadyRead = (total > headerEnd) ? (total - headerEnd) : 0u;
|
|
if (alreadyRead > 0u) {
|
|
(void)body.Write(buf + headerEnd, alreadyRead);
|
|
}
|
|
static const uint32 MAX_BODY = 16384u;
|
|
if (contentLength > alreadyRead && contentLength <= MAX_BODY) {
|
|
char8 bodyBuf[MAX_BODY];
|
|
uint32 toRead = contentLength - alreadyRead;
|
|
uint32 sz = toRead;
|
|
if (client->Read(bodyBuf, sz) && sz > 0u) {
|
|
(void)body.Write(bodyBuf, sz);
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void WebDebugService::SendHttp(BasicTCPSocket *client, uint32 code,
|
|
const char8 *ct, StreamString &body) {
|
|
StreamString resp;
|
|
resp.Printf("HTTP/1.1 %u %s\r\n"
|
|
"Content-Type: %s\r\n"
|
|
"Content-Length: %u\r\n"
|
|
"Cache-Control: no-cache\r\n"
|
|
"Access-Control-Allow-Origin: *\r\n"
|
|
"Connection: close\r\n"
|
|
"\r\n",
|
|
code, (code == 200u) ? "OK" : "Error",
|
|
ct, (uint32)body.Size());
|
|
resp += body;
|
|
uint32 sz = resp.Size();
|
|
(void)client->Write(resp.Buffer(), sz);
|
|
}
|
|
|
|
void WebDebugService::HandleRequest(BasicTCPSocket *client,
|
|
const StreamString &method,
|
|
const StreamString &path,
|
|
const StreamString &body) {
|
|
if (method == "GET" && (path == "/" || path == "/index.html")) {
|
|
StreamString html = WEB_UI_HTML;
|
|
SendHttp(client, 200u, "text/html; charset=utf-8", html);
|
|
} else if (method == "GET" && path == "/api/events") {
|
|
// SSE upgrade — hands off socket ownership; do not close after return
|
|
UpgradeToSse(client);
|
|
return; // caller must not close socket
|
|
} else if (method == "POST" && path == "/api/command") {
|
|
StreamString out;
|
|
HandleCommand(body, out);
|
|
SendHttp(client, 200u, "text/plain; charset=utf-8", out);
|
|
} else if (method == "OPTIONS") {
|
|
// CORS preflight
|
|
const char8 *hdr =
|
|
"HTTP/1.1 204 No Content\r\n"
|
|
"Access-Control-Allow-Origin: *\r\n"
|
|
"Access-Control-Allow-Methods: GET, POST, OPTIONS\r\n"
|
|
"Access-Control-Allow-Headers: Content-Type\r\n"
|
|
"Connection: close\r\n"
|
|
"\r\n";
|
|
uint32 sz = StringHelper::Length(hdr);
|
|
(void)client->Write(hdr, sz);
|
|
} else {
|
|
StreamString body404 = "Not Found";
|
|
SendHttp(client, 404u, "text/plain", body404);
|
|
}
|
|
client->Close();
|
|
delete client;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// HTTP server thread
|
|
// ---------------------------------------------------------------------------
|
|
|
|
ErrorManagement::ErrorType WebDebugService::HttpServer(ExecutionInfo &info) {
|
|
if (info.GetStage() == ExecutionInfo::TerminationStage)
|
|
return ErrorManagement::NoError;
|
|
if (info.GetStage() == ExecutionInfo::StartupStage) {
|
|
Sleep::MSec(500u); // wait for ORD to finish
|
|
return ErrorManagement::NoError;
|
|
}
|
|
|
|
BasicTCPSocket *client = tcpServer.WaitConnection(TimeoutType(100u));
|
|
if (client == NULL_PTR(BasicTCPSocket *))
|
|
return ErrorManagement::NoError;
|
|
|
|
StreamString method, path, body;
|
|
if (ParseRequest(client, method, path, body)) {
|
|
// Check if SSE upgrade — HandleRequest takes ownership if SSE
|
|
bool isSse = (method == "GET" && path == "/api/events");
|
|
HandleRequest(client, method, path, body);
|
|
if (isSse) {
|
|
// Socket ownership transferred to sseClients — don't touch it
|
|
}
|
|
// For non-SSE, HandleRequest already closed/deleted the client
|
|
} else {
|
|
client->Close();
|
|
delete client;
|
|
}
|
|
return ErrorManagement::NoError;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Streamer thread — reads ring buffer, broadcasts SSE events, polls monitors
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// Helper: convert raw bytes to float64 for SSE JSON
|
|
static float64 WDS_ToFloat64(const uint8 *data, TypeDescriptor td) {
|
|
float64 v = 0.0;
|
|
if (td == Float32Bit) {
|
|
float32 f; memcpy(&f, data, 4u); v = (float64)f;
|
|
} else if (td == Float64Bit) {
|
|
memcpy(&v, data, 8u);
|
|
} else if (td == SignedInteger8Bit) {
|
|
int8 x; memcpy(&x, data, 1u); v = (float64)x;
|
|
} else if (td == UnsignedInteger8Bit) {
|
|
uint8 x; memcpy(&x, data, 1u); v = (float64)x;
|
|
} else if (td == SignedInteger16Bit) {
|
|
int16 x; memcpy(&x, data, 2u); v = (float64)x;
|
|
} else if (td == UnsignedInteger16Bit) {
|
|
uint16 x; memcpy(&x, data, 2u); v = (float64)x;
|
|
} else if (td == SignedInteger32Bit) {
|
|
int32 x; memcpy(&x, data, 4u); v = (float64)x;
|
|
} else if (td == UnsignedInteger32Bit) {
|
|
uint32 x; memcpy(&x, data, 4u); v = (float64)x;
|
|
} else if (td == SignedInteger64Bit) {
|
|
int64 x; memcpy(&x, data, 8u); v = (float64)x;
|
|
} else if (td == UnsignedInteger64Bit) {
|
|
uint64 x; memcpy(&x, data, 8u); v = (float64)(int64)x;
|
|
}
|
|
return v;
|
|
}
|
|
|
|
ErrorManagement::ErrorType WebDebugService::Streamer(ExecutionInfo &info) {
|
|
if (info.GetStage() == ExecutionInfo::TerminationStage)
|
|
return ErrorManagement::NoError;
|
|
if (info.GetStage() == ExecutionInfo::StartupStage)
|
|
return ErrorManagement::NoError;
|
|
|
|
uint64 nowNs = (uint64)((float64)HighResolutionTimer::Counter() *
|
|
HighResolutionTimer::Period() * 1.0e9);
|
|
|
|
uint64 nowMs = (nowNs >= streamerT0Ns) ? (nowNs - streamerT0Ns) / 1000000u : 0u;
|
|
|
|
// -----------------------------------------------------------------
|
|
// Drain MARTe2 log pages → SSE log events
|
|
// -----------------------------------------------------------------
|
|
{
|
|
static const uint32 MAX_LOGS_PER_CYCLE = 32u;
|
|
uint32 logCount = 0u;
|
|
LoggerPage *logPage = Logger::Instance()->GetLogEntry();
|
|
while (logPage != NULL_PTR(LoggerPage *) && logCount < MAX_LOGS_PER_CYCLE) {
|
|
StreamString levelStr;
|
|
ErrorManagement::ErrorCodeToStream(
|
|
logPage->errorInfo.header.errorType, levelStr);
|
|
StreamString msgEsc;
|
|
WDS_EscapeJson(logPage->errorStrBuffer, msgEsc);
|
|
StreamString ev;
|
|
ev.Printf("{\"type\":\"log\",\"level\":\"%s\",\"msg\":\"%s\"}",
|
|
levelStr.Buffer(), msgEsc.Buffer());
|
|
Logger::Instance()->ReturnPage(logPage);
|
|
// Store in history for late-connecting clients
|
|
logHistoryMutex.FastLock();
|
|
{
|
|
uint32 evSz = ev.Size();
|
|
if (evSz >= LOG_ENTRY_MAX_SIZE) evSz = LOG_ENTRY_MAX_SIZE - 1u;
|
|
(void)StringHelper::CopyN(logHistory[logHistoryWriteIdx], ev.Buffer(), evSz);
|
|
logHistory[logHistoryWriteIdx][evSz] = '\0';
|
|
logHistoryWriteIdx = (logHistoryWriteIdx + 1u) % LOG_HISTORY_SIZE;
|
|
if (logHistoryFill < LOG_HISTORY_SIZE) logHistoryFill++;
|
|
}
|
|
logHistoryMutex.FastUnLock();
|
|
BroadcastSse("message", ev);
|
|
logPage = Logger::Instance()->GetLogEntry();
|
|
logCount++;
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------
|
|
// Poll monitored signals
|
|
// -----------------------------------------------------------------
|
|
mutex.FastLock();
|
|
for (uint32 i = 0u; i < monitoredSignals.Size(); i++) {
|
|
if (nowMs >= (monitoredSignals[i].lastPollTime + monitoredSignals[i].periodMs)) {
|
|
monitoredSignals[i].lastPollTime = nowMs;
|
|
uint32 tsMs = (uint32)nowMs;
|
|
void *addr = NULL_PTR(void *);
|
|
if (monitoredSignals[i].dataSource->GetSignalMemoryBuffer(
|
|
monitoredSignals[i].signalIdx, 0u, addr) &&
|
|
addr != NULL_PTR(void *)) {
|
|
uint32 sz = monitoredSignals[i].size;
|
|
if (sz > 1024u) sz = 1024u;
|
|
uint8 lb[1024]; memcpy(lb, addr, sz);
|
|
TypeDescriptor td = monitoredSignals[i].dataSource->GetSignalType(
|
|
monitoredSignals[i].signalIdx);
|
|
float64 val = WDS_ToFloat64(lb, td);
|
|
StreamString sigName;
|
|
for (uint32 j = 0u; j < aliases.Size(); j++) {
|
|
if (signals[aliases[j].signalIndex]->internalID ==
|
|
monitoredSignals[i].internalID) {
|
|
sigName = aliases[j].name;
|
|
break;
|
|
}
|
|
}
|
|
if (sigName.Size() == 0u) sigName = monitoredSignals[i].path;
|
|
// Safely convert float64 value to string via TypeConvert
|
|
char8 valBuf[64] = { '\0' };
|
|
{
|
|
AnyType vdst(CharString, 0u, valBuf); vdst.SetNumberOfElements(0u, 63u);
|
|
AnyType vsrc(Float64Bit, 0u, &val);
|
|
(void)TypeConvert(vdst, vsrc);
|
|
}
|
|
StreamString ev;
|
|
ev.Printf("{\"type\":\"monitor\",\"name\":\"%s\",\"ts\":%u,\"value\":%s}",
|
|
sigName.Buffer(), tsMs, valBuf);
|
|
mutex.FastUnLock();
|
|
BroadcastSse("message", ev);
|
|
mutex.FastLock();
|
|
}
|
|
}
|
|
}
|
|
mutex.FastUnLock();
|
|
|
|
// -----------------------------------------------------------------
|
|
// Drain ring buffer → SSE trace events (capped to limit CPU usage)
|
|
// -----------------------------------------------------------------
|
|
static const uint32 SAMPLE_BUF_SIZE = 1024u;
|
|
static const uint32 MAX_TRACE_CYCLE = 50u; // max samples per Streamer cycle
|
|
uint32 id, size;
|
|
uint64 sampleTs;
|
|
uint8 sampleData[SAMPLE_BUF_SIZE];
|
|
bool hasData = false;
|
|
uint32 traceCount = 0u;
|
|
|
|
while (traceCount < MAX_TRACE_CYCLE &&
|
|
traceBuffer.Pop(id, sampleTs, sampleData, size, SAMPLE_BUF_SIZE)) {
|
|
hasData = true;
|
|
traceCount++;
|
|
if (size > SAMPLE_BUF_SIZE) continue;
|
|
|
|
StreamString sigName;
|
|
TypeDescriptor sigType = UnsignedInteger32Bit;
|
|
mutex.FastLock();
|
|
for (uint32 i = 0u; i < signals.Size(); i++) {
|
|
if (signals[i]->internalID == id) {
|
|
sigName = signals[i]->name;
|
|
sigType = signals[i]->type;
|
|
break;
|
|
}
|
|
}
|
|
mutex.FastUnLock();
|
|
|
|
// Convert hardware-ns timestamp to relative ms (uint32)
|
|
uint32 tsMs = (sampleTs >= streamerT0Ns)
|
|
? (uint32)((sampleTs - streamerT0Ns) / 1000000u) : 0u;
|
|
|
|
float64 val = WDS_ToFloat64(sampleData, sigType);
|
|
// Safely convert float64 value to string via TypeConvert
|
|
char8 valBuf[64] = { '\0' };
|
|
{
|
|
AnyType vdst(CharString, 0u, valBuf); vdst.SetNumberOfElements(0u, 63u);
|
|
AnyType vsrc(Float64Bit, 0u, &val);
|
|
(void)TypeConvert(vdst, vsrc);
|
|
}
|
|
StreamString ev;
|
|
ev.Printf("{\"type\":\"trace\",\"name\":\"%s\",\"ts\":%u,\"value\":%s}",
|
|
sigName.Buffer(), tsMs, valBuf);
|
|
BroadcastSse("message", ev);
|
|
}
|
|
|
|
// -----------------------------------------------------------------
|
|
// Periodic status heartbeat (every 500 ms)
|
|
// -----------------------------------------------------------------
|
|
static uint64 lastStatusMs = 0u;
|
|
if (nowMs - lastStatusMs >= 500u) {
|
|
lastStatusMs = nowMs;
|
|
mutex.FastLock();
|
|
bool paused = isPaused;
|
|
StreamString gam = pausedAtGam;
|
|
uint32 rem = stepRemaining;
|
|
mutex.FastUnLock();
|
|
StreamString ev;
|
|
ev.Printf("{\"type\":\"status\",\"paused\":%s,\"gam\":\"%s\",\"remaining\":%u}",
|
|
paused ? "true" : "false", gam.Buffer(), rem);
|
|
BroadcastSse("message", ev);
|
|
}
|
|
|
|
// Clean up dead SSE clients
|
|
RemoveDeadSseClients();
|
|
|
|
// Always yield to prevent the Streamer from monopolising CPU.
|
|
// The RT ring buffer at 1000 Hz is drained in ~10 cycles (50 samples/cycle).
|
|
(void)hasData;
|
|
Sleep::MSec(10u);
|
|
return ErrorManagement::NoError;
|
|
}
|
|
|
|
} // namespace MARTe
|