1// 2// Copyright (c) 2026 Freddy Vandriessche. 3// All rights reserved. 4// 5// EMU_DEVICE_serialpro_line.js 6// 7// External byte-stream line owned by the Applied Engineering Serial Pro card. 8// The card/firmware/6551 remain the SPC peripheral; this device models the 9// external serial connection presented by the UART. 10// 11 12function SerialProLine() 13{ 14 var line = this; 15 var host = null; 16 var listeners = []; 17 18 this.id = { 19 "DCODE":"SPSERIAL" 20 ,"hostPCODE":"SPC" 21 ,"icon":"fa fa-exchange-alt" 22 ,"description":"Serial Pro external serial line" 23 }; 24 25 this.ports = { 26 "serial":{ 27 "direction":"duplex" 28 ,"mime":["application/octet-stream"] 29 ,"handler":"receive" 30 ,"description":"Serial Pro external 8-bit serial byte stream" 31 } 32 }; 33 34 function normalizeBytes(data) 35 { 36 if(data instanceof Uint8Array) 37 return data; 38 39 if(data instanceof ArrayBuffer) 40 return new Uint8Array(data); 41 42 if(ArrayBuffer.isView(data)) 43 return new Uint8Array( 44 data.buffer, 45 data.byteOffset, 46 data.byteLength 47 ); 48 49 if(Array.isArray(data)) 50 { 51 var bytes = new Uint8Array(data.length); 52 for(var i=0;i<data.length;i++) 53 bytes[i] = Number(data[i]) & 0xFF; 54 return bytes; 55 } 56 57 if(Number.isInteger(Number(data))) 58 return new Uint8Array([Number(data) & 0xFF]); 59 60 return null; 61 } 62 63 this.bindHost = function(card) 64 { 65 host = card || null; 66 67 if(host && typeof(host.bindSerialLineDevice)=="function") 68 host.bindSerialLineDevice(line); 69 70 return !!host; 71 }; 72 73 /* 74 * Bytes entering the public duplex port are remote -> Serial Pro traffic. 75 * The owning SPC card is responsible for applying 6551 receive timing, 76 * word length, RDRF and IRQ behavior. 77 */ 78 this.receive = function(message,context) 79 { 80 var bytes = normalizeBytes( 81 message && message.data!==undefined 82 ? message.data 83 : message 84 ); 85 86 if(!bytes || !host || 87 typeof(host.serialLineReceiveBytes)!="function") 88 return false; 89 90 return host.serialLineReceiveBytes( 91 bytes, 92 { 93 "source":"pipe" 94 ,"context":context || null 95 } 96 )==bytes.length; 97 }; 98 99 this.receiveBytes = function(data,meta) 100 { 101 var bytes = normalizeBytes(data); 102 103 if(!bytes || !host || 104 typeof(host.serialLineReceiveBytes)!="function") 105 return 0; 106 107 return host.serialLineReceiveBytes(bytes,meta || {}); 108 }; 109 110 /* 111 * ACIA -> external-line traffic is streaming/event-driven. Until the 112 * generic pipe layer grows persistent pipeConnect()/pipeDisconnect(), 113 * subscribers are the adapter boundary used by oTERM and Web Serial. 114 */ 115 this.subscribe = function(callback) 116 { 117 if(typeof(callback)!="function") 118 return function(){}; 119 120 if(listeners.indexOf(callback)<0) 121 listeners.push(callback); 122 123 var subscribed = true; 124 125 return function() 126 { 127 if(!subscribed) return; 128 subscribed = false; 129 130 var index = listeners.indexOf(callback); 131 if(index>=0) listeners.splice(index,1); 132 }; 133 }; 134 135 this.transmitBytes = function(data,meta) 136 { 137 var bytes = normalizeBytes(data); 138 if(!bytes) return false; 139 140 var snapshot = listeners.slice(); 141 142 for(var i=0;i<snapshot.length;i++) 143 { 144 try 145 { 146 snapshot[i](bytes,meta || {}); 147 } 148 catch(error) 149 { 150 console.error("SPSERIAL serial subscriber failed",error); 151 } 152 } 153 154 return bytes.length; 155 }; 156 157 this.reset = function() 158 { 159 // The external cable/line remains present across Apple II RESET. 160 return true; 161 }; 162}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.