131 lines
5.5 KiB
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;
|
|
}
|