Files
linuxcncWebWASM/wasm-core/src/sim_protocol.c

131 lines
5.5 KiB
C

#include "sim_protocol.h"
#include <stddef.h>
#include <stdint.h>
#include <string.h>
typedef struct {
uint32_t state;
uint32_t last_seq;
uint32_t tick;
} protocol_state;
static protocol_state state;
uint32_t sim_protocol_checkpoint_size(void) { return sizeof(state); }
int32_t sim_protocol_checkpoint_export(uint8_t *output, uint32_t capacity) {
if (output == NULL || capacity != sizeof(state)) return SIM_PROTOCOL_ERR_ARGUMENT;
memcpy(output, &state, sizeof(state)); return SIM_PROTOCOL_OK;
}
int32_t sim_protocol_checkpoint_restore(const uint8_t *input, uint32_t length) {
if (input == NULL || length != sizeof(state)) return SIM_PROTOCOL_ERR_ARGUMENT;
const protocol_state *saved = (const protocol_state *)input;
if (saved->state > SIM_PROTOCOL_DESTROYED) return SIM_PROTOCOL_ERR_ARGUMENT;
memcpy(&state, saved, sizeof(state)); return SIM_PROTOCOL_OK;
}
static int has_text(const char *value) {
return value != NULL && value[0] != '\0' && strlen(value) <= 128;
}
static int is_kind(const char *kind, const char *expected) {
return kind != NULL && strcmp(kind, expected) == 0;
}
static int is_side_effect(const char *kind) {
static const char *const commands[] = {
"SESSION_CREATE", "LOAD_PROJECT", "START", "STOP", "RESET",
"LOAD_WORKSPACE", "SIM_STEP", "COMMAND",
"PROGRAM_LOAD", "PROGRAM_RUN", "PROGRAM_PAUSE", "PROGRAM_RESUME",
"PROGRAM_ABORT", "PROGRAM_STEP", "PROGRAM_SEEK", "MDI_EXECUTE",
"JOG_START", "JOG_STOP", "HOME", "SET_MODE", "SET_OVERRIDE", "SET_JOG_SCALE", "SET_MAX_VELOCITY", "SET_SPINDLE_OVERRIDE",
"SPINDLE_COMMAND", "SPINDLE_BRAKE", "SET_SPINDLE_OVERRIDE", "COOLANT_COMMAND", "TOOL_PREPARE", "TOOL_CHANGE",
"PROBE_CONFIG", "HAL_COMMAND", "HAL_SET", "PLC_START", "PLC_STOP",
"PROJECT_SAVE", "IMPORT", "EXPORT", "CHECKPOINT", "RECOVER",
"SUBSCRIBE", "UNSUBSCRIBE", "CANCEL", NULL,
};
size_t index;
if (!has_text(kind)) return 0;
for (index = 0; commands[index] != NULL; ++index) {
if (is_kind(kind, commands[index])) return 1;
}
return 0;
}
static int state_accepts_kind(uint32_t current, const char *kind) {
if (current == SIM_PROTOCOL_NEW) return is_kind(kind, "HELLO");
if (current == SIM_PROTOCOL_HELLO) return is_kind(kind, "SESSION_ATTACH") || is_kind(kind, "CAPABILITIES");
if (current == SIM_PROTOCOL_ATTACHED) return is_kind(kind, "SESSION_READY");
/* Task pause does not disconnect the command channel. The task gate
decides whether a valid frame may execute, and records refusals at
that sequence so Resume/Abort cannot be stranded behind a gap. */
if (current == SIM_PROTOCOL_READY || current == SIM_PROTOCOL_PAUSED) return 1;
return is_kind(kind, "RESET");
}
void sim_protocol_reset(void) {
memset(&state, 0, sizeof(state));
}
int32_t sim_protocol_accept(uint32_t schema_version, uint32_t seq,
const char *kind, const char *operation_id,
uint32_t simulation_tick) {
if (schema_version != 1U) return SIM_PROTOCOL_ERR_SCHEMA;
if (!has_text(kind)) return SIM_PROTOCOL_ERR_KIND;
if (state.state == SIM_PROTOCOL_DESTROYED && !is_kind(kind, "RESET")) return SIM_PROTOCOL_ERR_STATE;
if (seq == 0U || seq != state.last_seq + 1U) return SIM_PROTOCOL_ERR_SEQUENCE;
if (simulation_tick < state.tick) return SIM_PROTOCOL_ERR_TICK;
if (!state_accepts_kind(state.state, kind)) return SIM_PROTOCOL_ERR_STATE;
if (is_side_effect(kind) && !has_text(operation_id)) return SIM_PROTOCOL_ERR_OPERATION_ID;
state.last_seq = seq;
state.tick = simulation_tick;
if (is_kind(kind, "HELLO")) state.state = SIM_PROTOCOL_HELLO;
/* SESSION_READY is the worker response; the next client command is valid immediately. */
else if (is_kind(kind, "SESSION_ATTACH")) state.state = SIM_PROTOCOL_READY;
else if (is_kind(kind, "SESSION_READY")) state.state = SIM_PROTOCOL_READY;
else if (is_kind(kind, "PROGRAM_PAUSE")) state.state = SIM_PROTOCOL_PAUSED;
else if (is_kind(kind, "PROGRAM_RESUME") || is_kind(kind, "RESUME")) state.state = SIM_PROTOCOL_READY;
else if (is_kind(kind, "PROGRAM_ABORT") || is_kind(kind, "ESTOP") || is_kind(kind, "POWER")) state.state = SIM_PROTOCOL_READY;
else if (is_kind(kind, "DESTROY")) state.state = SIM_PROTOCOL_DESTROYED;
else if (is_kind(kind, "RESET")) {
/* RESET is a machine/task reset once attached. Keep the transport
sequence alive so a Web client can continue issuing commands. A
RESET received after DESTROY remains the protocol reinitialization
edge used by the session lifecycle. */
if (state.state == SIM_PROTOCOL_DESTROYED) sim_protocol_reset();
else state.state = SIM_PROTOCOL_READY;
}
return SIM_PROTOCOL_OK;
}
int32_t sim_protocol_pause(void) {
if (state.state != SIM_PROTOCOL_READY) return SIM_PROTOCOL_ERR_STATE;
state.state = SIM_PROTOCOL_PAUSED;
return SIM_PROTOCOL_OK;
}
int32_t sim_protocol_resume(void) {
if (state.state != SIM_PROTOCOL_PAUSED) return SIM_PROTOCOL_ERR_STATE;
state.state = SIM_PROTOCOL_READY;
return SIM_PROTOCOL_OK;
}
int32_t sim_protocol_state_value(void) {
return (int32_t)state.state;
}
uint32_t sim_protocol_last_seq(void) {
return state.last_seq;
}
uint32_t sim_protocol_tick(void) {
return state.tick;
}
int32_t sim_protocol_set_tick(uint32_t simulation_tick) {
if (simulation_tick < state.tick) return SIM_PROTOCOL_ERR_TICK;
state.tick = simulation_tick;
return SIM_PROTOCOL_OK;
}