vt100/src/console.c
pos_of machine ask bell parse_number parse_int format_site verified all_verified valid send_meos commit clear_prescription execute accept cursor_up rub_out allowed type arrow console_input put put_right put_field treat_word civil_from_days build emit move_cursor refresh poll_machine console_init console_power_cycle console_tick console_wanted_text
1/* The Therac-25 treatment console program (see console.h).2 *3 * Sources: N. G. Leveson and C. S. Turner, "An Investigation of the Therac-25 Accidents",4 * IEEE Computer 26(7), July 1993 - text in "double quotes" is quoted from it. Where the paper5 * does not say, the choice is marked ASSUMPTION and listed in vt100/README.md. */6#include "console.h"78#include <math.h>9#include <stdio.h>10#include <stdlib.h>11#include <string.h>1213/* ---- screen layout ----14 * "Figure A shows the screen layout." The figure is typeset in a proportional font, so the15 * columns below are scaled from it onto the VT100's 80 columns (ASSUMPTION); the labels are16 * padded so their colons line up, as in the monospaced version of the figure. The status block17 * is on the bottom three lines: "The command line at the lower right corner of the screen is the18 * cursor's normal position". */19#define ROW_NAME 120#define ROW_MODE 221#define ROW_HEADER 422#define ROW_RATE 523#define ROW_SITE 9 /* the six treatment-site rows */24#define ROW_DATE 2125#define ROW_CLOCK 2226#define ROW_OPR 232728#define COL_LABEL 229#define COL_VALUE 1830#define COL_BEAM_LABEL 3131#define COL_BEAM 4232#define COL_ENERGY_LABEL 4533#define COL_RIGHT 6934#define COL_DOSE_LABEL 1235#define COL_ACTUAL_HEADER 3136#define COL_PRESCRIBED_HEADER 4437#define END_ACTUAL 37 /* ACTUAL values end just before this column */38#define END_PRESCRIBED 54 /* and PRESCRIBED ones before this one */39#define COL_STATUS_LABEL 2540#define COL_STATUS 3341#define STATUS_WIDTH 1842#define COL_OPMODE 5243#define COL_BEAM_WORD 5844#define COL_COMMAND 614546static const char *const site_labels[CONSOLE_SITE_ROWS] = {47 "GANTRY ROTATION (DEG)", "COLLIMATOR ROTATION (DEG)", "COLLIMATOR X (CM)",48 "COLLIMATOR Y (CM)", "WEDGE NUMBER", "ACCESSORY NUMBER"};4950/* The room set-up in Figure A. ASSUMPTION: the patient is already set up when the page opens. */51static const double room_setup[CONSOLE_SITE_ROWS] = {0.0, 359.2, 14.2, 27.2, 1, 0};5253/* The patient's treatment plan: unit rate and time from Figure A, the 202 monitor units from54 * the Tyler narrative. ASSUMPTION: RETURN on an empty dose field copies it, as it copies the55 * room settings, so "a quick series of carriage returns would thus complete data entry". */56static const char *const dose_plan[3] = {"200", "202", "1.00"};5758typedef struct {59 int row, col, width;60 bool right; /* right-aligned in [col, col + width) */61} field_pos;6263static field_pos pos_of(int f) {64 switch (f) {65 case F_NAME: return (field_pos){ROW_NAME, COL_VALUE, 20, false};66 case F_MODE: return (field_pos){ROW_MODE, COL_VALUE, 3, false};67 case F_BEAM: return (field_pos){ROW_MODE, COL_BEAM, 1, false};68 case F_ENERGY: return (field_pos){ROW_MODE, COL_RIGHT, 2, true};69 case F_COMMAND: return (field_pos){ROW_OPR, COL_COMMAND, 8, false};70 default:71 if (f <= F_TIME) return (field_pos){ROW_RATE + (f - F_RATE), END_PRESCRIBED - 6, 6, true};72 return (field_pos){ROW_SITE + (f - F_GANTRY), END_PRESCRIBED - 6, 6, true};73 }74}7576static bool is_site(int f) { return f >= F_GANTRY && f <= F_ACCESSORY; }7778/* ---- talking to the machine ---- */7980static void machine(console *c, ExtCallType call, int beam, int collimator, int value) {81 wrap_external_call(c->machine, call, (BeamType)beam, (CollimatorPosition)collimator, value);82}8384static void ask(console *c, StateInfoRequest r, char *out, size_t n) {85 char *s = request_state_info(c->machine, r);86 if (s) {87 snprintf(out, n, "%s", s);88 free_state_info(s);89 } else {90 out[0] = 0;91 }92}9394static void bell(console *c) {95 static const uint8_t bel = 0x07;96 c->out(c->out_ctx, &bel, 1);97}9899/* ---- field values ---- */100101static bool parse_number(const char *s, double *v) {102 if (!*s) return false;103 char *end;104 *v = strtod(s, &end);105 return *end == 0;106}107108static bool parse_int(const char *s, int *v) {109 if (!*s) return false;110 for (const char *p = s; *p; p++)111 if (*p < '0' || *p > '9') return false;112 *v = atoi(s);113 return true;114}115116static void format_site(int i, double v, char *out, size_t n, bool prescribed) {117 if (i >= 4) snprintf(out, n, "%d", (int)lround(v));118 else if (prescribed && i < 2) snprintf(out, n, "%d", (int)floor(v)); /* "359.2" -> "359" */119 else snprintf(out, n, "%.1f", v);120}121122/* "The system then compares the manually set values with those entered at the console. If123 * they match, a 'verified' message is displayed and treatment is permitted." Figure A verifies124 * 14.3 against 14.2 and 359 against 359.2, so there is some tolerance (ASSUMPTION: 1 degree,125 * 0.5 cm, exact for wedge and accessory numbers). */126static bool verified(const console *c, int i) {127 double v;128 if (!parse_number(c->committed[F_GANTRY + i], &v)) return false;129 double d = fabs(v - c->actual[i]);130 if (i < 2) {131 d = fmod(d, 360.0);132 if (d > 180.0) d = 360.0 - d;133 return d <= 1.0;134 }135 if (i < 4) return d <= 0.5 + 1e-9;136 return d < 1e-9;137}138139static bool all_verified(const console *c) {140 for (int i = 0; i < CONSOLE_SITE_ROWS; i++)141 if (!verified(c, i)) return false;142 return true;143}144145/* checks the field's text; normalises it and returns true if it can be accepted */146static bool valid(console *c, int f) {147 char *t = c->text[f];148 int n;149 double v;150 switch (f) {151 case F_NAME: return t[0] != 0;152 case F_MODE: return strcmp(t, "FIX") == 0; /* the only mode in Figure A */153 case F_BEAM: return strcmp(t, "X") == 0 || strcmp(t, "E") == 0;154 case F_ENERGY:155 if (!parse_int(t, &n)) return false;156 /* "the energy defaults to 25 MeV" in photon mode; electrons "from 5 to 25 MeV" */157 if (c->beam == 'X' ? n != 25 : (n < 5 || n > 25)) return false;158 snprintf(t, 24, "%d", n);159 return true;160 case F_RATE:161 if (!parse_int(t, &n) || n < 1 || n > 999) return false;162 snprintf(t, 24, "%d", n);163 return true;164 case F_MU:165 if (!parse_int(t, &n) || n < 1 || n > 9999) return false;166 snprintf(t, 24, "%d", n);167 return true;168 case F_TIME:169 if (!parse_number(t, &v) || v <= 0 || v >= 100) return false;170 snprintf(t, 24, "%.2f", v);171 return true;172 default:173 if (!is_site(f) || !parse_number(t, &v) || v < 0) return false;174 if (f - F_GANTRY < 2 && v >= 360) return false;175 if (f - F_GANTRY >= 4 && (v != floor(v) || v > 99)) return false;176 return true;177 }178}179180/* "The keyboard handler parses the mode and energy level specified by the operator and places181 * an encoded result in another shared variable, the 2-byte mode/energy offset (MEOS) variable."182 * The turntable request is left to follow the mode. */183static void send_meos(console *c) {184 if (!c->beam || !c->energy) return;185 machine(c, ExtCallSendMEOS, c->beam == 'X' ? BeamTypeXRay : BeamTypeElectron,186 CollimatorPositionUndefined, c->energy * 1000);187}188189static void commit(console *c, int f) {190 strcpy(c->committed[f], c->text[f]);191 if (f == F_BEAM) {192 c->beam = c->text[f][0];193 if (c->beam == 'X') {194 /* "except when the operator selects the photon mode, in which case the energy defaults195 * to 25 MeV" */196 strcpy(c->text[F_ENERGY], "25");197 strcpy(c->committed[F_ENERGY], "25");198 c->energy = 25;199 } else if (c->energy && (c->energy < 5 || c->energy > 25)) {200 c->energy = 0;201 }202 send_meos(c);203 } else if (f == F_ENERGY) {204 c->energy = atoi(c->text[f]);205 send_meos(c);206 } else if (f == F_MU) {207 machine(c, ExtCallPrescribeDose, 0, 0, atoi(c->text[f]));208 }209}210211static void clear_prescription(console *c) {212 for (int f = F_BEAM; f <= F_COMMAND; f++) c->text[f][0] = c->committed[f][0] = 0;213 c->beam = 0;214 c->energy = 0;215 c->cmd[0] = 0;216 c->field = F_NAME;217 c->fresh = true;218}219220/* ---- the command line ---- */221222static bool phase_is(const console *c, const char *p) { return strcmp(c->phase, p) == 0; }223224static void execute(console *c) {225 const char *cmd = c->text[F_COMMAND];226 if (!cmd[0]) return;227 if (strcmp(cmd, "B") == 0) {228 /* "She hit the one-key command 'B' (for 'beam on')" after "beam ready" */229 if (phase_is(c, "TP_SetupDone") && all_verified(c)) machine(c, ExtCallBeamOn, 0, 0, 0);230 else bell(c);231 } else if (strcmp(cmd, "P") == 0) {232 /* "the operator could press the 'P' key to 'proceed' and resume treatment" */233 if (phase_is(c, "TP_PauseTreatment")) machine(c, ExtCallProceed, 0, 0, 0);234 else bell(c);235 } else if (strcmp(cmd, "R") == 0) {236 /* "an 'R' reset command must be used and the whole prescription reentered" */237 machine(c, ExtCallReset, 0, 0, 0);238 clear_prescription(c);239 return;240 } else if (strcmp(cmd, "SET") == 0) {241 /* "presses the set button on the hand control or types 'set' at the console" */242 machine(c, ExtCallSet, 0, 0, 0);243 } else {244 bell(c);245 }246 c->text[F_COMMAND][0] = 0;247}248249/* ---- keyboard handler ---- */250251/* RETURN - and, the FDA noted, "the normal edit keys (down arrow, right arrow, or line feed)252 * will be interpreted as a CR". On the command line that means they execute what is typed. */253static void accept(console *c) {254 int f = c->field;255 if (f == F_COMMAND) {256 execute(c);257 return;258 }259 if (!c->text[f][0] && is_site(f)) {260 /* "operators could use a carriage return to merely copy the treatment site data" */261 int i = f - F_GANTRY;262 format_site(i, c->actual[i], c->text[f], sizeof c->text[f], true);263 } else if (!c->text[f][0] && f >= F_RATE && f <= F_TIME) {264 strcpy(c->text[f], dose_plan[f - F_RATE]);265 }266 if (!valid(c, f)) {267 bell(c);268 c->fresh = false;269 return;270 }271 commit(c, f);272 c->field++;273 c->fresh = true;274 if (c->field == F_COMMAND) {275 c->text[F_COMMAND][0] = 0;276 /* "the data-entry completion variable only indicates that the cursor has been down to the277 * command line" */278 machine(c, ExtCallDataEntryComplete, 0, 0, 0);279 }280}281282/* "the key used for moving the cursor back through the prescription sequence (i.e., cursor 'UP'283 * inscribed with an upward pointing arrow)". Anything typed in the field and not accepted with284 * RETURN is dropped (ASSUMPTION). */285static void cursor_up(console *c) {286 if (c->field == F_NAME) {287 bell(c);288 return;289 }290 if (c->field == F_COMMAND) {291 c->text[F_COMMAND][0] = 0;292 /* "Prescription editing is signified by cursor movement off the command line." */293 machine(c, ExtCallEditingTakingPlace, 0, 0, 0);294 } else {295 strcpy(c->text[c->field], c->committed[c->field]);296 }297 c->field--;298 c->fresh = true;299}300301/* BACKSPACE, DELETE and left arrow: "One must use either the backspace or left arrow key to302 * edit." */303static void rub_out(console *c) {304 char *t = c->text[c->field];305 size_t n = strlen(t);306 c->fresh = false;307 if (n == 0) {308 bell(c);309 return;310 }311 t[n - 1] = 0;312}313314static bool allowed(int f, char ch) {315 switch (f) {316 case F_NAME:317 case F_COMMAND: return ch >= ' ' && ch <= '~';318 case F_MODE: return ch >= 'A' && ch <= 'Z';319 case F_BEAM: return ch == 'X' || ch == 'E';320 case F_ENERGY:321 case F_RATE:322 case F_MU:323 case F_WEDGE:324 case F_ACCESSORY: return ch >= '0' && ch <= '9';325 default: return (ch >= '0' && ch <= '9') || ch == '.';326 }327}328329static void type(console *c, char ch) {330 if (ch >= 'a' && ch <= 'z') ch = (char)(ch - 'a' + 'A');331 int f = c->field;332 if (!allowed(f, ch)) {333 bell(c);334 return;335 }336 char *t = c->text[f];337 if (c->fresh || pos_of(f).width == 1) t[0] = 0;338 c->fresh = false;339 size_t n = strlen(t);340 if ((int)n >= pos_of(f).width) {341 bell(c);342 return;343 }344 t[n] = ch;345 t[n + 1] = 0;346}347348static void arrow(console *c, uint8_t final) {349 switch (final) {350 case 'A': cursor_up(c); break;351 case 'B':352 case 'C': accept(c); break;353 case 'D': rub_out(c); break;354 default: break;355 }356}357358static void refresh(console *c);359360void console_input(console *c, uint8_t b) {361 b &= 0x7F;362 /* cursor keys: ESC [ x (ANSI), ESC O x (cursor key mode), ESC x (VT52 mode) */363 if (c->esc_state == 1) {364 if (b == '[' || b == 'O') {365 c->esc_state = 2;366 return;367 }368 c->esc_state = 0;369 if (b >= 'A' && b <= 'D') arrow(c, b);370 refresh(c);371 return;372 }373 if (c->esc_state == 2) {374 if ((b >= '0' && b <= '9') || b == ';') return;375 c->esc_state = 0;376 arrow(c, b);377 refresh(c);378 return;379 }380 switch (b) {381 case 0x1B: c->esc_state = 1; return;382 case 0x0D:383 case 0x0A: accept(c); break;384 case 0x08:385 case 0x7F: rub_out(c); break;386 default:387 if (b >= ' ' && b <= '~') type(c, (char)b);388 else if (b == 0x09) bell(c);389 break;390 }391 refresh(c);392}393394/* ---- treatment console screen processor ---- */395396static void put(char w[24][80], int row, int col, const char *s) {397 for (; *s && col < 80; s++, col++)398 if (col >= 0) w[row][col] = *s;399}400401static void put_right(char w[24][80], int row, int end, const char *s) {402 put(w, row, end - (int)strlen(s), s);403}404405static void put_field(char w[24][80], int row, int col, int width, const char *s) {406 char buf[32];407 snprintf(buf, sizeof buf, "%.*s", width, s);408 put(w, row, col, buf);409}410411/* What the TREAT line says for each Tphase. Only "TREAT PAUSE" is in the paper (Figure A); the412 * others are the Figure 2 subroutine names (ASSUMPTION). */413static const char *treat_word(const console *c) {414 if (phase_is(c, "TP_Reset")) return "RESET";415 if (phase_is(c, "TP_Datent")) return "DATA ENTRY";416 if (phase_is(c, "TP_SetupTest")) return "SET-UP TEST";417 if (phase_is(c, "TP_SetupDone")) return "SET-UP DONE";418 if (phase_is(c, "TP_PatientTreatment")) return "TREAT ON";419 if (phase_is(c, "TP_PauseTreatment")) return "TREAT PAUSE";420 if (phase_is(c, "TP_TerminateTreatment"))421 return strcmp(c->outcome, "TREATMENT OK") == 0 ? "TERMINATE" : "TREAT SUSPEND";422 return "";423}424425static void civil_from_days(long long z, int *y, int *m, int *d) {426 z += 719468;427 long long era = (z >= 0 ? z : z - 146096) / 146097;428 long long doe = z - era * 146097;429 long long yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;430 long long doy = doe - (365 * yoe + yoe / 4 - yoe / 100);431 long long mp = (5 * doy + 2) / 153;432 *d = (int)(doy - (153 * mp + 2) / 5 + 1);433 *m = (int)(mp < 10 ? mp + 3 : mp - 9);434 *y = (int)(yoe + era * 400 + (*m <= 2));435}436437static void build(console *c, char w[24][80]) {438 char buf[64];439 memset(w, ' ', 24 * 80);440441 put(w, ROW_NAME, COL_LABEL, "PATIENT NAME : ");442 put(w, ROW_NAME, COL_RIGHT, "A");443 put(w, ROW_NAME, COL_RIGHT + 6, "1");444 put(w, ROW_MODE, COL_LABEL, "TREATMENT MODE: ");445 put(w, ROW_MODE, COL_BEAM_LABEL, "BEAM TYPE: ");446 put(w, ROW_MODE, COL_ENERGY_LABEL, "ENERGY (KeV):");447448 put(w, ROW_HEADER, COL_ACTUAL_HEADER, "ACTUAL");449 put(w, ROW_HEADER, COL_PRESCRIBED_HEADER, "PRESCRIBED");450 put(w, ROW_RATE, COL_DOSE_LABEL, "UNIT RATE/MINUTE");451 put(w, ROW_RATE + 1, COL_DOSE_LABEL, "MONITOR UNITS");452 put(w, ROW_RATE + 2, COL_DOSE_LABEL, "TIME (MIN)");453454 /* the dose monitor: two channels; the beam never stays on long enough here to show a rate */455 put_right(w, ROW_RATE, END_ACTUAL, "0");456 snprintf(buf, sizeof buf, "%d %d", c->displayed_mu, c->displayed_mu);457 put_right(w, ROW_RATE + 1, END_ACTUAL, buf);458 int rate = atoi(c->committed[F_RATE]);459 snprintf(buf, sizeof buf, "%.2f", rate > 0 ? (double)c->displayed_mu / rate : 0.0);460 put_right(w, ROW_RATE + 2, END_ACTUAL, buf);461462 for (int i = 0; i < CONSOLE_SITE_ROWS; i++) {463 put(w, ROW_SITE + i, COL_LABEL, site_labels[i]);464 format_site(i, c->actual[i], buf, sizeof buf, false);465 put_right(w, ROW_SITE + i, END_ACTUAL, buf);466 if (verified(c, i)) put(w, ROW_SITE + i, COL_RIGHT, "VERIFIED");467 }468469 for (int f = 0; f < F_COUNT; f++) {470 field_pos p = pos_of(f);471 if (p.right) {472 snprintf(buf, sizeof buf, "%.*s", p.width, c->text[f]);473 put_right(w, p.row, p.col + p.width, buf);474 } else {475 put_field(w, p.row, p.col, p.width, c->text[f]);476 }477 }478479 /* status block */480 static const char *const months[] = {"JAN", "FEB", "MAR", "APR", "MAY", "JUN",481 "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};482 long long days = c->clock_s >= 0 ? c->clock_s / 86400 : (c->clock_s - 86399) / 86400;483 long long secs = c->clock_s - days * 86400;484 int y, m, d;485 civil_from_days(days, &y, &m, &d);486 put(w, ROW_DATE, COL_LABEL, "DATE : ");487 snprintf(buf, sizeof buf, "%02d-%s-%02d", y % 100, months[m - 1], d);488 put(w, ROW_DATE, COL_LABEL + 8, buf);489 put(w, ROW_CLOCK, COL_LABEL, "TIME : ");490 /* Figure A: "12:55. 8" */491 snprintf(buf, sizeof buf, "%2d:%02d.%2d", (int)(secs / 3600), (int)(secs / 60 % 60), (int)(secs % 60));492 put(w, ROW_CLOCK, COL_LABEL + 8, buf);493 put(w, ROW_OPR, COL_LABEL, "OPR ID: T25V02-R03");494495 const char *system = "";496 if (c->set_prompt[0]) system = c->set_prompt; /* "Press set button" */497 else if (phase_is(c, "TP_SetupDone") && all_verified(c)) system = "BEAM READY";498 else if (phase_is(c, "TP_PatientTreatment")) system = "BEAM ON";499 put(w, ROW_DATE, COL_STATUS_LABEL, "SYSTEM: ");500 put_field(w, ROW_DATE, COL_STATUS, STATUS_WIDTH, system);501 put(w, ROW_CLOCK, COL_STATUS_LABEL, "TREAT : ");502 put_field(w, ROW_CLOCK, COL_STATUS, STATUS_WIDTH, treat_word(c));503 put(w, ROW_OPR, COL_STATUS_LABEL, "REASON: ");504 put_field(w, ROW_OPR, COL_STATUS, STATUS_WIDTH, c->reason);505506 put(w, ROW_DATE, COL_OPMODE, "OP.MODE: TREAT");507 put(w, ROW_DATE, COL_RIGHT, "AUTO");508 put(w, ROW_CLOCK, COL_BEAM_WORD, c->beam == 'X' ? "X-RAY" : c->beam == 'E' ? "ELECTRON" : "");509 put(w, ROW_CLOCK, COL_RIGHT, "173777");510 put(w, ROW_OPR, COL_OPMODE, "COMMAND: ");511}512513typedef struct {514 uint8_t buf[4096];515 size_t n;516} outbuf;517518static void emit(outbuf *o, const char *s, size_t n) {519 if (o->n + n > sizeof o->buf) return;520 memcpy(o->buf + o->n, s, n);521 o->n += n;522}523524static void move_cursor(console *c, outbuf *o, int row, int col) {525 if (c->cur_row == row && c->cur_col == col) return;526 char buf[16];527 int n;528 if (c->cur_row == row && c->cur_col == col + 1) n = snprintf(buf, sizeof buf, "\b");529 else n = snprintf(buf, sizeof buf, "\033[%d;%dH", row + 1, col + 1);530 emit(o, buf, (size_t)n);531 c->cur_row = row;532 c->cur_col = col;533}534535/* Rewrites only what changed, then puts the cursor back where the operator is typing. */536static void refresh(console *c) {537 char w[24][80];538 outbuf o;539 o.n = 0;540 build(c, w);541 if (c->need_clear) {542 emit(&o, "\033[H\033[2J", 7);543 memset(c->shown, ' ', sizeof c->shown);544 c->cur_row = c->cur_col = 0;545 c->need_clear = false;546 }547 for (int r = 0; r < 24; r++) {548 int col = 0;549 while (col < 80) {550 if (w[r][col] == c->shown[r][col]) {551 col++;552 continue;553 }554 /* a run of changes, swallowing short unchanged gaps */555 int end = col + 1, last = col;556 while (end < 80 && end - last <= 4) {557 if (w[r][end] != c->shown[r][end]) last = end;558 end++;559 }560 move_cursor(c, &o, r, col);561 emit(&o, &w[r][col], (size_t)(last - col + 1));562 memcpy(&c->shown[r][col], &w[r][col], (size_t)(last - col + 1));563 c->cur_col = last + 1;564 col = last + 1;565 }566 }567 /* the cursor waits on the field it has just moved to (the next key replaces it), and follows568 * the text while the operator types */569 field_pos p = pos_of(c->field);570 int len = (int)strlen(c->text[c->field]);571 if (len > p.width) len = p.width;572 int start = p.right ? p.col + p.width - len : p.col;573 move_cursor(c, &o, p.row, c->fresh ? (p.right && len == 0 ? p.col + p.width - 1 : start) : start + len);574 if (o.n) c->out(c->out_ctx, o.buf, o.n);575}576577static void poll_machine(console *c) {578 char buf[32];579 ask(c, RequestTreatmentOutcome, c->outcome, sizeof c->outcome);580 ask(c, RequestTreatmentState, c->phase, sizeof c->phase);581 ask(c, RequestReason, c->reason, sizeof c->reason);582 ask(c, RequestSetButtonPrompt, c->set_prompt, sizeof c->set_prompt);583 ask(c, RequestDisplayedDose, buf, sizeof buf);584 c->displayed_mu = atoi(buf);585}586587void console_init(console *c, HsStablePtr m, console_out_fn out, void *ctx) {588 memset(c, 0, sizeof *c);589 c->machine = m;590 c->out = out;591 c->out_ctx = ctx;592 for (int i = 0; i < CONSOLE_SITE_ROWS; i++) c->actual[i] = room_setup[i];593 /* ASSUMPTION: the treatment mode is filled in, as in Figure A; the operator still types the594 * rest */595 strcpy(c->text[F_MODE], "FIX");596 strcpy(c->committed[F_MODE], "FIX");597 c->field = F_NAME;598 c->fresh = true;599 c->need_clear = true;600 c->cur_row = c->cur_col = -1;601 /* this console has a "B" command, so BEAM READY must wait for it */602 machine(c, ExtCallUseBeamOnKey, 0, 0, 0);603 poll_machine(c);604}605606void console_power_cycle(console *c) {607 machine(c, ExtCallHardReset, 0, 0, 0);608 machine(c, ExtCallUseBeamOnKey, 0, 0, 0);609 c->text[F_NAME][0] = c->committed[F_NAME][0] = 0;610 clear_prescription(c);611 c->esc_state = 0;612 c->need_clear = true;613 poll_machine(c);614}615616void console_tick(console *c, double now_ms, long long local_epoch_s) {617 c->clock_s = local_epoch_s;618 if (now_ms < c->next_pass) return;619 /* "Treatment console screen processor (run periodically)"; the scheduler's period is 0.1 s */620 c->next_pass = now_ms + 100.0;621 poll_machine(c);622 refresh(c);623}624625void console_wanted_text(console *c, char *out) {626 char w[24][80];627 build(c, w);628 char *p = out;629 for (int r = 0; r < 24; r++) {630 memcpy(p, w[r], 80);631 p += 80;632 *p++ = '\n';633 }634 *p = 0;635}