Working websockets

This commit is contained in:
Ariejan de Vroom 2014-08-11 12:04:55 +02:00
parent 12f64bf7d3
commit b692c8a7b1
9 changed files with 339 additions and 4 deletions

108
connection.go Normal file
View 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()
}

View File

@ -30,12 +30,16 @@ type Acia6551 struct {
rxFull bool
txEmpty bool
rxInterruptEnabled bool
txInterruptEnabled bool
InterruptChan chan bool
RxChan chan byte
TxChan chan byte
}
func NewAcia6551() *Acia6551 {
fmt.Println("Resetting the Acia6551")
func NewAcia6551(cpu *Cpu) *Acia6551 {
acia := &Acia6551{}
acia.Reset()
return acia
@ -54,6 +58,11 @@ func (a *Acia6551) Reset() {
a.lastTxWrite = 0
a.lastRxRead = 0
a.overrun = false
a.rxInterruptEnabled = false
a.txInterruptEnabled = false
a.InterruptChan = make(chan bool, 0)
}
func (a *Acia6551) Size() int {
@ -91,7 +100,9 @@ func (a *Acia6551) RxWrite(data byte) {
a.rx = data
a.rxFull = true
// TODO: IRQs
if a.rxInterruptEnabled {
// getbus.assertIrq()
}
}
func (a *Acia6551) statusRegister() byte {
@ -153,7 +164,6 @@ func (a *Acia6551) HasRx() bool {
func (a *Acia6551) debugTxOutput() {
if a.HasTx() {
a.TxChan <- a.TxRead()
fmt.Printf("%c", a.TxRead())
}
}

99
home.html Normal file
View 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
View 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()
}

BIN
main Executable file

Binary file not shown.

40
main.go Normal file
View 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)
}
}

Binary file not shown.