diff --git a/.gitignore b/.gitignore index 66fd13c..99b794f 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ # Dependency directories (remove the comment below to include it) # vendor/ + +bin/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3feba30 --- /dev/null +++ b/Makefile @@ -0,0 +1,30 @@ +.PHONY: get deps test modtidy gofmt vet modlist + +GO111MODULE=on + +all: mhs5200a + +mhs5200a: main.go mhs5200a.go script.go + go build -o bin/mhs5200a + +get: + go get -u $(PKG) + +deps: + go get -d ./... + +test: + go test + +modtidy: + go mod tidy + +gofmt: + go fmt + +vet: + go vet + +modlist: + go list -m all + diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..56e6d09 --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module github.com/peterska/go-mhs5200a + +go 1.15 + +require ( + github.com/peterska/go-utils v1.0.3 + github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 + golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect + golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..2213dbc --- /dev/null +++ b/go.sum @@ -0,0 +1,19 @@ +github.com/peterska/go-utils v1.0.3 h1:xWPzuqq1lPNDfahwTpIa5XabN8pFgGFJ5vo9IvV/5ow= +github.com/peterska/go-utils v1.0.3/go.mod h1:/yv00nFZ9awb3XR7d9p0D3YSwO2DlFjMbLHosysPEho= +github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 h1:UyzmZLoiDWMRywV4DUYb9Fbt8uiOSooupjTq10vpvnU= +github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M= +golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/main.go b/main.go new file mode 100644 index 0000000..81f2364 --- /dev/null +++ b/main.go @@ -0,0 +1,351 @@ +/* + * + * cmdline utility to configure and control the MHS-5200A series for function generators + * + * Copyright (c) 2020 - 2021 Peter Skarpetis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package main + +import ( + "flag" + "fmt" + "github.com/peterska/go-utils" + "os" + "strconv" + "time" +) + +func main() { + var verbose = flag.Int("v", 0, "verbose level") + var debug = flag.Int("debug", 0, "debug level, 0=production, >0 is devmode") + var pprof = flag.Bool("pprof", false, "enable golang profling") + var port = flag.String("port", "/dev/ttyUSB0", "port the MHS-5200A is connected to") + var scriptfile = flag.String("script", "", "json script file") + flag.Parse() + + goutils.SetDebuglevel(*debug) + goutils.SetLoglevel(*verbose) + goutils.SetProfiling(*pprof) + + if len(*scriptfile) > 0 { + err := playbackScript(*scriptfile, *port) + if err != nil { + goutils.Log.Print(err) + os.Exit(10) + } + } + if len(flag.Args()) == 0 { // nothing to do + return + } + mhs5200, err := NewMHS5200A(*port) + if err != nil { + goutils.Log.Print(err) + os.Exit(10) + } + if mhs5200 == nil { + return + } + defer mhs5200.Close() + channel := uint(1) + needparam := false + cmd := "" + param := "" + for _, argv := range flag.Args() { + if needparam { + param = argv + needparam = false + } else { + cmd = argv + param = "" + } + switch cmd { + case "channel": + if len(param) == 0 { + needparam = true + continue + } + v, err := strconv.ParseUint(param, 10, 32) + if err != nil { + goutils.Log.Printf("%v, %v\n", goutils.Funcname(), err) + break + } + channel = uint(v) + err = mhs5200.SelectChannel(channel) + if err != nil { + goutils.Log.Printf("%v, %v\n", goutils.Funcname(), err) + break + } + + case "sleep": + if len(param) == 0 { + needparam = true + continue + } + v, err := strconv.ParseUint(param, 10, 32) + if err != nil { + goutils.Log.Printf("%v, %v\n", goutils.Funcname(), err) + break + } + fmt.Printf("Sleeping for %v seconds\n", v) + time.Sleep(time.Duration(v) * time.Second) + + case "delay": + if len(param) == 0 { + needparam = true + continue + } + v, err := strconv.ParseUint(param, 10, 32) + if err != nil { + goutils.Log.Printf("%v, %v\n", goutils.Funcname(), err) + break + } + fmt.Printf("Sleeping for %v seconds\n", v) + time.Sleep(time.Duration(v) * time.Second) + + case "showconfig": + var err error + err = mhs5200.ShowChannelConfig(channel) + if err != nil { + goutils.Log.Printf("%v, %v\n", goutils.Funcname(), err) + } + + case "showsweep": + err = mhs5200.ShowSweepConfig() + if err != nil { + goutils.Log.Printf("%v, %v\n", goutils.Funcname(), err) + } + + case "frequency": + if len(param) == 0 { + needparam = true + continue + } + v, err := strconv.ParseFloat(param, 64) + if err != nil { + goutils.Log.Printf("%v, %v\n", goutils.Funcname(), err) + break + } + err = mhs5200.SetFrequency(channel, v) + if err != nil { + goutils.Log.Printf("%v, %v\n", goutils.Funcname(), err) + break + } + + case "waveform": + if len(param) == 0 { + needparam = true + continue + } + err = mhs5200.SetWaveformFromString(channel, param) + if err != nil { + goutils.Log.Printf("%v, %v\n", goutils.Funcname(), err) + } + + case "amplitude": + if len(param) == 0 { + needparam = true + continue + } + v, err := strconv.ParseFloat(param, 64) + if err != nil { + goutils.Log.Printf("%v, %v\n", goutils.Funcname(), err) + break + } + err = mhs5200.SetAmplitude(channel, v) + if err != nil { + goutils.Log.Printf("%v, %v\n", goutils.Funcname(), err) + } + + case "duty": + if len(param) == 0 { + needparam = true + continue + } + v, err := strconv.ParseFloat(param, 64) + if err != nil { + goutils.Log.Printf("%v, %v\n", goutils.Funcname(), err) + break + } + err = mhs5200.SetDutyCycle(channel, v) + if err != nil { + goutils.Log.Printf("%v, %v\n", goutils.Funcname(), err) + } + + case "offset": + if len(param) == 0 { + needparam = true + continue + } + v, err := strconv.ParseFloat(param, 64) + if err != nil { + goutils.Log.Printf("%v, %v\n", goutils.Funcname(), err) + break + } + err = mhs5200.SetOffset(channel, int(v)) + if err != nil { + goutils.Log.Printf("%v, %v\n", goutils.Funcname(), err) + } + + case "phase": + if len(param) == 0 { + needparam = true + continue + } + v, err := strconv.ParseFloat(param, 64) + if err != nil { + goutils.Log.Printf("%v, %v\n", goutils.Funcname(), err) + break + } + err = mhs5200.SetPhase(channel, uint(v)) + if err != nil { + goutils.Log.Printf("%v, %v\n", goutils.Funcname(), err) + } + + case "attenuation": + if len(param) == 0 { + needparam = true + continue + } + if param == "on" { + err = mhs5200.SetAttenuation(channel, ATTENUATION_MINUS_20DB) + } else if param == "off" { + err = mhs5200.SetAttenuation(channel, ATTENUATION_0DB) + } else { + err = fmt.Errorf("Unknown parameter %v", param) + } + if err != nil { + goutils.Log.Printf("%v, %v\n", goutils.Funcname(), err) + } + + case "sweepstart": + if len(param) == 0 { + needparam = true + continue + } + v, err := strconv.ParseFloat(param, 64) + if err != nil { + goutils.Log.Printf("%v, %v\n", goutils.Funcname(), err) + break + } + err = mhs5200.SetSweepStart(v) + if err != nil { + goutils.Log.Printf("%v, %v\n", goutils.Funcname(), err) + break + } + + case "sweepend": + if len(param) == 0 { + needparam = true + continue + } + v, err := strconv.ParseFloat(param, 64) + if err != nil { + goutils.Log.Printf("%v, %v\n", goutils.Funcname(), err) + break + } + err = mhs5200.SetSweepEnd(v) + if err != nil { + goutils.Log.Printf("%v, %v\n", goutils.Funcname(), err) + break + } + + case "sweepduration": + if len(param) == 0 { + needparam = true + continue + } + v, err := strconv.ParseUint(param, 10, 64) + if err != nil { + goutils.Log.Printf("%v, %v\n", goutils.Funcname(), err) + break + } + err = mhs5200.SetSweepDuration(uint(v)) + if err != nil { + goutils.Log.Printf("%v, %v\n", goutils.Funcname(), err) + break + } + + case "sweeptype": + if len(param) == 0 { + needparam = true + continue + } + err = mhs5200.SetSweepType(mhs5200.SweepTypeStringToInt(param)) + if err != nil { + goutils.Log.Printf("%v, %v\n", goutils.Funcname(), err) + break + } + + case "on": + err = mhs5200.SetOnOff(true) + if err != nil { + goutils.Log.Printf("%v, %v\n", goutils.Funcname(), err) + } + + case "off": + err = mhs5200.SetOnOff(false) + if err != nil { + goutils.Log.Printf("%v, %v\n", goutils.Funcname(), err) + } + + case "save": + if len(param) == 0 { + needparam = true + continue + } + v, err := strconv.ParseUint(param, 10, 32) + if err != nil { + goutils.Log.Printf("%v, %v\n", goutils.Funcname(), err) + break + } + err = mhs5200.Save(uint(v)) + if err != nil { + goutils.Log.Printf("%v, %v\n", goutils.Funcname(), err) + } + + case "load": + if len(param) == 0 { + needparam = true + continue + } + v, err := strconv.ParseUint(param, 10, 32) + if err != nil { + goutils.Log.Printf("%v, %v\n", goutils.Funcname(), err) + break + } + err = mhs5200.Load(uint(v)) + if err != nil { + goutils.Log.Printf("%v, %v\n", goutils.Funcname(), err) + } + + default: + goutils.Log.Printf("%v, %v\n", goutils.Funcname(), fmt.Errorf("Unknown command %v", cmd)) + } + } + if err != nil { + os.Exit(10) + } + if needparam { + goutils.Log.Printf("Not enough parameters for %v command\n", cmd) + } +} diff --git a/mhs5200a.go b/mhs5200a.go new file mode 100644 index 0000000..e08cd4a --- /dev/null +++ b/mhs5200a.go @@ -0,0 +1,958 @@ +/* + * + * cmdline utility to configure and control the MHS-5200A series for function generators + * + * Copyright (c) 2020 - 2021 Peter Skarpetis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package main + +import ( + "fmt" + "github.com/peterska/go-utils" + "github.com/tarm/serial" + "io/ioutil" + "math" + "strconv" + "strings" + "sync" + "time" +) + +const ( + ATTENUATION_MINUS_20DB = 0 + ATTENUATION_0DB = 1 + + SWEEP_LINEAR = 0 + SWEEP_LOG = 1 + + SWEEP_START = 0 + SWEEP_STOP = 1 +) + +const ( + WAVEFORM_SINE = iota + WAVEFORM_SQUARE + WAVEFORM_TRIANGLE + WAVEFORM_RISING_SAWTOOTH + WAVEFORM_DESCENDING_SAWTOOTH + WAVEFORM_ARBITRARY_0 = iota + 100 + WAVEFORM_ARBITRARY_1 + WAVEFORM_ARBITRARY_2 + WAVEFORM_ARBITRARY_3 + WAVEFORM_ARBITRARY_4 + WAVEFORM_ARBITRARY_5 + WAVEFORM_ARBITRARY_6 + WAVEFORM_ARBITRARY_7 + WAVEFORM_ARBITRARY_8 + WAVEFORM_ARBITRARY_9 + WAVEFORM_ARBITRARY_10 + WAVEFORM_ARBITRARY_11 + WAVEFORM_ARBITRARY_12 + WAVEFORM_ARBITRARY_13 + WAVEFORM_ARBITRARY_14 + WAVEFORM_ARBITRARY_15 +) + +const ( + WAVEFORM_SINE_STR = "sine" + WAVEFORM_SQUARE_STR = "square" + WAVEFORM_TRIANGLE_STR = "triangle" + WAVEFORM_RISING_SAWTOOTH_STR = "rising sawtooth" + WAVEFORM_DESCENDING_SAWTOOTH_STR = "descending sawtooth" + WAVEFORM_ARBITRARY_STR = "arbitrary" +) + +type SWEEPVALS struct { + Startf float64 + Endf float64 + Duration uint + Type uint // log or linear + Waveform string + Duty float64 +} + +type CHANNELVALS struct { + Channel uint `json:"channel,omitempty"` + Frequency float64 `json:"frequency,omitempty"` + Waveform string `json:"waveform,omitempty"` + Amplitude float64 `json:"amplitude,omitempty"` + Phase float64 `json:"phase,omitempty"` + Duty float64 `json:"duty,omitempty"` + Offset float64 `json:"offset,omitempty"` + Attenuation uint `json:"attenuation,omitempty"` +} + +type MHS5200A struct { + stream *serial.Port + quit chan struct{} + wg sync.WaitGroup + port string +} + +func SiUnitsPrefix(exponent int) string { + switch exponent { + case 0: + return "" + case 3: + return "K" + case 6: + return "M" + case 9: + return "G" + case 12: + return "T" + case 15: + return "P" + case 18: + return "E" + case 21: + return "Z" + case 24: + return "Y" + case -3: + return "m" + case -6: + return "u" + case -9: + return "n" + case -12: + return "p" + case -15: + return "f" + case -18: + return "a" + case -21: + return "z" + case -24: + return "y" + } + return "" +} + +func (mhs5200 *MHS5200A) OnOffString(v uint) string { + if v == 0 { + return "Off" + } else { + return "On" + } +} + +func (mhs5200 *MHS5200A) BooleanString(v bool) string { + if v { + return "True" + } else { + return "False" + } +} + +func (mhs5200 *MHS5200A) UnitsString(v float64, units string, engmode bool) string { + if engmode { + exponent := 0 + for math.Abs(v) >= 1.0e3 { + exponent += 3 + v *= 1.0e-3 + if exponent > 9 { + break + } + } + for math.Abs(v) > 0.0 && math.Abs(v) < 1.0 { + exponent -= 3 + v *= 1.0e3 + if exponent < -9 { + break + } + } + return fmt.Sprintf("%.3g %s%s", v, SiUnitsPrefix(exponent), units) + } else { + return fmt.Sprintf("%.3g %s", v, units) + } +} + +func (mhs5200 *MHS5200A) sendCommand(cmd []byte) ([]byte, error) { + if goutils.Loglevel() > 1 { + goutils.Log.Printf("%v:\tsend:\t%s\n", goutils.Callername(), string(cmd)) + } + _, err := mhs5200.stream.Write(append(cmd, '\n')) + if err != nil { + goutils.Log.Print(err) + return nil, err + } + b, err := ioutil.ReadAll(mhs5200.stream) + if err != nil { + return nil, err + } + s := []byte(strings.TrimRight(string(b), " \n\r")) + if goutils.Loglevel() > 1 { + goutils.Log.Printf("%v:\treceive: %s", goutils.Callername(), s) + } + return s, nil +} + +func (mhs5200 *MHS5200A) sendCommandAndExpect(cmd []byte, expect string) error { + data, err := mhs5200.sendCommand([]byte(cmd)) + if err != nil { + return err + } + if string(data) != expect { + return fmt.Errorf("Expected %v, got %v, %v instead", expect, string(data), data) + } + return nil +} + +func (mhs5200 *MHS5200A) sendCommandAndExpectUint(cmd []byte) (uint, error) { + data, err := mhs5200.sendCommand([]byte(cmd)) + if err != nil { + return 0, err + } + if len(data) < 4 { + return 0, fmt.Errorf("data underlow") + } + data = data[4:] + v, err := strconv.ParseUint(string(data), 10, 64) + if err != nil { + return 0, err + } + return uint(v), nil +} + +func (mhs5200 *MHS5200A) FrequencyString(v float64) string { + return mhs5200.UnitsString(v, "Hz", true) +} + +func (mhs5200 *MHS5200A) SetFrequency(ch uint, v float64) error { + if math.IsNaN(v) { + return nil + } + if v < 0.0 || v > 25.0e6 { + return fmt.Errorf("%v is not a valid value", v) + } + return mhs5200.sendCommandAndExpect([]byte(fmt.Sprintf(":s%df%d", ch, int(v*100.0))), "ok") +} + +func (mhs5200 *MHS5200A) GetFrequency(ch uint) (float64, error) { + u, err := mhs5200.sendCommandAndExpectUint([]byte(fmt.Sprintf(":r%df", ch))) + if err != nil { + return 0.0, err + } + return float64(u) / 100.0, nil +} + +func (mhs5200 *MHS5200A) WaveformString(v uint) string { + switch v { + case WAVEFORM_SINE: + return WAVEFORM_SINE_STR + + case WAVEFORM_SQUARE: + return WAVEFORM_SQUARE_STR + + case WAVEFORM_TRIANGLE: + return WAVEFORM_TRIANGLE_STR + + case WAVEFORM_RISING_SAWTOOTH: + return WAVEFORM_RISING_SAWTOOTH_STR + + case WAVEFORM_DESCENDING_SAWTOOTH: + return WAVEFORM_DESCENDING_SAWTOOTH_STR + + } + if v >= WAVEFORM_ARBITRARY_0 && v <= WAVEFORM_ARBITRARY_15 { + return fmt.Sprintf("%s %v", WAVEFORM_ARBITRARY_STR, v-WAVEFORM_ARBITRARY_0) + } + return "unknown" +} + +func (mhs5200 *MHS5200A) WaveformStringToInt(s string) uint { + switch s { + case WAVEFORM_SINE_STR: + return WAVEFORM_SINE + + case WAVEFORM_SQUARE_STR: + return WAVEFORM_SQUARE + + case WAVEFORM_TRIANGLE_STR: + return WAVEFORM_TRIANGLE + + case WAVEFORM_RISING_SAWTOOTH_STR: + return WAVEFORM_RISING_SAWTOOTH + + case WAVEFORM_DESCENDING_SAWTOOTH_STR: + return WAVEFORM_DESCENDING_SAWTOOTH + + default: + return math.MaxUint32 + } +} + +func (mhs5200 *MHS5200A) SetWaveform(ch uint, v uint) error { + if v > 4 { + return fmt.Errorf("%v is not a valid value", v) + } + return mhs5200.sendCommandAndExpect([]byte(fmt.Sprintf(":s%dw%d", ch, v)), "ok") +} + +func (mhs5200 *MHS5200A) SetWaveformFromString(ch uint, s string) error { + if len(s) == 0 { + return nil + } + return mhs5200.SetWaveform(ch, mhs5200.WaveformStringToInt(s)) +} + +func (mhs5200 *MHS5200A) GetWaveform(ch uint) (uint, error) { + data, err := mhs5200.sendCommand([]byte(fmt.Sprintf(":r%dw", ch))) + if err != nil { + return 0, err + } + if len(data) < 4 { + return 0, fmt.Errorf("data underlow") + } + data = data[4:] + w, err := strconv.ParseUint(string(data), 10, 32) + if err != nil { + return 0, err + } + return uint(w), nil +} + +func (mhs5200 *MHS5200A) AmplitudeString(v float64) string { + return mhs5200.UnitsString(v, "V", true) +} + +func (mhs5200 *MHS5200A) SetAmplitude(ch uint, v float64) error { + if math.IsNaN(v) { + return nil + } + attenuation, err := mhs5200.GetAttenuation(ch) + if err != nil { + return err + } + if attenuation == ATTENUATION_MINUS_20DB { + if v < 5e-3 || v > 2.0 { + return fmt.Errorf("%v is not a valid value", v) + } + v *= 1000.0 + } else { + if v < 5e-3 || v > 20.0 { + return fmt.Errorf("%v is not a valid amplitude", v) + } + v *= 100.0 + } + return mhs5200.sendCommandAndExpect([]byte(fmt.Sprintf(":s%da%d", ch, int(v))), "ok") +} + +func (mhs5200 *MHS5200A) GetAmplitude(ch uint) (float64, error) { + data, err := mhs5200.sendCommand([]byte(fmt.Sprintf(":r%da", ch))) + if err != nil { + return 0.0, err + } + if len(data) < 4 { + return 0.0, fmt.Errorf("data underlow") + } + data = data[4:] + u, err := strconv.ParseUint(string(data), 10, 64) + if err != nil { + return 0.0, err + } + attenuation, err := mhs5200.GetAttenuation(ch) + if err != nil { + return 0.0, err + } + v := float64(u) / 100.0 + if attenuation == ATTENUATION_MINUS_20DB { + v /= 10.0 + } + return v, nil +} + +func (mhs5200 *MHS5200A) DutyCycleString(v float64) string { + return fmt.Sprintf("%.1f%%", v) +} + +func (mhs5200 *MHS5200A) SetDutyCycle(ch uint, v float64) error { + if math.IsNaN(v) { + return nil + } + if v < 0.0 || v > 99.9 { + return fmt.Errorf("%v is not a valid value", v) + } + return mhs5200.sendCommandAndExpect([]byte(fmt.Sprintf(":s%dd%d", ch, int(v*10.0))), "ok") +} + +func (mhs5200 *MHS5200A) GetDutyCycle(ch uint) (float64, error) { + data, err := mhs5200.sendCommand([]byte(fmt.Sprintf(":r%dd", ch))) + if err != nil { + return 0.0, err + } + if len(data) < 4 { + return 0.0, fmt.Errorf("data underlow") + } + data = data[4:] + u, err := strconv.ParseUint(string(data), 10, 64) + if err != nil { + return 0.0, err + } + v := float64(u) / 10.0 + return v, nil +} + +func (mhs5200 *MHS5200A) OffsetString(v int) string { + return fmt.Sprintf("%d%%", v) +} + +func (mhs5200 *MHS5200A) SetOffset(ch uint, v int) error { + if v == math.MaxUint32 { + return nil + } + if v < -120 || v > 120 { + return fmt.Errorf("%v is not a valid value", v) + } + return mhs5200.sendCommandAndExpect([]byte(fmt.Sprintf(":s%do%d", ch, v+120)), "ok") +} + +func (mhs5200 *MHS5200A) GetOffset(ch uint) (int, error) { + data, err := mhs5200.sendCommand([]byte(fmt.Sprintf(":r%do", ch))) + if err != nil { + return 0.0, err + } + if len(data) < 4 { + return 0.0, fmt.Errorf("data underlow") + } + data = data[4:] + v, err := strconv.ParseInt(string(data), 10, 64) + if err != nil { + return 0.0, err + } + return int(v - 120), nil +} + +func (mhs5200 *MHS5200A) PhaseString(v uint) string { + return fmt.Sprintf("%d°", v) +} + +func (mhs5200 *MHS5200A) SetPhase(ch uint, v uint) error { + if v == math.MaxUint32 { + return nil + } + if v > 360 { + return fmt.Errorf("%v is not a valid value", v) + } + return mhs5200.sendCommandAndExpect([]byte(fmt.Sprintf(":s%dp%d", ch, v)), "ok") +} + +func (mhs5200 *MHS5200A) GetPhase(ch uint) (uint, error) { + data, err := mhs5200.sendCommand([]byte(fmt.Sprintf(":r%dp", ch))) + if err != nil { + return 0, err + } + if len(data) < 4 { + return 0, fmt.Errorf("data underlow") + } + data = data[4:] + v, err := strconv.ParseUint(string(data), 10, 64) + if err != nil { + return 0, err + } + return uint(v), nil +} + +func (mhs5200 *MHS5200A) AttenuationString(v uint) string { + if v == ATTENUATION_MINUS_20DB { + return "-20dB" + } + return "0dB" +} + +func (mhs5200 *MHS5200A) SetAttenuation(ch uint, v uint) error { + if v == math.MaxUint32 { + return nil + } + return mhs5200.sendCommandAndExpect([]byte(fmt.Sprintf(":s%dy%d", ch, v)), "ok") +} + +func (mhs5200 *MHS5200A) GetAttenuation(ch uint) (uint, error) { + // attenuation 0 = -20dB, 1 = 0dB + data, err := mhs5200.sendCommand([]byte(fmt.Sprintf(":r%dy", ch))) + if err != nil { + return 0, err + } + if len(data) < 4 { + return 0, fmt.Errorf("data underlow") + } + data = data[4:] + v, err := strconv.ParseUint(string(data), 10, 64) + if err != nil { + return 0, err + } + return uint(v), nil +} + +func (mhs5200 *MHS5200A) SetSweepState(v bool) error { + state := 0 + if v { + state = 1 + } + return mhs5200.sendCommandAndExpect([]byte(fmt.Sprintf(":s8b%d", state)), "ok") +} + +func (mhs5200 *MHS5200A) GetSweepState() (bool, error) { + v, err := mhs5200.sendCommandAndExpectUint([]byte(fmt.Sprintf(":r8b"))) + if err != nil { + return false, err + } + return v != 0, err +} + +func (mhs5200 *MHS5200A) SweepTypeString(v uint) string { + switch v { + case SWEEP_LINEAR: + return "linear" + + case SWEEP_LOG: + return "logarithmic" + } + return "unknown" +} + +func (mhs5200 *MHS5200A) SweepTypeStringToInt(s string) uint { + switch s { + case "linear": + return SWEEP_LINEAR + + case "logarithmic": + return SWEEP_LOG + + case "log": + return SWEEP_LOG + default: + return math.MaxUint32 + } +} + +func (mhs5200 *MHS5200A) SetSweepStart(v float64) error { + if math.IsNaN(v) { + return nil + } + if v < 0.0 || v > 25.0e6 { + return fmt.Errorf("%v is not a valid frequency", v) + } + return mhs5200.sendCommandAndExpect([]byte(fmt.Sprintf(":s3f%d", int(v*100.0))), "ok") +} + +func (mhs5200 *MHS5200A) GetSweepStart() (float64, error) { + u, err := mhs5200.sendCommandAndExpectUint([]byte(fmt.Sprintf(":r3f"))) + if err != nil { + return 0.0, err + } + return float64(u) / 100.0, nil +} + +func (mhs5200 *MHS5200A) SetSweepEnd(v float64) error { + if math.IsNaN(v) { + return nil + } + if v < 0.0 || v > 25.0e6 { + return fmt.Errorf("%v is not a valid frequency", v) + } + return mhs5200.sendCommandAndExpect([]byte(fmt.Sprintf(":s4f%d", int(v*100.0))), "ok") +} + +func (mhs5200 *MHS5200A) GetSweepEnd() (float64, error) { + u, err := mhs5200.sendCommandAndExpectUint([]byte(fmt.Sprintf(":r4f"))) + if err != nil { + return 0.0, err + } + return float64(u) / 100.0, nil +} + +func (mhs5200 *MHS5200A) SetSweepDuration(v uint) error { + if v == math.MaxUint32 { + return nil + } + if v > 999 { + return fmt.Errorf("%v is not a valid duration", v) + } + return mhs5200.sendCommandAndExpect([]byte(fmt.Sprintf(":s1t%d", int(v))), "ok") +} + +func (mhs5200 *MHS5200A) GetSweepDuration() (uint, error) { + u, err := mhs5200.sendCommandAndExpectUint([]byte(fmt.Sprintf(":r1t"))) + if err != nil { + return 0, err + } + return uint(u), nil +} + +func (mhs5200 *MHS5200A) SetSweepType(v uint) error { + if v == math.MaxUint32 { + return nil + } + return mhs5200.sendCommandAndExpect([]byte(fmt.Sprintf(":s7b%d", int(v))), "ok") +} + +func (mhs5200 *MHS5200A) GetSweepType() (uint, error) { + u, err := mhs5200.sendCommandAndExpectUint([]byte(fmt.Sprintf(":r7b"))) + if err != nil { + return 0, err + } + return uint(u), nil +} + +func (mhs5200 *MHS5200A) SetSweep(v *SWEEPVALS) error { + if v == nil { + return fmt.Errorf("null parameters") + } + var err error + err = mhs5200.SetDutyCycle(1, v.Duty) + if err != nil { + return err + } + err = mhs5200.SetWaveformFromString(1, v.Waveform) + if err != nil { + return err + } + err = mhs5200.SetSweepStart(v.Startf) + if err != nil { + return err + } + err = mhs5200.SetSweepEnd(v.Endf) + if err != nil { + return err + } + err = mhs5200.SetSweepDuration(v.Duration) + if err != nil { + return err + } + err = mhs5200.SetSweepType(v.Type) + if err != nil { + return err + } + return nil +} + +func (mhs5200 *MHS5200A) GetSweep() (*SWEEPVALS, error) { + var err error + v := SWEEPVALS{} + v.Duty, err = mhs5200.GetDutyCycle(1) + if err != nil { + return nil, err + } + w, err := mhs5200.GetWaveform(1) + if err != nil { + return nil, err + } + v.Waveform = mhs5200.WaveformString(w) + + v.Startf, err = mhs5200.GetSweepStart() + if err != nil { + return nil, err + } + + v.Endf, err = mhs5200.GetSweepEnd() + if err != nil { + return nil, err + } + + v.Duration, err = mhs5200.GetSweepDuration() + if err != nil { + return nil, err + } + + v.Type, err = mhs5200.GetSweepType() + if err != nil { + return nil, err + } + + return &v, err +} + +func (mhs5200 *MHS5200A) SetOnOff(v bool) error { + state := 0 + if v { + state = 1 + } + return mhs5200.sendCommandAndExpect([]byte(fmt.Sprintf(":s1b%d", state)), "ok") +} + +func (mhs5200 *MHS5200A) SelectChannel(ch uint) error { + if ch == 0 { + return nil + } + if ch > 2 { + return fmt.Errorf("%v is not a valid channel", ch) + } + return mhs5200.sendCommandAndExpect([]byte(fmt.Sprintf(":s2b%d", ch)), "ok") +} + +func (mhs5200 *MHS5200A) Save(v uint) error { + if v > 15 { + return fmt.Errorf("%v is not a valid save position", v) + } + return mhs5200.sendCommandAndExpect([]byte(fmt.Sprintf(":su%02d", v)), "ok") +} + +func (mhs5200 *MHS5200A) Load(v uint) error { + if v > 15 { + return fmt.Errorf("%v is not a valid load position", v) + } + return mhs5200.sendCommandAndExpect([]byte(fmt.Sprintf(":sv%02d", v)), "ok") +} + +func (mhs5200 *MHS5200A) GetModel() (string, error) { + data, err := mhs5200.sendCommand([]byte(fmt.Sprintf(":r0c"))) + if err != nil { + return "", err + } + if len(data) < 4 { + return "", fmt.Errorf("data underlow") + } + data = data[4:] + return "MHS-" + string(data[:5]), nil +} + +func (mhs5200 *MHS5200A) GetFirmwareVersion() (float64, error) { + data, err := mhs5200.sendCommand([]byte(fmt.Sprintf(":r1c"))) + if err != nil { + return 0.0, err + } + if len(data) < 12 { + return 0.0, fmt.Errorf("data underlow") + } + data = data[9:12] + u, err := strconv.ParseUint(string(data), 10, 64) + if err != nil { + return 0.0, err + } + v := float64(u) / 100.0 + return v, nil +} + +func (mhs5200 *MHS5200A) GetSerial() (string, error) { + data, err := mhs5200.sendCommand([]byte(fmt.Sprintf(":r2c"))) + if err != nil { + return "", err + } + if len(data) < 16 { + return "", fmt.Errorf("data underlow") + } + data = data[12:16] + return string(data), nil +} + +func (mhs5200 *MHS5200A) ShowSweepConfig() error { + // sweep is only output on channel 1 + sweep, err := mhs5200.GetSweepState() + if err != nil { + return err + } + fmt.Printf("Sweep config\n") + fmt.Printf("\tActive:\t\t%v\n", mhs5200.BooleanString(sweep)) + sweepvals, err := mhs5200.GetSweep() + if err != nil { + return err + } + fmt.Printf("\tWaveform:\t%v\n", sweepvals.Waveform) + if sweepvals.Waveform == WAVEFORM_SQUARE_STR { + fmt.Printf("\tDutyCycle:\t%v\n", mhs5200.DutyCycleString(sweepvals.Duty)) + } + fmt.Printf("\tStart:\t\t%v\n", mhs5200.FrequencyString(sweepvals.Startf)) + fmt.Printf("\tEnd:\t\t%v\n", mhs5200.FrequencyString(sweepvals.Endf)) + fmt.Printf("\tDuration:\t%v seconds\n", sweepvals.Duration) + fmt.Printf("\tType:\t\t%v\n", mhs5200.SweepTypeString(sweepvals.Type)) + + return nil +} + +func (mhs5200 *MHS5200A) ShowChannelConfig(ch uint) error { + fmt.Printf("Channel %d config\n", ch) + err := mhs5200.SelectChannel(ch) + if err != nil { + return err + } + freq, err := mhs5200.GetFrequency(ch) + if err != nil { + return err + } + w, err := mhs5200.GetWaveform(ch) + if err != nil { + return err + } + ampl, err := mhs5200.GetAmplitude(ch) + if err != nil { + return err + } + duty, err := mhs5200.GetDutyCycle(ch) + if err != nil { + return err + } + offset, err := mhs5200.GetOffset(ch) + if err != nil { + return err + } + phase, err := mhs5200.GetPhase(ch) + if err != nil { + return err + } + attenuation, err := mhs5200.GetAttenuation(ch) + if err != nil { + return err + } + + fmt.Printf("\tFrequency:\t%v\n", mhs5200.FrequencyString(freq)) + fmt.Printf("\tWaveform:\t%v\n", mhs5200.WaveformString(w)) + fmt.Printf("\tAmplitude:\t%v\n", mhs5200.AmplitudeString(ampl)) + fmt.Printf("\tDutyCycle:\t%v\n", mhs5200.DutyCycleString(duty)) + fmt.Printf("\tOffset:\t\t%v\n", mhs5200.OffsetString(offset)) + fmt.Printf("\tPhase:\t\t%v\n", mhs5200.PhaseString(phase)) + fmt.Printf("\tAttenuation:\t%v\n", mhs5200.AttenuationString(attenuation)) + + return nil +} + +func (mhs5200 *MHS5200A) ShowConfig() error { + model, err := mhs5200.GetModel() + if err != nil { + return err + } + version, err := mhs5200.GetFirmwareVersion() + if err != nil { + return err + } + serial, err := mhs5200.GetSerial() + if err != nil { + return err + } + fmt.Printf("Model:\t\t%v\n", model) + fmt.Printf("Serial:\t\t%v\n", serial) + fmt.Printf("Firmware:\t%v\n", version) + fmt.Println("") + err = mhs5200.ShowChannelConfig(1) + if err != nil { + return err + } + fmt.Println("") + mhs5200.ShowChannelConfig(2) + if err != nil { + return err + } + return nil +} + +func (mhs5200 *MHS5200A) ApplyChannelConfig(v *CHANNELVALS) error { + if v == nil { + return fmt.Errorf("null data") + } + var err error + if v.Channel != math.MaxUint32 { + err = mhs5200.SelectChannel(v.Channel) + if err != nil { + return err + } + } + if v.Attenuation != math.MaxUint32 { + err = mhs5200.SetAttenuation(v.Channel, v.Attenuation) + if err != nil { + return err + } + } + if !math.IsNaN(v.Frequency) { + err = mhs5200.SetFrequency(v.Channel, v.Frequency) + if err != nil { + return err + } + } + if len(v.Waveform) > 0 { + err = mhs5200.SetWaveformFromString(v.Channel, v.Waveform) + if err != nil { + return err + } + } + if !math.IsNaN(v.Amplitude) { + err = mhs5200.SetAmplitude(v.Channel, v.Amplitude) + if err != nil { + return err + } + } + if !math.IsNaN(v.Phase) { + err = mhs5200.SetPhase(v.Channel, uint(math.Round(v.Phase))) + if err != nil { + return err + } + } + if !math.IsNaN(v.Duty) { + err = mhs5200.SetDutyCycle(v.Channel, v.Duty) + if err != nil { + return err + } + } + if !math.IsNaN(v.Offset) { + err = mhs5200.SetOffset(v.Channel, int(math.Round(v.Offset))) + if err != nil { + return err + } + } + return nil +} + +func (mhs5200 *MHS5200A) mhs5200() { + for { + select { + case <-mhs5200.quit: + mhs5200.wg.Done() + return + + } + } +} + +func NewMHS5200A(port string) (*MHS5200A, error) { + if len(port) == 0 { + err := fmt.Errorf("%s: no port specified", goutils.Funcname()) + return nil, err + } + config := &serial.Config{ + Name: port, + Baud: 57600, + Size: 8, + Parity: serial.ParityNone, + StopBits: serial.Stop1, + ReadTimeout: time.Millisecond * 5, + } + stream, err := serial.OpenPort(config) + if err != nil { + return nil, err + } + mhs5200 := &MHS5200A{ + stream: stream, + quit: make(chan struct{}), + } + mhs5200.wg.Add(1) + go mhs5200.mhs5200() + return mhs5200, nil +} + +func (mhs5200 *MHS5200A) Close() { + close(mhs5200.quit) + mhs5200.wg.Wait() + if mhs5200.stream != nil { + mhs5200.stream.Close() + } +} diff --git a/script.go b/script.go new file mode 100644 index 0000000..32c8c1b --- /dev/null +++ b/script.go @@ -0,0 +1,330 @@ +/* + * + * cmdline utility to configure and control the MHS-5200A series for function generators + * + * Copyright (c) 2020 - 2021 Peter Skarpetis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package main + +import ( + "encoding/json" + "fmt" + "github.com/peterska/go-utils" + "io/ioutil" + "math" + "time" +) + +const ( + defaultPollingIntervalSeconds = 60 +) + +type CMDPARAMS struct { + Channel *uint `json:"channel,omitempty"` + Frequency *float64 `json:"frequency,omitempty"` + Waveform *string `json:"waveform,omitempty"` + Amplitude *float64 `json:"amplitude,omitempty"` + Phase *float64 `json:"phase,omitempty"` + Duty *float64 `json:"duty,omitempty"` + Offset *float64 `json:"offset,omitempty"` + Attenuation *bool `json:"attenuation,omitempty"` + Seconds *uint `json:"seconds,omitempty"` + Slot *uint `json:"slot,omitempty"` + Startf *float64 `json:"startf,omitempty"` + Endf *float64 `json:"endf,omitempty"` + Type *string `json:"type,omitempty"` +} + +type CMD struct { + Cmd string `json:"cmd,omitempty"` + Data []CMDPARAMS `json:"data,omitempty"` +} + +type SCRIPT struct { + Port string `json:"port,omitempty"` + Cmds []CMD `json:"cmds,omitempty"` +} + +func (params *CMDPARAMS) convertToChannelVals(mhs5200 *MHS5200A) *CHANNELVALS { + v := CHANNELVALS{ + Channel: math.MaxUint32, + Frequency: math.NaN(), + Waveform: "", + Amplitude: math.NaN(), + Phase: math.NaN(), + Duty: math.NaN(), + Offset: math.NaN(), + Attenuation: math.MaxUint32, + } + if params.Channel != nil { + v.Channel = *params.Channel + } + if params.Frequency != nil { + v.Frequency = *params.Frequency + } + if params.Waveform != nil { + v.Waveform = *params.Waveform + } + if params.Amplitude != nil { + v.Amplitude = *params.Amplitude + } + if params.Phase != nil { + v.Phase = *params.Phase + } + if params.Duty != nil { + v.Duty = *params.Duty + } + if params.Offset != nil { + v.Offset = *params.Offset + } + if params.Attenuation != nil { + if *params.Attenuation { + v.Attenuation = ATTENUATION_MINUS_20DB + } else { + v.Attenuation = ATTENUATION_0DB + } + } + if goutils.Loglevel() > 0 { + goutils.Log.Printf("%v: %+v", goutils.Funcname(), v) + } + return &v +} + +func (params *CMDPARAMS) convertToSweepVals(mhs5200 *MHS5200A) *SWEEPVALS { + v := SWEEPVALS{ + Startf: math.NaN(), + Endf: math.NaN(), + Duration: math.MaxUint32, + Type: math.MaxUint32, + Waveform: "", + Duty: math.NaN(), + } + if params.Startf != nil { + v.Startf = *params.Startf + } + if params.Endf != nil { + v.Endf = *params.Endf + } + if params.Seconds != nil { + v.Duration = *params.Seconds + } + if params.Type != nil { + v.Type = mhs5200.SweepTypeStringToInt(*params.Type) + } + if params.Waveform != nil { + v.Waveform = *params.Waveform + } + if params.Duty != nil { + v.Duty = *params.Duty + } + if goutils.Loglevel() > 0 { + goutils.Log.Printf("%v: %+v", goutils.Funcname(), v) + } + return &v +} + +func script(scriptfile string, port string) (*SCRIPT, error) { + if len(scriptfile) == 0 { + return nil, fmt.Errorf("Cannot find configuration file") + } + jsn, err := ioutil.ReadFile(scriptfile) + if err != nil { + goutils.Log.Printf("%v", err) + return nil, err + } + script := SCRIPT{ + Port: port, + } + err = json.Unmarshal(jsn, &script) + if err != nil { + goutils.Log.Printf("%v", err) + return nil, err + } + if goutils.Loglevel() > 1 { + goutils.Log.Printf("Loaded config from %v, %+v", scriptfile, script) + } + return &script, nil +} + +func timestampString() string { + return time.Now().Format(time.Stamp) +} + +func playbackScript(scriptfile string, port string) error { + script, err := script(scriptfile, port) + if err != nil { + return err + } + if len(script.Port) == 0 { + goutils.Log.Printf("%v", fmt.Errorf("Port was not specified")) + return fmt.Errorf("Port was not specified") + } + mhs5200, err := NewMHS5200A(script.Port) + if err != nil { + return err + } + defer mhs5200.Close() + for _, cmd := range script.Cmds { + switch cmd.Cmd { + case "config": + for _, data := range cmd.Data { + fmt.Printf("%v: Configuring channel %v\n", timestampString(), *data.Channel) + err = mhs5200.ApplyChannelConfig(data.convertToChannelVals(mhs5200)) + if err != nil { + return err + } + } + + case "showconfig": + if cmd.Data != nil { + for _, data := range cmd.Data { + if data.Channel != nil { + err = mhs5200.ShowChannelConfig(*data.Channel) + } else { + err = mhs5200.ShowConfig() + } + if err != nil { + return err + } + } + } else { + err = mhs5200.ShowConfig() + if err != nil { + return err + } + } + + case "delay": + if cmd.Data != nil { + for _, data := range cmd.Data { + if data.Seconds != nil { + fmt.Printf("%v: Sleeping %v seconds\n", timestampString(), *data.Seconds) + time.Sleep(time.Second * time.Duration(*data.Seconds)) + } + } + } else { + time.Sleep(time.Second * 1) + } + + case "sleep": + if cmd.Data != nil { + for _, data := range cmd.Data { + if data.Seconds != nil { + fmt.Printf("%v: Sleeping %v seconds\n", timestampString(), *data.Seconds) + time.Sleep(time.Second * time.Duration(*data.Seconds)) + } + } + } else { + time.Sleep(time.Second * 1) + } + + case "on": + fmt.Printf("%v: Output on\n", timestampString()) + err = mhs5200.SetOnOff(true) + if err != nil { + return err + } + + case "off": + fmt.Printf("%v: Output off\n", timestampString()) + err = mhs5200.SetOnOff(false) + if err != nil { + return err + } + + case "save": + if cmd.Data != nil { + for _, data := range cmd.Data { + if data.Slot != nil { + fmt.Printf("%v: Saving to slot %v\n", timestampString(), *data.Slot) + err = mhs5200.Save(*data.Slot) + } else { + err = mhs5200.Save(0) + } + if err != nil { + return err + } + } + } else { + err = mhs5200.Save(0) + if err != nil { + return err + } + } + + case "load": + if cmd.Data != nil { + for _, data := range cmd.Data { + if data.Slot != nil { + fmt.Printf("%v: Loading from slot %v\n", timestampString(), *data.Slot) + err = mhs5200.Load(*data.Slot) + } else { + err = mhs5200.Load(0) + } + if err != nil { + return err + } + } + } else { + err = mhs5200.Load(0) + if err != nil { + return err + } + } + + case "showsweep": + err = mhs5200.ShowSweepConfig() + if err != nil { + return err + } + + case "configsweep": + // sweeps are only valid on channel 1 + for _, data := range cmd.Data { + fmt.Printf("%v: Configuring sweep\n", timestampString()) + err = mhs5200.SetSweep(data.convertToSweepVals(mhs5200)) + if err != nil { + return err + } + } + + case "sweepon": + fmt.Printf("%v: Sweep on\n", timestampString()) + err = mhs5200.SetSweepState(true) + if err != nil { + return err + } + + case "sweepoff": + fmt.Printf("%v: Sweep off\n", timestampString()) + err = mhs5200.SetSweepState(false) + if err != nil { + return err + } + + default: + return fmt.Errorf("Unknown command %s", cmd.Cmd) + } + } + return nil +}