%

% Copyright 2012-2013 The MathWorks, Inc.

classdef ServerConnection < ...
rtw.connectivity.RtIOStreamWrapper
%SERVERCONNECTION Class that establishes and manages the
%connection to a target running
%
% This is an undocumented class. Its methods and properties are likely to
% change without warning from one release to the next.

properties (GetAccess = 'public', SetAccess = 'private')
memUnitSize; % Size of the char type in bytes on target
littleEndian; % Is target little Endian or big Endian
charSize;
longSize;
shortSize;
floatSize;
pointerSize;
intSize;
doubleSize;
RTT; % round trip time of a command
targetBufferSize; % target reports BUFFER_SIZE
isTargetBlockingOnReceive = false;
verbose = false;
end

properties (Constant)

% those commands are private
op_macro_run = 1; % starts a macro

% macro commands
op_macro_repeat_start = 4; % marks a start of a repeat
op_macro_repeat = 5; % specifies repeat of macro
% commands from last repeat_start
op_macro_blockRcvSlow = 6; % does a blocking receive with 40
% bit counter block error
% detection
op_macro_blockSendSlow = 7; % does a blocking send with 40
% bit counter blocks error
% detection
op_macro_blockRcvFast = 8; % does a blocking receive with
% 8 bit counter check
op_macro_blockSendFast = 9; % does a blocking send with 8 bit
% counter check
op_macro_pause = 10; % calls sleep.
op_macro_sendWakeup = 11; % indicates sleep has finished.

% ACK codes
ACK_SUCCESS = 0;
ACK_UNKNOWN_COMMAND = 1;
ACK_RTIOSTREAM_ERROR = 2;
ACK_DATA_INTEGRITY_ERROR = 3;
ACK_COMMAND_CHECKSUM_ERROR = 4;
ACK_BUFFER_SPACE_ERROR = 5;

% Handshake constants
HS_INCORRECT_DATA_RECVD = 19;
HS_INCORRECT_DATA_RECVD_ACK = 20;
HS_STOP = 51;
HS_STOP_ACK = 52;
HS_REPEAT = 1;
HS_RTIOSTREAM_RECV_ERROR = 53;
end

properties (Constant, GetAccess = 'private')

op_shutdown = 0; % shuts down server
op_blockingReceive_test = 2; % tests blocking by receiving one byte
% on target
op_getBufferSize = 3; % get the size of the available buffer
% on the device
UINT32MAX = 2^32 -1;
UINT8MAX = 2^8 - 1;
end

properties (GetAccess = 'private', SetAccess = 'private')
IsStreamOpen = false;
SecondsPerTic = [];
tempMemUnitSize; % For saving memUnitSize
end

methods

function this = ServerConnection(library, parameters, ...
verbose)
% Create connection and detect device parameters.

% Initialize library
this = this@rtw.connectivity.RtIOStreamWrapper(library);
this.verbose = verbose;

% Assign parameters
for idx = 0:numel(parameters)/2-1
p1 = parameters(2*idx + 1);
p2 = parameters(2*idx+2);
this.setOpenRtIOStreamArgPair({p1{1}, p2{1}});
end

% Connect to server

try
% evalc hides retry messages from open
log = evalc('this.open(''uint8'')');
if ismac
speed = regexp(log, 'QE_DIAG: cfgetispeed returned \d*','match');
i_speed = regexp(speed, '\d+','match');
speed = regexp(log, 'QE_DIAG: cfgetospeed returned \d*','match');
o_speed = regexp(speed, '\d+','match');
end

this.IsStreamOpen = true;

this.memUnitSize = 1; % temporary
this.littleEndian = true; % temporary

%% START OF HANDSHAKE
coder.rtiostreamtest.Test.lnverboseln(verbose, 'VerboseEnterHandshake');

this.setRecvTimeout(0.5);

% Receive beacon
retries = 0;
validBeaconReceived = false;
beacon = [];
while ~validBeaconReceived && retries < 5
try
coder.rtiostreamtest.Test.verboseln(verbose, 'VerboseEnterWaitBeacon');

dataIn = this.blockingRecv(1);
coder.rtiostreamtest.Test.verboseln(verbose, 'VerboseFirstBeacon');
switch (dataIn)
case {1, 2, 4, 8, 101, 102, 104, 108}
beacon = double(dataIn);
% Valid beacon was received, decode it
if beacon < 100
tmpMemUnitSize = beacon;
memUnitBitSize = tmpMemUnitSize * 8;
this.littleEndian = true;

coder.rtiostreamtest.Test.verboseln(verbose, ...
'VerboseBeaconDecoded', ...
coder.rtiostreamtest.Test.message('LittleEndianDevice'), ...
memUnitBitSize);
else
tmpMemUnitSize = beacon-100;
memUnitBitSize = tmpMemUnitSize * 8;
this.littleEndian = false;
coder.rtiostreamtest.Test.verboseln(verbose, ...
'VerboseBeaconDecoded', ...
coder.rtiostreamtest.Test.message('BigEndianDevice'), ...
memUnitBitSize);
end
numBytesToClear = tmpMemUnitSize - 1;
if numBytesToClear > 0
% clear out remainder of the word that
% should have been sent from the target
try
clearedData = this.blockingRecv(numBytesToClear);
if ~isequal(clearedData, zeros(1, numBytesToClear))
coder.rtiostreamtest.Test.error('ErrorHSClearedWordNonZero', int2str(memUnitBitSize));
end
catch clearDataE
if strcmp(clearDataE.identifier,'Connectivity:target:ErrorRecvTimeout')
coder.rtiostreamtest.Test.error('ErrorHSClearWordTimeout', int2str(memUnitBitSize));
else
rethrow(clearDataE);
end
end
end
% store the memUnitSize so that future send
% / receives take account of it and pack /
% unpack data as necessary
this.memUnitSize = tmpMemUnitSize;
% we're done
validBeaconReceived = true;
case this.HS_RTIOSTREAM_RECV_ERROR
% target's rtiostream recv reported an error
coder.rtiostreamtest.Test.error('ErrorHSTargetRtiostreamRecvError');
otherwise
% target sent unexpected data
if ismac && ~isempty(i_speed) && ~isempty(o_speed)
fprintf('\nVerify that the following values match the expected baud rate\n');
fprintf('The in speed set on termios is %s\n', i_speed{1}{1});
fprintf('The out speed set on termios is %s\n', o_speed{1}{1});
end
coder.rtiostreamtest.Test.error('ErrorHSHostReceivedUnexpectedData', int2str(dataIn));
end
catch err
if strcmp(err.identifier,'Connectivity:target:ErrorRecvTimeout')
% Target is likely blocking on receive, nudge the
% device so it sends us something
coder.rtiostreamtest.Test.verboseln(verbose, 'VerboseMightBeBlocking');
retries = retries + 1;
this.blockingSend(uint8(this.HS_REPEAT));
else
rethrow(err);
end
end
end

if retries >= 5
coder.rtiostreamtest.Test.error('ErrorHSReceiveRetriesExceeded');
end

% Put back timeout to a more realistic value
this.setRecvTimeout(30);

if this.memUnitSize ~= 1 && this.memUnitSize ~= 2 && ...
this.memUnitSize ~= 4 && this.memUnitSize ~= 8
coder.rtiostreamtest.Test.error('ErrorWrongMemUnitSize');
end

% Send stop signal
this.blockingSend(uint8(this.HS_STOP));

this.setRecvTimeout(30);

% get stop ack to complete synchronization
syncComplete = false;
while ~syncComplete
stopACK = this.blockingRecv(1);
switch stopACK
case this.HS_STOP_ACK
% ok
syncComplete = true;
case this.HS_INCORRECT_DATA_RECVD
% ok
% target received unexpected data
coder.rtiostreamtest.Test.warning('ErrorHSTargetReceivedUnexpectedData');
syncComplete = true;
case beacon
% accept repeats of the beacon: owing to the
% way our UDP driver "connects" the target can
% only send once it has actually received some
% data therefore MATLAB may request a repeat of
% the beacon before the target has finished
% sending its initial beacon and we'll end up
% with two to process.
case this.HS_RTIOSTREAM_RECV_ERROR
% target's rtiostream recv reported an error
coder.rtiostreamtest.Test.error('ErrorHSTargetRtiostreamRecvError');
otherwise
% target sent unexpected data
coder.rtiostreamtest.Test.error('ErrorHSHostReceivedUnexpectedData', int2str(stopACK));
end
end
catch origException
% add generic message
[errMsg, errId] = DAStudio.message('Connectivity:RtIOStreamTest:ErrorHSCannotConnect');
exception = MException(errId, '%s', errMsg);
exception = exception.addCause(origException);
throw(exception);
end

coder.rtiostreamtest.Test.verboseln(verbose, 'VerboseReadyToAccept');

% Get other data
data = this.blockingRecv(7);

this.charSize = data(1);
this.longSize = data(2);
this.shortSize = data(3);
this.floatSize = data(4);
this.pointerSize = data(5);
this.intSize = data(6);
this.doubleSize = data(7);

coder.rtiostreamtest.Test.verboseln(verbose, 'VerboseDataDiscoveryDone', ...
this.charSize*8, this.doubleSize*8);

%% Test whether device blocks on receive

coder.rtiostreamtest.Test.verboseln(verbose, 'VerboseBlockingTest');

this.setRecvTimeout(0.5);
try
this.sendCommandAndAck(this.op_blockingReceive_test, 0);
coder.rtiostreamtest.Test.verboseln(verbose, 'VerboseDeviceDoesntBlock');
catch err
if strcmp(err.identifier,'Connectivity:target:ErrorRecvTimeout')
% If PC reports timeout, the device is blocking
this.isTargetBlockingOnReceive = true;
this.blockingSend(uint8(123)); % Send a byte to unblock
this.waitForAck();
coder.rtiostreamtest.Test.verboseln(verbose, 'VerboseDeviceBlocks');
else
rethrow(err);
end
end

%% Clock synchronization

this.setRecvTimeout(60);

coder.rtiostreamtest.Test.verboseln(verbose, 'VerboseSyncingClock');

% The number of tics the device should pause for initially
ticCount = 100;

% Arrays for containing data
secsPerTicArr = [];
RTTarr = [];

syncValuesOK = false;
syncTimer = tic;
syncTimeout = 60;
% Loop until tic size is detected or timeout
while toc(syncTimer) <= syncTimeout

id = tic;
% make sure ticCount remains valid
if ticCount > coder.rtiostreamtest.ServerConnection.UINT32MAX
% end the loop
break;
end
this.sendCommandAndAck(this.op_macro_pause, ticCount);
RTTvalue = toc(id);

id = tic;
try
this.sendCommandAndAck(this.op_macro_run);
catch err
coder.rtiostreamtest.Test.println('ErrorCannotSync');
rethrow(err);
end
elapsed = toc(id);

secsPerTic = (elapsed-RTTvalue) / ticCount;

coder.rtiostreamtest.Test.verboseln(verbose, 'VerboseSyncIteration', ...
elapsed, RTTvalue, ticCount);

% Detect if the time it took to pause is small so that
% the RTTvalue can influence it. If it is the case
% don't put it into the statistics and ramp up the
% ticCount
if elapsed < 5*RTTvalue
ticCount = ticCount*100;
continue;
end

RTTarr = [RTTarr RTTvalue]; %#ok<AGROW>
secsPerTicArr = [secsPerTicArr secsPerTic]; %#ok<AGROW>

% If we have reached a point where the RTT doesn't
% influence the results at all, we have acquired a good
% mean value so we can stop.
%
% Wait for at least 1 second to improve robustness
if elapsed > max(50*RTTvalue, 1)
syncValuesOK = true;
break;
end

ticCount = ticCount*2;
end

% make sure we were able to get representative values before
% timeout
if ~syncValuesOK
DAStudio.error('Connectivity:RtIOStreamTest:ErrorCannotSync');
end

assert(~isempty(RTTarr), 'RTTarr is empty!');
assert(~isempty(secsPerTicArr), 'secsPerTicArr is empty!');

% Filter data using standard deviation
if numel(secsPerTicArr) > 2, secsPerTicArr = secsPerTicArr(abs(secsPerTicArr-mean(secsPerTicArr)) < std(secsPerTicArr)); end;
if numel(RTTarr) > 2, RTTarr = RTTarr(abs(RTTarr-mean(RTTarr)) < std(RTTarr)); end;

% Extract the tic size
this.RTT = mean(RTTarr);
secsPerTic = mean(secsPerTicArr);

msRTT = this.RTT * 1000;
coder.rtiostreamtest.Test.verboseln(verbose, 'VerboseSyncReport', ...
msRTT, numel(RTTarr), ...
secsPerTic);

% Record filtered and averaged value
%
% Build in a safety tolerance so that we always sleep for
% significantly longer than requested!
this.SecondsPerTic = secsPerTic / 2.5;

this.setRecvTimeout(60);

% Wait a certain amount of seconds to check whether timer
% is synchronized
wait_amount = 0.5;
if verbose
coder.rtiostreamtest.Test.verboseln(verbose, 'VerboseConfirmSync', ...
wait_amount, round(this.secondsToTics(wait_amount)));
end

% Check whether timer was synchronized
this.sendCommandAndAck(this.op_macro_pause, ...
round(this.secondsToTics(wait_amount)));
id = tic;
this.sendCommandAndAck(this.op_macro_run);
actualSleep = toc(id);
% The aim is to be able to sleep for at least as long
% as requested.
if (actualSleep < wait_amount) || (actualSleep > 20 * wait_amount)
% Throw an error if we didn't synchronize.
coder.rtiostreamtest.Test.error('ErrorDeviceDidSleepEnough', ...
wait_amount, actualSleep);
end
% final adjustment
coder.rtiostreamtest.Test.verboseln(verbose, 'VerboseDeviceSleptEnough', actualSleep);
this.SecondsPerTic = ...
this.SecondsPerTic * actualSleep / wait_amount;

%% Get buffer size
[~, this.targetBufferSize] = ...
this.sendCommandAndAck(this.op_getBufferSize);

coder.rtiostreamtest.Test.verboseln(verbose, 'VerboseBufferDetected', this.targetBufferSize);

coder.rtiostreamtest.Test.verboseln(verbose, 'VerboseInitDone');

this.setRecvTimeout(60);
end

function sizeSent = send(this, data)
% if byte addressable do nothing
if this.memUnitSize == 1
sizeSent = send@rtw.connectivity.RtIOStreamWrapper ...
(this, data);
return;
end

% pack data into the first byte of each word
% (assume target reads bytes of word in little endian order)
newdata = uint8(zeros(1, numel(data)*this.memUnitSize));
newdata(1:this.memUnitSize:numel(newdata)) = data(:);

sizeSentNew = send@rtw.connectivity. ...
RtIOStreamWrapper(this, newdata);

% Return what was expected by sender
sizeSent = sizeSentNew / this.memUnitSize;
end

function data = recv(this, receiveSize)
% if byte addressable do nothing
if this.memUnitSize == 1
data = recv@rtw.connectivity. ...
RtIOStreamWrapper(this, receiveSize);
return;
end

% Receive raw data.
recvdata = recv@rtw.connectivity. ...
RtIOStreamWrapper(this, receiveSize * this.memUnitSize);

% The size of the received data must be divisible by the word
% size. If it is not, make sure we receive more bytes to
% complete the next word.
partialBytes = mod(numel(recvdata), this.memUnitSize);
if partialBytes ~= 0
numMissingBytes = this.memUnitSize - partialBytes;
else
numMissingBytes = 0;
end
if numMissingBytes ~= 0
missingBytes = [];
id = tic;
while (numMissingBytes > 0)
currBytes = recv@rtw.connectivity.RtIOStreamWrapper( ....
this, numMissingBytes);
missingBytes = [missingBytes currBytes]; %#ok<AGROW>
numMissingBytes = numMissingBytes - length(currBytes);
elapsed = toc(id);
if elapsed > 15
coder.rtiostreamtest.Test.error('ErrorMemoryChunkPart', ...
numel(recvdata), this.memUnitSize, ...
this.memUnitSize*8, this.memUnitSize, ...
this.memUnitSize, this.memUnitSize);
end
end
recvdata = [recvdata missingBytes];
assert(mod(numel(recvdata), this.memUnitSize) == 0, ...
'recvdata must contain complete words');
end

% unpack data from the first byte of each word
% (assume target writes bytes of word in little endian order)
data = uint8(zeros(1,numel(recvdata) / this.memUnitSize));
data(:) = recvdata(1:this.memUnitSize:numel(recvdata));

end

function tempAllowSendingOfBytes(this, enabled)
% Temporary sets memUnitSize to 1 and then reverts
if enabled
this.tempMemUnitSize = this.memUnitSize;
this.memUnitSize = 1;
else
this.memUnitSize = this.tempMemUnitSize;
end
end

function sendCommand(this, command, arg0)
% Allows for easy sending of commands and target -
% host synchronization

% command could be any integer from 0 to UINT8MAX
% arg0 could be any integer from 0 to UINT32MAX
if nargin == 2
% If no argument is specified, we really need to have one
arg0 = 0;
end

assert((arg0 >=0) && (arg0 <= coder.rtiostreamtest.ServerConnection.UINT32MAX), ...
'arg0 out of range!');
assert((command >=0) && (command <= coder.rtiostreamtest.ServerConnection.UINT8MAX), ...
'command out of range!');

command = uint8(command);

if nargin == 2 || nargin == 3 || nargin == 4
% Data to send
argument = typecast(uint32(arg0), 'uint8');
checksum = bitxor(command, bitxor(argument(1), ...
bitxor(argument(2), ...
bitxor(argument(3), argument(4)))));

% Send the command
this.blockingSend([command argument checksum]);
end

end

function [code, arg0] = sendCommandAndAck(this, command, ...
arg0)
% Sends the command and waits for an ACK. Commands that require
% data to be passed can't be executed like this

if nargin == 2
% If no argument is specified, we really need to have one
arg0 = 0;
end

this.sendCommand(command, arg0);
[code, arg0] = this.waitForAck;
end

function [code, arg0] = waitForAck(this, supressErrors)
% After sending, you should wait for an ack before sending
% another command.

if nargin == 1
supressErrors = false;
end

% Wait for ack
ACK = this.blockingRecv(6);
code = ACK(1);
arg0 = typecast(uint8(ACK(2:5)), 'uint32');

% Compare checksum
checksum = bitxor(ACK(1), bitxor(ACK(2), bitxor(ACK(3), ...
bitxor(ACK(4), ACK(5)))));
if checksum ~= ACK(6)
coder.rtiostreamtest.Test.error('ErrorCorruptedACK', code, arg0);
end

% If an ACK with an error code was received
if ~supressErrors
this.throwACKError(code, arg0);
end

end

function throwACKError(this, code, arg0)
% Translates an ACK error code into meaningful message
if code ~= this.ACK_SUCCESS
switch code
case this.ACK_UNKNOWN_COMMAND
coder.rtiostreamtest.Test.error('ErrorACKUnknownCommand', arg0);
case this.ACK_RTIOSTREAM_ERROR
coder.rtiostreamtest.Test.error('ErrorACKRTIOSTREAM_ERROR', arg0);
case this.ACK_DATA_INTEGRITY_ERROR
coder.rtiostreamtest.Test.error('ErrorACKDataIntegrity_Fail', arg0);
case this.ACK_COMMAND_CHECKSUM_ERROR
coder.rtiostreamtest.Test.error('ErrorACKCommandChecksum_Fail', arg0);
case this.ACK_BUFFER_SPACE_ERROR
coder.rtiostreamtest.Test.error('ErrorACKnotEnoughSpace', arg0);
otherwise
coder.rtiostreamtest.Test.error('ErrorUnrecognizedErrorACK', code);
end
end
end

function tics = secondsToTics(this, seconds)
% Converts seconds to tics (suitable for macro_pause argument)
tics = round(seconds / this.SecondsPerTic);
if tics < 1
% minimum wait
tics = 1;
elseif tics >= coder.rtiostreamtest.ServerConnection.UINT32MAX
% maximum wait
coder.rtiostreamtest.Test.warning('WarningTimerOverflow');
tics = coder.rtiostreamtest.ServerConnection.UINT32MAX;
end
end

function delete(this)
% cleanup
this.closeServerAndConnection;
end
end

methods (Access = 'private')
function closeServerAndConnection(this)
% Properly shut down the server and the connection

% first, check for valid stream otherwise we can't
% issue commands and there is nothing to close
if this.IsStreamOpen
this.setRecvTimeout(2);
coder.rtiostreamtest.Test.verboseln(this.verbose, 'VerboseSendingShutDown');
% Shut down server
try
this.sendCommand(this.op_shutdown);
this.waitForAck();
coder.rtiostreamtest.Test.verboseln(this.verbose, 'VerboseShutDownSuccess');
catch err %#ok<NASGU>
% Shutdown command cannot be sent but we don't really care
coder.rtiostreamtest.Test.verboseln(this.verbose, 'VerboseShutDownError');
end
% close stream
this.close();
this.IsStreamOpen = false;
end
end
end

methods(Static)

function data = generateSlowData(size)
% Generates the data that could be used to determine correctness

data = uint8(zeros(1, size));

i = 1;
while i < size-4
data(i:i+4) = coder.rtiostreamtest.ServerConnection.generateCounter(i-1);
i = i + 5;
end

last = coder.rtiostreamtest.ServerConnection.generateCounter(i-1);
data(i:end) = last(1:size-i+1);
end

function data = generateFastData(size)
% Generates an 8 bit counter

data = uint8(mod(0:size-1, 256));
end

function [errPos, errMess] = checkFastData(data)
% Checks whether the 8 bit counter matches

for c = 0:numel(data)-1
if data(c+1) ~= mod(c, 256)
errPos = c+1;
errMess = coder.rtiostreamtest.Test.message('IntegrityError', data(c+1), c+1);
return;
end
end

errPos = -1;
errMess = coder.rtiostreamtest.Test.message('IntegrityConfirmed');
end

function array = generateCounter(Z)
% Generates the 5 byte counter

Z = uint32(Z);
array = uint8(zeros(1, 5));
uint8max = coder.rtiostreamtest.ServerConnection.UINT8MAX;
array(1) = uint8max;
for idx = 2 : 5
array(idx) = mod(Z, uint8max);
Z = idivide(Z, uint8max);
end
end

function Z = parseCounter(array)
% Reads the value encoded in a 5 byte counter

multiplier = uint32(1);
Z = uint32(array(2));
uint8max = coder.rtiostreamtest.ServerConnection.UINT8MAX;

if array(1) ~= uint8max
Z = coder.rtiostreamtest.ServerConnection.FIRST_INVALID_COUNTER_VALUE;
return;
end

if array(2) == uint8max
Z = coder.rtiostreamtest.ServerConnection.FIRST_INVALID_COUNTER_VALUE + 1;
return;
end

for idx = 3 : 5
multiplier = multiplier * uint8max;
Z = Z + uint32(uint32(array(idx)) * multiplier);

if array(idx) == uint8max
Z = coder.rtiostreamtest.ServerConnection. ...
FIRST_INVALID_COUNTER_VALUE + idx - 1;
return;
end
end
end
end

end