www.gusucode.com > Zaber Device Control Toolbox > +Zaber/BinaryDevice.m

    classdef BinaryDevice < Zaber.Device
%   BINARYDEVICE Implements the Zaber.Device interface for the Binary protocol.
%
%   device = Zaber.BINARYDEVICE.initialize(protocol, address);
%   protocol - An instance of Zaber.BinaryProtocol.
%   address  - The numeric address of the device on a daisy chain. 
%              Legal values are 1-99.
%   device   - Output: An initialized instance of this class.
%
%   This class represents a single Zaber device on a possible daisy chain
%   of multiple devices. An instance of this class can be used to
%   communicate with the device, and its peripherals if it has any. The
%   generic methods and properties defined by the base class can be used
%   for interaction with basic features. More advanced usage requires
%   use of protocol-specific commands; the request method is useful for
%   that as it will automatically ensure your message is addressed to the
%   device represented by the class instance.
%
%   See also Zaber.Device, Zaber.Protocol.detect, Zaber.BinaryProtocol 

%   Author: Zaber Technologies Software Team <contact@zaber.com>
    
    
%% Public instance properties
    properties (SetAccess = protected)
        % MESSAGEIDSENABLED Set to true if the device is using message IDs.
        MessageIdsEnabled
    end
    
    
%% Public instance methods
    methods
        function reply = request(obj, aCommand, aData)
        % REQUEST Convenience method to transact with a device.
        % reply = device.REQUEST(command, data);
        %
        % command - Command to send the device. This is a byte.
        % data    - Integer argument for the command.
        % reply   - Response from the device, as a BinaryMessage. 
        %           Note that if there are overlapping requests this
        %           will actually be the next response received on the
        %           port, which could potentially be from another
        %           device or another command.
        %
        % This method ignores timeouts and will block until a message
        % is received.
        %
        % See also Zaber.BinaryProtocol.request, Zaber.BinaryProtocol.send,
        % Zaber.BinaryProtocol.receive, Zaber.BinaryErrorType
            
            message = Zaber.BinaryMessage(obj.DeviceNo, aCommand, aData);
            
            obj.Protocol.send(message);
            while (~obj.Protocol.canreceive())
                pause(0.01);
            end
            
            reply = obj.Protocol.receive(obj.MessageIdsEnabled);
        end
        
        
        function value = get(obj, aSetting)
        % GET Read a setting from the device.
        % value = device.get(setting)
        %
        % setting - Numeric identifier for the setting to read. See the 
        %           Zaber Binary protocol manual for legal values:
        %           https://www.zaber.com/wiki/Manuals/Binary_Protocol_Manual#Return_Setting_-_Cmd_53
        %           or use the Zaber.BinaryCommandType enumeration.
        % value   - Current value of the setting, as a 32-bit integer. 
        %
        % In the event of a communication error, an error will be thrown.
        % If the device returns an error result, a warning will occur and
        % the method will return the empty array.
        %
        % See also set, Zaber.BinaryCommandType, Zaber.BinaryErrorType
        
            value = [];
            reply = obj.request(Zaber.BinaryCommandType.Return_Setting, aSetting);
            
            if (~isa(reply, 'Zaber.BinaryMessage'))
                error('Zaber:BinaryDevice:get:communicationError', ...
                      'Device %d failed to respond to request to read setting %d.', ...
                      obj.DeviceNo, aSetting);
            elseif (reply.IsError)
                warning('Zaber:BinaryDevice:get:readError', ...
                        'Attempt to read setting %d from device %d resulted in error %d (%s).', ...
                        aSetting, obj.DeviceNo, reply.Data, ...
                        char(Zaber.BinaryErrorType(reply.Data)));
            elseif (reply.Command ~= aSetting)
                error('Zaber:BinaryDevice:get:badSetting', ...
                      'Device %d responded with wrong setting number when reading setting %d.', ...
                      obj.DeviceNo, aSetting);
            else
                value = reply.Data;
            end
        end
            
        
        function result = set(obj, aSetting, aValue)
        % SET Write a value to a device setting.
        % result = device.set(setting, value)
        %
        % setting - Numeric identifier for the setting to write. For legal
        %           values, see the Zaber binary protocol manual:
        %           https://www.zaber.com/wiki/Manuals/Binary_Protocol_Manual
        %           or use the Zaber.BinaryCommandType enumeration.
        % value   - New value of the setting, as a 32-bit integer.
        % result  - True if the write succeeded, or the reply message if
        %           the device returned an error response.
        %
        % Errors will be thrown if there is a communication error. If the
        % setting does not exist, if the setting is read-only, or if the
        % value provided is out of range for the setting then a warning
        % will occur and the device's response message will be returned.
        %
        % See also get, Zaber.BinaryCommandType, Zaber.BinaryErrorType
        
            result = false;
            reply = obj.Protocol.request(...
                Zaber.BinaryMessage(obj.DeviceNo, int32(aSetting), aValue));
            
            if (~isa(reply, 'Zaber.BinaryMessage'))
                error('Zaber:BinaryDevice:set:commandFailed', ...
                      'Device %d failed to respond to request to write setting %d.', ...
                      obj.DeviceNo, aSetting);
            elseif (reply.IsError)
                warning('Zaber:BinaryDevice:set:writeError', ...
                        'Attempt to read setting %d from device %d resulted in error %d (%s).', ...
                        aSetting, obj.DeviceNo, reply.Data, ...
                        char(Zaber.BinaryErrorType(reply.Data)));
            else
                result = true;
            end
        end
        
        
        function range = getrange(obj)
        % GETRANGE Determine the movement limits of the device.
        % range = device.GETRANGE();
        %
        % range - A 1x2 matrix with the first entry being the lower bound
        %         on legal device position and the second entry being the
        %         upper bound. Empty array if the concept doesn't apply.
        %         For a multi-axis controller the result will be an Nx2
        %         array where N is the number of axes. For rotary devices
        %         the result is the range for one full rotation. Note
        %         that the range returned may not reflect physical limits
        %         if the device has been configured to use less than its
        %         full range of travel.
        %
        % Reads device settings to determine the device's current idea of
        % its range of movement. Returns the empty array if the concept
        % does not apply to the device type.
        %
        % See also getposition, moveabsolute, getnumindices, Units
        
            minPos = 0;
            if (obj.FirmwareVersion >= 6.0)
                minPos = obj.get(Zaber.BinaryCommandType.Set_Minimum_Position);
            end
        
            if (obj.MotionType == Zaber.MotionType.Rotary)
                if (obj.FirmwareVersion >= 6.22)
                    maxPos = obj.get(Zaber.BinaryCommandType.Set_Cycle_Distance);
                else
                    maxPos = obj.get(Zaber.BinaryCommandType.Set_Maximum_Position);
                end
                
                circle = obj.Units.positiontonative(360.0);
                maxPos = min(circle, maxPos);
                
            else
                maxPos = obj.get(Zaber.BinaryCommandType.Set_Maximum_Position);
            end
            
            range = [minPos maxPos];
        end
        
        
        function error = waitforidle(obj, aPingInterval)
        % WAITFORIDLE Block until the device stops moving.
        % error = device.WAITFORIDLE();
        % error = device.WAITFORIDLE(interval);
        %
        % interval - Optional; number of seconds to wait between checks
        %            of the device's state. Defaults to 0.1 seconds.
        % error    - Return value, normally empty. If the device
        %            entered an error state while this method was
        %            checking for idleness, this method will return the
        %            error message.
        %
        % This method will ping the device repeatedly until the device
        % either becomes idle or produces an error response.
        %
        % Note this command is of little utility when using the Binary
        % protocol because most of the command helpers block until they get
        % a response. It's included for API completeness and to aid protocol
        % independence. This method returns immediately if there is a
        % device error, the device is stalled or the device is parked.
            
            interval = 0.1;
            if (nargin > 1)
                interval = aPingInterval;
            end
        
            moving = true;
            if (~obj.IsAxis)
                moving = false;
            end
            
            while (moving)
                reply = obj.request(...
                    Zaber.BinaryCommandType.Return_Status, 0);
                
                if (~isa(reply, 'Zaber.BinaryMessage'))
                    error = reply;
                    moving = false;
                elseif (reply.IsError || ...
                    (reply.Data == Zaber.BinaryStatusType.Idle) || ...
                    (reply.Data == Zaber.BinaryStatusType.Stalled_or_Displaced) || ...
                    (reply.Data == Zaber.BinaryStatusType.Parked))
                    error = reply.Data;
                    moving = false;
                else
                    pause(interval);
                end
            end
        end

        
        function error = home(obj)
        % HOME Move the device to its home position.
        % error = device.HOME();
        %
        % error - Error code from the device, if the command fails. See
        %         the list of error codes in the Zaber Binary protocol
        %         manual:
        %         https://www.zaber.com/wiki/Manuals/Binary_Protocol_Manual#Error_Codes
        %         or use the Zaber.BinaryErrorType enumeration.
        %
        % This command will block until the move completes or an error
        % occurs - potentially a long time. 
        %
        % See also moveabsolute, moverelative, moveatvelocity, moveindexed,
        % getposition, stop, waitforidle, Zaber.BinaryErrorType
        
            error = [];
            
            response = obj.request(Zaber.BinaryCommandType.Home, 0);
            if (response.IsError)
                error = response.Data;
            end
        end
        
        
        function error = moveabsolute(obj, aPosition)
        % MOVEABSOLUTE Move the state to an absolute position.
        % error = device.moveabsolute(position);
        % 
        % position - Position to move to, in native device units.
        % error    - Error code from the device, if the command fails. See
        %            the list of error codes in the Zaber Binary protocol
        %            manual:
        %            https://www.zaber.com/wiki/Manuals/Binary_Protocol_Manual#Error_Codes
        %            or use the Zaber.BinaryErrorType enumeration.
        %
        % This command will block until the move completes or an error
        % occurs - potentially a long time. 
        % If the move completes successfully, the empty array is returned.
        %
        % See also home, stop, waitforidle, getposition, moverelative,
        % moveatvelocity, moveindexed, Units, Zaber.BinaryErrorType  
        
            error = [];
            
            response = obj.request(...
                Zaber.BinaryCommandType.Move_Absolute, int32(aPosition));
            
            if (response.IsError)
                error = response.Data;
            end
        end        
        
        
        function error = moverelative(obj, aDelta)
        % MOVERELATIVE Move the device by a relative amount.
        % error = device.MOVERELATIVE(delta);
        % 
        % delta    - Distance to move by, in native device units.
        % error    - Error code from the device, if the command fails. See
        %            the list of error codes in the Zaber Binary protocol
        %            manual:
        %            https://www.zaber.com/wiki/Manuals/Binary_Protocol_Manual#Error_Codes
        %            or use the Zaber.BinaryErrorType enumeration.
        %
        % This method blocks until the move completes.
        %
        % See also home, stop, waitforidle, getposition, moveabsolute,
        % moveatvelocity, moveindexed, Units, Zaber.BinaryErrorType  
        
            error = [];
            
            response = obj.request(...
                Zaber.BinaryCommandType.Move_Relative, int32(aDelta));
            
            if (response.IsError)
                error = response.Data;
            end
        end
        
        
        function error = moveatvelocity(obj, aVelocity)
        % MOVEATVELOCITY Move the device at a specified velocity.
        % error = device.MOVEATVELOCITY(velocity);
        % 
        % velocity - Speed to move at, in native device units.
        % error    - Error code from the device, if the command fails. See
        %            the list of error codes in the Zaber Binary protocol
        %            manual:
        %            https://www.zaber.com/wiki/Manuals/Binary_Protocol_Manual#Error_Codes
        %            or use the Zaber.BinaryErrorType enumeration.
        %
        % This method blocks until the move completes.
        %
        % See also home, stop, waitforidle, getposition, moverelative,
        % moveabsolute, moveindexed, Units, Zaber.BinaryErrorType 
        
            error = [];
            
            response = obj.request(...
                Zaber.BinaryCommandType.Move_at_Constant_Speed, int32(aVelocity));
            
            if (response.IsError)
                error = response.Data;
            end
        end
        
        
        function error = stop(obj)
        % STOP Stop the device if it is moving.
        % error = device.STOP();
        %
        % error     - Error code from the device, if the command fails. See
        %             the list of error codes in the Zaber Binary protocol
        %             manual:
        %             https://www.zaber.com/wiki/Manuals/Binary_Protocol_Manual#Error_Codes
        %             or use the Zaber.BinaryErrorType enumeration.
        %
        % See also home, waitforidle, getposition, moveabsolute,
        % moverelative, moveatvelocity, moveindexed, Units,
        % Zaber.BinaryErrorType 
        
            error = [];
            
            response = obj.request(Zaber.BinaryCommandType.Stop, 0);
            if (response.IsError)
                error = response.Data;
            end
        end
        
        
        function pos = getposition(obj)
        % GETPOSITION Get the current device position in native units.
        % pos = device.GETPOSITION();
        %
        % If there is a communication error or the device is not an axis,
        % the empty array will be returned.
        %
        % See also range, stop, Units, moveabsolute, moverelative,
        % moveatvelocity, moveindexed, waitforidle
        
            pos = [];
            
            response = obj.request(...
                Zaber.BinaryCommandType.Return_Current_Position, 0);
            
            if (~response.IsError)
                pos = response.Data;
            end
        end
        
        
        function num = getnumindices(obj)
        % GETNUMINDICES Determine how many indexed positions the device has.
        % num = getvice.GETNUMINDICES();
        %
        % Return value is the maximum position that can be passed to
        % MOVEINDEXED. Returns zero on devices that do not support the
        % MOVEINDEXED command.
        %
        % See also range, moveindexed, getposition
            
            num = 0;
            
            range = obj.getrange();
            indexSize = obj.get(Zaber.BinaryCommandType.Set_Index_Distance);
            
            if (~isempty(range) && ~isempty(indexSize))
                num = range(2) / indexSize;
            end
        end
        
        
        function error = moveindexed(obj, aIndex)
        % MOVEINDEXED Move to an indexed position.
        % error = device.MOVEINDEXED(index);
        %
        % index - The index of the position to move to. Minimum value is 1,
        %         and maximum value is the number returned by
        %         GETNUMINDICES. Will be rejected on devices that don't
        %         support indexed moves.
        % error - Error code from the device, if the command fails. See
        %         the list of error codes in the Zaber Binary protocol
        %         manual:
        %         https://www.zaber.com/wiki/Manuals/Binary_Protocol_Manual#Error_Codes
        %         or use the Zaber.BinaryErrorType enumeration.
        %
        % This command is intended for use with indexed-position devices
        % such as the Filter Wheel. Although the other movement commands
        % will often work with such devices, this method provides an easier
        % way to reach a useful position.
        %
        % This command will block until the movement completes.
        %
        % See also home, stop, waitforidle, moveabsolute, moverelative,
        % moveatvelocity, Zaber.BinaryErrorType
        
            error = [];
            
            response = obj.request(...
                Zaber.BinaryCommandType.Move_Index, int32(aIndex));
            
            if (response.IsError)
                error = response.Data;
            end
        end
        
    end
    
    
 %% Public static methods
    methods (Static)
        function instance = initialize(aProtocol, aDeviceNumber, aDeviceId)
        % INITIALIZE Construct a representation for a single device.
        % device = Zaber.BinaryDevice.INITIALIZE(protocol, address, id)
        %
        % protocol - A BinaryProtocol instance.
        % address  - The daisy chain address of a device to represent.
        % id       - The numeric device type ID of the device.
        %            Optional. If not provided it will be queried for.
        % obj      - An initialized BinaryDevice instance.
        %
        % Given the daisy chain address and device type ID for a device
        % that has been found using the given protocol, queries the device
        % for its properties and constructs a new Zaber.Device subclass
        % instance to represent that device.
        %
        % See also Zaber.BinaryProtocol, Zaber.Device,
        % Zaber.AsciiDevice.initialize
            
            if (~isa(aProtocol, 'Zaber.BinaryProtocol'))
                error('Zaber:BinaryDevice:initialize:badProtocol', ...
                      'Protocol must be binary to use this method.');
            end
            
            if (nargin > 2)
                deviceId = aDeviceId;
            else
                reply = aProtocol.request(...
                    Zaber.BinaryMessage(aDeviceNumber, ...
                        Zaber.BinaryCommandType.Return_Device_ID, 0));
                
                if (~isempty(reply) && ~reply.IsError)
                    deviceId = reply.Data;
                else
                    error('Zaber:BinaryDevice:initialize:idError', ...
                          'Failed to get the type ID for device %d.', ...
                          aDeviceNumber);
                end
            end
                  
            
            instance = Zaber.BinaryDevice(aProtocol, aDeviceNumber, deviceId);
            
            % Get the firmware version.
            reply = aProtocol.request(...
                Zaber.BinaryMessage(instance.DeviceNo, ...
                    Zaber.BinaryCommandType.Return_Firmware_Version, 0));
                
            if (~reply.IsError)
                instance.FirmwareVersion = double(reply.Data) / 100.0;
            end
            
            % Determine if message ID mode is enabled.
            if (instance.FirmwareVersion < 6.0)                
                data = instance.get(Zaber.BinaryCommandType.Set_Device_Mode);
                if (isnumeric(data))
                    instance.MessageIdsEnabled = (0 ~= bitand(data, 64));
                else
                    error('Zaber:BinaryDevice:initialize:messageIdCheckFailed', ...
                          'Failed to determine message ID mode on device %d.', ...
                          aDeviceNumber);
                end
            else
                data = instance.get(Zaber.BinaryCommandType.Set_Message_ID_Mode);
                if (isnumeric(data))
                    instance.MessageIdsEnabled = (data == 1);
                else
                    error('Zaber:BinaryDevice:initialize:messageIdCheckFailed', ...
                          'Failed to determine message ID mode on device %d.', ...
                          aDeviceNumber);
                end
            end
            
            % Get the peripheral ID.
            reply = instance.Protocol.request(...
                Zaber.BinaryMessage(instance.DeviceNo, ...
                    Zaber.BinaryCommandType.Return_Setting, ...
                    Zaber.BinaryCommandType.Set_Peripheral_ID));
            
            if (isa(reply, 'Zaber.BinaryMessage') && ...
                ~reply.IsError && (reply.Command == Zaber.BinaryCommandType.Set_Peripheral_ID))
                instance.PeripheralId = reply.Data;
            end
            
            % Find database records for this device and peripheral.
            db = Zaber.DeviceDatabase.instance();
            deviceRecord = db.finddevice(instance.DeviceId);
            periRecord = db.findperipheral(deviceRecord, instance.PeripheralId);
            instance.Name = db.getdevicename(deviceRecord, periRecord);
            
            % Get unit conversion properties from the database.
            if (~isempty(periRecord))
                [instance.MotionType, instance.Units] = ...
                    db.determinemotiontype(deviceRecord, periRecord);
                % Get the current resolution.
                if (instance.Units.IsScaleResolutionDependent)
                    instance.Units.Resolution = instance.get(...
                        Zaber.BinaryCommandType.Set_Microstep_Resolution);
                end
            end
            
            instance.IsAxis = (instance.MotionType ~= Zaber.MotionType.None);
            
            instance.IO = Zaber.BinaryIoPort.detect(instance);
        end
    end

    
 %% Protected instance methods
    methods (Access = protected)
        function obj = BinaryDevice(aProtocol, aDeviceNumber, aDeviceId)
        % BINARYDEVICE Initializes properties to their default values.
        % device = Zaber.BINARYDEVICE(protocol, address, id)
        %
        % protocol - A BinaryProtocol instance.
        % address  - The daisy chain address of a device to represent.
        % id       - The numeric device type ID of the device.
        % obj      - An initialized BinaryDevice instance.
        %
        % The device ID argument is the type number of the device. If not
        % detected through code, you can look these IDs up on Zaber's
        % website at: https://www.zaber.com/support/?tab=ID%20Mapping
        %
        % This constructor does not fill in the properties of the
        % device. Use the static initialize method to do that.
        %
        % See also initialize, Device
            
            obj = obj@Zaber.Device(aProtocol, aDeviceNumber, aDeviceId);
            obj.MessageIdsEnabled = false;
        end
    end
end