Browse Source

more cleanup

Keelan Lightfoot 6 years ago
parent
commit
1c04e8ec10
4 changed files with 521 additions and 425 deletions
  1. 65
    408
      main.go
  2. 421
    0
      modem.go
  3. 15
    17
      netconn.go
  4. 20
    0
      rttymodem.toml

+ 65
- 408
main.go View File

@@ -1,444 +1,101 @@
1 1
 package main
2 2
 
3 3
 import (
4
+	"context"
5
+	"flag"
4 6
 	"fmt"
5
-
6
-	"bufio"
7
-	"github.com/gordonklaus/portaudio"
7
+	"github.com/BurntSushi/toml"
8 8
 	"github.com/naleek/gortty"
9
-	"io"
10
-	"regexp"
11
-	"strings"
12
-	"time"
9
+	"log"
10
+	"os"
13 11
 	"strconv"
14 12
 )
15 13
 
14
+const (
15
+	verMajor = 0
16
+	verMinor = 1
17
+	verPatch = 0
18
+)
19
+
20
+type config struct {
21
+	Baud           float64           `toml:"baud"`
22
+	StopBits       float64           `toml:"stopbits"`
23
+	DataBits       int               `toml:"databits"`
24
+	SampleRate     int               `toml:"samplerate"`
25
+	MarkFrequency  int               `toml:"markfrequency"`
26
+	SpaceFrequency int               `toml:"spacefrequency"`
27
+	LocalEcho      bool              `toml:"localecho"`
28
+	CodeSet        string            `toml:"codeset"`
29
+	Registers      map[string]string `toml:"registers"`
30
+}
16 31
 
17 32
 func main() {
18 33
 
19
-	m := &modem{}
20
-	
21
-	rttySettings := &gortty.ModemSettings{
22
-		Baud:       45,
23
-		StopBits:   2,
24
-		DataBits:   5,
25
-		SampleRate: 8000,
26
-		OneFreq:    2125,
27
-		ZeroFreq:   2295,
28
-	}
34
+	var configFile string
29 35
 
30
-	charset := gortty.LoadCharset(&gortty.USTTY)
36
+	flag.StringVar(&configFile, "c", "rttymodem.toml", "Configuration file path")
31 37
 
32
-	in := make([]int16, 64)
33
-	out := make([]int16, 64)
34
-	var stream *portaudio.Stream
35
-	
36
-	useStdio := false
37
-	
38
-	if !useStdio {
39
-		// Set up audio with system default input and output
40
-		portaudio.Initialize()
41
-		defer portaudio.Terminate()
42
-		var err error
43
-		stream, err = portaudio.OpenDefaultStream(1, 1, float64(rttySettings.SampleRate), len(in), in, out)
44
-		if err != nil {
45
-			panic(err)
46
-		}
47
-		defer stream.Close()
48
-	}
49
-	modulatorToAudio := make(chan int16)
50
-	audioToDemodulator := make(chan int16)
38
+	showHelp := flag.Bool("h", false, "Show usage and exit")
39
+	showVersion := flag.Bool("v", false, "Show version and exit")
51 40
 
52
-	if !useStdio {
53
-		// Set up modulator pipeline
54
-		ttyToEncoder := make(chan rune, 100)
55
-		encoderToModulator := make(chan byte)
56
-		m.encoder = gortty.NewEncoder(ttyToEncoder, encoderToModulator, charset)
57
-		m.modulator = gortty.NewModulator(rttySettings, encoderToModulator, modulatorToAudio)
58
-		m.modulator.SetIdleDelay(1000)
59
-		
60
-		// Set up demodulator pipeline	
61
-		demodulatorToDecoder := make(chan byte)
62
-		m.demodulator = gortty.NewDemodulator(rttySettings, audioToDemodulator, demodulatorToDecoder)
63
-		decoderToTTY := make(chan rune)
64
-		m.decoder = gortty.NewDecoder(demodulatorToDecoder, decoderToTTY, charset)
65
-	}
66
-		
67
-	go m.cli()
41
+	flag.Parse()
68 42
 
69
-	if !useStdio {
70
-		// start audio
71
-		stream.Start()
72
-		defer stream.Stop()
73
-		// Run audio output in goroutine
74
-		go func() {
75
-			l := 0
76
-			for v := range modulatorToAudio {
77
-				out[l] = v
78
-				l++
79
-				if l == 64 {
80
-					l = 0
81
-					stream.Write()
82
-				}
83
-			}
84
-		}()
85
-	
86
-		// Run audio input 
87
-		for {
88
-			stream.Read()
89
-			for i := range in {
90
-				audioToDemodulator <- in[i]
91
-			}
92
-		}
43
+	if *showHelp {
44
+		flag.PrintDefaults()
45
+		os.Exit(0)
93 46
 	}
94
-	
95
-}
96
-
97
-//type ModemReader struct {
98
-//	input chan rune
99
-//}
100
-//
101
-//func NewModemReader(c chan rune) *ModemReader {
102
-//	m := &ModemReader{}
103
-//	m.input = c
104
-//	return m
105
-//}
106
-//
107
-//func (m *ModemReader) Read(p []byte) (n int, err error) {
108
-//	r := <-m.input
109
-//	l := utf8.EncodeRune(p, r)
110
-//	return l, nil
111
-//}
112 47
 
113
-
114
-type modem struct {
115
-	ttyReader io.Reader
116
-	ttyWriter io.Writer
117
-	modulator *gortty.Modulator
118
-	demodulator *gortty.Demodulator
119
-	encoder *gortty.Encoder
120
-	decoder *gortty.Decoder
121
-}
122
-
123
-func (m *modem) modulatorCallback(event int, data interface{}) {
124
-	switch event {
125
-	case gortty.EventActive:
126
-		fmt.Println("EventActive")
127
-		m.demodulator.Inhibit(true)
128
-	case gortty.EventIdle:
129
-		fmt.Println("EventIdle")
130
-		m.demodulator.Inhibit(false)
48
+	if *showVersion {
49
+		fmt.Printf("%d.%d.%d\n", verMajor, verMinor, verPatch)
50
+		os.Exit(0)
131 51
 	}
132
-}
133 52
 
134
-type configFunction func(action int, value string) string
53
+	log.Printf("starting up with config from %s")
135 54
 
136
-type configRegister struct {
137
-	help		 string
138
-	f configFunction
139
-}
140
-
141
-const (
142
-	registerGet = iota
143
-	registerSet
144
-)
145
-
146
-func (m *modem) cli() {
55
+	conf := &config{}
147 56
 
148
-	fmt.Println("RTTY TCP Modem 1.0")
149
-
150
-	autoAnswer := false
151
-	escapeRune := '?'
152
-	modulatorOn := true
153
-	turnaroundDelay := 0
154
-	usos := false
155
-	
156
-	settings := map[string]configRegister{
157
-		"0": {
158
-			help: "auto-answer enable",
159
-			f: func(action int, value string) string {
160
-				switch action {
161
-				case registerGet:
162
-					if autoAnswer {
163
-						return "1"
164
-					} else {
165
-						return "0"
166
-					}
167
-				case registerSet:
168
-					autoAnswer = (value == "1")
169
-					return "OK"
170
-				}
171
-				return ""
172
-			},
173
-		},
174
-		"2": {
175
-			help: "escape character",
176
-			f: func(action int, value string) string {
177
-				switch action {
178
-				case registerGet:
179
-					return string(escapeRune)
180
-				case registerSet:
181
-					runes := []rune(value)
182
-					escapeRune = runes[0]
183
-					return "OK"
184
-				}
185
-				return ""
186
-			},
187
-		},
188
-		"3": {
189
-			help: "modulator enable",
190
-			f: func(action int, value string) string {
191
-				switch action {
192
-				case registerGet:
193
-					if modulatorOn {
194
-						return "1"
195
-					} else {
196
-						return "0"
197
-					}
198
-				case registerSet:
199
-					modulatorOn = (value == "1")
200
-					if m.modulator != nil {
201
-						if modulatorOn {
202
-							m.modulator.SetAmplitude(16384)
203
-						} else {	
204
-							m.modulator.SetAmplitude(0)
205
-						}
206
-					}
207
-					return "OK"
208
-				}
209
-				return ""
210
-			},
211
-		},
212
-		"4": {
213
-			help: "turnaround delay",
214
-			f: func(action int, value string) string {
215
-				switch action {
216
-				case registerGet:
217
-					return fmt.Sprintln(turnaroundDelay)
218
-				case registerSet:
219
-					if d, err := strconv.Atoi(value); err == nil {
220
-						turnaroundDelay = d
221
-						return "OK"
222
-					} else {
223
-						return "ERROR"
224
-					}
225
-				}
226
-				return ""
227
-			},
228
-		},
229
-		"5": {
230
-			help: "unshift on space",
231
-			f: func(action int, value string) string {
232
-				switch action {
233
-				case registerGet:
234
-					if usos {
235
-						return "1"
236
-					} else {
237
-						return "0"
238
-					}
239
-				case registerSet:
240
-					usos = (value == "1")
241
-					m.decoder.UnshiftOnSpace(usos)
242
-					return "OK"
243
-				}
244
-				return ""
245
-			},
246
-		},
247
-	}
248
-	
249
-	localEcho := false
250
-	ttyBufferedReader := bufio.NewReader(m.decoder)
251
-	
252
-	var netConn *NetConn
253
-	
254
-	if m.modulator != nil {
255
-		m.modulator.SetCallback(m.modulatorCallback)
57
+	if _, err := toml.DecodeFile(configFile, &conf); err != nil {
58
+		log.Fatal(err)
256 59
 	}
257
-	
258
-
259
-	for { 
260
-
261
-		s, _ := ttyBufferedReader.ReadString('\n')
262
-
263
-		//fmt.Println(s) 
264
-		s = strings.ToUpper(s)
265
-		regex, _ := regexp.Compile(`^AT([SHOED?])([0-9])?(\?)?(?:[=|\:]([\S]*))?(?: (.*))?`)
266
-
267
-		params := regex.FindStringSubmatch(s)
268
-
269
-		if params == nil {
270
-			continue
271
-		}
272
-
273
-		command := params[1]
274
-
275
-		var register string
276
-		if len(params) > 2 {
277
-			register = params[2]
278
-		}
279 60
 
280
-		var registerQuery string
281
-		if len(params) > 3 {
282
-			registerQuery = params[3]
283
-		}
284
-
285
-		var value string
286
-		if len(params) > 4 {
287
-			value = params[4]
288
-		}
289
-
290
-		var parameter string
291
-		if len(params) > 5 {
292
-			parameter = params[5]
293
-		}
294
-
295
-		//fmt.Fprintf(m.encoder, "command: '%s' register: '%s' query: '%s' value: '%s' parameter: '%s'\n", command, register, registerQuery, value, parameter)
296
-		switch command {
297
-		case "?":
298
-			fmt.Fprintf(m.encoder, "Setting Registers\n")
299
-			for s, conf := range settings {
300
-				fmt.Fprintf(m.encoder, "%s: %s (current value: %s)\n", s, conf.help, conf.f(registerGet, ""))
301
-			}
302
-		case "O": // online
303
-			if !netConn.Connected() {
304
-				fmt.Fprintln(m.encoder, "ERROR No active session")
305
-				break
306
-			}
307
-			fmt.Fprintln(m.encoder, "OK")
308
-			dataMode(netConn, ttyBufferedReader, m.encoder, escapeRune)
309
-		case "D": // dial
310
-			if netConn.Connected() {
311
-				fmt.Fprintln(m.encoder, "ERROR Session in progress")
312
-				break
313
-			}
314
-			addr := strings.TrimSpace(parameter)
315
-			
316
-			var err error
317
-			netConn, err = NewNetConn(addr)
318
-			if err != nil {
319
-				fmt.Fprintln(m.encoder, "ERROR", err)
320
-				break
321
-			} else {
322
-				fmt.Fprintln(m.encoder, "CONNECTED")
323
-				dataMode(netConn, ttyBufferedReader, m.encoder, escapeRune)
324
-			}
325
-		case "H": // hangup
326
-			if !netConn.Connected() {
327
-				fmt.Fprintln(m.encoder, "ERROR No active session")
328
-				break
329
-			}
330
-			netConn.Hangup()
331
-			fmt.Fprintln(m.encoder, "OK")
332
-		case "E": // local echo
333
-			localEcho = (register == "1")
334
-			fmt.Fprintln(m.encoder, "local echo", localEcho)
335
-		case "S": // local echo
336
-			if conf, ok := settings[register]; ok {
337
-				if registerQuery == "?" {
338
-					fmt.Fprintln(m.encoder, conf.f(registerGet, ""))
339
-				} else if value != "" {
340
-					fmt.Fprintln(m.encoder, conf.f(registerSet, value))
341
-				} else {
342
-					fmt.Fprintln(m.encoder, "ERROR")
343
-				}
344
-			}
345
-		}
61
+	s := &gortty.ModemSettings{
62
+		Baud:       conf.Baud,
63
+		StopBits:   conf.StopBits,
64
+		DataBits:   conf.DataBits,
65
+		SampleRate: conf.SampleRate,
66
+		OneFreq:    conf.MarkFrequency,
67
+		ZeroFreq:   conf.SpaceFrequency,
346 68
 	}
347 69
 
348
-}
349
-
70
+	if conf.CodeSet == "" {
71
+		conf.CodeSet = "USTTY"
72
+	}
350 73
 
74
+	c, err := gortty.LoadCodeSet(conf.CodeSet)
75
+	if err != nil {
76
+		log.Fatal(err)
77
+	}
351 78
 
352
-func dataMode(netConn *NetConn, ttyBufferedReader *bufio.Reader, ttyWriter io.Writer, escapeRune rune) {
353
-	
354
-	ttyCancelReader := make(chan struct{})
355
-	ttyReadChan := make(chan rune)
356
-	ttyReadPending := make(chan struct{})
357
-	
358
-	var t *time.Timer
359
-	escapeRunes := 0
79
+	ctx, cancel := context.WithCancel(context.Background())
360 80
 
81
+	m, err := NewModem(ctx, s, c)
82
+	if err != nil {
83
+		log.Fatal(err)
84
+	}
361 85
 
362
-	go func() {
363
-		for {
364
-			r, l, err := ttyBufferedReader.ReadRune()
365
-			if err != nil && err != io.EOF {
366
-				return
367
-			}
368
-			if l > 0 {
369
-				select {
370
-				case <-ttyCancelReader:
371
-					ttyBufferedReader.UnreadRune() // oops this wasn't ours. let's put it back.
372
-					close(ttyReadPending)
373
-					return
374
-				default:
375
-					ttyReadChan <- r
376
-				}
377
-			}
378
-		}
379
-	}()
380
-	
381
-	netReadChan := netConn.OutputChannel()
382
-	netConn.Resume()
383
-pipeLoop:	
384
-	for {
385
-		var r rune	
386
-		if t == nil {
387
-			select {
388
-				case r = <-ttyReadChan:
389
-				case q := <-netReadChan:
390
-					ttyWriter.Write([]byte(string(q)))
391
-					continue pipeLoop
392
-			}
393
-		} else {
394
-			select {
395
-				case r = <-ttyReadChan:
396
-				case q := <-netReadChan:
397
-					ttyWriter.Write([]byte(string(q)))
398
-					continue pipeLoop
399
-				case <-t.C:
400
-					fmt.Fprintln(ttyWriter, "\nOK")
401
-					close(ttyCancelReader)
402
-					netConn.Pause()
403
-					break pipeLoop
86
+	m.SetLocalEcho(conf.LocalEcho)
404 87
 
405
-			}
406
-		}
407
-		if r == escapeRune && escapeRunes < 3 {
408
-			escapeRunes++
409
-		} else {
410
-			if t != nil {
411
-				t.Stop()
412
-			}
413
-			escapeRunes = 0
414
-		}
415
-		if escapeRunes == 3 {
416
-			t = time.NewTimer(1*time.Second)
417
-		}
418
-		err := netConn.Write(r)
88
+	for i, r := range conf.Registers {
89
+		n, err := strconv.Atoi(i)
419 90
 		if err != nil {
420
-			fmt.Fprintln(ttyWriter, "NO CARRIER")
421
-			close(ttyCancelReader)
422
-			break pipeLoop
91
+			continue
423 92
 		}
93
+		m.SetRegister(n, r)
424 94
 	}
425
-	<- ttyReadPending
426
-	
427
-}
428
-
429
-
430
-
431
-
432
-
433
-
434
-
435
-
436
-
437
-
438
-
439
-
440
-
441
-
442 95
 
96
+	defer m.Close()
443 97
 
98
+	m.run()
444 99
 
100
+	cancel()
101
+}

+ 421
- 0
modem.go View File

@@ -0,0 +1,421 @@
1
+package main
2
+
3
+import (
4
+	"bufio"
5
+	"context"
6
+	"fmt"
7
+	"io"
8
+	"regexp"
9
+	"strconv"
10
+	"strings"
11
+	"time"
12
+
13
+	"github.com/gordonklaus/portaudio"
14
+	"github.com/naleek/gortty"
15
+)
16
+
17
+type Modem struct {
18
+	ttyReader       io.Reader
19
+	ttyWriter       io.Writer
20
+	modulator       *gortty.Modulator
21
+	demodulator     *gortty.Demodulator
22
+	encoder         *gortty.Encoder
23
+	decoder         *gortty.Decoder
24
+	stream          *portaudio.Stream
25
+	registers       map[int]configRegister
26
+	autoAnswer      bool
27
+	escapeRune      rune
28
+	modulatorOn     bool
29
+	turnaroundDelay int
30
+	usos            bool
31
+	localEcho       bool
32
+}
33
+
34
+func (m *Modem) Close() {
35
+	m.stream.Close()
36
+	portaudio.Terminate()
37
+}
38
+
39
+func (m *Modem) SetLocalEcho(e bool) {
40
+	m.localEcho = e
41
+}
42
+
43
+func (m *Modem) GetRegister(index int) (string, bool) {
44
+	if r, ok := m.registers[index]; ok {
45
+		return r.f(registerGet, ""), true
46
+	}
47
+	return "", false
48
+}
49
+
50
+func (m *Modem) SetRegister(index int, value string) bool {
51
+	if r, ok := m.registers[index]; ok {
52
+		r.f(registerSet, value)
53
+		return true
54
+	} else {
55
+		return false
56
+	}
57
+}
58
+
59
+func NewModem(ctx context.Context, rttySettings *gortty.ModemSettings, charset *gortty.CodeSet) (*Modem, error) {
60
+	m := &Modem{}
61
+
62
+	m.registers = map[int]configRegister{
63
+		0: {
64
+			help: "auto-answer enable",
65
+			f: func(action int, value string) string {
66
+				switch action {
67
+				case registerGet:
68
+					if m.autoAnswer {
69
+						return "1"
70
+					} else {
71
+						return "0"
72
+					}
73
+				case registerSet:
74
+					m.autoAnswer = (value == "1")
75
+					return "OK"
76
+				}
77
+				return ""
78
+			},
79
+		},
80
+		2: {
81
+			help: "escape character",
82
+			f: func(action int, value string) string {
83
+				switch action {
84
+				case registerGet:
85
+					return string(m.escapeRune)
86
+				case registerSet:
87
+					runes := []rune(value)
88
+					m.escapeRune = runes[0]
89
+					return "OK"
90
+				}
91
+				return ""
92
+			},
93
+		},
94
+		101: {
95
+			help: "modulator enable",
96
+			f: func(action int, value string) string {
97
+				switch action {
98
+				case registerGet:
99
+					if m.modulatorOn {
100
+						return "1"
101
+					} else {
102
+						return "0"
103
+					}
104
+				case registerSet:
105
+					m.modulatorOn = (value == "1")
106
+					if m.modulator != nil {
107
+						if m.modulatorOn {
108
+							m.modulator.SetAmplitude(16384)
109
+						} else {
110
+							m.modulator.SetAmplitude(0)
111
+						}
112
+					}
113
+					return "OK"
114
+				}
115
+				return ""
116
+			},
117
+		},
118
+		102: {
119
+			help: "turnaround delay",
120
+			f: func(action int, value string) string {
121
+				switch action {
122
+				case registerGet:
123
+					return fmt.Sprintln(m.turnaroundDelay)
124
+				case registerSet:
125
+					if d, err := strconv.Atoi(value); err == nil {
126
+						m.turnaroundDelay = d
127
+						return "OK"
128
+					} else {
129
+						return "ERROR"
130
+					}
131
+				}
132
+				return ""
133
+			},
134
+		},
135
+		103: {
136
+			help: "unshift on space",
137
+			f: func(action int, value string) string {
138
+				switch action {
139
+				case registerGet:
140
+					if m.usos {
141
+						return "1"
142
+					} else {
143
+						return "0"
144
+					}
145
+				case registerSet:
146
+					m.usos = (value == "1")
147
+					m.decoder.UnshiftOnSpace(m.usos)
148
+					return "OK"
149
+				}
150
+				return ""
151
+			},
152
+		},
153
+	}
154
+
155
+	m.autoAnswer = false
156
+	m.escapeRune = '?'
157
+	m.modulatorOn = true
158
+	m.turnaroundDelay = 0
159
+	m.usos = false
160
+
161
+	in := make([]int16, 64)
162
+	out := make([]int16, 64)
163
+
164
+	// Set up audio with system default input and output
165
+	portaudio.Initialize()
166
+	var err error
167
+	m.stream, err = portaudio.OpenDefaultStream(1, 1, float64(rttySettings.SampleRate), len(in), in, out)
168
+	if err != nil {
169
+		panic(err)
170
+	}
171
+
172
+	modulatorToAudio := make(chan int16)
173
+	audioToDemodulator := make(chan int16)
174
+
175
+	// Set up modulator pipeline
176
+	encoderToModulator := make(chan byte)
177
+	m.encoder = gortty.NewEncoder(ctx, nil, encoderToModulator, charset)
178
+	m.modulator = gortty.NewModulator(ctx, rttySettings, encoderToModulator, modulatorToAudio)
179
+	m.modulator.SetIdleDelay(1000)
180
+
181
+	// Set up demodulator pipeline
182
+	demodulatorToDecoder := make(chan byte)
183
+	m.demodulator = gortty.NewDemodulator(ctx, rttySettings, audioToDemodulator, demodulatorToDecoder)
184
+	m.decoder = gortty.NewDecoder(ctx, demodulatorToDecoder, nil, charset)
185
+
186
+	// start audio
187
+	m.stream.Start()
188
+
189
+	// Audio output
190
+	go func() {
191
+		l := 0
192
+		for v := range modulatorToAudio {
193
+			out[l] = v
194
+			l++
195
+			if l == 64 {
196
+				l = 0
197
+				m.stream.Write()
198
+			}
199
+		}
200
+	}()
201
+
202
+	// Audio input
203
+	go func() {
204
+		for {
205
+			m.stream.Read()
206
+			for i := range in {
207
+				audioToDemodulator <- in[i]
208
+			}
209
+		}
210
+	}()
211
+
212
+	return m, nil
213
+}
214
+
215
+func (m *Modem) modulatorCallback(event int, data interface{}) {
216
+	switch event {
217
+	case gortty.EventActive:
218
+		fmt.Println("EventActive")
219
+		m.demodulator.Inhibit(true)
220
+	case gortty.EventIdle:
221
+		fmt.Println("EventIdle")
222
+		m.demodulator.Inhibit(false)
223
+	}
224
+}
225
+
226
+type configFunction func(action int, value string) string
227
+
228
+type configRegister struct {
229
+	help string
230
+	f    configFunction
231
+}
232
+
233
+const (
234
+	registerGet = iota
235
+	registerSet
236
+)
237
+
238
+func (m *Modem) run() {
239
+
240
+	ttyBufferedReader := bufio.NewReader(m.decoder)
241
+
242
+	var netConn *NetConn
243
+
244
+	if m.modulator != nil {
245
+		m.modulator.SetCallback(m.modulatorCallback)
246
+	}
247
+
248
+	for {
249
+
250
+		s, _ := ttyBufferedReader.ReadString('\n')
251
+
252
+		//fmt.Println(s)
253
+		s = strings.ToUpper(s)
254
+		regex, _ := regexp.Compile(`^AT([SHOED?])([0-9])?(\?)?(?:[=|\:]([\S]*))?(?: (.*))?`)
255
+
256
+		params := regex.FindStringSubmatch(s)
257
+
258
+		if params == nil {
259
+			continue
260
+		}
261
+
262
+		command := params[1]
263
+
264
+		var register string
265
+		if len(params) > 2 {
266
+			register = params[2]
267
+		}
268
+
269
+		var registerQuery string
270
+		if len(params) > 3 {
271
+			registerQuery = params[3]
272
+		}
273
+
274
+		var value string
275
+		if len(params) > 4 {
276
+			value = params[4]
277
+		}
278
+
279
+		var parameter string
280
+		if len(params) > 5 {
281
+			parameter = params[5]
282
+		}
283
+
284
+		//fmt.Fprintf(m.encoder, "command: '%s' register: '%s' query: '%s' value: '%s' parameter: '%s'\n", command, register, registerQuery, value, parameter)
285
+		switch command {
286
+		case "?":
287
+			fmt.Fprintf(m.encoder, "Setting Registers\n")
288
+			for s, conf := range m.registers {
289
+				fmt.Fprintf(m.encoder, "%s: %s (current value: %s)\n", s, conf.help, conf.f(registerGet, ""))
290
+			}
291
+		case "O": // online
292
+			if !netConn.Connected() {
293
+				fmt.Fprintln(m.encoder, "ERROR No active session")
294
+				break
295
+			}
296
+			fmt.Fprintln(m.encoder, "OK")
297
+			dataMode(netConn, ttyBufferedReader, m.encoder, m.escapeRune)
298
+		case "D": // dial
299
+			if netConn.Connected() {
300
+				fmt.Fprintln(m.encoder, "ERROR Session in progress")
301
+				break
302
+			}
303
+			addr := strings.TrimSpace(parameter)
304
+
305
+			var err error
306
+			netConn, err = NewNetConn(addr)
307
+			if err != nil {
308
+				fmt.Fprintln(m.encoder, "ERROR", err)
309
+				break
310
+			} else {
311
+				fmt.Fprintln(m.encoder, "CONNECTED")
312
+				dataMode(netConn, ttyBufferedReader, m.encoder, m.escapeRune)
313
+			}
314
+		case "H": // hangup
315
+			if !netConn.Connected() {
316
+				fmt.Fprintln(m.encoder, "ERROR No active session")
317
+				break
318
+			}
319
+			netConn.Hangup()
320
+			fmt.Fprintln(m.encoder, "OK")
321
+		case "E": // local echo
322
+			m.localEcho = (register == "1")
323
+			fmt.Fprintln(m.encoder, "local echo", m.localEcho)
324
+		case "S": // register
325
+			r, _ := strconv.Atoi(register)
326
+			if registerQuery == "?" { // get
327
+				if v, ok := m.GetRegister(r); ok {
328
+					fmt.Fprintln(m.encoder, v)
329
+				} else {
330
+					fmt.Fprintln(m.encoder, "ERROR")
331
+				}
332
+			} else if value != "" { //set
333
+				if m.SetRegister(r, value) {
334
+					fmt.Fprintln(m.encoder, "OK")
335
+				} else {
336
+					fmt.Fprintln(m.encoder, "ERROR")
337
+				}
338
+			} else {
339
+				fmt.Fprintln(m.encoder, "ERROR")
340
+			}
341
+
342
+		}
343
+	}
344
+
345
+}
346
+
347
+func dataMode(netConn *NetConn, ttyBufferedReader *bufio.Reader, ttyWriter io.Writer, escapeRune rune) {
348
+
349
+	ttyCancelReader := make(chan struct{})
350
+	ttyReadChan := make(chan rune)
351
+	ttyReadPending := make(chan struct{})
352
+
353
+	var t *time.Timer
354
+	escapeRunes := 0
355
+
356
+	go func() {
357
+		for {
358
+			r, l, err := ttyBufferedReader.ReadRune()
359
+			if err != nil && err != io.EOF {
360
+				return
361
+			}
362
+			if l > 0 {
363
+				select {
364
+				case <-ttyCancelReader:
365
+					ttyBufferedReader.UnreadRune() // oops this wasn't ours. let's put it back.
366
+					close(ttyReadPending)
367
+					return
368
+				default:
369
+					ttyReadChan <- r
370
+				}
371
+			}
372
+		}
373
+	}()
374
+
375
+	netReadChan := netConn.OutputChannel()
376
+	netConn.Resume()
377
+pipeLoop:
378
+	for {
379
+		var r rune
380
+		if t == nil {
381
+			select {
382
+			case r = <-ttyReadChan:
383
+			case q := <-netReadChan:
384
+				ttyWriter.Write([]byte(string(q)))
385
+				continue pipeLoop
386
+			}
387
+		} else {
388
+			select {
389
+			case r = <-ttyReadChan:
390
+			case q := <-netReadChan:
391
+				ttyWriter.Write([]byte(string(q)))
392
+				continue pipeLoop
393
+			case <-t.C:
394
+				fmt.Fprintln(ttyWriter, "\nOK")
395
+				close(ttyCancelReader)
396
+				netConn.Pause()
397
+				break pipeLoop
398
+
399
+			}
400
+		}
401
+		if r == escapeRune && escapeRunes < 3 {
402
+			escapeRunes++
403
+		} else {
404
+			if t != nil {
405
+				t.Stop()
406
+			}
407
+			escapeRunes = 0
408
+		}
409
+		if escapeRunes == 3 {
410
+			t = time.NewTimer(1 * time.Second)
411
+		}
412
+		err := netConn.Write(r)
413
+		if err != nil {
414
+			fmt.Fprintln(ttyWriter, "NO CARRIER")
415
+			close(ttyCancelReader)
416
+			break pipeLoop
417
+		}
418
+	}
419
+	<-ttyReadPending
420
+
421
+}

+ 15
- 17
netconn.go View File

@@ -2,18 +2,18 @@ package main
2 2
 
3 3
 import (
4 4
 	"bufio"
5
-	"net"
6
-	"io"
7 5
 	"fmt"
6
+	"io"
7
+	"net"
8 8
 )
9 9
 
10 10
 type NetConn struct {
11
-	netReader *bufio.Reader
12
-	conn net.Conn
11
+	netReader   *bufio.Reader
12
+	conn        net.Conn
13 13
 	netReadChan chan rune
14
-	hangup chan struct{}
15
-	paused  bool
16
-	connected bool
14
+	hangup      chan struct{}
15
+	paused      bool
16
+	connected   bool
17 17
 }
18 18
 
19 19
 func NewNetConn(addr string) (*NetConn, error) {
@@ -26,11 +26,11 @@ func NewNetConn(addr string) (*NetConn, error) {
26 26
 	c.connected = true
27 27
 
28 28
 	c.netReader = bufio.NewReader(c.conn)
29
-	
29
+
30 30
 	c.netReadChan = make(chan rune)
31 31
 	c.hangup = make(chan struct{})
32 32
 	c.paused = false
33
-	
33
+
34 34
 	go func() {
35 35
 		for {
36 36
 			r, l, err := c.netReader.ReadRune()
@@ -40,7 +40,7 @@ func NewNetConn(addr string) (*NetConn, error) {
40 40
 			}
41 41
 			if c.paused || l == 0 {
42 42
 				select {
43
-				case <- c.hangup:
43
+				case <-c.hangup:
44 44
 					c.cleanup()
45 45
 					return
46 46
 				default: // discard
@@ -48,16 +48,15 @@ func NewNetConn(addr string) (*NetConn, error) {
48 48
 			} else {
49 49
 				select {
50 50
 				case c.netReadChan <- r:
51
-				case <- c.hangup:
51
+				case <-c.hangup:
52 52
 					c.cleanup()
53 53
 					return
54 54
 				}
55 55
 			}
56 56
 		}
57
-		
57
+
58 58
 	}()
59
-	
60
-	
59
+
61 60
 	return c, nil
62 61
 }
63 62
 
@@ -66,7 +65,7 @@ func (c *NetConn) Write(r rune) error {
66 65
 		return fmt.Errorf("NetConn not initialized")
67 66
 	}
68 67
 	_, err := c.conn.Write([]byte(string(r)))
69
-	if err != nil {	
68
+	if err != nil {
70 69
 		close(c.hangup)
71 70
 	}
72 71
 	return err
@@ -82,7 +81,7 @@ func (c *NetConn) Hangup() {
82 81
 
83 82
 func (c *NetConn) cleanup() {
84 83
 	close(c.netReadChan)
85
-	c.connected = false 
84
+	c.connected = false
86 85
 }
87 86
 
88 87
 func (c *NetConn) Pause() {
@@ -112,4 +111,3 @@ func (c *NetConn) Connected() bool {
112 111
 	}
113 112
 	return c.connected
114 113
 }
115
-

+ 20
- 0
rttymodem.toml View File

@@ -0,0 +1,20 @@
1
+
2
+baud = 45.45
3
+stopbits = 1.42
4
+databits = 5
5
+samplerate = 8000
6
+markfrequency = 2125
7
+spacefrequency = 2295
8
+
9
+codeset = "USTTY"
10
+
11
+localecho = false
12
+
13
+
14
+[registers]
15
+0   = "0"   	# auto-answer
16
+2   = "?"		# escape character
17
+101 = "1"		# modulator enable
18
+102 = "0"		# turnaround delay
19
+103 = "0"		# unshift on space
20
+104 = ":3333"	# listen address