mirror of
https://github.com/ariejan/i6502.git
synced 2024-06-09 10:29:27 +00:00
Working websockets
This commit is contained in:
parent
12f64bf7d3
commit
b692c8a7b1
108
connection.go
Normal file
108
connection.go
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
pongWait = 60 * time.Second
|
||||||
|
pingPeriod = (pongWait * 9) / 10
|
||||||
|
maxMessageSize = 512
|
||||||
|
)
|
||||||
|
|
||||||
|
type connection struct {
|
||||||
|
// The i6502 machine
|
||||||
|
machine *Machine
|
||||||
|
|
||||||
|
// Websocket connection
|
||||||
|
ws *websocket.Conn
|
||||||
|
|
||||||
|
// Outgoing data channel
|
||||||
|
send chan []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
upgrader = websocket.Upgrader{
|
||||||
|
ReadBufferSize: 1024,
|
||||||
|
WriteBufferSize: 1024,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pump received websocket messages into the machine
|
||||||
|
func (c *connection) readPump() {
|
||||||
|
defer func() {
|
||||||
|
c.ws.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
c.ws.SetReadLimit(maxMessageSize)
|
||||||
|
c.ws.SetReadDeadline(time.Now().Add(pongWait))
|
||||||
|
c.ws.SetPongHandler(func(string) error { c.ws.SetReadDeadline(time.Now().Add(pongWait)); return nil })
|
||||||
|
for {
|
||||||
|
_, message, err := c.ws.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
c.writeBytesToMachine(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pump serial output from the machine into the socket
|
||||||
|
func (c *connection) writePump() {
|
||||||
|
ticker := time.NewTicker(pingPeriod)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
c.ws.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case data, ok := <-c.machine.SerialTx:
|
||||||
|
if !ok {
|
||||||
|
c.write(websocket.CloseMessage, []byte{})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := c.write(websocket.TextMessage, []byte{data}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case <-ticker.C:
|
||||||
|
if err := c.write(websocket.PingMessage, []byte{}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *connection) write(messageType int, payload []byte) error {
|
||||||
|
c.ws.SetWriteDeadline(time.Now().Add(10 * time.Second))
|
||||||
|
return c.ws.WriteMessage(messageType, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *connection) writeBytesToMachine(data []byte) {
|
||||||
|
for _, b := range data {
|
||||||
|
log.Printf("%c", b)
|
||||||
|
c.machine.SerialRx <- b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveWs(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != "GET" {
|
||||||
|
http.Error(w, "Method not allowed", 405)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ws, err := upgrader.Upgrade(w, r, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c := &connection{machine: CreateMachine(), send: make(chan []byte, 256), ws: ws}
|
||||||
|
|
||||||
|
go c.writePump()
|
||||||
|
|
||||||
|
c.machine.Reset()
|
||||||
|
c.readPump()
|
||||||
|
}
|
|
@ -30,12 +30,16 @@ type Acia6551 struct {
|
||||||
rxFull bool
|
rxFull bool
|
||||||
txEmpty bool
|
txEmpty bool
|
||||||
|
|
||||||
|
rxInterruptEnabled bool
|
||||||
|
txInterruptEnabled bool
|
||||||
|
|
||||||
|
InterruptChan chan bool
|
||||||
|
|
||||||
RxChan chan byte
|
RxChan chan byte
|
||||||
TxChan chan byte
|
TxChan chan byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAcia6551() *Acia6551 {
|
func NewAcia6551(cpu *Cpu) *Acia6551 {
|
||||||
fmt.Println("Resetting the Acia6551")
|
|
||||||
acia := &Acia6551{}
|
acia := &Acia6551{}
|
||||||
acia.Reset()
|
acia.Reset()
|
||||||
return acia
|
return acia
|
||||||
|
@ -54,6 +58,11 @@ func (a *Acia6551) Reset() {
|
||||||
a.lastTxWrite = 0
|
a.lastTxWrite = 0
|
||||||
a.lastRxRead = 0
|
a.lastRxRead = 0
|
||||||
a.overrun = false
|
a.overrun = false
|
||||||
|
|
||||||
|
a.rxInterruptEnabled = false
|
||||||
|
a.txInterruptEnabled = false
|
||||||
|
|
||||||
|
a.InterruptChan = make(chan bool, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Acia6551) Size() int {
|
func (a *Acia6551) Size() int {
|
||||||
|
@ -91,7 +100,9 @@ func (a *Acia6551) RxWrite(data byte) {
|
||||||
a.rx = data
|
a.rx = data
|
||||||
a.rxFull = true
|
a.rxFull = true
|
||||||
|
|
||||||
// TODO: IRQs
|
if a.rxInterruptEnabled {
|
||||||
|
// getbus.assertIrq()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Acia6551) statusRegister() byte {
|
func (a *Acia6551) statusRegister() byte {
|
||||||
|
@ -153,7 +164,6 @@ func (a *Acia6551) HasRx() bool {
|
||||||
func (a *Acia6551) debugTxOutput() {
|
func (a *Acia6551) debugTxOutput() {
|
||||||
if a.HasTx() {
|
if a.HasTx() {
|
||||||
a.TxChan <- a.TxRead()
|
a.TxChan <- a.TxRead()
|
||||||
fmt.Printf("%c", a.TxRead())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
99
home.html
Normal file
99
home.html
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>i6502 Serial Terminal</title>
|
||||||
|
<link href='http://fonts.googleapis.com/css?family=Droid+Sans+Mono' rel='stylesheet' type='text/css'>
|
||||||
|
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(function() {
|
||||||
|
|
||||||
|
var conn;
|
||||||
|
var msg = $("#msg");
|
||||||
|
var log = $("#log");
|
||||||
|
|
||||||
|
function appendLog(msg) {
|
||||||
|
var d = log[0]
|
||||||
|
var doScroll = d.scrollTop == d.scrollHeight - d.clientHeight;
|
||||||
|
msg.appendTo(log)
|
||||||
|
if (doScroll) {
|
||||||
|
d.scrollTop = d.scrollHeight - d.clientHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#form").submit(function() {
|
||||||
|
if (!conn) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!msg.val()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
conn.send(msg.val());
|
||||||
|
msg.val("");
|
||||||
|
return false
|
||||||
|
});
|
||||||
|
|
||||||
|
if (window["WebSocket"]) {
|
||||||
|
conn = new WebSocket("ws://{{$}}/ws");
|
||||||
|
conn.onclose = function(evt) {
|
||||||
|
appendLog($("<div><b>Connection closed.</b></div>"))
|
||||||
|
}
|
||||||
|
conn.onmessage = function(evt) {
|
||||||
|
if (evt.data.indexOf("\n") == 0) {
|
||||||
|
appendLog($("<br/>"))
|
||||||
|
} else {
|
||||||
|
appendLog($("<span/>").text(evt.data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
appendLog($("<div><b>Your browser does not support WebSockets.</b></div>"))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style type="text/css">
|
||||||
|
html {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
#log {
|
||||||
|
background: black;
|
||||||
|
color: #e0e0e0;
|
||||||
|
font-family: 'Droid Sans Mono', courier;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.5em 0.5em 0.5em 0.5em;
|
||||||
|
position: absolute;
|
||||||
|
top: 0.5em;
|
||||||
|
left: 0.5em;
|
||||||
|
right: 0.5em;
|
||||||
|
bottom: 3em;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#form {
|
||||||
|
padding: 0 0.5em 0 0.5em;
|
||||||
|
margin: 0;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 1em;
|
||||||
|
left: 0px;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="log"></div>
|
||||||
|
<form id="form">
|
||||||
|
<input type="submit" value="Send" />
|
||||||
|
<input type="text" id="msg" size="64"/>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
78
machine.go
Normal file
78
machine.go
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ariejan/i6502/bus"
|
||||||
|
"github.com/ariejan/i6502/cpu"
|
||||||
|
"github.com/ariejan/i6502/devices"
|
||||||
|
"github.com/ariejan/i6502/memory"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Machine struct {
|
||||||
|
// Outgoing bytes, using the serial interface
|
||||||
|
SerialTx chan byte
|
||||||
|
|
||||||
|
// Incoming bytes, using the serial interface
|
||||||
|
SerialRx chan byte
|
||||||
|
|
||||||
|
// The cpu, bus etc.
|
||||||
|
cpu *cpu.Cpu
|
||||||
|
bus *bus.Bus
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a new i6502 Machine instance
|
||||||
|
func CreateMachine() *Machine {
|
||||||
|
ram := memory.CreateRam()
|
||||||
|
|
||||||
|
rom, err := memory.LoadRomFromFile("rom/ehbasic.rom")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
acia6551 := devices.NewAcia6551()
|
||||||
|
|
||||||
|
bus, _ := bus.CreateBus()
|
||||||
|
bus.Attach(ram, "32kB RAM", 0x0000)
|
||||||
|
bus.Attach(rom, "16kB ROM", 0xC000)
|
||||||
|
bus.Attach(acia6551, "ACIA 6551 Serial", 0x8800)
|
||||||
|
|
||||||
|
cpu := &cpu.Cpu{Bus: bus, ExitChan: make(chan int, 0)}
|
||||||
|
|
||||||
|
machine := &Machine{SerialTx: make(chan byte, 256), SerialRx: make(chan byte, 256), cpu: cpu, bus: bus}
|
||||||
|
|
||||||
|
// Run the CPU
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
cpu.Step()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Connect acia6551 Tx to SerialTx
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case data := <-acia6551.TxChan:
|
||||||
|
machine.SerialTx <- data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Connect SerialRx to acia6551 Rx
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case data := <-machine.SerialRx:
|
||||||
|
log.Printf("Rx: %c", data)
|
||||||
|
acia6551.RxChan <- data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
cpu.Reset()
|
||||||
|
|
||||||
|
return machine
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Machine) Reset() {
|
||||||
|
m.cpu.Reset()
|
||||||
|
}
|
40
main.go
Normal file
40
main.go
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"text/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
addr = flag.String("addr", ":6123", "http service address")
|
||||||
|
homeTmpl = template.Must(template.ParseFiles("home.html"))
|
||||||
|
)
|
||||||
|
|
||||||
|
func serveHome(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path != "/" {
|
||||||
|
http.Error(w, "Not found", 404)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Method != "GET" {
|
||||||
|
http.Error(w, "Method not allowed", 405)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
homeTmpl.Execute(w, r.Host)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
http.HandleFunc("/", serveHome)
|
||||||
|
http.HandleFunc("/ws", serveWs)
|
||||||
|
|
||||||
|
err := http.ListenAndServe(*addr, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("ListenAndServe: ", err)
|
||||||
|
}
|
||||||
|
}
|
BIN
rom/ehbasic.rom
BIN
rom/ehbasic.rom
Binary file not shown.
Loading…
Reference in New Issue
Block a user