Photon.m - MAE221/Thermodynamics-Lab GitHub Wiki

%{
See this site for info on how to connect to
serial pots in matlab:
https://www.mathworks.com/help/matlab/ref/serialport.html

NOTE: This should be called after flashing "setup.c"
%}


% Creates the Photon class with all usual Photon commands inside. Added on
% code to help connect via Serial.
classdef Photon < IP_Photon
    
    % Gets properties of Photon (name, access token, and port)
    properties (SetAccess = protected, GetAccess = private)
        port;
        p_serial;
        p_cleaner;
    end
    
    properties (Constant)
        %{
            This is a set of constants that link a photon side function
            to an integer.
        %}
        f_CONNECT = 1;
        f_GETPINMODE = 2;
        f_ISCONNECTED = 3;
        f_SETINPUT = 4;
        f_SETOUTPUT = 5;
        f_ANALOGREAD = 6;
        f_DIGITALREAD = 7;
        f_ANALOGWRITE = 8;
        f_DIGITALWRITE = 9;
        f_GETPIN = 10;
        f_DISCONNECT = 11;
    end
    
    methods
        
        % Creates Photon object
        function obj = Photon(name, accesstoken, port)
            obj = obj@IP_Photon(name, accesstoken);
            
            % If port isn't given, turn off usb functionality
            if nargin == 2
                port = '';
            end
            if strcmp(strtrim(port), '')
                % tell the student usb functionality is off
                warning(strcat('No port was given when creating this photon object; ',...
                    'MATLAB will not be able to communicate with your photon through usb.'));
                
                % set the port to 'NOPORT' so MATLAB knows not to use usb
                obj.port = 'NOPORT';
            elseif nargin == 3
                obj.port = port;
                
                % Check if the serial port is already open
                ports = instrfind('Port', port);
                if length(ports)
                    for p = ports
                        if strcmp(p.Status, 'open') & Photon.valid(p)
                            obj.p_serial = p;
                            break;
                        end
                    end
                end
                
                % make sure the photon is connected to a valid prot
                obj.serial_reconnect()
                
                % Assume you want to connect to the internet, since its not experimental
                obj.connect();
            end
        end
        
        % Gets rid of object and closes the port.
        function delete(obj)
            try
                p = obj.p_serial;
                % stop all communication
                w = warning('off', 'instrument:stopasync:invalid'); % suppresses a warning that keeps coming up
                stopasync(p);
                warning(w); % turns the warning back on
                
                % fully close the port
                fclose(p);
                delete(p);
            catch ME
                % ignore errors if obj.p_serial either was not initialized or was unable to connect
                if strcmp(ME.identifier, 'MATLAB:serial:get:invalidOBJ')
                    % ignore
                elseif strcmp(ME.identifier, 'MATLAB:serial:stopasync:opfailed')
                    % ignore
                elseif strcmp(ME.identifier, 'MATLAB:UndefinedFunction')
                    % ignore
                else
                    % don't ignore other errors
                    rethrow(ME);
                end
            end
        end
        
        % Try connect to the cloud, Returns true if able, false otherwise
        function resp = connect(obj)
            if ~obj.isConnected()
                fwrite(obj.p_serial, obj.f_CONNECT, "uint8");
                while ~obj.p_serial.BytesAvailable
                    pause(1);
                end
            end
            resp = obj.isConnected();
            resp = obj.isConnected();
        end
        
        % Disconnect from wifi, Returns true if able, false otherwise
        function resp = disconnect(obj)
            if obj.isConnected() & ~strcmp(obj.port, 'NOPORT')
                fwrite(obj.p_serial, obj.f_DISCONNECT, "uint8");
                while ~obj.p_serial.BytesAvailable
                    pause(1);
                end
            end
            resp = ~obj.isConnected();
        end
        
        %{
            Returns true if the photon is connected to the cloud,
            false otherwise.

            NOTE: if MATLAB can't use usb, this returns true.
        %}
        function resp = isConnected(obj)
            % set resp to true by default, that way, if the photon is not connected by
            % usb, isConnected tells the other methods to use the cloud
            resp = true;
            
            % Check if connected
            if ~strcmp(obj.port, 'NOPORT')
                % Make sure it is valid
                if ~Photon.valid(obj.p_serial)
                    obj.serial_reconnect();
                end
                
                % Clear the serial in
                if obj.p_serial.BytesAvailable
                    fread(obj.p_serial, obj.p_serial.BytesAvailable, "uint8");
                end
                
                % ask the photon if it is connected to wifi
                fwrite(obj.p_serial, obj.f_ISCONNECTED, "uint8");
                if ~fread(obj.p_serial, 1, "uint8")
                    % photon is not connected to cloud, return false
                    resp = false;
                end
            end
        end
        
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        %{
            The following functions are implmented for both usb and the cloud.
            Each one follows the following general process:
            - checks if the object is connected to the internet w/
              obj.isConnected(). If so, it calls the internet version
            - else:
                - sends a byte from the list of constants on lines 32 to 42
                  that tells the photon what command to run
                - reads a byte response from the photon, stores the byte in
                  the feedback variable so it can be returned
            Any extra operations are the same as the ones in the original photon
            (now IP_Photon).
        %}
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        
        % Returns the pin mode (input or output) of pin and prints its mode
        function feedback = getPinMode(obj,pin)
            if obj.isConnected()
                feedback = getPinMode@IP_Photon(obj, pin);
            else
                % Send the command and arg to the photon
                fwrite(obj.p_serial, [obj.f_GETPINMODE, double(pin)], "uint8");
                
                % Get & use the response
                feedback = fread(obj.p_serial, 1, "uint8");
                if feedback == 1
                    fprintf('INPUT\n')
                else
                    fprintf('OUTPUT\n')
                end
            end
        end
        
        %setInput pin
        function feedback = setInput(obj,pin)
            if obj.isConnected()
                feedback = setInput@IP_Photon(obj, pin);
            else
                % Send the command and arg to the photon
                fwrite(obj.p_serial, [obj.f_SETINPUT, double(pin)], "uint8");
                
                % Get & use the response
                feedback = fread(obj.p_serial, 1, "uint8");
            end
        end
        
        %setOuput pin
        function feedback = setOutput(obj,pin)
            if obj.isConnected()
                feedback = setOutput@IP_Photon(obj, pin);
            else
                % Send the command and arg to the photon
                fwrite(obj.p_serial, [obj.f_SETOUTPUT, double(pin)], "int8");
                
                % Get & use the response
                feedback = fread(obj.p_serial, 1, "int8");
            end
        end
        
        % Reads the value of an analog pin returning a value of 0-3.33V
        function feedback = analogRead(obj,pin)
            if obj.isConnected()
                feedback = analogRead@IP_Photon(obj, pin);
            else
                % Make sure its an input pin
                obj.setInput(pin);
                
                % Send the command and arg to the photon
                fwrite(obj.p_serial, [obj.f_ANALOGREAD, double(pin)], "uint8");
                
                % Get & use the response
                feedback = fread(obj.p_serial, 1, "uint16"); %Read in as bit value
                feedback = feedback .* 3.33 ./ 2^12; %Convert from 12 bit value
            end
        end
        
        % Reads the value of a digital pin
        function feedback = digitalRead(obj,pin)
            if obj.isConnected()
                feedback = digitalRead@IP_Photon(obj, pin);
            else
                % Make sure its an input pin
                obj.setInput(pin);
                
                % Send the command and arg to the photon
                fwrite(obj.p_serial, [obj.f_DIGITALREAD, double(pin)], "uint8");
                
                % Get & use the response
                feedback = fread(obj.p_serial, 1, "uint8");
            end
            
            feedback = (feedback ~= 0) * 1
        end
        
        % Writes an analog voltage value to an analog pin
        function feedback = analogWrite(obj,pin,value)
            if obj.isConnected()
                feedback = analogWrite@IP_Photon(obj, pin, value);
            else
                if strcmp('A3', pin) || strcmp ('A6', pin)
                    if value<0.0 || value>3.33
                        warning('Analog voltage set outside range (0 to 3.33 V)!')
                    else
                        value = round(value .* 4095./3.33); %Convert to 8 bit value
                        
                        % Make sure its an output pin
                        obj.setOutput(pin);
                        
                        % Send the command and arg to the photon
                        fwrite(obj.p_serial, [obj.f_ANALOGWRITE, double(strcat(pin,',',int2str(value)))], "uint8");
                        
                        % Get & use the response
                        feedback = fread(obj.p_serial, 1, "uint8"); %Read in as bit value
                        if (obj.p_serial.BytesAvailable)
                            feedback = bitshift(feedback, 8) + fread(obj.p_serial, 1, "uint8");
                        end
                    end
                else
                    if value<0.0 || value>3.33
                        warning('Analog voltage set outside range (0 to 3.33 V)!')
                    else
                        value = round(value .* 255./3.33); %Convert to 8 bit value
                        
                        % Make sure its an output pin
                        obj.setOutput(pin);
                        
                        % Send the command and arg to the photon
                        fwrite(obj.p_serial, [obj.f_ANALOGWRITE, double(strcat(pin,',',int2str(value)))], "uint8");
                        
                        % Get & use the response
                        feedback = fread(obj.p_serial, 1, "uint8"); %Read in as bit value
                        if (obj.p_serial.BytesAvailable)
                            feedback = bitshift(feedback, 8) + fread(obj.p_serial, 1, "uint8");
                        end
                    end
                end
            end
            
            feedback = feedback .* 3.33 ./ 255; % convert from 12 bit value
        end
        
        % Writes a 0 or 1 value to a digital pin
        function feedback = digitalWrite(obj,pin,value)
            if obj.isConnected()
                feedback = digitalWrite@IP_Photon(obj, pin, value);
            else
                % Make sure its an output pin
                obj.setOutput(pin);
                
                % Send the command and arg to the photon
                fwrite(obj.p_serial, [obj.f_DIGITALWRITE, double(strcat(pin,',',int2str(value)))], "uint8");
                
                % get & use the response
                feedback = fread(obj.p_serial, 1, "uint8");
            end
        end
        
        %Returns the number that corresponds to pin. (ex: D7=7, A7=17)
        function feedback = getPin(obj,pin)
            if obj.isConnected()
                feedback = getPin@IP_Photon(obj, pin);
            else
                % Send the command and arg to the photon
                fwrite(obj.p_serial, [obj.f_GETPIN, double(pin)], "uint8");
                
                % Get & use the response
                feedback = fread(obj.p_serial, 1, "uint8");
            end
        end
        
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        %{
            Check IP_Photon for unimplemented methods. Each of those may be used
            with the cloud but not without.
        %}
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    end
    
    
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %{
        These are usb specific methods.
    %}
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    methods (Access = private)
        % Reconnects this photon object to the photon's serial port
        function serial_reconnect(obj, varargin)
            % Check if can use usb
            if ~strcmp(obj.port, 'NOPORT')
                % look for an existent, valid port connected to the photon
                ports = instrfind('Port', obj.port);
                val = false;
                if ismember(obj.p_serial, ports)
                    val = Photon.valid(obj.p_serial);
                end
                
                % if no valid port exists, create a new one
                if ~val
                    % Delete any invalid connections to the port
                    instrreset;
                    
                    % Reopen the port
                    obj.p_serial = serial(obj.port);
                    obj.p_serial.BaudRate = 9600;
                    fopen(obj.p_serial);
                end
            end
        end
    end
    
    methods (Static)
        % Returns true if p is a valid serial port, false otherwise
        % NOTE: this is used rather than isvalid(p) because in testing
        %       isvalid() would return true when p was not valid
        function b = valid(p)
            b = false;
            try
                p.BytesAvailable;
                b = true;
            catch ME
                % ignore errors that tell you p is invalid
                if ~strcmp(ME.identifier, 'MATLAB:serial:get:invalidOBJ')
                    % ignore
                elseif strcmp(ME.identifier, 'MATLAB:structRefFromNonStruct')
                    % ignore
                else
                    % throw anything else
                    rethrow(ME);
                end
            end
        end
    end
end