diff --git a/Kernal64/src/ucesoft/cbm/CBMComputer.scala b/Kernal64/src/ucesoft/cbm/CBMComputer.scala index 6f40fa3..b6c4f6c 100644 --- a/Kernal64/src/ucesoft/cbm/CBMComputer.scala +++ b/Kernal64/src/ucesoft/cbm/CBMComputer.scala @@ -244,7 +244,9 @@ trait CBMComputer extends CBMComponent with GamePlayer { cbmComputer => } // ------------------------------------ Drag and Drop ---------------------------- protected val DNDHandler = new DNDHandler(handleDND(_,true,true)) - // ------------------- INITIALIZATION ------------------------ + // ----------------------WiC64 --------------------------------------------------- + protected var wic64Panel : WiC64Panel = _ + // ------------------- INITIALIZATION -------------------------------------------- initComputer protected def initComputer : Unit = { @@ -1366,6 +1368,8 @@ trait CBMComputer extends CBMComponent with GamePlayer { cbmComputer => if (vicChip.VISIBLE_SCREEN_WIDTH == dim.width && vicChip.VISIBLE_SCREEN_HEIGHT == dim.height) vicZoomFactor = 1 else if (vicChip.VISIBLE_SCREEN_WIDTH * 2 == dim.width && vicChip.VISIBLE_SCREEN_HEIGHT * 2 == dim.height) vicZoomFactor = 2 + else + if (vicChip.VISIBLE_SCREEN_WIDTH * 3 == dim.width && vicChip.VISIBLE_SCREEN_HEIGHT * 3 == dim.height) vicZoomFactor = 3 else vicZoomFactor = 0 // undefined } @@ -1469,7 +1473,7 @@ trait CBMComputer extends CBMComponent with GamePlayer { cbmComputer => finally loadStateFromOptions = false } } - preferences.add(PREF_SCREENDIM,"Zoom factor. Valued accepted are 1 and 2",0,Set(1,2),false) { dim => + preferences.add(PREF_SCREENDIM,"Zoom factor. Valued accepted are 1,2,3",0,Set(1,2,3),false) { dim => vicZoom(dim) zoomOverride = true } @@ -2261,6 +2265,14 @@ trait CBMComputer extends CBMComponent with GamePlayer { cbmComputer => }) :: resetSettingsActions } + protected def setWiC64Settings(parent:JMenu): Unit = { + val wic64Item = new JMenuItem("WiC64 panel ...") + parent.add(wic64Item) + wic64Item.addActionListener(_ => { + wic64Panel.dialog.setVisible(true) + }) + } + protected def setFlyerSettings(parent:JMenu) : Unit = { // FLYED-ENABLED ======================================================================================= val flyerItem = new JMenu("Flyer internet modem") diff --git a/Kernal64/src/ucesoft/cbm/c128/C128.scala b/Kernal64/src/ucesoft/cbm/c128/C128.scala index eaecb13..4be0dab 100644 --- a/Kernal64/src/ucesoft/cbm/c128/C128.scala +++ b/Kernal64/src/ucesoft/cbm/c128/C128.scala @@ -144,6 +144,9 @@ class C128 extends CBMComputer with MMUChangeListener { cia2CP1, cia2CP2, nmiSwitcher.setLine(Switcher.CIA,_),idle => cia12Running(1) = !idle) + WiC64.flag2Action = cia2.setFlagLow _ + wic64Panel = new WiC64Panel(displayFrame) + WiC64.setListener(wic64Panel) rs232.setCIA12(cia1,cia2) ParallelCable.ca2Callback = cia2.setFlagLow _ add(ParallelCable) @@ -599,12 +602,13 @@ class C128 extends CBMComputer with MMUChangeListener { val groupZ = new ButtonGroup vicAdjMenu.add(zoomItem) setVICModel(vicAdjMenu) - for(z <- 1 to 2) { + for(z <- 1 to 3) { val zoom1Item = new JRadioButtonMenuItem(s"Zoom x $z") zoom1Item.addActionListener(_ => vicZoom(z) ) val kea = z match { case 1 => java.awt.event.KeyEvent.VK_1 case 2 => java.awt.event.KeyEvent.VK_2 + case 3 => java.awt.event.KeyEvent.VK_3 } zoom1Item.setAccelerator(KeyStroke.getKeyStroke(kea,java.awt.event.InputEvent.ALT_DOWN_MASK)) zoomItem.add(zoom1Item) @@ -695,6 +699,8 @@ class C128 extends CBMComputer with MMUChangeListener { setFlyerSettings(IOItem) + setWiC64Settings(IOItem) + setREUSettings(IOItem) setGEORamSettings(IOItem) diff --git a/Kernal64/src/ucesoft/cbm/c64/C64.scala b/Kernal64/src/ucesoft/cbm/c64/C64.scala index 368ed19..d561a63 100644 --- a/Kernal64/src/ucesoft/cbm/c64/C64.scala +++ b/Kernal64/src/ucesoft/cbm/c64/C64.scala @@ -96,6 +96,10 @@ class C64 extends CBMComputer { cia2CP2, nmiSwitcher.setLine(Switcher.CIA,_), idle => cia12Running(1) = !idle) + WiC64.flag2Action = cia2.setFlagLow _ + wic64Panel = new WiC64Panel(displayFrame) + WiC64.setListener(wic64Panel) + add(WiC64) rs232.setCIA12(cia1,cia2) ParallelCable.ca2Callback = cia2.setFlagLow _ add(ParallelCable) @@ -249,12 +253,13 @@ class C64 extends CBMComputer { val zoomItem = new JMenu("Zoom") val groupZ = new ButtonGroup optionMenu.add(zoomItem) - for(z <- 1 to 2) { + for(z <- 1 to 3) { val zoom1Item = new JRadioButtonMenuItem(s"Zoom x $z") zoom1Item.addActionListener(_ => vicZoom(z) ) val kea = z match { case 1 => java.awt.event.KeyEvent.VK_1 case 2 => java.awt.event.KeyEvent.VK_2 + case 3 => java.awt.event.KeyEvent.VK_3 } zoom1Item.setAccelerator(KeyStroke.getKeyStroke(kea,java.awt.event.InputEvent.ALT_DOWN_MASK)) zoomItem.add(zoom1Item) @@ -343,6 +348,8 @@ class C64 extends CBMComputer { setFlyerSettings(IOItem) + setWiC64Settings(IOItem) + setREUSettings(IOItem) setGEORamSettings(IOItem) diff --git a/Kernal64/src/ucesoft/cbm/expansion/WiC64.scala b/Kernal64/src/ucesoft/cbm/expansion/WiC64.scala new file mode 100644 index 0000000..5d40ec0 --- /dev/null +++ b/Kernal64/src/ucesoft/cbm/expansion/WiC64.scala @@ -0,0 +1,663 @@ +package ucesoft.cbm.expansion + +import org.apache.commons.net.telnet.TelnetClient +import ucesoft.cbm.{CBMComponent, CBMComponentType, Log} + +import java.io.{IOException, InputStream, ObjectInputStream, ObjectOutputStream} +import java.net._ +import java.text.SimpleDateFormat +import java.util.Properties +import java.util.concurrent.LinkedBlockingDeque + +object WiC64 extends CBMComponent with Runnable { + override val componentID: String = "WiC64" + override val componentType: CBMComponentType.Type = CBMComponentType.USER_PORT + + trait WiC64Listener { + def onCMD(cmdDescr:String): Unit + def turnGreenLed(on:Boolean): Unit + def log(info:String): Unit + def newFirmwareAvaiilable(): Unit + } + + var flag2Action : () => Unit = _ + + private var _enabled = false + + final val RECEIVING_MODE = 4 + final val SENDING_MODE = 0 + + private var networkif : NetworkInterface = _ + private var MAC_ADDRESS = getMacAddress() + + private final val WIC64_REAL_FIRMWARE_VERSION = 30 + private final val FIRMWARE_VERSION = "WIC64FWV:K1.0.2" + final val SSID = "KERNALSID" + private final val DEFAULT_SERVER = "http://www.wic64.de/prg/" + + private final val RECEIVING_MODE_WAITING_W = 0 + private final val RECEIVING_MODE_WAITING_LEN_LO = 1 + private final val RECEIVING_MODE_WAITING_LEN_HI = 2 + private final val RECEIVING_MODE_WAITING_CMD = 3 + private final val RECEIVING_MODE_WAITING_DATA = 4 + private final val RECEIVING_MODE_WAITING_LAST = 5 + + private final val SENDING_MODE_WAITING_DUMMY = 0 + private final val SENDING_MODE_LO = 1 + private final val SENDING_MODE_HI = 2 + private final val SENDING_MODE_WAITING_END = 3 + private final val SENDING_MODE_STREAMING = 4 + + private var mode = SENDING_MODE + private var sendMode = SENDING_MODE_WAITING_DUMMY + private var recMode = RECEIVING_MODE_WAITING_W + private var bufferLen = 0 + private var adjustBufferLenForPRG = 0 + private var recCmd = 0 + private var buffer : Array[Int] = _ + private var bufferIndex = 0 + + private final val TOKEN_NAME = "sectokenname" + private var token = "" + private val prefMap = new Properties() + + private var telnetClient : TelnetClient = _ + private val dateFormatter = new SimpleDateFormat("HH:mm:ss dd/MM/yyyy") + + // UDP + private var udpPort = 8080 + private var udp : DatagramSocket = _ + private var udpThread : Thread = _ + private var udpThreadRunning = false + private case class UDPPacket(from:Array[Byte],data:Array[Byte]) + private val udpReceivedQueue = new LinkedBlockingDeque[UDPPacket](10) + + private var server = DEFAULT_SERVER + + private var listener : WiC64Listener = _ + private var totalCmdIssued = 0 + + private var logEnabled = false + + private var streamingIn : InputStream = _ + + private final val CMD_DESCR = Map( + 0x0 -> "get FW version", + 0x01 -> "loading http", + 0x02 -> "config wifi", + 0x03 -> "FW update 1", + 0x04 -> "FW update 2", + 0x05 -> "FW update 3", + 0x06 -> "get ip", + 0x07 -> "get stats", + 0x08 -> "set server", + 0x09 -> "REM", + 0x0A -> "get upd", + 0x0B -> "send udp", + 0x0C -> "scanning wlan", + 0x0D -> "config wifi id", + 0x0E -> "change udp port", + 0x0F -> "loading httpchat", + 0x10 -> "get ssid", + 0x11 -> "get rssi", + 0x12 -> "get server", + 0x13 -> "get external ip", + 0x14 -> "get mac", + 0x15 -> "get time and date", + 0x16 -> "set timezone", + 0x17 -> "get timezone", + 0x18 -> "check update", + 0x19 -> "read prefs", + 0x1A -> "save prefs", + 0x1E -> "get tcp", + 0x1F -> "send tcp", + 0x20 -> "set tcp port", + 0x21 -> "connect tcp1", + 0x22 -> "get tcp1", + 0x23 -> "send tcp1", + 0x25 -> "streaming", // ? + 0x63 -> "factory reset" + ) + + override def getProperties: Properties = { + val properties = super.getProperties + properties.setProperty("Enabled",_enabled.toString) + properties.setProperty("Command counts:",totalCmdIssued.toString) + properties.setProperty("Local IP",getIPAddress()) + properties.setProperty("MAC",getMacAddress()) + properties.setProperty("Mode",mode.toString) + properties.setProperty("Read mode",recMode.toString) + properties.setProperty("Write mode",sendMode.toString) + properties + } + + def enabled : Boolean = _enabled + def enabled_=(on:Boolean): Unit = { + _enabled = on + if (!on) reset + else { + // check firmware version + sendHttp("http://sk.sx-64.de/wic64/version.txt") + if (buffer != null && buffer.length == 4) { + val version = buffer.map(_.toChar).mkString.substring(2).toInt + if (version > WIC64_REAL_FIRMWARE_VERSION && listener != null) listener.newFirmwareAvaiilable() + } + } + } + + def setLogEnabled(enabled:Boolean): Unit = logEnabled = enabled + + def getTotalCmdIssued: Int = totalCmdIssued + + def setListener(l:WiC64Listener): Unit = listener = l + + def setNetworkInterface(ni:NetworkInterface): Unit = { + networkif = ni + MAC_ADDRESS = getMacAddress() + } + + def reset: Unit = {} + + def resetWiC64(): Unit = { + if (telnetClient != null && telnetClient.isConnected) { + try { + telnetClient.disconnect() + telnetClient = null + } + catch { + case _:Exception => + } + } + + totalCmdIssued = 0 + token = "" + setMode(SENDING_MODE) + recMode = RECEIVING_MODE_WAITING_W + sendMode = SENDING_MODE_WAITING_DUMMY + server = DEFAULT_SERVER + + udpPort = 8080 + if (udpThreadRunning) { + udpThreadRunning = false + udpThread.interrupt() + } + + if (listener != null) listener.onCMD(CMD_DESCR(0x63)) + log("Resetting ...") + } + + def setMode(mode:Int): Unit = { + this.mode = mode + //println(s"Mode: $mode") + recMode = RECEIVING_MODE_WAITING_W + sendMode = SENDING_MODE_WAITING_DUMMY + if (listener != null) listener.turnGreenLed(mode == RECEIVING_MODE) + } + + def write(byte:Int): Unit = { + flag2Action() + if (mode != RECEIVING_MODE) { + //println("NOT IN RECEIVING MODE ...") + return + } + + //println(s"WRITE: ${byte.toHexString} '${byte.toChar}' recMode = $recMode bufferIndex=$bufferIndex len=$bufferLen") + + recMode match { + case RECEIVING_MODE_WAITING_W if byte == 0x57 => + recMode = RECEIVING_MODE_WAITING_LEN_LO + case RECEIVING_MODE_WAITING_LEN_LO => + bufferLen = byte + recMode = RECEIVING_MODE_WAITING_LEN_HI + case RECEIVING_MODE_WAITING_LEN_HI => + bufferLen = (bufferLen | byte << 8) - 4 + buffer = Array.ofDim[Int](bufferLen) + bufferIndex = 0 + recMode = RECEIVING_MODE_WAITING_CMD + case RECEIVING_MODE_WAITING_CMD => + recCmd = byte + if (bufferLen == 0) { + executeCmd() + } + else recMode = RECEIVING_MODE_WAITING_DATA + case RECEIVING_MODE_WAITING_DATA => + buffer(bufferIndex) = byte + bufferIndex += 1 + if (bufferIndex == bufferLen) executeCmd() //recMode = RECEIVING_MODE_WAITING_LAST + case RECEIVING_MODE_WAITING_LAST => + executeCmd() + case _ => + } + } + + @inline private def log(s:String): Unit = if (logEnabled && listener != null) listener.log(s) + + private def sendHttp(target:String,streaming:Boolean = false): Unit = { + log(s"Opening connection: $target") + val url = new URL(target) + val connection = url.openConnection().asInstanceOf[HttpURLConnection] + connection.setRequestMethod("GET") + connection.setInstanceFollowRedirects(true) + connection.setRequestProperty("User-Agent","ESP32HTTPClient") + try { + connection.connect() + val rcode = connection.getResponseCode + log(s"HTTP code=$rcode") + if (rcode != 200 && rcode != 201) { + log("Returning error code") + buffer = "!0".toArray.map(_.toInt) + } + else { + val in = connection.getInputStream + if (streaming) { + streamingIn = in + return + } + buffer = in.readAllBytes().map(_.toInt & 0xFF) + if (target.endsWith(".prg")) { + adjustBufferLenForPRG = 2 + log("Adjusting PRG size ...") + } + log(s"HTTP [${buffer.length}]:\n${buffer.map(_.toChar).mkString}\n${buffer.map(_.toHexString).mkString(" ")}") + if (rcode == 201) { + // search var name and value + var retCode = "0" + buffer.map(_.toChar).mkString.split("\\u0001") match { + case Array(_, name, value, rtCode) => + prefMap.setProperty(name, value) + log(s"PREF $name = $value") + retCode = rtCode + + if (prefMap.getProperty(TOKEN_NAME, "") == name) { + token = value + log(s"TOKEN = $token") + } + case _ => + } + buffer = retCode.toArray.map(_.toInt) + } + in.close() + } + sendMode = SENDING_MODE_WAITING_DUMMY + } + catch { + case io:IOException => + log(s"Http I/O error: $io") + } + } + + private def encodeURL(urlString:String): String = { + val url = new URL(urlString) + val queryPos = urlString.lastIndexOf("?") + if (queryPos == -1) return urlString + + val pars = url.getQuery.split("&") + val parameters = for (p <- pars) yield { + p.split("=") match { + case Array(n, v) => + s"$n=${URLEncoder.encode(v, "UTF-8")}" + case Array(_) => p + } + + } + urlString.substring(0, queryPos) + "?" + parameters.mkString("&") + } + + private def en_code(s:String): String = { + val sb = new StringBuilder + var i = 0 + while (i < s.length) { + if (s.charAt(i) == '<' && i + 1 < s.length && s.charAt(i + 1) == '$') { + val len = s.charAt(i + 2).toInt + (s.charAt(i + 3).toInt << 8) + for (j <- 0 until len) { + val hex = s.charAt(i + j + 4).toInt + if (hex <= 0xF) { + sb += '0' + sb.append(hex.toHexString) + } + else { + sb.append(hex.toHexString) + } + } + i += 4 + len + } + else { + sb += s.charAt(i) + i += 1 + } + } + sb.toString() + } + + private def resolve(urlString:String): String = { + var url = urlString + if (url.charAt(0) == '!') url = server + urlString.substring(1) + url.replaceAll("%mac",MAC_ADDRESS.replaceAll(":","") + token).replaceAll("%ser",server) + } + + private def getMacAddress(): String = { + import scala.jdk.CollectionConverters._ + val ni = if (networkif == null) NetworkInterface.getNetworkInterfaces.asScala.find(_.getHardwareAddress != null).map(_.getHardwareAddress) + else Option(networkif.getHardwareAddress) + + ni match { + case Some(addr) => addr.map(_.toInt & 0xFF).map(_.toHexString).mkString(":") + case None => "N/A" + } + } + + def getIPAddress(): String = { + import scala.jdk.CollectionConverters._ + if (networkif == null) InetAddress.getLocalHost.getHostAddress + else { + networkif.getInetAddresses.asScala.toList.headOption match { + case None => "0.0.0.0" + case Some(n) => n.getHostAddress + } + } + } + + private def executeCmd(): Unit = { + totalCmdIssued += 1 + + // TO BE CHECKED + if (streamingIn != null) { + streamingIn.close() + streamingIn = null + } + + adjustBufferLenForPRG = 0 + recMode = RECEIVING_MODE_WAITING_W + log(s"CMD=[${recCmd.toHexString} ${CMD_DESCR.getOrElse(recCmd,"???")}] BUFFER RECEIVED: ${if (bufferLen > 0) buffer.map(_.toHexString).mkString(" ") else "EMPTY"}") + log(s"> ${buffer.map(_.toChar).mkString}") + recCmd match { + case 0 => // GET FIRMWARE VERSION + buffer = FIRMWARE_VERSION.toArray.map(_.toInt) + sendMode = SENDING_MODE_WAITING_DUMMY + case 1 => // LOAD HTTP / HTTPS + sendHttp(encodeURL(resolve(buffer.map(_.toChar).mkString))) + case 2 => // CONFIG WIFI + log(s"CONFIG WIFI: ${buffer.map(_.toChar).mkString}") + buffer = "Wlan config not changed".toArray.map(_.toInt) + sendMode = SENDING_MODE_WAITING_DUMMY + case 3|4|5 => // FIRMWARE UPDATE STANDARD/DEVELOPER/ESP32 DEVELOPER + buffer = "OK".toArray.map(_.toInt) + sendMode = SENDING_MODE_WAITING_DUMMY + case 6 => // GET IP ADDRESS OF WIC64 + buffer = getIPAddress().toArray.map(_.toInt) + sendMode = SENDING_MODE_WAITING_DUMMY + case 7 => // GET FIRMWARE VERSION STATUS + buffer = s"${ucesoft.cbm.Version.BUILD_DATE} ${ucesoft.cbm.Version.VERSION}".toArray.map(_.toInt) + sendMode = SENDING_MODE_WAITING_DUMMY + case 8 => // SET DEFAULT SERVER + server = String.valueOf(buffer.map(_.toChar)) + prefMap.setProperty("server",server) + log(s"SERVER = $server") + case 9 => // REM COMMAND FOR DEVELOPER CONSOLE + log(s"WiC64 Serial: ${buffer.map(_.toChar).mkString}") + case 0xA => // GET UDP PACKAGE + receiveUDP() + case 0xB => // SEND UDP PACKAGE + if (buffer.length > 4) { + val ip = s"${buffer(0)}.${buffer(1)}.${buffer(2)}.${buffer(3)}" + val data = buffer.drop(4) + log(s"UDP SEND to $ip data = ${data.map(_.toHexString).mkString(" ")}") + val ipAddress = Array(buffer(0).toByte,buffer(1).toByte,buffer(2).toByte,buffer(3).toByte) + sendUDP(ipAddress,udpPort,data) + } + case 0xC => // WLAN SCAN SSID + val scan = s"0\u0001${SSID}\u0001255\u0001" + buffer = scan.toArray.map(_.toInt) + sendMode = SENDING_MODE_WAITING_DUMMY + case 0xD => // CONFIG WLAN SCAN SSID + log(s"CONFIG WIFI (SCAN): ${buffer.map(_.toChar).mkString}") + buffer = "Wlan config not changed".toArray.map(_.toInt) + sendMode = SENDING_MODE_WAITING_DUMMY + case 0xE => // CHANGE UDP PORT + if (buffer.length == 2) { + udpPort = buffer(0) | buffer(1) << 8 + if (udp != null) udp.close() + try { + checkUDP() + log(s"UDP PORT set to $udpPort") + } + catch { + case e:Exception => + log(s"Error while creating UDP: $e") + udp = null + } + } + case 0xF => // HTTP STRING CONVERSION FOR HTTP CHAT + sendHttp(encodeURL(resolve(en_code(buffer.map(_.toChar).mkString)))) + case 0x10 => // GET ACTUAL CONNECTED SSID + buffer = SSID.toArray.map(_.toInt) + sendMode = SENDING_MODE_WAITING_DUMMY + case 0x11 => // GET ACTUAL WIFI SIGNAL RSSI + buffer = "255".toArray.map(_.toInt) + sendMode = SENDING_MODE_WAITING_DUMMY + case 0x12 => // GET DEFAULT SERVER + buffer = server.toArray.map(_.toInt) + sendMode = SENDING_MODE_WAITING_DUMMY + case 0x13 => // GET EXTERNAL IP OF WIC64 INTERNET CONNECTION + sendHttp("http://sk.sx-64.de/wic64/ip.php") + case 0x14 => // GET MAC ADDRESS OF WIC64 + buffer = MAC_ADDRESS.toArray.map(_.toInt) + sendMode = SENDING_MODE_WAITING_DUMMY + case 0x15 => // GET TIME AND DATE FROM NTP SERVER + buffer = dateFormatter.format(new java.util.Date).toArray.map(_.toInt) + sendMode = SENDING_MODE_WAITING_DUMMY + case 0x16 => // SET TIMEZONE OF NTP SERVER + log(s"TIME ZONE: ${buffer.mkString(",")}") + buffer = "Time zone set".toArray.map(_.toInt) + sendMode = SENDING_MODE_WAITING_DUMMY + case 0x17 => // GET TIMEZONE OF NTP SERVER + buffer = Array(2,0) // European + sendMode = SENDING_MODE_WAITING_DUMMY + case 0x18 => // CHECK IF DEVELOPER/STANDARD FIRMWARE UPDATE IS AVAILABLE + buffer = Array('0'.toInt) + sendMode = SENDING_MODE_WAITING_DUMMY + case 0x19 => // READ PREFS STRING FROM EEPROM + // TODO CHECK IF MUST BE SKIPPED SEPARATOR (1) + val prefName = buffer.map(_.toChar).mkString + val value = prefMap.getProperty(prefName,"") + log(s"READING PREF $prefName = '$value'") + buffer = value.toArray.map(_.toInt) + sendMode = SENDING_MODE_WAITING_DUMMY + case 0x1A => // SAVE PREFS STRING TO EEPROM + val pref = buffer.map(_.toChar).mkString + pref.split("\u00001") match { + case Array(_,name,value,retVal) => + log(s"WRITING PREF $name = '$value") + prefMap.setProperty(name,value) + buffer = retVal.toArray.map(_.toInt) + sendMode = SENDING_MODE_WAITING_DUMMY + case _ => + log(s"WRITING PREF ERROR: $pref") + } + // case 0x1E => // GET TCP + // case 0x1F => // SEND TCP + // case 0x20 => // SET TCP PORT + case 0x21 => // CONNECT TO SERVER:PORT VIA TCP (TELNET) + if (telnetClient != null && telnetClient.isConnected) telnetClient.disconnect() + telnetClient = new TelnetClient + telnetClient.setDefaultTimeout(5000) + val Array(h,p) = buffer.map(_.toChar).mkString.split(":") + try { + telnetClient.connect(h, p.toInt) + buffer = "0".toArray.map(_.toInt) + sendMode = SENDING_MODE_WAITING_DUMMY + } + catch { + case e:Exception => + log(s"Telnet error: $e") + buffer = "!E".toArray.map(_.toInt) + sendMode = SENDING_MODE_WAITING_DUMMY + } + case 0x22 => // GET DATA FROM TCP CONNECTION + try { + val in = telnetClient.getInputStream + var av = in.available() + if (av > 0xFFFF) av = 0xFFFF + val bf = Array.ofDim[Byte](av) + in.read(bf) + buffer = bf.map(_.toInt & 0xFF) + sendMode = SENDING_MODE_WAITING_DUMMY + } + catch { + case e:Exception => + log(s"Telnet error: $e") + buffer = Array() + sendMode = SENDING_MODE_WAITING_DUMMY + } + case 0x23 => + val out = telnetClient.getOutputStream + val bf = buffer.map(_.toByte) + try { + out.write(bf) + out.flush() + buffer = "0".toArray.map(_.toInt) + sendMode = SENDING_MODE_WAITING_DUMMY + } + catch { + case e:Exception => + log(s"Telnet error: $e") + buffer = "!E".toArray.map(_.toInt) + sendMode = SENDING_MODE_WAITING_DUMMY + } + case 0x25 => // STREAMING + sendHttp(encodeURL(resolve(buffer.map(_.toChar).mkString)),true) + case 99 => + reset + case _ => + log(s"Command ${recCmd.toHexString} not implemented") + } + + if (listener != null) listener.onCMD(CMD_DESCR.getOrElse(recCmd,"???")) + } + + def read(): Int = { + if (mode != SENDING_MODE) { + //println("NOT IN SENDING MODE ...") + return 0 + } + + var rd = 0 + + if (buffer != null || streamingIn != null) { + sendMode match { + case SENDING_MODE_WAITING_DUMMY => + if (streamingIn != null) sendMode = SENDING_MODE_STREAMING else sendMode = SENDING_MODE_HI + bufferIndex = 0 + flag2Action() + case SENDING_MODE_HI => + sendMode = SENDING_MODE_LO + rd = (buffer.length - adjustBufferLenForPRG) >> 8 + flag2Action() + case SENDING_MODE_LO => + sendMode = SENDING_MODE_WAITING_END + rd = (buffer.length - adjustBufferLenForPRG) & 0xFF + flag2Action() + case SENDING_MODE_WAITING_END => + if (bufferIndex < buffer.length) { + rd = buffer(bufferIndex) + bufferIndex += 1 + if (bufferIndex == buffer.length) { + sendMode = SENDING_MODE_WAITING_DUMMY + buffer = null + } + //println(s"Sending '${rd.toChar}'") + else flag2Action() + } + case SENDING_MODE_STREAMING => + rd = streamingIn.read() + if (rd == -1) { + streamingIn.close() + streamingIn = null + buffer = null + sendMode = SENDING_MODE_WAITING_DUMMY + rd = 0 + } + flag2Action() + } + } + + rd + } + + private def checkUDP(): DatagramSocket = { + if (udp == null || udp.getPort != udpPort) { + if (udp != null) udp.close() + udp = new DatagramSocket(udpPort) + if (udpThread != null) udpThread.interrupt() + else { + udpThread = new Thread(this,"WiC64UDP") + udpThread.start() + } + } + + udp + } + + private def sendUDP(ip: Array[Byte], port: Int, data:Array[Int]): Unit = { + val packet = new DatagramPacket(data.map(_.toByte),data.length,InetAddress.getByAddress(ip),port) + try { + udp.send(packet) + } + catch { + case e:Exception => + log(s"UDP: can't send packet to ${ip.map(_.toInt & 0xFF).mkString(".")}:$udpPort : $e") + } + } + + private def receiveUDP(): Unit = { + val head = udpReceivedQueue.poll() + if (head != null) { + buffer = Array.ofDim[Int](4 + head.data.length) + var i = 0 + while (i < 4) { + buffer(i) = head.from(i).toInt & 0xFF + i += 1 + } + i = 0 + while (i < head.data.length) { + buffer(4 + i) = head.data(i).toInt & 0xFF + i += 1 + } + log(s"UDP received from ${head.from.map(_.toInt & 0xFF).mkString(".")} : ${head.data.map(_.toInt & 0xFF).mkString(" ")}") + } + else { + buffer = Array() + } + sendMode = SENDING_MODE_WAITING_DUMMY + } + + override def run(): Unit = { + log("UDP Thread started") + val buffer = Array.ofDim[Byte](1024) + val packet = new DatagramPacket(buffer,0,buffer.length) + udpThreadRunning = true + while (udpThreadRunning) { + try { + val socket = udp.receive(packet) + val data = Array.ofDim[Byte](packet.getLength) + System.arraycopy(packet.getData, 0, data, 0, data.length) + if (!udpReceivedQueue.offer(UDPPacket(packet.getAddress.getAddress, data))) { + log(s"UDP queue overflow: ${udpReceivedQueue.size()}") + } + } + catch { + case _:InterruptedException => + } + } + udpThread = null + log("UPD Thread stopped") + } + + override def init: Unit = {} + + override protected def saveState(out: ObjectOutputStream): Unit = {} + + override protected def loadState(in: ObjectInputStream): Unit = {} + + override protected def allowsStateRestoring: Boolean = false +} diff --git a/Kernal64/src/ucesoft/cbm/misc/WiC64Panel.scala b/Kernal64/src/ucesoft/cbm/misc/WiC64Panel.scala new file mode 100644 index 0000000..47dfbff --- /dev/null +++ b/Kernal64/src/ucesoft/cbm/misc/WiC64Panel.scala @@ -0,0 +1,194 @@ +package ucesoft.cbm.misc + +import ucesoft.cbm.Version +import ucesoft.cbm.expansion.WiC64 + +import java.awt.event.{WindowAdapter, WindowEvent} +import java.awt._ +import java.net.NetworkInterface +import javax.swing._ +import javax.swing.text.{Style, StyleConstants, StyleContext} + +class WiC64Panel(frame:JFrame) extends JPanel with WiC64.WiC64Listener { + private class Led(colorOn:Color) extends JComponent { + var on = false + setPreferredSize(new Dimension(15,15)) + override def paint(g:Graphics) : Unit = { + val size = getSize() + if (on) { + g.setColor(colorOn) + g.fillOval(0,0,size.width,size.height) + } + } + } + + private val greenLed = new Led(Color.GREEN) + private val enabledCheck = new JCheckBox("Enabled") + private val updateCmd = new JCheckBox("Update cmd") + private val networks = { + import scala.jdk.CollectionConverters._ + val nw = NetworkInterface.getNetworkInterfaces.asScala.toArray + val combo = new JComboBox[String](nw.map(_.getName)) + nw.zipWithIndex.find(z => z._1.getName.startsWith("wlan")) match { + case None => + WiC64.setNetworkInterface(nw(0)) + case Some((n,i)) => + combo.setSelectedIndex(i) + WiC64.setNetworkInterface(n) + } + combo.setEditable(false) + combo + } + private val newFirmwareLabel = new JLabel(" ! ") + private val textPane = new JTextPane() + private final val COLS = 25 + private var regular,large : Style = _ + private var lastCmd = "" + private val firmwareStats = { + val d = Version.DATE + val t = Version.TIME + s"${d.substring(6,8)}/${d.substring(4,6)}/${d.substring(0,4)} ${t.substring(0,2)}:${t.substring(2)}:00" + } + private val logPanel = new JTextArea(10,30) + private val logButton = new JToggleButton("Debug log") + private val logDialog = { + val d = new JDialog(frame,"WiC64 log panel") + logPanel.setEditable(false) + logPanel.setForeground(Color.BLACK) + val sp = new JScrollPane(logPanel) + d.getContentPane.add("Center",sp) + val southPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)) + val clear = new JButton("Clear") + southPanel.add(clear) + d.getContentPane.add("South",southPanel) + clear.addActionListener(_ => logPanel.setText("")) + d.pack() + d.setLocationRelativeTo(frame) + d.addWindowListener(new WindowAdapter { + override def windowClosing(e: WindowEvent): Unit = logButton.setSelected(false) + }) + d + } + + val dialog = { + init() + val d = new JDialog(frame,"WiC64 panel") + d.getContentPane.add("Center",this) + d.pack() + d.setLocationRelativeTo(frame) + d.setResizable(false) + d.addWindowListener(new WindowAdapter { + override def windowClosing(e: WindowEvent): Unit = { + logDialog.setVisible(false) + logButton.setSelected(false) + } + }) + d.setIconImage(new ImageIcon(getClass.getResource("/resources/commodore.png")).getImage) + d + } + + private def init(): Unit = { + newFirmwareLabel.setForeground(Color.RED) + newFirmwareLabel.setVisible(false) + newFirmwareLabel.setToolTipText("Warning: a new REAL firmware version is available.") + newFirmwareLabel.setBorder(BorderFactory.createLineBorder(Color.RED)) + val font = newFirmwareLabel.getFont + newFirmwareLabel.setFont(new Font(font.getName,Font.BOLD,font.getSize)) + textPane.setEnabled(false) + networks.setEnabled(false) + logButton.setEnabled(false) + updateCmd.setEnabled(false) + textPane.setBackground(Color.BLACK) + textPane.setForeground(Color.WHITE) + textPane.setEditable(false) + + val doc = textPane.getStyledDocument() + val df = StyleContext.getDefaultStyleContext().getStyle(StyleContext.DEFAULT_STYLE) + regular = doc.addStyle("regular", df) + StyleConstants.setFontFamily(regular,"Monospaced") + StyleConstants.setBold(regular, true) + StyleConstants.setFontSize(regular, 16) + large = doc.addStyle("large", regular) + StyleConstants.setFontSize(large, 24) + + enabledCheck.addActionListener(_ => { + WiC64.enabled = enabledCheck.isSelected + textPane.setEnabled(enabledCheck.isSelected) + networks.setEnabled(enabledCheck.isSelected) + logButton.setEnabled(enabledCheck.isSelected) + updateCmd.setEnabled(enabledCheck.isSelected) + }) + networks.addActionListener(_ => { + import scala.jdk.CollectionConverters._ + val nw = NetworkInterface.getNetworkInterfaces.asScala.toArray + WiC64.setNetworkInterface(nw(networks.getSelectedIndex)) + update() + }) + + val resetButton = new JButton("Reset") + resetButton.addActionListener(_ => WiC64.resetWiC64()) + + setLayout(new BorderLayout()) + val northPanel = new JPanel(new BorderLayout()) + var northDummyPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)) + northPanel.add("West",northDummyPanel) + northDummyPanel.add(enabledCheck) + northDummyPanel.add(updateCmd) + northDummyPanel.add(resetButton) + northDummyPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)) + northPanel.add("East",northDummyPanel) + northDummyPanel.add(newFirmwareLabel) + val southPanel = new JPanel(new BorderLayout()) + val netPanel = new JPanel + netPanel.add(new JLabel("Local network:",SwingConstants.RIGHT)) + netPanel.add(networks) + southPanel.add(logButton) + logButton.addActionListener(_ => { + logDialog.setVisible(logButton.isSelected) + WiC64.setLogEnabled(logButton.isSelected) + }) + southPanel.add("West",netPanel) + val ledPanel = new JPanel + ledPanel.add(greenLed) + southPanel.add("East",ledPanel) + add("Center",textPane) + add("North",northPanel) + add("South",southPanel) + update() + } + + private def update(): Unit = { + val doc = textPane.getStyledDocument() + doc.remove(0,doc.getLength) + doc.insertString(doc.getLength,WiC64.getIPAddress(),large) + doc.insertString(doc.getLength,"\n\n",regular) + doc.insertString(doc.getLength,fill(s"SSID:${WiC64.SSID} -30",COLS) + "\n",regular) + doc.insertString(doc.getLength,fill(fill(lastCmd,16) + WiC64.getTotalCmdIssued.toHexString.toUpperCase() ,COLS) + "\n",regular) + doc.insertString(doc.getLength,fill(" WiC64 emulator by",COLS) + "\n",regular) + doc.insertString(doc.getLength,fill("Kernal64",COLS) + "\n",regular) + doc.insertString(doc.getLength,fill(firmwareStats,COLS),regular) + } + + def fill(s:String,size:Int,left:Boolean = false): String = { + if (s.length < size) { + if (left) (" " * (size - s.length)) + s else s + (" " * (size - s.length)) + } else s.substring(0,size) + } + + override def onCMD(cmdDescr: String): Unit = { + lastCmd = cmdDescr + if (updateCmd.isSelected) update() + } + + override def turnGreenLed(on: Boolean): Unit = { + greenLed.on = on + greenLed.repaint() + } + + override def log(info: String): Unit = { + logPanel.append(info + "\n") + logPanel.setCaretPosition(logPanel.getText().length()) + } + + override def newFirmwareAvaiilable(): Unit = newFirmwareLabel.setVisible(true) +} diff --git a/Kernal64/src/ucesoft/cbm/peripheral/cia/CIA2Connectors.scala b/Kernal64/src/ucesoft/cbm/peripheral/cia/CIA2Connectors.scala index e81d52d..0b765a1 100644 --- a/Kernal64/src/ucesoft/cbm/peripheral/cia/CIA2Connectors.scala +++ b/Kernal64/src/ucesoft/cbm/peripheral/cia/CIA2Connectors.scala @@ -4,10 +4,11 @@ import ucesoft.cbm.peripheral.bus._ import ucesoft.cbm.peripheral.Connector import ucesoft.cbm.peripheral.rs232.RS232 import ucesoft.cbm.peripheral.drive.ParallelCable + import java.io.ObjectOutputStream import java.io.ObjectInputStream import ucesoft.cbm.peripheral.vic.VICMemory -import ucesoft.cbm.expansion.DigiMAX +import ucesoft.cbm.expansion.{DigiMAX, WiC64} object CIA2Connectors { val CIA2_PORTA_BUSID = "CIA2_PortA" @@ -39,6 +40,9 @@ object CIA2Connectors { val a0a1 = (data >> 2) & 3 DigiMAX.selectChannel(a0a1) } + if (WiC64.enabled) { + WiC64.setMode(data & 4) + } } // state override protected def saveState(out:ObjectOutputStream) : Unit = { @@ -54,13 +58,17 @@ object CIA2Connectors { class PortBConnector(rs232:RS232) extends Connector { val componentID = "CIA2 Port B Connector" final def read = { - if (ParallelCable.enabled) { + if (WiC64.enabled) WiC64.read() + else if (ParallelCable.enabled) { ParallelCable.onPC ParallelCable.read } else if (rs232.isEnabled) rs232.getOthers else (latch | ~ddr) & 0xFF } final protected def performWrite(data:Int) : Unit = { + if (WiC64.enabled) { + WiC64.write(data) + } if (ParallelCable.enabled) { ParallelCable.onPC ParallelCable.write(data)