austinbv / dino

Dino is a ruby gem that helps you bootstrap prototyping with an Arduino
MIT License
388 stars 84 forks source link

Connect to servo without setting position #54

Closed wm closed 11 years ago

wm commented 11 years ago

The after_initialize sets the servo position to the passed in option or 0. However I would like to have my servo stay in the same position upon connection and reconnection. Is there a way to do this?

I am willing to write the code to extend the servo to do this but was wondering if there is a particular approach you would like me to take.

One way may be something like this

     def after_initialize(options={})
        set_pin_mode(:out)
        board.servo_toggle(pin, 1)
        unless options[:maintain_position]
          self.position = options[:position] || 0
        end
      end

Any guidance here would be great

vickash commented 11 years ago

Is there a reason you're reconnecting to the servo? When the Components::Servo instance is created, after_initialize runs just once and then the Servo object keeps track of the position. Are you destroying that object and creating another?

wm commented 11 years ago

I have two reasons

  1. The program that controls the camera is not always running. This is because I will be connecting to the machine using ssh and launching the program. I would like for the camera not to move to some default position upon this connection but rather stay where it is.
  2. I would love to have multiple people connect to this machine and run the program concurrently. It would suck if as people connect the camera shoots back to 0, 0 (or some other default)

I am hoping to make it work for multiple programs running together but failing that approach I will probably use a client server approach. That said point 1 above still stands.

There may well be a limitation that would not make point 2 usable. I just have not read too much of the Dino source yet.

vickash commented 11 years ago

It's not just the behavior inside the Servo instance you need to worry about. Reconnecting to the board will reset the position of the servo for a couple other reasons:

In any case, the solution to your second problem also solves your first, so let me explain that.

Dino relies on Ruby either opening a serial connection or a TCP socket to talk to the board. The Arduino sketches are written to use exactly one of either. So having multiple users (or applications) accessing the board directly, is tricky. You'd have to keep disconnecting and reconnecting.

When the board gets reset upon connection, this lets Ruby keep track of, and be sure of, the state of all the hardware on the board. In other words, once connected, the instance of Dino::Board assumes it has total control of the board and any communication with the board has to go through it. I don't know if @austinbv agrees, but I think this should be a fundamental design principle for this project.

Imagine if you removed this "reset on connect and then keep track of everything" behavior. One user connects and turns an LED on. Another user connects, turns it off. The first user's code is still running with the assumption that the LED is on, but it isn't.

You could create a system at the board level to let multiple users connect and push updates about the hardware state to them, but there's still the fact that serial connections and TCP sockets are both 1-1 connections. Are users going to keep trying to connect until the board is available and then disconnect as soon as they've written/read what they need? Also, the whole point of this gem is to write less Arduino code.

Some kind of messaging system IS what you need, just not at that low level.

I currently have a single Arduino running Dino with 4 separate applications using it:

All of it runs on RabbitMQ. A 5th Ruby script, a daemon, makes the actual connection to the board, offers up a custom API via a message queue and then just waits for and sends messages. It does nothing else.

The 4 applications interact with the board indirectly by exchanging messages with the daemon. So I can stop any one of the applications, make changes, and start it back up, without stopping anything else. In fact, I've rewritten the applications multiple times, but the daemon has been running, connected to the board with the same TCP connection, for almost a month.

I'm halfway done with a writeup on how this works, but it isn't very complicated. You can apply the same design to your system. Create a daemon that runs on the machine, connects to the board, and can set the position of the servo. Write the daemon in a way where you set the position by sending messages to one queue, and it then publishes the new position on another queue. Check out https://github.com/ruby-amqp/amqp/.

Since the Board instance and the Servo instance belong to the daemon, the daemon is always sure of the position of the servo, since it gets stored as an instance variable, and only the daemon can actually write to the board. If one user sends a message to the first queue asking to change the position, the daemon writes it to the board, and then publishes the updated position to the second queue. Once other users/applications are subscribed to the second queue, they'll be updated with the new position.

Since the daemon is always running now, and you interact with it only by sending messages to a queue, there's no need to disconnect from the board and reconnect to it when you log in via SSH, so the position doesn't get reset each time.

wm commented 11 years ago

Wow - thanks for the thorough response. I suspected much of what you mentioned.

The queue approach is great. I will follow that path I think. I also look forward to your writeup on it. Thanks again