Compare commits

..

No commits in common. "b910676539700975b0b9004a6225079e1b6fa2c4" and "15e43c28df6119821035bd6b3e7542c998163b3f" have entirely different histories.

12 changed files with 90 additions and 216 deletions

View File

@ -20,7 +20,7 @@ func TestBook(t *testing.T) {
c1.Expect(1) c1.Expect(1)
// Send to an empty channel // Send to an empty channel
m := Message{0, 0, []uint16{22, 33}} m := Message{0, 0, []uint8{22, 33}}
b.Send("merf", m) b.Send("merf", m)
b.loop() b.loop()
if c1.Len() > 0 { if c1.Len() > 0 {

View File

@ -1,21 +1,17 @@
package main package main
import ( import (
"context" "fmt"
"encoding/json"
"log" "log"
"net/http" "net/http"
"os" "os"
"time" "time"
"nhooyr.io/websocket" "golang.org/x/net/websocket"
) )
var book Book var book Book
const JsonProtocol = "json.vail.woozle.org"
const BinaryProtocol = "binary.vail.woozle.org"
// Clock defines an interface for getting the current time. // Clock defines an interface for getting the current time.
// //
// We use this in testing to provide a fixed value for the current time, so we // We use this in testing to provide a fixed value for the current time, so we
@ -24,7 +20,7 @@ type Clock interface {
Now() time.Time Now() time.Time
} }
// WallClock is a Clock which provides the actual time // WallClock provides the actual time
type WallClock struct{} type WallClock struct{}
func (WallClock) Now() time.Time { func (WallClock) Now() time.Time {
@ -34,87 +30,32 @@ func (WallClock) Now() time.Time {
// VailWebSocketConnection reads and writes Message structs // VailWebSocketConnection reads and writes Message structs
type VailWebSocketConnection struct { type VailWebSocketConnection struct {
*websocket.Conn *websocket.Conn
usingJSON bool
} }
func (c *VailWebSocketConnection) Receive() (Message, error) { func (c *VailWebSocketConnection) Receive() (Message, error) {
var m Message var m Message
messageType, buf, err := c.Read(context.Background()) err := websocket.JSON.Receive(c.Conn, &m)
if err != nil {
return m, err
}
if messageType == websocket.MessageText {
err = json.Unmarshal(buf, &m)
} else {
err = m.UnmarshalBinary(buf)
}
return m, err return m, err
} }
func (c *VailWebSocketConnection) Send(m Message) error { func (c *VailWebSocketConnection) Send(m Message) error {
var err error return websocket.JSON.Send(c.Conn, m)
var buf []byte
var messageType websocket.MessageType
log.Println("Send", m)
if c.usingJSON {
messageType = websocket.MessageText
buf, err = json.Marshal(m)
} else {
messageType = websocket.MessageBinary
buf, err = m.MarshalBinary()
}
log.Println(buf, err)
if err != nil {
return err
} }
log.Println("Sending") type Client struct {
return c.Write(context.Background(), messageType, buf) repeaterName string
} }
func ChatHandler(w http.ResponseWriter, r *http.Request) { func (c Client) Handle(ws *websocket.Conn) {
// Set up websocket sock := &VailWebSocketConnection{ws}
ws, err := websocket.Accept( nowMilli := time.Now().UnixMilli()
w, r, ws.MaxPayloadBytes = 50
&websocket.AcceptOptions{ book.Join(c.repeaterName, sock)
Subprotocols: []string{JsonProtocol, BinaryProtocol}, defer book.Part(c.repeaterName, sock)
},
)
if err != nil {
log.Println(err)
return
}
defer ws.Close(websocket.StatusInternalError, "Internal error")
// Create our Vail websocket connection for books to send to
sock := VailWebSocketConnection{
Conn: ws,
}
// websockets apparently sends a subprotocol string, so we can ignore Accept headers!
switch ws.Subprotocol() {
case JsonProtocol:
sock.usingJSON = true
case BinaryProtocol:
sock.usingJSON = false
default:
ws.Close(websocket.StatusPolicyViolation, "client must speak a vail protocol")
return
}
// Join the repeater
repeaterName := r.FormValue("repeater")
book.Join(repeaterName, &sock)
defer book.Part(repeaterName, &sock)
for { for {
// Read a packet
m, err := sock.Receive() m, err := sock.Receive()
if err != nil { if err != nil {
log.Println(err)
ws.Close(websocket.StatusInvalidFramePayloadData, err.Error())
break break
} }
@ -124,20 +65,30 @@ func ChatHandler(w http.ResponseWriter, r *http.Request) {
} }
// If it's wildly out of time, reject it // If it's wildly out of time, reject it
timeDelta := (time.Now().UnixMilli() - m.Timestamp) timeDelta := (nowMilli - m.Timestamp)
if timeDelta < 0 { if timeDelta < 0 {
timeDelta = -timeDelta timeDelta = -timeDelta
} }
if timeDelta > 9999 { if timeDelta > 9999 {
log.Println(err) fmt.Fprintln(ws, "Bad timestamp")
ws.Close(websocket.StatusInvalidFramePayloadData, "Your clock is off by too much") ws.Close()
break return
} }
book.Send(repeaterName, m) book.Send(c.repeaterName, m)
} }
} }
func ChatHandler(w http.ResponseWriter, r *http.Request) {
c := Client{
repeaterName: r.FormValue("repeater"),
}
// This API is confusing as hell.
// I suspect there's a better way to do this.
websocket.Handler(c.Handle).ServeHTTP(w, r)
}
func main() { func main() {
book = NewBook() book = NewBook()
http.Handle("/chat", http.HandlerFunc(ChatHandler)) http.Handle("/chat", http.HandlerFunc(ChatHandler))

View File

@ -3,6 +3,7 @@ package main
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"fmt"
"time" "time"
) )
@ -33,13 +34,13 @@ type Message struct {
// Message timing in ms. // Message timing in ms.
// Timings alternate between tone and silence. // Timings alternate between tone and silence.
// For example, `A` could be sent as [80, 80, 240] // For example, `A` could be sent as [80, 80, 240]
Duration []uint16 Duration []uint8
} }
func NewMessage(ts time.Time, durations ...time.Duration) Message { func NewMessage(ts time.Time, durations ...time.Duration) Message {
msg := Message{ msg := Message{
Timestamp: ts.UnixNano() / time.Millisecond.Nanoseconds(), Timestamp: ts.UnixNano() / time.Millisecond.Nanoseconds(),
Duration: make([]uint16, len(durations)), Duration: make([]uint8, len(durations)),
} }
for i, dns := range durations { for i, dns := range durations {
ms := dns.Milliseconds() ms := dns.Milliseconds()
@ -48,7 +49,7 @@ func NewMessage(ts time.Time, durations ...time.Duration) Message {
} else if ms < 0 { } else if ms < 0 {
ms = 0 ms = 0
} }
msg.Duration[i] = uint16(ms) msg.Duration[i] = uint8(ms)
} }
return msg return msg
} }
@ -68,7 +69,7 @@ func (m Message) MarshalBinary() ([]byte, error) {
return w.Bytes(), nil return w.Bytes(), nil
} }
// UnmarshalBinary unpacks a binary buffer into a Message. // Unmarshaling presumes something else is keeping track of lengths
func (m *Message) UnmarshalBinary(data []byte) error { func (m *Message) UnmarshalBinary(data []byte) error {
r := bytes.NewReader(data) r := bytes.NewReader(data)
if err := binary.Read(r, binary.BigEndian, &m.Timestamp); err != nil { if err := binary.Read(r, binary.BigEndian, &m.Timestamp); err != nil {
@ -77,14 +78,32 @@ func (m *Message) UnmarshalBinary(data []byte) error {
if err := binary.Read(r, binary.BigEndian, &m.Clients); err != nil { if err := binary.Read(r, binary.BigEndian, &m.Clients); err != nil {
return err return err
} }
dlen := r.Len() / 2 dlen := r.Len()
m.Duration = make([]uint16, dlen) m.Duration = make([]uint8, dlen)
if err := binary.Read(r, binary.BigEndian, &m.Duration); err != nil { if err := binary.Read(r, binary.BigEndian, &m.Duration); err != nil {
return err return err
} }
return nil return nil
} }
func (m Message) MarshalJSON() ([]byte, error) {
buf := new(bytes.Buffer)
fmt.Fprint(buf, "{")
fmt.Fprintf(buf, "\"Timestamp\":%d,", m.Timestamp)
fmt.Fprintf(buf, "\"Clients\":%d,", m.Clients)
fmt.Fprint(buf, "\"Duration\":[")
for i := 0; i < len(m.Duration); i++ {
fmt.Fprint(buf, m.Duration[i])
if i <= len(m.Duration)-1 {
fmt.Fprint(buf, ",")
}
}
fmt.Fprint(buf)
fmt.Fprint(buf, "]")
fmt.Fprint(buf, "}")
return buf.Bytes(), nil
}
func (m Message) Equal(m2 Message) bool { func (m Message) Equal(m2 Message) bool {
if m.Timestamp != m2.Timestamp { if m.Timestamp != m2.Timestamp {
return false return false

View File

@ -7,8 +7,8 @@ import (
) )
func TestMessageStruct(t *testing.T) { func TestMessageStruct(t *testing.T) {
m := Message{0x1122334455, 0, []uint16{0xaa, 0xbb, 0xcc}} m := Message{0x1122334455, 0, []uint8{0xaa, 0xbb, 0xcc}}
m2 := Message{12, 0, []uint16{1}} m2 := Message{12, 0, []uint8{1}}
if !m.Equal(m) { if !m.Equal(m) {
t.Error("Equal messages did not compare equal") t.Error("Equal messages did not compare equal")
@ -16,7 +16,7 @@ func TestMessageStruct(t *testing.T) {
if m.Equal(m2) { if m.Equal(m2) {
t.Error("Unequal messages compared equal") t.Error("Unequal messages compared equal")
} }
if m.Equal(Message{m.Timestamp, 0, []uint16{1, 2, 3}}) { if m.Equal(Message{m.Timestamp, 0, []uint8{1, 2, 3}}) {
t.Error("Messages with different payloads compared equal") t.Error("Messages with different payloads compared equal")
} }
@ -24,7 +24,7 @@ func TestMessageStruct(t *testing.T) {
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
if !bytes.Equal(bm, []byte("\x00\x00\x00\x11\x22\x33\x44\x55\x00\x00\x00\xaa\x00\xbb\x00\xcc")) { if !bytes.Equal(bm, []byte("\x00\x00\x00\x11\x22\x33\x44\x55\x00\x00\xaa\xbb\xcc")) {
t.Error("Encoded wrong:", bm) t.Error("Encoded wrong:", bm)
} }

View File

@ -32,7 +32,7 @@ func (tc *TestingClient) Len() int {
return len(tc.buf) return len(tc.buf)
} }
func (tc *TestingClient) Expect(clients uint16, payload ...uint16) { func (tc *TestingClient) Expect(clients uint16, payload ...uint8) {
m := Message{0, clients, payload} m := Message{0, clients, payload}
tc.expected = append(tc.expected, m) tc.expected = append(tc.expected, m)
if len(tc.buf) != len(tc.expected) { if len(tc.buf) != len(tc.expected) {

5
go.mod
View File

@ -2,7 +2,4 @@ module github.com/nealey/vail
go 1.12 go 1.12
require ( require golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5
nhooyr.io/websocket v1.8.7 // indirect
)

39
go.sum
View File

@ -1,45 +1,6 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eTO0Q8=
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5 h1:WQ8q63x+f/zpC8Ac1s9wLElVoHhm32p6tudrU72n1QA= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5 h1:WQ8q63x+f/zpC8Ac1s9wLElVoHhm32p6tudrU72n1QA=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=

View File

@ -72,7 +72,7 @@
<div class="level-right"> <div class="level-right">
<div class="level-item"> <div class="level-item">
<!-- This appears as a little light that turns on when someone's sending --> <!-- This appears as a little light that turns on when someone's sending -->
<span class="tag recv-lamp"> <span class="tag" class="recv-lamp">
<output class="has-text-info" id="note"></output> <output class="has-text-info" id="note"></output>
<i class="mdi mdi-volume-off" id="muted"></i> <i class="mdi mdi-volume-off" id="muted"></i>
</span> </span>

View File

@ -137,10 +137,6 @@ class Sample {
* A (mostly) virtual class defining a buzzer. * A (mostly) virtual class defining a buzzer.
*/ */
class Buzzer { class Buzzer {
constructor() {
this.connected = true
}
/** /**
* Signal an error * Signal an error
*/ */
@ -179,15 +175,6 @@ class Buzzer {
this.Buzz(tx, when) this.Buzz(tx, when)
this.Silence(tx, when + duration) this.Silence(tx, when + duration)
} }
/**
* Set the "connectedness" indicator.
*
* @param {boolean} connected True if connected
*/
SetConnected(connected) {
this.connected = connected
}
} }
class AudioBuzzer extends Buzzer { class AudioBuzzer extends Buzzer {
@ -310,17 +297,6 @@ class LampBuzzer extends Buzzer {
ms, ms,
) )
} }
SetConnected(connected) {
console.log(connected)
for (let e of this.elements) {
if (connected) {
e.classList.add("connected")
} else {
e.classList.remove("connected")
}
}
}
} }
class MIDIBuzzer extends Buzzer { class MIDIBuzzer extends Buzzer {
@ -439,7 +415,7 @@ class Collection {
* *
* @param tx True if transmitting * @param tx True if transmitting
*/ */
Silence(tx=false) { Silence(tx=False) {
for (let b of this.collection) { for (let b of this.collection) {
b.Silence(tx) b.Silence(tx)
} }
@ -457,19 +433,6 @@ class Collection {
b.BuzzDuration(tx, when, duration) b.BuzzDuration(tx, when, duration)
} }
} }
/**
* Update the "connected" status display.
*
* For example, turn the receive light to black if the repeater is not connected.
*
* @param {boolean} connected True if we are "connected"
*/
SetConnected(connected) {
for (let b of this.collection) {
b.SetConnected(connected)
}
}
} }
export {AudioReady, Collection} export {AudioReady, Collection}

View File

@ -30,7 +30,6 @@ export class Vail {
this.lagDurations = [] this.lagDurations = []
this.sent = [] this.sent = []
this.wantConnected = true this.wantConnected = true
this.connected = false
this.wsUrl = new URL("chat", window.location) this.wsUrl = new URL("chat", window.location)
this.wsUrl.protocol = this.wsUrl.protocol.replace("http", "ws") this.wsUrl.protocol = this.wsUrl.protocol.replace("http", "ws")
@ -44,23 +43,15 @@ export class Vail {
if (!this.wantConnected) { if (!this.wantConnected) {
return return
} }
this.rx(0, 0, {connected: false})
console.info("Attempting to reconnect", this.wsUrl.href) console.info("Attempting to reconnect", this.wsUrl.href)
this.clockOffset = 0 this.clockOffset = 0
this.socket = new WebSocket(this.wsUrl, ["json.vail.woozle.org"]) this.socket = new WebSocket(this.wsUrl)
this.socket.addEventListener("message", e => this.wsMessage(e)) this.socket.addEventListener("message", e => this.wsMessage(e))
this.socket.addEventListener( this.socket.addEventListener(
"open",
msg => {
this.connected = true
this.rx(0, 0, {connected: true})
}
)
this.socket.addEventListener(
"close", "close",
msg => { () => {
console.error("Repeater connection dropped:", msg.reason) console.info("Repeater connection dropped.")
setTimeout(() => this.reopen(), 2*Second) setTimeout(() => this.reopen(), 5*Second)
} }
) )
} }
@ -80,12 +71,6 @@ export class Vail {
averageLag: this.lagDurations.reduce((a,b) => (a+b), 0) / this.lagDurations.length, averageLag: this.lagDurations.reduce((a,b) => (a+b), 0) / this.lagDurations.length,
clockOffset: this.clockOffset, clockOffset: this.clockOffset,
clients: msg.Clients, clients: msg.Clients,
connected: this.connected,
}
console.log(msg)
if (typeof(msg) == "string") {
console.error(msg)
return
} }
// XXX: Why is this happening? // XXX: Why is this happening?
@ -162,14 +147,13 @@ export class Vail {
} }
export class Null { export class Null {
constructor(rx, interval=3*Second) { constructor(rx) {
this.rx = rx this.rx = rx
this.interval = setInterval(() => this.pulse(), interval) this.interval = setInterval(() => this.pulse(), 3 * Second)
this.pulse()
} }
pulse() { pulse() {
this.rx(0, 0, {note: "local", connected: false}) this.rx(0, 0, {note: "local"})
} }
Transmit(time, duration, squelch=true) { Transmit(time, duration, squelch=true) {
@ -180,32 +164,38 @@ export class Null {
} }
} }
export class Echo extends Null { export class Echo {
constructor(rx, delay=0) { constructor(rx, delay=0) {
super(rx) this.rx = rx
this.delay = delay this.delay = delay
this.Transmit(0, 0)
} }
Transmit(time, duration, squelch=true) { Transmit(time, duration, squelch=true) {
this.rx(time + this.delay, duration, {note: "local"}) this.rx(time + this.delay, duration, {note: "local"})
} }
Close() {
}
} }
export class Fortune extends Null { export class Fortune {
/** /**
* *
* @param rx Receive callback * @param rx Receive callback
* @param {Keyer} keyer Keyer object * @param {Keyer} keyer Keyer object
*/ */
constructor(rx, keyer) { constructor(rx, keyer) {
super(rx, 1*Minute) this.rx = rx
this.keyer = keyer this.keyer = keyer
this.interval = setInterval(() => this.pulse(), 1 * Minute)
this.pulse() this.pulse()
} }
pulse() { pulse() {
super.pulse() this.rx(0, 0, {note: "local"})
if (!this.keyer || this.keyer.Busy()) { if (this.keyer.Busy()) {
return return
} }
@ -213,8 +203,12 @@ export class Fortune extends Null {
this.keyer.EnqueueAsciiString(`${fortune}\x04 `) this.keyer.EnqueueAsciiString(`${fortune}\x04 `)
} }
Transmit(time, duration, squelch=true) {
// Do nothing.
}
Close() { Close() {
this.keyer.Flush() this.keyer.Flush()
super.Close() clearInterval(this.interval)
} }
} }

View File

@ -16,15 +16,7 @@
-webkit-user-select: none; /* 2022-04-26 Safari still needs this */ -webkit-user-select: none; /* 2022-04-26 Safari still needs this */
} }
.tag.recv-lamp { .recv-lamp.rx {
background-color: #444;
color: white;
}
.tag.recv-lamp.connected {
background-color: #fec;
}
.tag.recv-lamp.rx,
.tag.recv-lamp.connected.rx {
background-color: orange; background-color: orange;
} }

View File

@ -375,9 +375,6 @@ class VailClient {
let longestRxDuration = this.rxDurations.reduce((a,b) => Math.max(a,b)) let longestRxDuration = this.rxDurations.reduce((a,b) => Math.max(a,b))
let suggestedDelay = ((averageLag + longestRxDuration) * 1.2).toFixed(0) let suggestedDelay = ((averageLag + longestRxDuration) * 1.2).toFixed(0)
if (stats.connected !== undefined) {
this.outputs.SetConnected(stats.connected)
}
this.updateReading("#note", stats.note || "☁") this.updateReading("#note", stats.note || "☁")
this.updateReading("#lag-value", averageLag) this.updateReading("#lag-value", averageLag)
this.updateReading("#longest-rx-value", longestRxDuration) this.updateReading("#longest-rx-value", longestRxDuration)