{-| A simple message-based control of the NXT brick via remotely executed program. The main goal of this approach is to reduce latency otherwise encountered when using NXT Bluetooth communication. Namely, changing the direction of communication takes around 30 ms. This means that one cycle of requesting motor status, receiving data and then sending motor control update takes at least 60 ms (it takes two changes of communication direction). And this is especially a problem if you want to control multiple motors at the same time (and built-in synchronize mechanism is not good enough for you). One solution to this problem is that Bluetooth is used only for communication in one direction and an additional program on the NXT brick is checking motor status and correcting possible errors. Of course this also means that information about motor position have to be obtained in some other way: by predicting from sent commands, or measuring/probing with regular NXT commands when there is time for that (and latency at that time is not a problem), or by using some external sensors attached to the controlling computer and not to the NXT brick (camera tracking system for example). Use NBC () to compile basic NXC code (@remote.nxc@) included with this module into @remote.rxe@ NXT program (or use already compiled version) and then upload it with @nxt-upload@ program. This module will then run that program and sends it messages to control the NXT brick. Because commands will be executed and controlled directly on the NXT brick less latency and more powerful control is possible. Check "Robotics.NXT.MotorControl" for another (more precise but more complex) approach to message-based control. It would be great to one day combine @MotorControl@'s precision with API (especially interruptability) of @remote.nxc@. -} module Robotics.NXT.Remote ( -- * Initialization startRemoteProgram, stopRemoteProgram, -- * Control sendRemoteCommand, RemoteCommand(..), RemoteCommandType(..) ) where import Control.Exception import Text.Printf import Robotics.NXT programFilename :: FileName programFilename = "remote.rxe" data RemoteCommandType = MoveFor OutputPower TachoLimit -- ^ Move specified motors for a given number of degrees at a given speed. | SetTo OutputPower TachoCount -- ^ Set specified motors' position to a given offset in degrees from a zero position (it is assumed -- that motors are at zero position at @remote.rxe@ program start, you can use 'resetMotorPosition' -- to assure that, but probably not needed as the NXT brick resets things at program start) at a given speed. -- Probably not a good idea to mix 'SetTo' and 'MoveFor' on the same motor as 'MoveFor' resets -- position. deriving (Eq, Ord, Read, Show) data RemoteCommand = RemoteCommand [OutputPort] RemoteCommandType -- ^ Data type of remote command for specified output port(s). deriving (Eq, Ord, Read, Show) {-| Sends a command to the @remote.rxe@ program. Commands can be interrupted (or supplemented, for other motors) immediately by a next command. -} sendRemoteCommand :: RemoteCommand -> NXT () sendRemoteCommand = messageWrite Inbox1 . encodeRemoteCommand encodeRemoteCommand :: RemoteCommand -> String encodeRemoteCommand (RemoteCommand ports command) = case command of (MoveFor power limit) | (-100) <= power && power <= 100 && 0 <= limit && limit <= 999999 -> "6" ++ ports' ++ power' ++ limit' | otherwise -> throw $ PatternMatchFail "encodeRemoteCommand" where power' = printf "%03d" $ power + 100 limit' = printf "%06d" limit (SetTo power count) | 0 <= power && power <= 100 && (-99999) <= count && count <= 99999 -> "7" ++ ports' ++ power' ++ count' | otherwise -> throw $ PatternMatchFail "encodeRemoteCommand" where power' = printf "%03d" $ power + 100 count' = printf "%+06d" count where ports' = show . sum . zipWith (*) [1, 2, 4] . map (fromEnum . flip elem ports) $ [A ..] {-| Starts @remote.rxe@ program on the NXT brick. Program has to be uploaded/available on the NXT brick. -} startRemoteProgram :: NXT () startRemoteProgram = ensureStartProgram programFilename {-| Stops current running program on the NXT brick. Probably this means @remote.rxe@ program on the NXT brick. -} stopRemoteProgram :: NXT () stopRemoteProgram = stopProgram