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}