Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Am I doing this right? #39

Closed
dlech opened this issue Feb 15, 2017 · 9 comments
Closed

Am I doing this right? #39

dlech opened this issue Feb 15, 2017 · 9 comments

Comments

@dlech
Copy link
Contributor

dlech commented Feb 15, 2017

I'm inexperienced in go, so I thought I would share my first ev3go program and see what you think.

package main

import (
	"log"

	"strconv"

	"image/color"

	"os/exec"

	"github.com/ev3go/ev3"
	"github.com/ev3go/ev3dev"
)

const (
	top    = 30
	bottom = 100
	left   = 70
	right  = 110
)

func main() {
	// configure the LEGO EV3 IR sensor on input port 1 in IR-SEEK mode
	irSensor, err := ev3dev.SensorFor("in1", "lego-ev3-ir")
	if err != nil {
		log.Fatal(err)
	}
	irSensor.SetMode("IR-SEEK")

	// configure LEGO EV3 Large motor in output port A
	motor, err := ev3dev.TachoMotorFor("outA", "lego-ev3-l-motor")
	if err != nil {
		log.Fatal(err)
	}

	// init poller so we can monitor buttons
	buttonPoller := ev3dev.ButtonPoller{}

	// init the LCD so we can draw stuff
	ev3.LCD.Init(true)
	defer ev3.LCD.Close()

	motor.Command("run-direct")
	beaconPresent := false

	for {
		// read the channel 1 heading value from the IR sensor
		v0, _ := irSensor.Value(0)
		heading, _ := strconv.Atoi(v0)

		// motor rotation is in the same direction as heading, so this will
		// cause the motor to rotate towards the beacon
		motor.SetDutyCycleSetpoint(heading)

		v1, _ := irSensor.Value(1)
		distance, _ := strconv.Atoi(v1)

		// sound feedback when beacon is in or out of range
		if beaconPresent && distance == -128 {
			beaconPresent = false
			exec.Command("beep", "-f", "400", "-n", "-f", "200").Start()
		} else if !beaconPresent && distance != -128 {
			beaconPresent = true
			exec.Command("beep", "-f", "200", "-n", "-f", "400").Start()
		}

		// Draw a bar graph to indicate how far away the beacon is. The closer
		// the beacon, the taller the graph.
		for x := left; x < right; x++ {
			for y := top; y < bottom; y++ {
				if y > top+distance {
					ev3.LCD.Set(x, y, color.Black)
				} else {
					ev3.LCD.Set(x, y, color.White)
				}
			}
		}

		// check for button presses
		b, err := buttonPoller.Poll()
		if err != nil {
			log.Fatal(err)
		}
		if (b & ev3dev.Back) == ev3dev.Back {
			// exit the program
			break
		}
		if (b & ev3dev.Middle) == ev3dev.Middle {
			speak := exec.Command("espeak", "--stdout", "-a", "200", "-s", "100",
				"Don't press that button")
			play := exec.Command("aplay", "-q")
			play.Stdin, _ = speak.StdoutPipe()
			speak.Start()
			play.Start()
		}
	}
}
@kortschak
Copy link
Member

A quick read; it looks OK to me. There are some C-isms there 😁. If you'd like I can massage it to how I would express it when I'm home from work and then you can take what you like and leave what you don't.

@dlech
Copy link
Contributor Author

dlech commented Feb 16, 2017

That would be great. I really like go so far, the little bit that I have used it.

@kortschak
Copy link
Member

Yeah, I noticed ev3dev/lmsasm. Nice.

@kortschak
Copy link
Member

I've left commentary, though added some where I thought it would be useful for you.

package main

import (
	"image/color"
	"log"
	"os/exec"
	"strconv"

	"github.com/ev3go/ev3"
	"github.com/ev3go/ev3dev"
)

func main() {
	log.Println("Hello World!")

	// configure the LEGO EV3 IR sensor on input port 1 in IR-SEEK mode
	irSensor, err := ev3dev.SensorFor("in1", "lego-ev3-ir")
	if err != nil {
		log.Fatal(err)
	}
	// We could capture the error here and finish
	// early but it doesn't really matter. If more
	// work were being done, I would fail here.
	irSensor.SetMode("IR-SEEK")

	// configure LEGO EV3 Large motor in output port A
	motor, err := ev3dev.TachoMotorFor("outA", "lego-ev3-l-motor")
	if err != nil {
		log.Fatal(err)
	}
	// Same here... it will be caught in the loop.
	motor.Command("run-direct")

	// init the LCD so we can draw stuff
	ev3.LCD.Init(true)
	// This will only happen in the happy path since
	// defered function will not be called if we
	// log.Fatal, but the file will be closed by the
	// kernel in that case. Here the defer is really
	// politeness more than anything else.
	defer ev3.LCD.Close()

	seen := false
	for !done() {
		// read the channel 1 heading value from the IR sensor
		heading, err := intValueFrom(irSensor, 0)
		if err != nil {
			log.Fatal(err)
		}

		// motor rotation is in the same direction as heading, so this will
		// cause the motor to rotate towards the beacon
		err = motor.SetDutyCycleSetpoint(heading).Err()
		if err != nil {
			log.Fatal(err)
		}

		distance, err := intValueFrom(irSensor, 1)
		if err != nil {
			log.Fatal(err)
		}
		seen, err = checkBeacon(seen, distance)
		if err != nil {
			log.Fatal(err)
		}

		// Draw a bar graph to indicate how far away the beacon is. The closer
		// the beacon, the taller the graph.
		plot(distance)
	}
}

func intValueFrom(s *ev3dev.Sensor, i int) (int, error) {
	v, err := s.Value(i)
	if err != nil {
		return -1, err
	}
	return strconv.Atoi(v)
}

var (
	fallingTones = []string{"-f", "400", "-n", "-f", "200"}
	risingTones  = []string{"-f", "200", "-n", "-f", "400"}
)

func checkBeacon(wasPresent bool, distance int) (seen bool, err error) {
	// sound feedback when beacon is in or out of range
	if wasPresent && distance == -128 {
		err = exec.Command("beep", fallingTones...).Start()
		return false, err
	} else if !wasPresent && distance != -128 {
		err = exec.Command("beep", risingTones...).Start()
		return true, err
	}
	return wasPresent, nil
}

func plot(distance int) {
	const (
		top    = 30
		bottom = 100
		left   = 70
		right  = 110
	)

	for x := left; x < right; x++ {
		for y := top; y < bottom; y++ {
			if y > top+distance {
				ev3.LCD.Set(x, y, color.Black)
			} else {
				ev3.LCD.Set(x, y, color.White)
			}
		}
	}
}

func say(text string) error {
	speak := exec.Command("espeak", "--stdout", "-a", "200", "-s", "100", text)
	play := exec.Command("aplay", "-q")
	var err error
	play.Stdin, err = speak.StdoutPipe()
	if err != nil {
		return err
	}
	err = speak.Start()
	if err != nil {
		return err
	}
	return play.Start()
}

var buttons ev3dev.ButtonPoller

func done() bool {
	b, err := buttons.Poll()
	if err != nil {
		log.Fatal(err)
	}
	if b&ev3dev.Back != 0 {
		return true
	}
	if b&ev3dev.Middle != 0 {
		err = say("Don't press that button")
		if err != nil {
			log.Fatal(err)
		}
	}
	return false
}

The use of fatal over panic and recovery is something I am not yet convinced of either way.

One thing that is odd is that when I run this, I get the following failure after variable amounts of time:

2017/02/16 10:15:21 Hello World!
2017/02/16 10:15:27 ev3dev: failed to read sensor0 value0 attribute /sys/class/lego-sensor/sensor0/value0: read /sys/class/lego-sensor/sensor0/value0: no such device or address at ev3dev.go:469 github.com/ev3go/ev3dev.attributeOf

It works for a while, then the sensor attribute seems to disappear, though it comes back. Do you have any ideas what might be causing that?

@kortschak
Copy link
Member

kortschak commented Feb 16, 2017

Ahh, it looks to me like it was a problem with the old kernel I was using - though now I have lost wireless (rtl8188eu-modules-4.4.47-19-ev3dev-ev3 is installed correctly). Running through the connmanctl process brought that back.

@dlech
Copy link
Contributor Author

dlech commented Feb 16, 2017

Thanks for the feedback. I think panic actually makes very good sense when working with motors. For example, I am now doing this:

err = motor.Command("run-direct").Err()
if err != nil {
	log.Panic(err)
}
defer motor.Command("reset")

So, if something bad happens (like a sensor comes unplugged, the motor will stop when the program exits because of the panic.

@kortschak
Copy link
Member

kortschak commented Feb 16, 2017

You can do it more easily since panic unwinds the stack. Conventionally you use a must function.

func must(err error) {
    if err != nil {
        panic(err)
    }
}

This gives you something like this (the balance of when to add a new must is up to taste - this might want a mustInt if there were more cases where we returned a (int, err).

package main

import (
	"image/color"
	"log"
	"os/exec"
	"strconv"

	"github.com/ev3go/ev3"
	"github.com/ev3go/ev3dev"
	"github.com/ev3go/ev3dev/motorutil"
)

func main() {
	log.Println("Hello World!")

	// configure the LEGO EV3 IR sensor on input port 1 in IR-SEEK mode
	irSensor, err := ev3dev.SensorFor("in1", "lego-ev3-ir")
	if err != nil {
		log.Fatal(err)
	}
	// We could capture the error here and finish
	// early but it doesn't really matter. If more
	// work were being done, I would fail here.
	irSensor.SetMode("IR-SEEK")

	// Set up defer recover to handle motor resets on failure.
	defer func() {
		r := recover()
		if r != nil {
			err := motorutil.ResetAll()
			if err != nil {
				log.Printf("failed to reset all motors: %v", err)
			}
			log.Fatal(r)
		}
	}()

	// configure LEGO EV3 Large motor in output port A
	motor, err := ev3dev.TachoMotorFor("outA", "lego-ev3-l-motor")
	if err != nil {
		log.Fatal(err)
	}
	must(motor.Command("run-direct").Err())

	// init the LCD so we can draw stuff
	ev3.LCD.Init(true)
	defer ev3.LCD.Close()

	seen := false
	for !done() {
		// read the channel 1 heading value from the IR sensor
		heading := intValueFrom(irSensor, 0)

		// motor rotation is in the same direction as heading, so this will
		// cause the motor to rotate towards the beacon
		must(motor.SetDutyCycleSetpoint(heading).Err())

		distance := intValueFrom(irSensor, 1)
		seen = checkBeacon(seen, distance)

		// Draw a bar graph to indicate how far away the beacon is. The closer
		// the beacon, the taller the graph.
		plot(distance)
	}
}

func must(err error) {
	if err != nil {
		panic(err)
	}
}

func intValueFrom(s *ev3dev.Sensor, i int) int {
	v, err := s.Value(i)
	if err != nil {
		panic(err)
	}
	i, err = strconv.Atoi(v)
	if err != nil {
		panic(err)
	}
	return i
}

var (
	fallingTones = []string{"-f", "400", "-n", "-f", "200"}
	risingTones  = []string{"-f", "200", "-n", "-f", "400"}
)

func checkBeacon(wasPresent bool, distance int) (seen bool) {
	// sound feedback when beacon is in or out of range
	if wasPresent && distance == -128 {
		must(exec.Command("beep", fallingTones...).Start())
		return false
	} else if !wasPresent && distance != -128 {
		must(exec.Command("beep", risingTones...).Start())
		return true
	}
	return wasPresent
}

func plot(distance int) {
	const (
		top    = 30
		bottom = 100
		left   = 70
		right  = 110
	)

	for x := left; x < right; x++ {
		for y := top; y < bottom; y++ {
			if y > top+distance {
				ev3.LCD.Set(x, y, color.Black)
			} else {
				ev3.LCD.Set(x, y, color.White)
			}
		}
	}
}

func say(text string) error {
	speak := exec.Command("espeak", "--stdout", "-a", "200", "-s", "100", text)
	play := exec.Command("aplay", "-q")
	var err error
	play.Stdin, err = speak.StdoutPipe()
	if err != nil {
		return err
	}
	err = speak.Start()
	if err != nil {
		return err
	}
	return play.Start()
}

var buttons ev3dev.ButtonPoller

func done() bool {
	b, err := buttons.Poll()
	if err != nil {
		panic(err)
	}
	if b&ev3dev.Back != 0 {
		return true
	}
	if b&ev3dev.Middle != 0 {
		must(say("Don't press that button"))
	}
	return false
}

@kortschak
Copy link
Member

@dlech Can I close this?

@dlech
Copy link
Contributor Author

dlech commented Aug 18, 2017

sure.

@dlech dlech closed this as completed Aug 18, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants