Skip to content

Instantly share code, notes, and snippets.

@BSoDium
Last active April 4, 2022 22:08
Show Gist options
  • Save BSoDium/39aeac95b262654549b3ac997e58362d to your computer and use it in GitHub Desktop.
Save BSoDium/39aeac95b262654549b3ac997e58362d to your computer and use it in GitHub Desktop.
CIWS Phallanx-like weapon software (besiege replica) created thanks to the LogicExtensions mod by Lambda & fanzhuyifan

CIWS controller software

Close-in weapon system "Sentinel"

Defined PIOs

PIO Value
00 5001
01 5000
02 6024
03 E
04 Alpha3
05 Alpha4
06 Alpha1
07 Alpha2
08 Alpha5
09 Alpha6

Internal logic

stateDiagram-v2
  state "Calibrate" as calib
  state "Run auto test" as auto
  state "Scan surroundings" as scan
  state "Engage available targets" as engage
  state if_state <<choice>>
  state "Main loop" as main
  state main {
    [*] --> scan
    scan --> if_state
    if_state --> engage: targets.length > 0
    if_state --> scan: targets.length == 0
    engage --> scan
  }
  [*] --> calib
  calib --> auto
  auto --> main
Loading

Close-in weapon system "Gyro"

Documentation under construction

/**
* CIWS "Gyro" weapon controller software.
*
* @see LogicExtensions Besiege mod by Lambda & fanzhuyifan
* @author BSoDium
*/
/**
* CIWS "Sentinel" weapon controller software.
*
* @see LogicExtensions Besiege mod by Lambda & fanzhuyifan
* @author BSoDium
*/
// system variables
const Verbose = true;
const SysVar = {
"calibrate" : true, // calibrate on startup
"autotest" : false, // autotest on startup
"calibration_delay_x" : 200, // frames - this should vary between computers
"calibration_delay_z" : 20,
"update_delay" : 0.9, // time between mainloop calls
"X_incert" : 0.005, // positionning uncertainty on X axis
"Z_incert" : 0.05, // positionning uncertainty on Z axis
"max_decel_time" : 10
};
const Sources = {
100 : "startup debugger",
101 : "runtime debugger",
102 : "shutdown debugger",
103 : "runtime notifier",
104 : "error stream",
105 : "auto test"
};
const PIOdict = {
"X_anglo" : 0, // PIO in
"Z_anglo" : 1, // PIO in
"entity_sensor" : 2, // PIO in
"weapon" : 3, // PIO in
"X_hinge_l" : 4, // PIO out
"X_hinge_r" : 5, // PIO out
"Z_wheel_fslow" : 6, // PIO out
"Z_wheel_rslow" : 7, // PIO out
"Z_wheel_ffast" : 8, // PIO out
"Z_wheel_rfast" : 9, // PIO out
};
// Active PIO list :
// X anglometer -> 5001
// Z anglometer -> 5000
// entity sensor -> 6024
// fire weapon -> E
// X hinge -> 3 left 4 right
// Z wheel slow -> 1 forwards 2 reverse
// Z wheel fast -> 5 forwards 6 reverse
let AngloDict = {
"X_anglo_l" : 0.24675, // left threshold
"X_anglo_r" : 0.75217, // right threshold
"Z_anglo_start" : 0,
"Z_decel_dist" : 0,
};
let targets = [];
/**
* Send a message along with its associated source/emitter
* basically an upgraded print function
* @param {number} F_SourceId Message emitter Id
* @param {string} F_Message Message content
* @param {boolean} O_Verbose Toggle print, omitting argument always displays message
*/
function ConsoleOutput(F_SourceId, F_Message, O_Verbose = 1) {
if (O_Verbose) {
message = `[${Sources[F_SourceId]}] ${F_Message}`;
print(message);
}
}
function GetVal(F_PioId) {
return in(PIOdict[F_PioId]);
}
function SetVal(F_PioId, F_Value) {
out(PIOdict[F_PioId], F_Value);
}
function SleepFrames(F_Frames = 1) { // if no argument is provided SleepFrames() equals to asleep(), it skips a frame
for (let i = 0; i < F_Frames; i++) {
UpdatePos();
asleep();
}
return;
}
function wait(F_Event, O_Event_args = []) {
let FrameCounter = 0;
while (!(F_Event.apply(null, O_Event_args))) {
// ConsoleOutput(103, F_Event.apply(null, F_Event_args));
UpdatePos();
FrameCounter++;
asleep();
}
return FrameCounter;
}
function UpdatePos() {
LastPos = [GetVal("X_anglo"), GetVal("Z_anglo")];
}
function KeepPressed(F_PioId, O_Frames = 10, O_Action = SleepFrames, O_Action_args) {
SetVal(F_PioId, true); // start pressing
if (O_Action == SleepFrames) {
O_Action(O_Frames); // wait
} else {
O_Action.apply(null, O_Action_args);
}
SetVal(F_PioId, false); // stop pressing
return;
}
function min(F_a, F_b) {
if (F_a > F_b) {
return F_b;
} else {
return F_a;
}
}
function max(F_a, F_b) {
if (F_a > F_b) {
return F_a;
} else {
return F_b;
}
}
function avg(table) {
let sum = 0;
for (let i = 0; i < table.length; i++) {
sum += table[i];
}
sum /= table.length;
return sum;
}
function Calibrate(F_AngloDict) {
// X axis calibration
ConsoleOutput(103, "Calibrating X axis...", Verbose);
KeepPressed("X_hinge_r", SysVar["calibration_delay_x"]);
F_AngloDict["X_anglo_r"] = GetVal("X_anglo");
KeepPressed("X_hinge_l", 2*SysVar["calibration_delay_x"]);
F_AngloDict["X_anglo_l"] = GetVal("X_anglo");
ConsoleOutput(103, "X axis successfully calibrated", Verbose);
// Z axis calibration
ConsoleOutput(103, "Calibrating Z axis...", Verbose);
KeepPressed("Z_wheel_ffast", SysVar["calibration_delay_z"]);
let start = GetVal("Z_anglo");
SleepFrames(SysVar["calibration_delay_z"]);
let end = GetVal("Z_anglo");
if (end >= start) {
let Delta = end - start;
F_AngloDict["Z_decel_dist"] = Delta;
} else {
ConsoleOutput(104, "Deceleration time too long. Failed to calibrate Z axis", Verbose)
return;
}
ConsoleOutput(103, F_AngloDict["Z_decel_dist"]);
ConsoleOutput(103, "Z axis successfully calibrated", Verbose);
SleepFrames(100); // remove when not debugging
return;
}
// events
function ReachedEventRight(F_Angle, F_PioId) {
let Current = GetVal(F_PioId);
return (F_Angle > Current || Current > 0.25);
}
function ReachedEventLeft(F_Angle, F_PioId) {
let Current = GetVal(F_PioId);
return (F_Angle < Current && Current <= 0.25);
}
function ReachedEventForwards(F_Angle, F_PioId) {
let Current = GetVal(F_PioId);
let Delta = Math.abs(F_Angle - Current);
if (Delta < AngloDict["Z_decel_dist"]) {
return true; // stop
}
if (Delta < 0.5) { // the 0|1 threshold isn't in the way
return (F_Angle < Current);
} else if (Delta > 0.5) { // 0|1 threshold not crossed yet
return (F_Angle > Current);
} else {
return true; // Delta = 0.5
}
}
function ReachedEventBackwards(F_Angle, F_PioId) {
let Current = GetVal(F_PioId);
let Delta = Math.abs(F_Angle - Current);
if (Delta < AngloDict["Z_decel_dist"]) {
return true;
}
if (Delta < 0.5) { // cf ReachedEventBackwards
return (F_Angle > GetVal(F_PioId));
} else if (Delta > 0.5) {
return (F_Angle < GetVal(F_PioId));
} else {
return true;
}
}
function ReachedFullRotation(F_PioId) {
let Current = GetVal(F_PioId);
let Sensor = GetVal("entity_sensor");
if (Sensor) {
targets = targets.concat([[GetVal("X_anglo"), GetVal("Z_anglo")]]);
}
return Current > (1 - SysVar["Z_incert"]);
}
// actions
/**
*
* @param {number} F_Angle Target angle
*/
function GoToPosX(F_Angle) {
let target_left = (0 <= F_Angle && F_Angle <= (AngloDict["X_anglo_l"]) + SysVar["X_incert"]); // target valid
let hinge_left = (0 <= GetVal("X_anglo") && GetVal("X_anglo") <= (AngloDict["X_anglo_l"]) + SysVar["X_incert"]); // hinge position normal
let hinge_left_of_target = (GetVal("X_anglo") > F_Angle); // hinge position relative to target
if (target_left) { // target angle on the left side (valid)
if (hinge_left_of_target && hinge_left) { // go right
KeepPressed("X_hinge_r", null, wait, [ReachedEventRight, [F_Angle, "X_anglo"]]);
} else if (!hinge_left_of_target && hinge_left) {
KeepPressed("X_hinge_l", null, wait, [ReachedEventLeft, [F_Angle, "X_anglo"]]);
} else if (!hinge_left) {
KeepPressed("X_hinge_l", null, wait, [ReachedEventLeft, [0, "X_anglo"]]);
ConsoleOutput(101, "Hinge out of working area, resetting", Verbose);
}
} else { // target angle on the right side (invalid)
ConsoleOutput(104, "Hinge target angle in dead zone", Verbose);
}
}
/**
*
* @param {number} F_Angle Target angle
* @param {string} O_Path Path to target ("shortest" | "longest"), not implemented yet
*/
function GoToPosZ(F_Angle, O_Path = "shortest") {
let Current = GetVal("Z_anglo"); // Current angle
//let Delta = F_Angle - Current;
let Delta0 = max(F_Angle, Current) - min(F_Angle, Current);
let Delta1 = 1 - max(F_Angle, Current) + min(F_Angle, Current);
// choose speed
if (min(Delta0, Delta1) <= 2*AngloDict["Z_decel_dist"]) {
var speedstr = "slow";
} else {
var speedstr = "fast";
}
if (max(F_Angle, Current) == F_Angle) {
if (Delta0 < 0.5) {
KeepPressed(`Z_wheel_f${speedstr}`, null, wait, [ReachedEventForwards, [F_Angle, "Z_anglo"]]);
} else if (Delta1 < 0.5) {
KeepPressed(`Z_wheel_r${speedstr}`, null, wait, [ReachedEventBackwards, [F_Angle, "Z_anglo"]]);
} else if (Delta0 == 0.5) { // no shortest path, choose forwards
KeepPressed(`Z_wheel_f${speedstr}`, null, wait, [ReachedEventForwards, [F_Angle, "Z_anglo"]]);
}
} else {
if (Delta0 < 0.5) {
KeepPressed(`Z_wheel_r${speedstr}`, null, wait, [ReachedEventBackwards, [F_Angle, "Z_anglo"]]);
} else if (Delta1 < 0.5) {
KeepPressed(`Z_wheel_f${speedstr}`, null, wait, [ReachedEventForwards, [F_Angle, "Z_anglo"]]);
} else if (Delta0 == 0.5) { // no shortest path, choose forwards
KeepPressed(`Z_wheel_r${speedstr}`, null, wait, [ReachedEventBackwards, [F_Angle, "Z_anglo"]]);
}
}
}
/**
* Set the angle of the Hinge and the wheel to F_AngleX and F_AngleZ respectively
*
* @param {number} F_AngleX Target angle - X axis
* @param {number} F_AngleZ Target angle - Z axis
* @param {number} O_Loops Number of GoToPosX and GoToPosZ calls in the loop (a high value means a higher precision, and obviously a slower process)
*/
function setPos(F_AngleX, F_AngleZ, O_Loops = 1) {
for (let i = 0; i < O_Loops; i++) {
GoToPosX(F_AngleX);
GoToPosZ(F_AngleZ);
}
}
function AutoTest() {
let positions = [
[0.125, 0.5, 1], // x, z, loop_count
[0, 0, 1],
[0.125, 0.5, 1]
]
let error = Array(positions.length).fill(0);
for (let i = 0; i < positions.length; i++) {
setPos.apply(null, positions[i]);
SleepFrames(150);
ConsoleOutput(105, `${GetVal("X_anglo")} ${GetVal("Z_anglo")} final`, Verbose);
let X_current = GetVal("X_anglo"), Z_current = GetVal("Z_anglo");
let errorX = min(Math.abs(X_current - positions[i][0]), Math.abs(1 - max(X_current, positions[i][0]) + min(X_current, positions[i][0])));
let errorZ = min(Math.abs(Z_current - positions[i][1]), Math.abs(1 - max(Z_current, positions[i][1]) + min(Z_current, positions[i][1])));
error[i] = 360*(errorX + errorZ)/2;
}
ConsoleOutput(103, `avg positionning error : ${avg(error)} °`);
}
function scan(F_PassCount) {
targets = [];
let res = 0.25/F_PassCount;
setPos(0,0);
for (let i = 0; i < F_PassCount; i++) {
// do a full rotation
GoToPosX(res*i);
KeepPressed("Z_wheel_ffast", null, wait, [ReachedFullRotation, ["Z_anglo"]]);
SleepFrames(10); // allow the wheel to rotate enough for the ReachedFullRotation Event to turn off
}
}
function fire() {
SetVal("weapon", true);
SleepFrames(1);
SetVal("weapon", false);
}
function engage(F_Targets) {
for (let i = 0; i < F_Targets.length; i++) {
let target = F_Targets[i];
setPos.apply(null, target);
while (GetVal("entity_sensor")) {
fire();
print("firing");
}
print(`target ${i+1} of ${F_Targets.length} lost`)
}
}
// main loop
function MainLoop() {
scan(10);
if (targets.length > 0) {
ConsoleOutput(103, `${targets.length} targets located, engaging`, Verbose);
engage(targets);
}
// program next execution of MainLoop
setTimeout(SysVar["update_delay"], MainLoop);
}
// program start
print('\n');
ConsoleOutput(100, "booting up...", Verbose);
SysVar["calibrate"] && Calibrate(AngloDict);
SysVar["autotest"] && AutoTest();
MainLoop();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment