ATTACH: A Remote Operating Facility for NadaNet
Michael J. Mahon - November 29, 2004
Introduction
ATTACH is an Applesoft program that runs on the master machine. Its purpose is to remotely operate any serving machine on the network. It loads a small assembly language program, ATTACH.SLAVE, which redirects the CSW and KSW hooks of the attached machine to Message Server queues for the display and keyboard of the attached machine, allowing the master to display its outputs and provide it with keyboard input.
The ATTACH Program
The Applesoft BASIC program, ATTACH, is presented here, with liberal commentary:
100 REM ATTACH slave for remote operation 110 REM MJM - 11/14/04 120 :
This program presumes that NadaNet, with the AmperNada extensions, has already been installed below the OS, HIMEM has been adjusted, and an initializing call has been made. The following GOSUB merely verifies that NadaNet is installed and sets up some pointer variables for application use.
200 GOSUB 60000: REM Set up NadaNet Defs 210 PFX$ = "/ap/merlin/work/nadanet/":
The next lines set up some definitions for ATTACH. P$() is an array whose Ith element contains the last known prompt string for machine I. If this is null, it flags that machine I is not serving, and therefore cannot be ATTACHed.
The other definitions are for Apple II addresses, two for the keyboard and its strobe, and one for the Open-Apple key, which is used as a modifier key for commands to the ATTACH program.
Finally, a short machine language program is POKEd into $300 to provide a fast way to write out a line of characters.
220 DIM P$(15): REM Last prompt string 230 KBD = 49152: REM Kbd port 240 KST = 49168: REM Kbd strobe 250 PB0 = 12 * 4096 + 6 * 16 + 1: REM $C061 260 : 270 REM POKE routine to Write Line to COUT 280 WL = 3 * 256: REM $300 290 FOR I = 0 TO 13: READ X: POKE WL + I,X: NEXT 300 DATA 160,0,185,0,00,32,237,253,200,192,00,144,245,96 310 POKE WL + 4,BUF / 256: REM fix address 320 REM ldy #0; lda BUF,y; jsr COUT; iny; cpy #len; bcc $302; rts 330 :
Next, we take a "census" of all machines from 2 up to MX to determine which ones are serving and, if so, what type of machine they are. If machine #2 is already running the Message Server we note that.
We then boot all unbooted AppleCrate machines with the NADA.CRATE code and re-take the census.
340 GOSUB 61000: REM Take census 350 MSRV = PEEK (ITBL + 2) = 3: REM Save pre-boot Message Server status 360 : 370 REM Boot unbooted slaves 380 PRINT CHR$ (4)"bload "PFX$"nada.crate,A"BUF 390 PSRT = PEEK (BUF + 2) * 256: REM Prog start 400 PLNG = PEEK (48840) + 256 * PEEK (48841): REM Prog length 410 & BOOTCODE(PSRT,PLNG,BUF): REM Set bootcode info 420 & SERVE#(100): REM for BOOT 430 PRINT "Boot of slaves completed." 440 : 450 GOSUB 61000: REM Re-take census
Since machine #2 is required as the message server, we require that it now be serving. Then, if it is not already running the Message Server code, we &POKE that code and &CALL it. (Note that I have a convention that any assembly language program to be &POKEd has its run-time load page as its third byte. This is consistent with the program starting with a JMP to a routine within its first page.)
460 IF NOT PEEK (ITBL + 2) THEN PRINT "No Msg Server (2)": STOP 470 : 480 REM Load Message Server (if not running) 490 IF MSRV GOTO 1000 500 PRINT CHR$ (4)"bload "PFX$"nada.mserve,A"BUF 510 PSRT = PEEK (BUF + 2) * 256: REM Prog start 520 PLNG = PEEK (48840) + 256 * PEEK (48841): REM Prog length 530 REM Poke NADA.MSERVE code 540 & POKE (2,PSRT,PLNG,BUF): REM POKE Message Server 550 & CALL (): REM Run Message Server 560 PRINT "Message Server now running on 2" 570 MSRV = 1: POKE ITBL + 2,3: REM Mark Msg Server 580 :
Now we load the ATTACH.SLAVE program into memory for later use.
Then P$() is initialized to contain an "unknown" prompt (
1000 REM Load ATTACH.SLAVE program
1010 PRINT CHR$ (4)"bload "PFX$"attach.slave,A"BUF + 256
1020 PSRT = PEEK (BUF + 258) * 256: REM Prog start
1030 PLNG = PEEK (48840) + 256 * PEEK (48841): REM Prog Length
1040 :
1050 FOR D = 3 TO MX: REM Initialize prompt if not serving
1060 IF NOT PEEK (ITBL + D) THEN P$(D) = MID$ ("123456789ABCDEF",D,1) + ":?"
1070 NEXT
1080 :
Now we prime the program by asking for the ID of the first machine to be attached. The ID is required to be 3 or more, since we cannot attach the master machine (#1) or the Message Server (#2). It is also possible to quit the program from this prompt.
1090 INPUT "Attach to which machine? ";M$ 1100 IF M$ = "q" OR M$ = "Q" THEN END 1110 D = VAL (M$): IF D < 3 GOTO 1090 1120 OUTQ = 48: REM Output queue ($30) 1130 :
When we arrive here, D has been set to the machine to be attached. We compute the input queue number for machine D and check whether it is serving by checking the ID table entry and resetting the prompt string array, P$(), if it is. If a machine is not serving, we assume that it is already attached and is looping in ATTACH.SLAVE's "keyboard input" routine.
If a machine is serving, then we (re-)load the ATTACH.SLAVE code and give it control, then print out our best guess at a prompt for the machine.
1140 REM Attach to machine D 1150 INQ = OUTQ + D: REM Input queue ($3#) 1160 IF PEEK (ITBL + D) THEN P$(D) = "": REM Now serving 1170 IF P$(D) < > "" GOTO 1270: REM Skip load if prompt and not serving 1180 : 1190 REM Poke ATTACH.SLAVE code 1200 POKE BUF + 259,INQ: POKE BUF + 261,OUTQ: REM ATTACH.SLAVE queues 1210 & POKE #(D,PSRT,PLNG,BUF + 256) 1220 IF PEEK (1) GOTO 1270: REM Not serving, maybe attached.. 1230 & CALL (): REM Run slave program 1240 POKE ITBL + D,0: REM Mark not serving 1250 PRINT "ATTACH.SLAVE loaded on "D 1260 : 1270 PRINT P$(D);: REM Initial prompt 1280 :
This it the main loop of ATTACH, polling the local keyboard for input or commands and polling the output queue for any pending output from attached machines.
2000 REM Remote operating loop 2010 : 2020 IF PEEK (KBD) < 128 GOTO 2260: REM No key 2030 L = 0 2040 : 2050 K = PEEK (KBD): IF K < 128 GOTO 2050: REM Get char 2060 I = PEEK (KST): REM Clear strobe 2070 IF L > 0 OR PEEK (PB0) < 128 GOTO 2190 2080 :
Commands are signalled by a single keypress with Open-Apple depressed, so we skip command processing if it's not the first character on a line or if Open-Apple is not depressed.
Commands for ATTACH are:
|
Keypress |
Action |
|
Q or q |
Quit |
|
? or / |
Take census |
|
"0" to "F" |
Attach to machine 0 to F |
When an Open-Apple hex digit is used to switch to another machine (attaching it if not already attached), the most recent prompt for the current machine is updated in P$() for future use.
2090 REM Handle initial input char = Open-Apple keypress 2100 IF K = 209 OR K = 241 THEN END : REM OA-Q or OA-q = Quit 2110 REM OA-? or OA-/ = Census 2120 IF K = 191 OR K = 175 THEN GOSUB 3000:I = D: GOSUB 61000:D = I: GOTO 1150 2130 REM Open-Apple= Switch machines 2140 IF 176 < K AND K < 186 THEN GOSUB 3000:D = K - 176: GOTO 1150 2150 IF 224 < K THEN K = K - 32: REM Convert a-f to A-F 2160 IF 192 < K AND K < 200 THEN GOSUB 3000:D = K - 183: GOTO 1150 2170 :
Here we handle non-command input characters. We ignore the left arrow character, since it disables 80-column firmware, and echo all other characters and add them to the input buffer.
If the buffer is full, we send the partial input line. If the character is a carriage return we send the input line.
2180 REM Handle normal characters 2190 IF K = 149 GOTO 2050: REM Skip left arrow (NAK) 2200 IF K < > 141 THEN PRINT CHR$ (K - 128);: REM Echo if not CR 2210 POKE BUF + L,K:L = L + 1: REM Buffer char 2220 IF L > 115 THEN GOSUB 2500: REM Limit msg length 2230 IF K < > 141 GOTO 2050: REM Loop until CR 2240 GOSUB 2500: REM Send input line 2250 :
After sending input, or if there is no input line started, we check the output queue (merged for all attached machines) to see if there is any pending output to be displayed. If there is, we print it, otherwise, we loop back to check for input.
2260 REM Check for pending output 2270 & GET MSG#(2,OUTQ,LL,BUF) 2280 IF PEEK (1) GOTO 2020: REM No output 2290 POKE WL + 10,LL: CALL WL: REM Print output 2300 GOTO 2020: REM Loop... 2310 :
Following are two subroutines used by the main loop. The only "tricky" code is in saving the last prompt, where we do not save it if it does not appear to be a prompt--that is, if it is longer than three characters or a lone carriage return.
2500 REM Send input line 2510 & PUTMSG#(2,INQ,L,BUF) 2520 IF PEEK (1) GOTO 2510: REM Keep trying... 2530 L = 0:LL = 0 2540 RETURN 2550 : 3000 REM Save last prompt string 3010 IF LL > 3 GOTO 3070: REM Keep previous 3020 P$(D) = "" 3030 FOR J = 0 TO LL - 1 3040 P$(D) = P$(D) + CHR$ ( PEEK (BUF + J) - 128) 3050 NEXT 3060 IF P$(D) = CHR$ (13) THEN P$(D) = "": REM Lone CR means detached 3070 PRINT 3080 RETURN 3090 :
This is the standard "suffix" for NadaNet applications, added by EXECing the text file NADADEFS. The subroutine at 60000 sets up a few named pointers. The subroutine at 61000 performs a "census" of serving machines on the net and saves the results in a table at ITBL. (Note that because the census loop is probing machines that may not be serving (or even exist), it temporarily sets the &TIMEOUT value down to 3 * 60ms. so that unresponsive machines do not stall the &PEEK for very long. The default timeout is restored when the census is completed.)
60000 REM NadaNet Definitions for Applesoft 60010 REM MJM - 11/08/04 60020 : 60030 NP = 3 * 256 + 12 * 16 + 15: REM NADANET page ($3CF) 60040 IF PEEK (NP - 2) < > 76 THEN PRINT "NadaNet not loaded.": STOP 60050 IF PEEK (NP) < > 145 THEN PRINT "Not ProDOS version of NadaNet.": STOP 60060 : 60070 BUF = 4 * 4096: REM $4000 60080 MX = 10: REM Max machine ID 60090 BSLF = NP - 3: REM Boot ID in $3CC 60100 & IDTBL(ITBL): REM ID table 60120 RETURN 60130 : 61000 REM Take census of serving machines 61010 & TIMEOUT(3) 61020 FOR D = 2 TO MX: REM PEEK to verify service 61030 & PEEK #(D,NP,4,BUF): REM PEEK config bytes 61040 IF PEEK (1) THEN TY$ = "not":TY = 0: GOTO 61100 61050 TY$ = "(unknown)":TY = 16 61060 IF PEEK (BUF) = 184 THEN TY$ = "(Crate)":TY = 2 61070 IF PEEK (BUF) = 8 THEN TY$ = "(Mserve)":TY = 3 61080 IF PEEK (BUF) = 145 THEN TY$ = "(ProDOS)":TY = 4 61090 IF PEEK (BUF) = 141 THEN TY$ = "(DOS)":TY = 3 61100 POKE ITBL + D,TY 61110 PRINT "Machine "D" "TY$" serving." 61120 NEXT D 61130 & TIMEOUT(): REM Reset retrys 61140 PRINT "Census completed." 61150 RETURN