classdef TMS < matlab.apps.AppBase

    % Properties that correspond to app components
    properties (Access = public)
        UIFigure                    matlab.ui.Figure
        StatusPanel                 matlab.ui.container.Panel
        CapacitorsChargedLampLabel  matlab.ui.control.Label
        CapacitorsChargedLamp       matlab.ui.control.Lamp
        WallFeedbackProtectionLampLabel  matlab.ui.control.Label
        WallFeedbackProtectionLamp  matlab.ui.control.Lamp
        IGBTTempLampLabel           matlab.ui.control.Label
        IGBTTempLamp                matlab.ui.control.Lamp
        GlobalDeviceReadyLampLabel  matlab.ui.control.Label
        GlobalDeviceReadyLamp       matlab.ui.control.Lamp
        ConfigurationPanel          matlab.ui.container.Panel
        WaveFormDropDownLabel       matlab.ui.control.Label
        WaveFormDropDown            matlab.ui.control.DropDown
        PowerLevelKnobLabel         matlab.ui.control.Label
        PowerLevelKnob              matlab.ui.control.Knob
        PlotWaveformDropDownLabel   matlab.ui.control.Label
        PlotWaveformDropDown        matlab.ui.control.DropDown
        PulseCountDropDownLabel     matlab.ui.control.Label
        PulseCountDropDown          matlab.ui.control.DropDown
        FrequencyHzDropDownLabel    matlab.ui.control.Label
        FrequencyHzDropDown         matlab.ui.control.DropDown
        ControlPanel                matlab.ui.container.Panel
        connect_mc                  matlab.ui.control.Button
        charge_caps                 matlab.ui.control.Button
        pulse                       matlab.ui.control.Button
        discharge_caps              matlab.ui.control.Button
        disconnect_mc               matlab.ui.control.Button
        refresh_status              matlab.ui.control.Button
    end

    %                           %
    % Private app properties    %
    %                           %
    properties (Access = private)
        Serial; % Serial port object
        MAX_PULSE_COUNT % Max number of pulses to allow per pulse command, per requirements
        MIN_PULSE_COUNT % Minimum number of pulses to allow per pulse command, a derived requirement
        MAX_FREQUENCY % Max frequency allowed, per requirements
        MIN_FREQUENCY % Minimum frequency to allow, a derived requirement
        STARTUP_PULSE_COUNT % Value of pulse count when app starts or resets
        STARTUP_FREQUENCY % Value of frequecny when app starts or resets
        STARTUP_POWER_LEVEL % Power level setting when app starts or resets
        TIMEOUT % Seconds to wait for response from Microcontroller before reporting an error
        E_CAP_CHARGE_TIMEOUT % Error to indicate capacitor charging failed
    end
    
    %                           %
    % Private utility methods   %
    %                           %
    methods (Access = private)
     
        % Blink the UI to bring main app window back into focus after a msgbox
        function blink_UI(app)
            app.UIFigure.Visible = 'off';
            app.UIFigure.Visible = 'on';
            return;
        end
        
        % Initialize serial connection
        function init_serial(app)
            delete(instrfindall);
            app.Serial = serial('/dev/tty.lilblue-DevB');
            app.Serial.BaudRate = 9600;
            app.Serial.Terminator = 'LF'; %(newline, ascii 10)
            return;
        end
        
        % Initialize parameter limits
        function init_limits(app)
            app.MAX_PULSE_COUNT = 10;
            app.MIN_PULSE_COUNT = 1;
            app.MAX_FREQUENCY = 36;
            app.MIN_FREQUENCY = 1;
            return;
        end
        
        % Initialize parameter default values
        function init_defaults(app)
            app.STARTUP_PULSE_COUNT = 1;
            app.STARTUP_FREQUENCY = 1;
            app.STARTUP_POWER_LEVEL = 1;
            app.TIMEOUT = 60;
            app.E_CAP_CHARGE_TIMEOUT = 101;
            return;
        end
        
        % Reset app to known state
        function reset(app)
            app.PulseCountDropDown.Value = num2str(app.STARTUP_PULSE_COUNT);
            app.FrequencyHzDropDown.Value = num2str(app.STARTUP_FREQUENCY);
            app.PowerLevelKnob.Value = double(app.STARTUP_POWER_LEVEL);
            return;
        end
        
        function ready = device_ready(app)
            if strcmp(app.Serial.Status, 'closed')
                ready = 0;
            end
            fprintf(app.Serial, 's');
            power_level = uint8(fread(app.Serial,1));
            frequency = uint8(fread(app.Serial,1));
            pulse_count = uint8(fread(app.Serial,1));
            ready = 1;
        end
        
    end


    methods (Access = private)

        % Code that executes after component creation
        function startupFcn(app)
            % Define rgb states of indicator lamps.
%             global GREEN;
%             GREEN = [0, 1, 0];
%             global WHITE;
%             WHITE = [1, 1, 1];
            app.UIFigure.Interruptible = 'off';
            app.UIFigure.BusyAction  = 'cancel';
            init_serial(app);
            init_limits(app);
            init_defaults(app);
            reset(app);
            
         
%             % Alias indicator lamp names to shorten and initialize all to OFF.
%             global cap_lamp; 
%             cap_lamp = app.CapacitorsChargedLamp;
%             cap_lamp.Color = WHITE;
%             global igbt_lamp;
%             igbt_lamp = app.IGBTTempLamp;
%             igbt_lamp.Color = WHITE;
%             global wall_lamp;
%             wall_lamp = app.WallFeedbackProtectionLamp;
%             wall_lamp.Color = WHITE;
%             global global_lamp;
%             global_lamp = app.GlobalDeviceReadyLamp;
%             global_lamp.Color = WHITE;
        end

        % Button pushed function: connect_mc
        function connect_mc_button(app, event)
            if strcmp(app.Serial.Status, 'closed')
                fopen(app.Serial);
                if strcmp(app.Serial.Status, 'closed')
                    waitfor(msgbox('Microcontroller connection failed.'));
                    blink_UI(app);
                elseif strcmp(app.Serial.Status, 'open')
                    fprintf(app.Serial, 'r');
                    response = fread(app.Serial,1);
                    if response == 1
                        waitfor(msgbox('Microcontroller connected and reset.'));
                        blink_UI(app);
                    else
                        waitfor(msgbox('Microcontroller connected but reset FAILED.'));
                        blink_UI(app);
                    end   
                end
            else
                waitfor(msgbox('Microcontroller connected.'));
                blink_UI(app);
            end
        end

        % Button pushed function: pulse
        function pulseButtonPushed(app, event)
            if strcmp(app.Serial.Status, 'open')
                fprintf(app.Serial, 'p');
                return;
            else
                waitfor(msgbox('Please connect to microcontroller and try again.'));
                blink_UI(app);
                return;
            end
        end

        % Button pushed function: disconnect_mc
        function disconnect_mcButtonPushed(app, event)
            fclose(app.Serial);
            if strcmp(app.Serial.Status, 'closed')
                waitfor(msgbox('Microcontroller disconnected.'));
                blink_UI(app);
            else
                waitfor(msgbox('Microcontroller could not be disconnected.'));
                blink_UI(app);
            end
        end

        % Button pushed function: refresh_status
        function refresh_statusButtonPushed(app, event)
            waitfor(msgbox('This selection is not enabled.'));
            blink_UI(app);
            return;
        end

        % Button pushed function: charge_caps
        function charge_capsButtonPushed(app, event)
            if strcmp(app.Serial.Status, 'closed')
                waitfor(msgbox('Please connect to microcontroller and try again.'));
                blink_UI(app);
                return;
            end
            flushinput(app.Serial); 
            %disp(app.Serial.BytesAvailable); %debug
            app.pulse.Enable = 'off';
            fprintf(app.Serial, 'c');
            tic;
            while (1)
                if (app.Serial.BytesAvailable > 0)
                    break;
                elseif (toc >= app.TIMEOUT)
                    break;
                end
            end
            
            if (app.Serial.BytesAvailable > 0)
                response_val = int8(fread(app.Serial,1));
                if response_val == int8(round(app.PowerLevelKnob.Value))
                    waitfor(msgbox(sprintf('Capacitors charged to level %d.', response_val)));
                    app.pulse.Enable = 'on';
                    blink_UI(app);
                elseif response_val == app.E_CAP_CHARGE_TIMEOUT
                    waitfor(msgbox('Capacitor charging failed. Please manually reset the device'));
                    blink_UI(app);
                end
            else
                waitfor(msgbox('No response from Microcontroller. Please manually reset the device'));
                blink_UI(app);
            end
            return;
        end

        % Value changed function: PowerLevelKnob
        function PowerLevelKnobValueChanged(app, event)
            if strcmp(app.Serial.Status, 'closed')
                waitfor(msgbox('Please connect to microcontroller and try again.'));
                app.PowerLevelKnob.Value = double(app.STARTUP_POWER_LEVEL);
                blink_UI(app);
                return;
            end
            value = string(round(app.PowerLevelKnob.Value));
            command_str = strcat('l',value);
            fprintf(app.Serial, command_str);
            response_val = double(fread(app.Serial,1));
            if response_val == str2double(value)
                waitfor(msgbox(sprintf('Power level set to %s.', value)));
            else
                waitfor(msgbox('Power level not successfully set.'));
            end
            blink_UI(app);
            return;
        end

        % Value changed function: PulseCountDropDown
        function PulseCountDropDownValueChanged(app, event)
            if strcmp(app.Serial.Status, 'closed')
                waitfor(msgbox('Please connect to microcontroller and try again.'));
                app.PulseCountDropDown.Value = num2str(app.STARTUP_PULSE_COUNT);
                blink_UI(app);
                return;
            end
            value = string(app.PulseCountDropDown.Value);
            command_str = strcat('n', value);
            fprintf(app.Serial, command_str);
            response_val = double(fread(app.Serial,1));
            %wait here for response from MC: either OK or ERROR
            if response_val == str2double(value)
                waitfor(msgbox(sprintf('Pulse count set to %s.', value)));
                blink_UI(app);
            else
                waitfor(msgbox('Pulse count not successfully set.'));
                blink_UI(app);
            end
            return;
        end

        % Value changed function: FrequencyHzDropDown
        function FrequencyHzDropDownValueChanged(app, event)
            if strcmp(app.Serial.Status, 'closed')
                app.FrequencyHzDropDown.Value = num2str(app.STARTUP_FREQUENCY);
                waitfor(msgbox('Please connect to microcontroller and try again.'));
                blink_UI(app);
                return;
            end
            value = string(app.FrequencyHzDropDown.Value);
            command_str = strcat('f', value);
            fprintf(app.Serial, command_str);
            response_val = double(fread(app.Serial,1));
            
            if response_val == str2double(value)
                waitfor(msgbox(sprintf('Frequency set to %s.', value)));
                blink_UI(app);
            else
                waitfor(msgbox('The value was not sucessfully set.'));
                blink_UI(app);
            end
            return;
        end

        % Button pushed function: discharge_caps
        function discharge_capsButtonPushed(app, event)
            if strcmp(app.Serial.Status, 'closed')
                waitfor(msgbox('Please connect to microcontroller and try again.'));
                blink_UI(app);
                return;
            end
            waitfor(msgbox('This selection is not enabled.'));
            blink_UI(app);
            return;
        end

        % Value changed function: PlotWaveformDropDown
        function PlotWaveformDropDownValueChanged(app, event)
            waitfor(msgbox('This selection is not enabled.'));
            blink_UI(app);
            return;
        end

        % Value changed function: WaveFormDropDown
        function WaveFormDropDownValueChanged(app, event)
            waitfor(msgbox('This selection is not enabled.'));
            blink_UI(app);
            return;
        end
    end

    % App initialization and construction
    methods (Access = private)

        % Create UIFigure and components
        function createComponents(app)

            % Create UIFigure
            app.UIFigure = uifigure;
            app.UIFigure.Position = [100 100 871 480];
            app.UIFigure.Name = 'UI Figure';

            % Create StatusPanel
            app.StatusPanel = uipanel(app.UIFigure);
            app.StatusPanel.ForegroundColor = [0 1 1];
            app.StatusPanel.TitlePosition = 'centertop';
            app.StatusPanel.Title = 'Status';
            app.StatusPanel.BackgroundColor = [0.651 0.651 0.651];
            app.StatusPanel.FontSize = 14;
            app.StatusPanel.Position = [23 286 260 185];

            % Create CapacitorsChargedLampLabel
            app.CapacitorsChargedLampLabel = uilabel(app.StatusPanel);
            app.CapacitorsChargedLampLabel.HorizontalAlignment = 'right';
            app.CapacitorsChargedLampLabel.Position = [7 94 113 15];
            app.CapacitorsChargedLampLabel.Text = 'Capacitors Charged';

            % Create CapacitorsChargedLamp
            app.CapacitorsChargedLamp = uilamp(app.StatusPanel);
            app.CapacitorsChargedLamp.Position = [135 91 20 20];

            % Create WallFeedbackProtectionLampLabel
            app.WallFeedbackProtectionLampLabel = uilabel(app.StatusPanel);
            app.WallFeedbackProtectionLampLabel.HorizontalAlignment = 'right';
            app.WallFeedbackProtectionLampLabel.Position = [7 123 143 15];
            app.WallFeedbackProtectionLampLabel.Text = 'Wall Feedback Protection';

            % Create WallFeedbackProtectionLamp
            app.WallFeedbackProtectionLamp = uilamp(app.StatusPanel);
            app.WallFeedbackProtectionLamp.Position = [165 120 20 20];

            % Create IGBTTempLampLabel
            app.IGBTTempLampLabel = uilabel(app.StatusPanel);
            app.IGBTTempLampLabel.HorizontalAlignment = 'right';
            app.IGBTTempLampLabel.Position = [7 64 66 15];
            app.IGBTTempLampLabel.Text = 'IGBT Temp';

            % Create IGBTTempLamp
            app.IGBTTempLamp = uilamp(app.StatusPanel);
            app.IGBTTempLamp.Position = [88 61 20 20];

            % Create GlobalDeviceReadyLampLabel
            app.GlobalDeviceReadyLampLabel = uilabel(app.StatusPanel);
            app.GlobalDeviceReadyLampLabel.HorizontalAlignment = 'right';
            app.GlobalDeviceReadyLampLabel.Position = [7 11 118 15];
            app.GlobalDeviceReadyLampLabel.Text = 'Global Device Ready';

            % Create GlobalDeviceReadyLamp
            app.GlobalDeviceReadyLamp = uilamp(app.StatusPanel);
            app.GlobalDeviceReadyLamp.Position = [140 8 20 20];

            % Create ConfigurationPanel
            app.ConfigurationPanel = uipanel(app.UIFigure);
            app.ConfigurationPanel.ForegroundColor = [0 1 1];
            app.ConfigurationPanel.TitlePosition = 'centertop';
            app.ConfigurationPanel.Title = 'Configuration';
            app.ConfigurationPanel.BackgroundColor = [0.651 0.651 0.651];
            app.ConfigurationPanel.FontSize = 14;
            app.ConfigurationPanel.Position = [23 55 473 212];

            % Create WaveFormDropDownLabel
            app.WaveFormDropDownLabel = uilabel(app.ConfigurationPanel);
            app.WaveFormDropDownLabel.HorizontalAlignment = 'right';
            app.WaveFormDropDownLabel.Position = [270 59 66 15];
            app.WaveFormDropDownLabel.Text = 'Wave Form';

            % Create WaveFormDropDown
            app.WaveFormDropDown = uidropdown(app.ConfigurationPanel);
            app.WaveFormDropDown.Items = {'square', 'triangle', 'sinusoid'};
            app.WaveFormDropDown.ValueChangedFcn = createCallbackFcn(app, @WaveFormDropDownValueChanged, true);
            app.WaveFormDropDown.Position = [354 55 100 22];
            app.WaveFormDropDown.Value = 'square';

            % Create PowerLevelKnobLabel
            app.PowerLevelKnobLabel = uilabel(app.ConfigurationPanel);
            app.PowerLevelKnobLabel.HorizontalAlignment = 'center';
            app.PowerLevelKnobLabel.Position = [78 6 71 15];
            app.PowerLevelKnobLabel.Text = 'Power Level';

            % Create PowerLevelKnob
            app.PowerLevelKnob = uiknob(app.ConfigurationPanel, 'continuous');
            app.PowerLevelKnob.Limits = [1 3];
            app.PowerLevelKnob.MajorTicks = [1 2 3];
            app.PowerLevelKnob.ValueChangedFcn = createCallbackFcn(app, @PowerLevelKnobValueChanged, true);
            app.PowerLevelKnob.MinorTicks = [];
            app.PowerLevelKnob.Position = [68 59 88 88];
            app.PowerLevelKnob.Value = 1;

            % Create PlotWaveformDropDownLabel
            app.PlotWaveformDropDownLabel = uilabel(app.ConfigurationPanel);
            app.PlotWaveformDropDownLabel.HorizontalAlignment = 'right';
            app.PlotWaveformDropDownLabel.Position = [257 21 84 15];
            app.PlotWaveformDropDownLabel.Text = 'Plot Waveform';

            % Create PlotWaveformDropDown
            app.PlotWaveformDropDown = uidropdown(app.ConfigurationPanel);
            app.PlotWaveformDropDown.Items = {'Yes', 'No'};
            app.PlotWaveformDropDown.ValueChangedFcn = createCallbackFcn(app, @PlotWaveformDropDownValueChanged, true);
            app.PlotWaveformDropDown.Position = [346 17 109 22];
            app.PlotWaveformDropDown.Value = 'No';

            % Create PulseCountDropDownLabel
            app.PulseCountDropDownLabel = uilabel(app.ConfigurationPanel);
            app.PulseCountDropDownLabel.HorizontalAlignment = 'right';
            app.PulseCountDropDownLabel.Position = [266 95 71 15];
            app.PulseCountDropDownLabel.Text = 'Pulse Count';

            % Create PulseCountDropDown
            app.PulseCountDropDown = uidropdown(app.ConfigurationPanel);
            app.PulseCountDropDown.Items = {'1', '2', '3', '4', '5', '6', '7', '8', '9', '10'};
            app.PulseCountDropDown.ItemsData = {'1', '2', '3', '4', '5', '6', '7', '8', '9', '10'};
            app.PulseCountDropDown.ValueChangedFcn = createCallbackFcn(app, @PulseCountDropDownValueChanged, true);
            app.PulseCountDropDown.Position = [353 93 100 22];
            app.PulseCountDropDown.Value = '1';

            % Create FrequencyHzDropDownLabel
            app.FrequencyHzDropDownLabel = uilabel(app.ConfigurationPanel);
            app.FrequencyHzDropDownLabel.HorizontalAlignment = 'right';
            app.FrequencyHzDropDownLabel.Position = [252 136 86 15];
            app.FrequencyHzDropDownLabel.Text = 'Frequency (Hz)';

            % Create FrequencyHzDropDown
            app.FrequencyHzDropDown = uidropdown(app.ConfigurationPanel);
            app.FrequencyHzDropDown.Items = {'1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31', '32', '33', '34', '35', '36'};
            app.FrequencyHzDropDown.ItemsData = {'1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31', '32', '33', '34', '35', '36'};
            app.FrequencyHzDropDown.ValueChangedFcn = createCallbackFcn(app, @FrequencyHzDropDownValueChanged, true);
            app.FrequencyHzDropDown.Position = [353 132 100 22];
            app.FrequencyHzDropDown.Value = '1';

            % Create ControlPanel
            app.ControlPanel = uipanel(app.UIFigure);
            app.ControlPanel.ForegroundColor = [0 1 1];
            app.ControlPanel.TitlePosition = 'centertop';
            app.ControlPanel.Title = 'Control';
            app.ControlPanel.BackgroundColor = [0.651 0.651 0.651];
            app.ControlPanel.FontSize = 14;
            app.ControlPanel.Position = [516 55 260 416];

            % Create connect_mc
            app.connect_mc = uibutton(app.ControlPanel, 'push');
            app.connect_mc.ButtonPushedFcn = createCallbackFcn(app, @connect_mc_button, true);
            app.connect_mc.BackgroundColor = [1 1 1];
            app.connect_mc.FontColor = [0.149 0.149 0.149];
            app.connect_mc.Position = [50 352 159 22];
            app.connect_mc.Text = 'Connect Microcontroller';

            % Create charge_caps
            app.charge_caps = uibutton(app.ControlPanel, 'push');
            app.charge_caps.ButtonPushedFcn = createCallbackFcn(app, @charge_capsButtonPushed, true);
            app.charge_caps.BackgroundColor = [1 1 1];
            app.charge_caps.FontColor = [0.149 0.149 0.149];
            app.charge_caps.Position = [49 290 159 22];
            app.charge_caps.Text = 'Charge Capacitors';

            % Create pulse
            app.pulse = uibutton(app.ControlPanel, 'push');
            app.pulse.ButtonPushedFcn = createCallbackFcn(app, @pulseButtonPushed, true);
            app.pulse.BackgroundColor = [1 0 0];
            app.pulse.FontColor = [0.149 0.149 0.149];
            app.pulse.Position = [50 57 159 82];
            app.pulse.Text = 'Pulse';

            % Create discharge_caps
            app.discharge_caps = uibutton(app.ControlPanel, 'push');
            app.discharge_caps.ButtonPushedFcn = createCallbackFcn(app, @discharge_capsButtonPushed, true);
            app.discharge_caps.BackgroundColor = [1 1 1];
            app.discharge_caps.FontColor = [0.149 0.149 0.149];
            app.discharge_caps.Position = [50 259 159 22];
            app.discharge_caps.Text = 'Discharge Capacitors';

            % Create disconnect_mc
            app.disconnect_mc = uibutton(app.ControlPanel, 'push');
            app.disconnect_mc.ButtonPushedFcn = createCallbackFcn(app, @disconnect_mcButtonPushed, true);
            app.disconnect_mc.BackgroundColor = [1 1 1];
            app.disconnect_mc.FontColor = [0.149 0.149 0.149];
            app.disconnect_mc.Position = [49 321 160 22];
            app.disconnect_mc.Text = 'Disconnect Microcontroller';

            % Create refresh_status
            app.refresh_status = uibutton(app.ControlPanel, 'push');
            app.refresh_status.ButtonPushedFcn = createCallbackFcn(app, @refresh_statusButtonPushed, true);
            app.refresh_status.BackgroundColor = [1 1 1];
            app.refresh_status.FontColor = [0.149 0.149 0.149];
            app.refresh_status.Position = [50 228 159 22];
            app.refresh_status.Text = 'Refresh Status Panel';
        end
    end

    methods (Access = public)

        % Construct app
        function app = TMS

            % Create and configure components
            createComponents(app)

            % Register the app with App Designer
            registerApp(app, app.UIFigure)

            % Execute the startup function
            runStartupFcn(app, @startupFcn)

            if nargout == 0
                clear app
            end
        end

        % Code that executes before app deletion
        function delete(app)

            % Delete UIFigure when app is deleted
            delete(app.UIFigure)
        end
    end
end