Browse Source

Initial commit

Keelan Lightfoot 6 years ago
commit
0582ca2954
1 changed files with 592 additions and 0 deletions
  1. 592
    0
      main.go

+ 592
- 0
main.go View File

@@ -0,0 +1,592 @@
1
+package main
2
+
3
+import (
4
+	"fmt"
5
+
6
+	"bufio"
7
+	"bytes"
8
+	"github.com/gordonklaus/portaudio"
9
+	"github.com/naleek/gortty"
10
+	"io"
11
+	"net"
12
+	"regexp"
13
+	"strings"
14
+	"unicode/utf8"
15
+	"time"
16
+	"os"
17
+	"strconv"
18
+)
19
+
20
+
21
+func main() {
22
+
23
+	m := &modem{}
24
+	
25
+	rttySettings := &gortty.ModemSettings{
26
+		Baud:       45,
27
+		StopBits:   2,
28
+		DataBits:   5,
29
+		SampleRate: 8000,
30
+		OneFreq:    2125,
31
+		ZeroFreq:   2295,
32
+	}
33
+
34
+	charset := gortty.LoadCharset(&gortty.USTTY)
35
+
36
+	in := make([]int16, 64)
37
+	out := make([]int16, 64)
38
+	var stream *portaudio.Stream
39
+	
40
+	useStdio := false
41
+	
42
+	if !useStdio {
43
+		// Set up audio with system default input and output
44
+		portaudio.Initialize()
45
+		defer portaudio.Terminate()
46
+		var err error
47
+		stream, err = portaudio.OpenDefaultStream(1, 1, float64(rttySettings.SampleRate), len(in), in, out)
48
+		if err != nil {
49
+			panic(err)
50
+		}
51
+		defer stream.Close()
52
+	}
53
+	modulatorToAudio := make(chan int16)
54
+	audioToDemodulator := make(chan int16)
55
+
56
+	if !useStdio {
57
+		// Set up modulator pipeline
58
+		ttyToEncoder := make(chan rune, 100)
59
+		m.ttyWriter = NewModemWriter(ttyToEncoder)
60
+		encoderToModulator := make(chan byte)
61
+		m.encoder = gortty.NewEncoder(ttyToEncoder, encoderToModulator, charset)
62
+		m.modulator = gortty.NewModulator(rttySettings, encoderToModulator, modulatorToAudio)
63
+		m.modulator.SetIdleDelay(1000)
64
+		
65
+		// Set up demodulator pipeline	
66
+		demodulatorToDecoder := make(chan byte)
67
+		m.demodulator = gortty.NewDemodulator(rttySettings, audioToDemodulator, demodulatorToDecoder)
68
+		decoderToTTY := make(chan rune)
69
+		m.decoder = gortty.NewDecoder(demodulatorToDecoder, decoderToTTY, charset)
70
+		m.ttyReader = NewModemReader(decoderToTTY)
71
+	} else {
72
+		m.ttyReader = os.Stdin
73
+		m.ttyWriter = os.Stdout
74
+	}
75
+		
76
+	go m.cli()
77
+
78
+	if !useStdio {
79
+		// start audio
80
+		stream.Start()
81
+		defer stream.Stop()
82
+		// Run audio output in goroutine
83
+		go func() {
84
+			l := 0
85
+			for v := range modulatorToAudio {
86
+				out[l] = v
87
+				l++
88
+				if l == 64 {
89
+					l = 0
90
+					stream.Write()
91
+				}
92
+			}
93
+		}()
94
+	
95
+		// Run audio input 
96
+		for {
97
+			stream.Read()
98
+			for i := range in {
99
+				audioToDemodulator <- in[i]
100
+			}
101
+		}
102
+	}
103
+	
104
+}
105
+
106
+type ModemReader struct {
107
+	input chan rune
108
+}
109
+
110
+func NewModemReader(c chan rune) *ModemReader {
111
+	m := &ModemReader{}
112
+	m.input = c
113
+	return m
114
+}
115
+
116
+func (m *ModemReader) Read(p []byte) (n int, err error) {
117
+	r := <-m.input
118
+
119
+	fmt.Print(string(r))
120
+
121
+	l := utf8.EncodeRune(p, r)
122
+	return l, nil
123
+}
124
+
125
+func (m *ModemReader) Add(b rune) {
126
+	m.input <- b
127
+}
128
+
129
+type ModemWriter struct {
130
+	output chan rune
131
+}
132
+
133
+func NewModemWriter(c chan rune) *ModemWriter {
134
+	m := &ModemWriter{}
135
+	m.output = c
136
+	return m
137
+}
138
+
139
+func (m *ModemWriter) Write(p []byte) (n int, err error) {
140
+
141
+	runes := bytes.Runes(p)
142
+
143
+	fmt.Print(string(runes))
144
+	
145
+	for _, r := range runes {
146
+		m.output <- r
147
+	}
148
+	return len(p), nil
149
+}
150
+
151
+func (m *ModemWriter) Get() rune {
152
+	return <-m.output
153
+}
154
+
155
+type modem struct {
156
+	ttyReader io.Reader
157
+	ttyWriter io.Writer
158
+	modulator *gortty.Modulator
159
+	demodulator *gortty.Demodulator
160
+	encoder *gortty.Encoder
161
+	decoder *gortty.Decoder
162
+}
163
+
164
+func (m *modem) modulatorCallback(event int, data interface{}) {
165
+	switch event {
166
+	case gortty.EventActive:
167
+		fmt.Println("EventActive")
168
+		m.demodulator.Inhibit(true)
169
+	case gortty.EventIdle:
170
+		fmt.Println("EventIdle")
171
+		m.demodulator.Inhibit(false)
172
+	}
173
+}
174
+
175
+type configFunction func(action int, value string) string
176
+
177
+type configRegister struct {
178
+	help		 string
179
+	f configFunction
180
+}
181
+
182
+const (
183
+	registerGet = iota
184
+	registerSet
185
+)
186
+
187
+func (m *modem) cli() {
188
+
189
+	fmt.Println("RTTY TCP Modem 1.0")
190
+
191
+	autoAnswer := false
192
+	escapeRune := '?'
193
+	modulatorOn := true
194
+	turnaroundDelay := 0
195
+	usos := false
196
+	
197
+	settings := map[string]configRegister{
198
+		"0": {
199
+			help: "auto-answer enable",
200
+			f: func(action int, value string) string {
201
+				switch action {
202
+				case registerGet:
203
+					if autoAnswer {
204
+						return "1"
205
+					} else {
206
+						return "0"
207
+					}
208
+				case registerSet:
209
+					autoAnswer = (value == "1")
210
+					return "OK"
211
+				}
212
+				return ""
213
+			},
214
+		},
215
+		"2": {
216
+			help: "escape character",
217
+			f: func(action int, value string) string {
218
+				switch action {
219
+				case registerGet:
220
+					return string(escapeRune)
221
+				case registerSet:
222
+					runes := []rune(value)
223
+					escapeRune = runes[0]
224
+					return "OK"
225
+				}
226
+				return ""
227
+			},
228
+		},
229
+		"3": {
230
+			help: "modulator enable",
231
+			f: func(action int, value string) string {
232
+				switch action {
233
+				case registerGet:
234
+					if modulatorOn {
235
+						return "1"
236
+					} else {
237
+						return "0"
238
+					}
239
+				case registerSet:
240
+					modulatorOn = (value == "1")
241
+					if m.modulator != nil {
242
+						if modulatorOn {
243
+							m.modulator.SetAmplitude(16384)
244
+						} else {	
245
+							m.modulator.SetAmplitude(0)
246
+						}
247
+					}
248
+					return "OK"
249
+				}
250
+				return ""
251
+			},
252
+		},
253
+		"4": {
254
+			help: "turnaround delay",
255
+			f: func(action int, value string) string {
256
+				switch action {
257
+				case registerGet:
258
+					return fmt.Sprintln(turnaroundDelay)
259
+				case registerSet:
260
+					if d, err := strconv.Atoi(value); err == nil {
261
+						turnaroundDelay = d
262
+						return "OK"
263
+					} else {
264
+						return "ERROR"
265
+					}
266
+				}
267
+				return ""
268
+			},
269
+		},
270
+		"5": {
271
+			help: "unshift on space",
272
+			f: func(action int, value string) string {
273
+				switch action {
274
+				case registerGet:
275
+					if usos {
276
+						return "1"
277
+					} else {
278
+						return "0"
279
+					}
280
+				case registerSet:
281
+					usos = (value == "1")
282
+					m.decoder.UnshiftOnSpace(usos)
283
+					return "OK"
284
+				}
285
+				return ""
286
+			},
287
+		},
288
+	}
289
+	
290
+	localEcho := false
291
+	ttyBufferedReader := bufio.NewReader(m.ttyReader)
292
+	
293
+	var netConn *NetConn
294
+	
295
+	if m.modulator != nil {
296
+		m.modulator.SetCallback(m.modulatorCallback)
297
+	}
298
+	
299
+
300
+	for { 
301
+
302
+		s, _ := ttyBufferedReader.ReadString('\n')
303
+
304
+		//fmt.Println(s) 
305
+		s = strings.ToUpper(s)
306
+		regex, _ := regexp.Compile(`^AT([SHOED?])([0-9])?(\?)?(?:[=|\:]([\S]*))?(?: (.*))?`)
307
+
308
+		params := regex.FindStringSubmatch(s)
309
+
310
+		if params == nil {
311
+			continue
312
+		}
313
+
314
+		command := params[1]
315
+
316
+		var register string
317
+		if len(params) > 2 {
318
+			register = params[2]
319
+		}
320
+
321
+		var registerQuery string
322
+		if len(params) > 3 {
323
+			registerQuery = params[3]
324
+		}
325
+
326
+		var value string
327
+		if len(params) > 4 {
328
+			value = params[4]
329
+		}
330
+
331
+		var parameter string
332
+		if len(params) > 5 {
333
+			parameter = params[5]
334
+		}
335
+
336
+		//fmt.Fprintf(m.ttyWriter, "command: '%s' register: '%s' query: '%s' value: '%s' parameter: '%s'\n", command, register, registerQuery, value, parameter)
337
+		switch command {
338
+		case "?":
339
+			fmt.Fprintf(m.ttyWriter, "Setting Registers\n")
340
+			for s, conf := range settings {
341
+				fmt.Fprintf(m.ttyWriter, "%s: %s (current value: %s)\n", s, conf.help, conf.f(registerGet, ""))
342
+			}
343
+		case "O": // online
344
+			if !netConn.Connected() {
345
+				fmt.Fprintln(m.ttyWriter, "ERROR No active session")
346
+				break
347
+			}
348
+			fmt.Fprintln(m.ttyWriter, "OK")
349
+			dataMode(netConn, ttyBufferedReader, m.ttyWriter, escapeRune)
350
+		case "D": // dial
351
+			if netConn.Connected() {
352
+				fmt.Fprintln(m.ttyWriter, "ERROR Session in progress")
353
+				break
354
+			}
355
+			addr := strings.TrimSpace(parameter)
356
+			
357
+			var err error
358
+			netConn, err = NewNetConn(addr)
359
+			if err != nil {
360
+				fmt.Fprintln(m.ttyWriter, "ERROR", err)
361
+				break
362
+			} else {
363
+				fmt.Fprintln(m.ttyWriter, "CONNECTED")
364
+				dataMode(netConn, ttyBufferedReader, m.ttyWriter, escapeRune)
365
+			}
366
+		case "H": // hangup
367
+			if !netConn.Connected() {
368
+				fmt.Fprintln(m.ttyWriter, "ERROR No active session")
369
+				break
370
+			}
371
+			netConn.Hangup()
372
+			fmt.Fprintln(m.ttyWriter, "OK")
373
+		case "E": // local echo
374
+			localEcho = (register == "1")
375
+			fmt.Fprintln(m.ttyWriter, "local echo", localEcho)
376
+		case "S": // local echo
377
+			if conf, ok := settings[register]; ok {
378
+				if registerQuery == "?" {
379
+					fmt.Fprintln(m.ttyWriter, conf.f(registerGet, ""))
380
+				} else if value != "" {
381
+					fmt.Fprintln(m.ttyWriter, conf.f(registerSet, value))
382
+				} else {
383
+					fmt.Fprintln(m.ttyWriter, "ERROR")
384
+				}
385
+			}
386
+		}
387
+	}
388
+
389
+}
390
+
391
+
392
+
393
+func dataMode(netConn *NetConn, ttyBufferedReader *bufio.Reader, ttyWriter io.Writer, escapeRune rune) {
394
+	
395
+	ttyCancelReader := make(chan struct{})
396
+	ttyReadChan := make(chan rune)
397
+	ttyReadPending := make(chan struct{})
398
+	
399
+	var t *time.Timer
400
+	escapeRunes := 0
401
+
402
+
403
+	go func() {
404
+		for {
405
+			r, l, err := ttyBufferedReader.ReadRune()
406
+			if err != nil && err != io.EOF {
407
+				return
408
+			}
409
+			if l > 0 {
410
+				select {
411
+				case <-ttyCancelReader:
412
+					ttyBufferedReader.UnreadRune() // oops this wasn't ours. let's put it back.
413
+					close(ttyReadPending)
414
+					return
415
+				default:
416
+					ttyReadChan <- r
417
+				}
418
+			}
419
+		}
420
+	}()
421
+	
422
+	netReadChan := netConn.OutputChannel()
423
+	netConn.Resume()
424
+pipeLoop:	
425
+	for {
426
+		var r rune	
427
+		if t == nil {
428
+			select {
429
+				case r = <-ttyReadChan:
430
+				case q := <-netReadChan:
431
+					ttyWriter.Write([]byte(string(q)))
432
+					continue pipeLoop
433
+			}
434
+		} else {
435
+			select {
436
+				case r = <-ttyReadChan:
437
+				case q := <-netReadChan:
438
+					ttyWriter.Write([]byte(string(q)))
439
+					continue pipeLoop
440
+				case <-t.C:
441
+					fmt.Fprintln(ttyWriter, "\nOK")
442
+					close(ttyCancelReader)
443
+					netConn.Pause()
444
+					break pipeLoop
445
+
446
+			}
447
+		}
448
+		if r == escapeRune && escapeRunes < 3 {
449
+			escapeRunes++
450
+		} else {
451
+			if t != nil {
452
+				t.Stop()
453
+			}
454
+			escapeRunes = 0
455
+		}
456
+		if escapeRunes == 3 {
457
+			t = time.NewTimer(1*time.Second)
458
+		}
459
+		err := netConn.Write(r)
460
+		if err != nil {
461
+			fmt.Fprintln(ttyWriter, "NO CARRIER")
462
+			close(ttyCancelReader)
463
+			break pipeLoop
464
+		}
465
+	}
466
+	<- ttyReadPending
467
+	
468
+}
469
+
470
+
471
+
472
+
473
+
474
+
475
+
476
+
477
+type NetConn struct {
478
+	netReader *bufio.Reader
479
+	conn net.Conn
480
+	netReadChan chan rune
481
+	hangup chan struct{}
482
+	paused  bool
483
+	connected bool
484
+}
485
+
486
+func NewNetConn(addr string) (*NetConn, error) {
487
+	c := &NetConn{}
488
+	var err error
489
+	c.conn, err = net.Dial("tcp", addr)
490
+	if err != nil {
491
+		return nil, err
492
+	}
493
+	c.connected = true
494
+
495
+	c.netReader = bufio.NewReader(c.conn)
496
+	
497
+	c.netReadChan = make(chan rune)
498
+	c.hangup = make(chan struct{})
499
+	c.paused = false
500
+	
501
+	go func() {
502
+		for {
503
+			r, l, err := c.netReader.ReadRune()
504
+			if err != nil && err != io.EOF {
505
+				c.cleanup()
506
+				return
507
+			}
508
+			if c.paused || l == 0 {
509
+				select {
510
+				case <- c.hangup:
511
+					c.cleanup()
512
+					return
513
+				default: // discard
514
+				}
515
+			} else {
516
+				select {
517
+				case c.netReadChan <- r:
518
+				case <- c.hangup:
519
+					c.cleanup()
520
+					return
521
+				}
522
+			}
523
+		}
524
+		
525
+	}()
526
+	
527
+	
528
+	return c, nil
529
+}
530
+
531
+func (c *NetConn) Write(r rune) error {
532
+	if c == nil {
533
+		return fmt.Errorf("NetConn not initialized")
534
+	}
535
+	_, err := c.conn.Write([]byte(string(r)))
536
+	if err != nil {	
537
+		close(c.hangup)
538
+	}
539
+	return err
540
+}
541
+
542
+func (c *NetConn) Hangup() {
543
+	if c == nil {
544
+		return
545
+	}
546
+	c.conn.Close()
547
+	close(c.hangup)
548
+}
549
+
550
+func (c *NetConn) cleanup() {
551
+	close(c.netReadChan)
552
+	c.connected = false 
553
+}
554
+
555
+func (c *NetConn) Pause() {
556
+	if c == nil {
557
+		return
558
+	}
559
+	c.paused = true
560
+}
561
+
562
+func (c *NetConn) Resume() {
563
+	if c == nil {
564
+		return
565
+	}
566
+	c.paused = false
567
+}
568
+
569
+func (c *NetConn) OutputChannel() chan rune {
570
+	if c == nil {
571
+		return nil
572
+	}
573
+	return c.netReadChan
574
+}
575
+
576
+func (c *NetConn) Connected() bool {
577
+	if c == nil {
578
+		return false
579
+	}
580
+	return c.connected
581
+}
582
+
583
+
584
+
585
+
586
+
587
+
588
+
589
+
590
+
591
+
592
+