diff --git a/assets/icons/ic_pwm_pic.png b/assets/icons/ic_pwm_pic.png new file mode 100644 index 000000000..ec19cfe71 Binary files /dev/null and b/assets/icons/ic_pwm_pic.png differ diff --git a/assets/images/sin_wave_circuit.png b/assets/images/sin_wave_circuit.png new file mode 100644 index 000000000..b54ecc237 Binary files /dev/null and b/assets/images/sin_wave_circuit.png differ diff --git a/assets/images/square_wave_circuit.png b/assets/images/square_wave_circuit.png new file mode 100644 index 000000000..90ffc67fc Binary files /dev/null and b/assets/images/square_wave_circuit.png differ diff --git a/lib/communication/science_lab.dart b/lib/communication/science_lab.dart index 8971400cc..77dda9043 100644 --- a/lib/communication/science_lab.dart +++ b/lib/communication/science_lab.dart @@ -157,7 +157,7 @@ class ScienceLab { for (String temp in ['CH1', 'CH2']) { await setGain(temp, 0, true); } - for (String temp in ['SI1', 'SI1']) { + for (String temp in ['SI1', 'SI2']) { await loadEquation(temp, 'sine'); } } @@ -1090,6 +1090,282 @@ class ScienceLab { } } + Future setSI1(double frequency, String? waveType) async { + double freqLowLimit = 0.1; + int highRes, tableSize; + + if (frequency < freqLowLimit) { + logger.e("frequency too low"); + return -1; + } else if (frequency < 1100) { + highRes = 1; + tableSize = 512; + } else { + highRes = 0; + tableSize = 32; + } + + if (waveType != null) { + if (waveType == "sine" || waveType == "tria") { + if (this.waveType["SI1"] != waveType) { + loadEquation("SI1", waveType); + } + } else { + logger.e("Not a valid waveform. try sine or tria"); + } + } + + List p = [1, 8, 64, 256]; + int prescalar = 0; + int wavelength = 0; + + while (prescalar <= 3) { + wavelength = (64e6 ~/ frequency ~/ p[prescalar] ~/ tableSize); + frequency = 64e6 / wavelength / p[prescalar] / tableSize; + if (wavelength < 65525) break; + prescalar++; + } + + if (prescalar == 4) { + logger.e("Out of range"); + return -1; + } + + try { + mPacketHandler.sendByte(mCommandsProto.wavegen); + mPacketHandler.sendByte(mCommandsProto.setSine1); + mPacketHandler.sendByte(highRes | (prescalar << 1)); + mPacketHandler.sendInt(wavelength - 1); + await mPacketHandler.getAcknowledgement(); + + sin1Frequency = frequency; + return sin1Frequency; + } catch (e) { + logger.e("Error setting SI1: $e"); + } + + return -1; + } + + Future setSI2(double frequency, String? waveType) async { + double freqLowLimit = 0.1; + int highRes, tableSize; + + if (frequency < freqLowLimit) { + logger.e("frequency too low"); + return -1; + } else if (frequency < 1100) { + highRes = 1; + tableSize = 512; + } else { + highRes = 0; + tableSize = 32; + } + + if (waveType != null) { + if (waveType == "sine" || waveType == "tria") { + if (this.waveType["SI2"] != waveType) { + loadEquation("SI2", waveType); + } + } else { + logger.e("Not a valid waveform. try sine or tria"); + } + } + + List p = [1, 8, 64, 256]; + int prescalar = 0; + int wavelength = 0; + + while (prescalar <= 3) { + wavelength = (64e6 ~/ frequency ~/ p[prescalar] ~/ tableSize); + frequency = 64e6 / wavelength / p[prescalar] / tableSize; + if (wavelength < 65525) break; + prescalar++; + } + + if (prescalar == 4) { + logger.e("Out of range"); + return -1; + } + + try { + mPacketHandler.sendByte(mCommandsProto.wavegen); + mPacketHandler.sendByte(mCommandsProto.setSine2); + mPacketHandler.sendByte(highRes | (prescalar << 1)); + mPacketHandler.sendInt(wavelength - 1); + await mPacketHandler.getAcknowledgement(); + + sin2Frequency = frequency; + return sin2Frequency; + } catch (e) { + logger.e("Error setting SI2: $e"); + } + + return -1; + } + + Future setWaves( + double frequency, double phase, double frequency2) async { + int highRes, tableSize, highRes2, tableSize2; + int wavelength = 0, wavelength2 = 0; + + if (frequency2 == -1) frequency2 = frequency; + + if (frequency < 0.1) { + logger.e("frequency 1 too low"); + return -1; + } else if (frequency < 1100) { + highRes = 1; + tableSize = 512; + } else { + highRes = 0; + tableSize = 32; + } + + if (frequency2 < 0.1) { + logger.e("frequency 2 too low"); + return -1; + } else if (frequency2 < 1100) { + highRes2 = 1; + tableSize2 = 512; + } else { + highRes2 = 0; + tableSize2 = 32; + } + + if (frequency < 1 || frequency2 < 1) { + logger.e( + "extremely low frequencies will have reduced amplitudes due to AC coupling restrictions"); + } + + List p = [1, 8, 64, 256]; + + int prescalar = 0; + double retFrequency = 0; + while (prescalar <= 3) { + wavelength = (64e6 ~/ frequency ~/ p[prescalar] ~/ tableSize); + retFrequency = 64e6 / wavelength / p[prescalar] / tableSize; + if (wavelength < 65525) break; + prescalar++; + } + if (prescalar == 4) { + logger.e("#1 out of range"); + return -1; + } + + int prescalar2 = 0; + double retFrequency2 = 0; + while (prescalar2 <= 3) { + wavelength2 = (64e6 ~/ frequency2 ~/ p[prescalar2] ~/ tableSize2); + retFrequency2 = 64e6 / wavelength2 / p[prescalar2] / tableSize2; + if (wavelength2 < 65525) break; + prescalar2++; + } + if (prescalar2 == 4) { + logger.e("#2 out of range"); + return -1; + } + + int phaseCoarse = (tableSize2 * (phase) / 360).toInt(); + int phaseFine = (wavelength2 * + (phase - (phaseCoarse) * 360 / tableSize2) / + (360 / tableSize2)) + .toInt(); + + try { + mPacketHandler.sendByte(mCommandsProto.wavegen); + mPacketHandler.sendByte(mCommandsProto.setBothWg); + mPacketHandler.sendInt(wavelength - 1); + mPacketHandler.sendInt(wavelength2 - 1); + mPacketHandler.sendInt(phaseCoarse); + mPacketHandler.sendInt(phaseFine); + mPacketHandler.sendByte( + (prescalar2 << 4) | (prescalar << 2) | (highRes2 << 1) | (highRes)); + await mPacketHandler.getAcknowledgement(); + + sin1Frequency = retFrequency; + sin2Frequency = retFrequency2; + return retFrequency; + } catch (e) { + logger.e("Error setting waves: $e"); + } + + return -1; + } + + Future sqrPWM( + double frequency, + double h0, + double p1, + double h1, + double p2, + double h2, + double p3, + double h3, + bool pulse, + ) async { + if (frequency == 0) return -1; + + if (h0 == 0) h0 = 0.1; + if (h1 == 0) h1 = 0.1; + if (h2 == 0) h2 = 0.1; + if (h3 == 0) h3 = 0.1; + + if (frequency > 10e6) { + logger.e( + "Frequency is greater than 10MHz. Please use map_reference_clock for 16 & 32MHz outputs", + ); + return -1; + } + + List p = [1, 8, 64, 256]; + int prescalar = 0; + int wavelength = 0; + + while (prescalar <= 3) { + wavelength = (64e6 ~/ frequency ~/ p[prescalar]); + if (wavelength < 65525) break; + prescalar++; + } + + if (prescalar == 4 || wavelength == 0) { + logger.e("Out of Range"); + return -1; + } + + if (!pulse) prescalar |= (1 << 5); + + int a1 = ((p1 % 1) * wavelength).toInt(); + int b1 = (((h1 + p1) % 1) * wavelength).toInt(); + int a2 = ((p2 % 1) * wavelength).toInt(); + int b2 = (((h2 + p2) % 1) * wavelength).toInt(); + int a3 = ((p3 % 1) * wavelength).toInt(); + int b3 = (((h3 + p3) % 1) * wavelength).toInt(); + + try { + mPacketHandler.sendByte(mCommandsProto.wavegen); + mPacketHandler.sendByte(mCommandsProto.sqr4); + mPacketHandler.sendInt(wavelength - 1); + mPacketHandler.sendInt((wavelength * h0).toInt() - 1); + mPacketHandler.sendInt(a1 > 0 ? a1 - 1 : 0); + mPacketHandler.sendInt(b1 > 1 ? b1 - 1 : 1); + mPacketHandler.sendInt(a2 > 0 ? a2 - 1 : 0); + mPacketHandler.sendInt(b2 > 1 ? b2 - 1 : 1); + mPacketHandler.sendInt(a3 > 0 ? a3 - 1 : 0); + mPacketHandler.sendInt(b3 > 1 ? b3 - 1 : 1); + mPacketHandler.sendByte(prescalar); + await mPacketHandler.getAcknowledgement(); + } catch (e) { + logger.e("Error sending data: $e"); + } + + for (var channel in ["SQR1", "SQR2", "SQR3", "SQR4"]) { + squareWaveFrequency[channel] = 64e6 / wavelength / p[prescalar & 0x3]; + } + + return (64e6 / wavelength / p[prescalar & 0x3]); + } + Future servo4( double? angle1, double? angle2, diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 1c7bf511a..81b233d6b 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -111,6 +111,31 @@ "phase": "Phase", "duty": "Duty", "produceSound": "Produce Sound", + "frequency": "Frequency", + "phaseOffset": "Phase Offset", + "unitDeg": "°", + "unitPercentage": "%", + "sine": "Sine", + "tri": "Tri", + "pwm": "pwm", + "waveGeneratorIntro": "The wave generator can be used to generate different types of waves like Sine wave, square wave and saw-tooth wave allow us to change their characteristics like frequency, phase and duty. It also allows us to produce PWM signals having different phase and duty.", + "sineWaveCaption": "To generate Sine wave or Saw-Tooth wave:", + "sineWaveBulletPoint1": "Connect the Wave pins S1 and S2 to the channel pins CH1, CH2 as shown in the above figure.", + "sineWaveBulletPoint2": "Select the Wave1 button for S1 pin and Wave2 button for S2 pin.", + "sineWaveBulletPoint3": "Press Sine image button for Sine wave and Saw-Tooth image button for Saw-Tooth wave.", + "sineWaveBulletPoint4": "Set their respective frequencies and phase difference(optional) using buttons in waveform panel.", + "sineWaveBulletPoint5": "Press the View button to view the waves in oscilloscope.", + "squareWaveCaption": "To generate Square wave:", + "squareWaveBulletPoint1": "Connect the Wave pins SQ1 to the channel pin CH1 as shown in the above figure.", + "squareWaveBulletPoint2": "Ensure the mode is selected to the Square, if not press the mode button to switch to Square mode.", + "squareWaveBulletPoint3": "Select the SQ1 button", + "squareWaveBulletPoint4": "Set its Frequency and Duty Cycle", + "squareWaveBulletPoint5": "Press the View button to view the square wave in oscilloscope.", + "pwmCaption": "Similarly, to produce four different PWM signals:", + "pwmBulletPoint1": "Switch over to PWM mode(In this mode S1 and S2 pin will be disabled).", + "pwmBulletPoint2": "Set the common frequency for all the SQ pins.", + "pwmBulletPoint3": "Set the duty and phase for all the SQ pins.", + "pwmBulletPoint4": "Press View button to generate the PWM signals.", "analyze": "Analyze", "settings": "Settings", "autoStart": "Auto Start", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index eed713b84..b4dbb77d3 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -760,6 +760,156 @@ abstract class AppLocalizations { /// **'Produce Sound'** String get produceSound; + /// No description provided for @frequency. + /// + /// In en, this message translates to: + /// **'Frequency'** + String get frequency; + + /// No description provided for @phaseOffset. + /// + /// In en, this message translates to: + /// **'Phase Offset'** + String get phaseOffset; + + /// No description provided for @unitDeg. + /// + /// In en, this message translates to: + /// **'°'** + String get unitDeg; + + /// No description provided for @unitPercentage. + /// + /// In en, this message translates to: + /// **'%'** + String get unitPercentage; + + /// No description provided for @sine. + /// + /// In en, this message translates to: + /// **'Sine'** + String get sine; + + /// No description provided for @tri. + /// + /// In en, this message translates to: + /// **'Tri'** + String get tri; + + /// No description provided for @pwm. + /// + /// In en, this message translates to: + /// **'pwm'** + String get pwm; + + /// No description provided for @waveGeneratorIntro. + /// + /// In en, this message translates to: + /// **'The wave generator can be used to generate different types of waves like Sine wave, square wave and saw-tooth wave allow us to change their characteristics like frequency, phase and duty. It also allows us to produce PWM signals having different phase and duty.'** + String get waveGeneratorIntro; + + /// No description provided for @sineWaveCaption. + /// + /// In en, this message translates to: + /// **'To generate Sine wave or Saw-Tooth wave:'** + String get sineWaveCaption; + + /// No description provided for @sineWaveBulletPoint1. + /// + /// In en, this message translates to: + /// **'Connect the Wave pins S1 and S2 to the channel pins CH1, CH2 as shown in the above figure.'** + String get sineWaveBulletPoint1; + + /// No description provided for @sineWaveBulletPoint2. + /// + /// In en, this message translates to: + /// **'Select the Wave1 button for S1 pin and Wave2 button for S2 pin.'** + String get sineWaveBulletPoint2; + + /// No description provided for @sineWaveBulletPoint3. + /// + /// In en, this message translates to: + /// **'Press Sine image button for Sine wave and Saw-Tooth image button for Saw-Tooth wave.'** + String get sineWaveBulletPoint3; + + /// No description provided for @sineWaveBulletPoint4. + /// + /// In en, this message translates to: + /// **'Set their respective frequencies and phase difference(optional) using buttons in waveform panel.'** + String get sineWaveBulletPoint4; + + /// No description provided for @sineWaveBulletPoint5. + /// + /// In en, this message translates to: + /// **'Press the View button to view the waves in oscilloscope.'** + String get sineWaveBulletPoint5; + + /// No description provided for @squareWaveCaption. + /// + /// In en, this message translates to: + /// **'To generate Square wave:'** + String get squareWaveCaption; + + /// No description provided for @squareWaveBulletPoint1. + /// + /// In en, this message translates to: + /// **'Connect the Wave pins SQ1 to the channel pin CH1 as shown in the above figure.'** + String get squareWaveBulletPoint1; + + /// No description provided for @squareWaveBulletPoint2. + /// + /// In en, this message translates to: + /// **'Ensure the mode is selected to the Square, if not press the mode button to switch to Square mode.'** + String get squareWaveBulletPoint2; + + /// No description provided for @squareWaveBulletPoint3. + /// + /// In en, this message translates to: + /// **'Select the SQ1 button'** + String get squareWaveBulletPoint3; + + /// No description provided for @squareWaveBulletPoint4. + /// + /// In en, this message translates to: + /// **'Set its Frequency and Duty Cycle'** + String get squareWaveBulletPoint4; + + /// No description provided for @squareWaveBulletPoint5. + /// + /// In en, this message translates to: + /// **'Press the View button to view the square wave in oscilloscope.'** + String get squareWaveBulletPoint5; + + /// No description provided for @pwmCaption. + /// + /// In en, this message translates to: + /// **'Similarly, to produce four different PWM signals:'** + String get pwmCaption; + + /// No description provided for @pwmBulletPoint1. + /// + /// In en, this message translates to: + /// **'Switch over to PWM mode(In this mode S1 and S2 pin will be disabled).'** + String get pwmBulletPoint1; + + /// No description provided for @pwmBulletPoint2. + /// + /// In en, this message translates to: + /// **'Set the common frequency for all the SQ pins.'** + String get pwmBulletPoint2; + + /// No description provided for @pwmBulletPoint3. + /// + /// In en, this message translates to: + /// **'Set the duty and phase for all the SQ pins.'** + String get pwmBulletPoint3; + + /// No description provided for @pwmBulletPoint4. + /// + /// In en, this message translates to: + /// **'Press View button to generate the PWM signals.'** + String get pwmBulletPoint4; + /// No description provided for @analyze. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 6fc2a349f..6c8d6cbd8 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -352,6 +352,92 @@ class AppLocalizationsEn extends AppLocalizations { @override String get produceSound => 'Produce Sound'; + @override + String get frequency => 'Frequency'; + + @override + String get phaseOffset => 'Phase Offset'; + + @override + String get unitDeg => '°'; + + @override + String get unitPercentage => '%'; + + @override + String get sine => 'Sine'; + + @override + String get tri => 'Tri'; + + @override + String get pwm => 'pwm'; + + @override + String get waveGeneratorIntro => + 'The wave generator can be used to generate different types of waves like Sine wave, square wave and saw-tooth wave allow us to change their characteristics like frequency, phase and duty. It also allows us to produce PWM signals having different phase and duty.'; + + @override + String get sineWaveCaption => 'To generate Sine wave or Saw-Tooth wave:'; + + @override + String get sineWaveBulletPoint1 => + 'Connect the Wave pins S1 and S2 to the channel pins CH1, CH2 as shown in the above figure.'; + + @override + String get sineWaveBulletPoint2 => + 'Select the Wave1 button for S1 pin and Wave2 button for S2 pin.'; + + @override + String get sineWaveBulletPoint3 => + 'Press Sine image button for Sine wave and Saw-Tooth image button for Saw-Tooth wave.'; + + @override + String get sineWaveBulletPoint4 => + 'Set their respective frequencies and phase difference(optional) using buttons in waveform panel.'; + + @override + String get sineWaveBulletPoint5 => + 'Press the View button to view the waves in oscilloscope.'; + + @override + String get squareWaveCaption => 'To generate Square wave:'; + + @override + String get squareWaveBulletPoint1 => + 'Connect the Wave pins SQ1 to the channel pin CH1 as shown in the above figure.'; + + @override + String get squareWaveBulletPoint2 => + 'Ensure the mode is selected to the Square, if not press the mode button to switch to Square mode.'; + + @override + String get squareWaveBulletPoint3 => 'Select the SQ1 button'; + + @override + String get squareWaveBulletPoint4 => 'Set its Frequency and Duty Cycle'; + + @override + String get squareWaveBulletPoint5 => + 'Press the View button to view the square wave in oscilloscope.'; + + @override + String get pwmCaption => 'Similarly, to produce four different PWM signals:'; + + @override + String get pwmBulletPoint1 => + 'Switch over to PWM mode(In this mode S1 and S2 pin will be disabled).'; + + @override + String get pwmBulletPoint2 => 'Set the common frequency for all the SQ pins.'; + + @override + String get pwmBulletPoint3 => 'Set the duty and phase for all the SQ pins.'; + + @override + String get pwmBulletPoint4 => + 'Press View button to generate the PWM signals.'; + @override String get analyze => 'Analyze'; diff --git a/lib/others/wave_generator_constants.dart b/lib/others/wave_generator_constants.dart new file mode 100644 index 000000000..8a3a51aca --- /dev/null +++ b/lib/others/wave_generator_constants.dart @@ -0,0 +1,41 @@ +import 'package:pslab/providers/wave_generator_state_provider.dart'; + +class WaveGeneratorConstants { + final Map> wave = { + WaveConst.wave1: { + WaveConst.frequency: WaveData.freqMin.value, + WaveConst.waveType: WaveGeneratorStateProvider.sin, + }, + WaveConst.wave2: { + WaveConst.phase: WaveData.phaseMin.value, + WaveConst.frequency: WaveData.freqMin.value, + WaveConst.waveType: WaveGeneratorStateProvider.sin, + }, + WaveConst.waveType: {}, + WaveConst.sqr1: { + WaveConst.frequency: WaveData.freqMin.value, + WaveConst.duty: WaveData.dutyMin.value, + }, + WaveConst.sqr2: { + WaveConst.phase: WaveData.phaseMin.value, + WaveConst.duty: WaveData.dutyMin.value, + }, + WaveConst.sqr3: { + WaveConst.phase: WaveData.phaseMin.value, + WaveConst.duty: WaveData.dutyMin.value, + }, + WaveConst.sqr4: { + WaveConst.phase: WaveData.phaseMin.value, + WaveConst.duty: WaveData.dutyMin.value, + }, + }; + + WaveConst modeSelected = WaveConst.square; + + final Map state = { + 'SQR1': 0, + 'SQR2': 0, + 'SQR3': 0, + 'SQR4': 0, + }; +} diff --git a/lib/providers/wave_generator_state_provider.dart b/lib/providers/wave_generator_state_provider.dart new file mode 100644 index 000000000..ff969163c --- /dev/null +++ b/lib/providers/wave_generator_state_provider.dart @@ -0,0 +1,332 @@ +import 'dart:math'; +import 'dart:math' as math; + +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; +import 'package:pslab/communication/science_lab.dart'; +import 'package:pslab/others/wave_generator_constants.dart'; +import 'package:pslab/providers/locator.dart'; + +enum WaveConst { + waveType, + wave1, + wave2, + sqr1, + sqr2, + sqr3, + sqr4, + frequency, + phase, + duty, + sine, + triangular, + square, + pwm +} + +enum WaveData { + freqMin(10), + dutyMin(0), + phaseMin(0), + freqMax(5000), + phaseMax(360), + dutyMax(100); + + final int value; + const WaveData(this.value); + + int get getValue => value; +} + +class WaveGeneratorStateProvider extends ChangeNotifier { + static final int sin = 1; + static final int triangular = 2; + static final int pwm = 3; + + late WaveConst? selectedAnalogWave; + + late WaveConst? selectedDigitalWave; + + late WaveConst? propSelected; + + late WaveGeneratorConstants waveGeneratorConstants; + + late List> waveData; + + late ScienceLab _scienceLab; + + WaveGeneratorStateProvider() { + selectedAnalogWave = WaveConst.wave1; + + selectedDigitalWave = WaveConst.sqr1; + + _scienceLab = getIt.get(); + + propSelected = null; + + waveGeneratorConstants = WaveGeneratorConstants(); + + waveData = []; + } + + void setAnalogSelectedWave(WaveConst wave) { + selectedAnalogWave = wave; + propSelected = null; + previewWave(); + notifyListeners(); + } + + void setDigitalSelectedWave(WaveConst wave) { + selectedDigitalWave = wave; + propSelected = null; + previewWave(); + notifyListeners(); + } + + void setPropSelected(WaveConst wave) { + propSelected = wave; + previewWave(); + notifyListeners(); + } + + void setAnalogWaveType(int waveType) { + waveGeneratorConstants.wave[selectedAnalogWave]?[WaveConst.waveType] = + waveType; + previewWave(); + notifyListeners(); + } + + Future setValue(int value) async { + if (waveGeneratorConstants.modeSelected == WaveConst.square) { + waveGeneratorConstants.wave[selectedAnalogWave]?[propSelected!] = value; + } else { + if (propSelected == WaveConst.frequency) { + waveGeneratorConstants.wave[WaveConst.sqr1]?[propSelected!] = value; + } else { + waveGeneratorConstants.wave[selectedDigitalWave]?[propSelected!] = + value; + } + } + previewWave(); + await setWave(); + notifyListeners(); + } + + Future setWave() async { + double freq1 = waveGeneratorConstants + .wave[WaveConst.wave1]![WaveConst.frequency]! + .toDouble(); + double freq2 = waveGeneratorConstants + .wave[WaveConst.wave2]![WaveConst.frequency]! + .toDouble(); + double phase = waveGeneratorConstants + .wave[WaveConst.wave2]![WaveConst.phase]! + .toDouble(); + + String waveType1 = + waveGeneratorConstants.wave[WaveConst.wave1]![WaveConst.waveType]! == + sin + ? "sine" + : "tria"; + String waveType2 = + waveGeneratorConstants.wave[WaveConst.wave2]![WaveConst.waveType]! == + sin + ? "sine" + : "tria"; + + if (_scienceLab.isConnected()) { + if (waveGeneratorConstants.modeSelected == WaveConst.square) { + if (phase == WaveData.phaseMin.getValue) { + await _scienceLab.setSI1(freq1, waveType1); + await _scienceLab.setSI2(freq2, waveType2); + } else { + await _scienceLab.setWaves(freq1, phase, freq2); + } + } else { + double freqSqr1 = waveGeneratorConstants + .wave[WaveConst.sqr1]![WaveConst.frequency]! + .toDouble(); + double dutySqr1 = waveGeneratorConstants + .wave[WaveConst.sqr1]![WaveConst.duty]! + .toDouble() / + 100; + double dutySqr2 = waveGeneratorConstants + .wave[WaveConst.sqr2]![WaveConst.duty]! + .toDouble() / + 100; + double phaseSqr2 = waveGeneratorConstants + .wave[WaveConst.sqr2]![WaveConst.phase]! + .toDouble() / + 360; + double dutySqr3 = waveGeneratorConstants + .wave[WaveConst.sqr3]![WaveConst.duty]! + .toDouble() / + 100; + double phaseSqr3 = waveGeneratorConstants + .wave[WaveConst.sqr3]![WaveConst.phase]! + .toDouble() / + 360; + double dutySqr4 = waveGeneratorConstants + .wave[WaveConst.sqr4]![WaveConst.duty]! + .toDouble() / + 100; + double phaseSqr4 = waveGeneratorConstants + .wave[WaveConst.sqr4]![WaveConst.phase]! + .toDouble() / + 360; + + await _scienceLab.sqrPWM(freqSqr1, dutySqr1, phaseSqr2, dutySqr2, + phaseSqr3, dutySqr3, phaseSqr4, dutySqr4, false); + } + } + } + + Future incrementValue() async { + int min, max; + switch (propSelected) { + case WaveConst.frequency: + min = WaveData.freqMin.getValue; + max = WaveData.freqMax.getValue; + break; + case WaveConst.phase: + min = WaveData.phaseMin.getValue; + max = WaveData.phaseMax.getValue; + break; + case WaveConst.duty: + min = WaveData.dutyMin.getValue; + max = WaveData.dutyMax.getValue; + break; + default: + return; + } + + int current = waveGeneratorConstants.modeSelected == WaveConst.square + ? (waveGeneratorConstants.wave[selectedAnalogWave]?[propSelected!] ?? + min) + : propSelected == WaveConst.frequency + ? (waveGeneratorConstants.wave[WaveConst.sqr1]?[propSelected!] ?? + min) + : (waveGeneratorConstants.wave[selectedDigitalWave] + ?[propSelected!] ?? + min); + + if (current < max) await setValue(current + 1); + } + + Future decrementValue() async { + int min; + switch (propSelected) { + case WaveConst.frequency: + min = WaveData.freqMin.getValue; + break; + case WaveConst.phase: + min = WaveData.phaseMin.getValue; + break; + case WaveConst.duty: + min = WaveData.dutyMin.getValue; + break; + default: + return; + } + + int current = waveGeneratorConstants.modeSelected == WaveConst.square + ? (waveGeneratorConstants.wave[selectedAnalogWave]?[propSelected!] ?? + min) + : propSelected == WaveConst.frequency + ? (waveGeneratorConstants.wave[WaveConst.sqr1]?[propSelected!] ?? + min) + : (waveGeneratorConstants.wave[selectedDigitalWave] + ?[propSelected!] ?? + min); + + if (current > min) await setValue(current - 1); + } + + void previewWave() { + waveData.clear(); + List samplePoints = getSamplePoints(false); + List referencePoints = getSamplePoints(true); + waveData.add(referencePoints); + waveData.add(samplePoints); + notifyListeners(); + } + + List getSamplePoints(bool isReference) { + List entries = []; + if (waveGeneratorConstants.modeSelected == WaveConst.pwm) { + double freq = waveGeneratorConstants + .wave[WaveConst.sqr1]![WaveConst.frequency]! + .toDouble(); + double duty = waveGeneratorConstants + .wave[selectedDigitalWave]![WaveConst.duty]! + .toDouble() / + 100; + double phase = 0; + if (selectedDigitalWave != WaveConst.sqr1 && !isReference) { + phase = waveGeneratorConstants.wave[selectedDigitalWave] + ?[WaveConst.phase]! + .toDouble() ?? + 0; + } + for (int i = 0; i < 5000; i++) { + double t = 2 * pi * freq * i / 1e6 + phase * pi / 180; + double y; + if (t % (2 * pi) < 2 * pi * duty) { + y = 5; + } else { + y = -5; + } + entries.add(FlSpot(i.toDouble(), y)); + } + } else { + double phase = 0; + int shape = + waveGeneratorConstants.wave[selectedAnalogWave]![WaveConst.waveType]!; + + double freq = waveGeneratorConstants + .wave[selectedAnalogWave]![WaveConst.frequency]! + .toDouble(); + + if (selectedAnalogWave != WaveConst.wave1 && !isReference) { + phase = waveGeneratorConstants.wave[WaveConst.wave2]![WaveConst.phase]! + .toDouble(); + } + if (shape == 1) { + for (int i = 0; i < 5000; i++) { + double y = 5 * math.sin(2 * pi * (freq / 1e6) * i + phase * pi / 180); + entries.add(FlSpot(i.toDouble(), y)); + } + } else { + for (int i = 0; i < 5000; i++) { + double y = (10 / pi) * + (math.asin( + math.sin(2 * pi * (freq / 1e6) * i + phase * pi / 180))); + entries.add(FlSpot(i.toDouble(), y)); + } + } + } + return entries; + } + + List createPlots() { + List colors = [Colors.white, Colors.white60]; + List plots = []; + plots.addAll( + List.generate( + waveData.length, + (index) { + return LineChartBarData( + spots: waveData[index], + isCurved: false, + color: colors[index % colors.length], + barWidth: 1, + dotData: const FlDotData( + show: false, + ), + ); + }, + ), + ); + return plots; + } +} diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index b0f3db96c..61c280ee2 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -84,3 +84,6 @@ Color sensorControlsTextBox = Colors.grey.shade400; Color sensorControlIconColor = Colors.grey.shade600; List bmp180ChartColors = [Colors.blue, Colors.green, Colors.red]; Color chartHintTextColor = Colors.yellow; +Color buttonEnabledColor = primaryRed; +Color buttonDisabledColor = Color.fromARGB(255, 240, 162, 162); +Color waveGeneratorPropTextColor = Colors.deepOrange; diff --git a/lib/view/wave_generator_screen.dart b/lib/view/wave_generator_screen.dart index eaa72f7f9..b586d3a44 100644 --- a/lib/view/wave_generator_screen.dart +++ b/lib/view/wave_generator_screen.dart @@ -1,13 +1,19 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'package:pslab/l10n/app_localizations.dart'; import 'package:pslab/providers/locator.dart'; +import 'package:pslab/providers/wave_generator_state_provider.dart'; import 'package:pslab/theme/colors.dart'; import 'package:pslab/view/widgets/common_scaffold_widget.dart'; import 'package:pslab/view/widgets/analog_waveform_controls.dart'; +import 'package:pslab/view/widgets/digital_waveform_controls.dart'; +import 'package:pslab/view/widgets/guide_widget.dart'; import 'package:pslab/view/widgets/wave_generator_graph.dart'; import 'package:pslab/view/widgets/wave_generator_main_controls.dart'; class WaveGeneratorScreen extends StatefulWidget { + final String sineWaveCircuit = 'assets/images/sin_wave_circuit.png'; + final String squareWaveCircuit = 'assets/images/square_wave_circuit.png'; const WaveGeneratorScreen({super.key}); @override @@ -16,102 +22,223 @@ class WaveGeneratorScreen extends StatefulWidget { class _WaveGeneratorScreenState extends State { AppLocalizations appLocalizations = getIt.get(); + bool _showGuide = false; + + void _hideInstrumentGuide() { + setState(() { + _showGuide = false; + }); + } + + List _getWaveGeneratorContent() { + return [ + InstrumentIntroText(text: appLocalizations.waveGeneratorIntro), + InstrumentIntroText( + text: appLocalizations.sineWaveCaption, + style: TextStyle(fontWeight: FontWeight.bold), + ), + InstrumentImage(imagePath: widget.sineWaveCircuit), + InstrumentBulletPoint(text: appLocalizations.sineWaveBulletPoint1), + InstrumentBulletPoint(text: appLocalizations.sineWaveBulletPoint2), + InstrumentBulletPoint(text: appLocalizations.sineWaveBulletPoint3), + InstrumentBulletPoint(text: appLocalizations.sineWaveBulletPoint4), + InstrumentBulletPoint(text: appLocalizations.sineWaveBulletPoint5), + InstrumentIntroText( + text: appLocalizations.squareWaveCaption, + style: TextStyle(fontWeight: FontWeight.bold), + ), + InstrumentImage(imagePath: widget.squareWaveCircuit), + InstrumentBulletPoint(text: appLocalizations.squareWaveBulletPoint1), + InstrumentBulletPoint(text: appLocalizations.squareWaveBulletPoint2), + InstrumentBulletPoint(text: appLocalizations.squareWaveBulletPoint3), + InstrumentBulletPoint(text: appLocalizations.squareWaveBulletPoint4), + InstrumentBulletPoint(text: appLocalizations.squareWaveBulletPoint5), + InstrumentIntroText( + text: appLocalizations.pwmCaption, + style: TextStyle(fontWeight: FontWeight.bold), + ), + InstrumentBulletPoint(text: appLocalizations.pwmBulletPoint1), + InstrumentBulletPoint(text: appLocalizations.pwmBulletPoint2), + InstrumentBulletPoint(text: appLocalizations.pwmBulletPoint3), + InstrumentBulletPoint(text: appLocalizations.pwmBulletPoint4), + ]; + } + @override Widget build(BuildContext context) { - return CommonScaffold( - title: 'Wave Generator', - body: Container( - margin: const EdgeInsets.all(8.0), - child: Column( - children: [ - Expanded( - flex: 30, - child: Container( - color: chartBackgroundColor, - child: WaveGeneratorGraph(), - ), - ), - Expanded( - flex: 30, - child: Column( - children: [ - Expanded( - flex: 70, - child: AnalogWaveformControls(), - ), - Expanded( - flex: 30, - child: Row( - crossAxisAlignment: CrossAxisAlignment.stretch, + return SafeArea( + child: MultiProvider( + providers: [ + ChangeNotifierProvider( + create: (_) => WaveGeneratorStateProvider(), + ), + ], + child: Consumer( + builder: (context, provider, _) { + return Stack( + children: [ + CommonScaffold( + title: appLocalizations.waveGenerator, + body: Container( + margin: + const EdgeInsets.only(left: 8.0, top: 8.0, right: 8.0), + child: Column( children: [ Expanded( - child: TextButton( - style: TextButton.styleFrom( - backgroundColor: primaryRed, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(6), - ), - ), - child: Text( - appLocalizations.produceSound, - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white, - fontSize: 14, - ), - ), - onPressed: () => {}, + flex: 30, + child: Container( + color: chartBackgroundColor, + child: WaveGeneratorGraph(), ), ), - const SizedBox(width: 4), - Expanded( - child: TextButton( - style: TextButton.styleFrom( - backgroundColor: primaryRed, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(6), + Column( + children: [ + provider.waveGeneratorConstants.modeSelected == + WaveConst.square + ? AnalogWaveformControls() + : DigitalWaveformControls(), + SizedBox( + height: 60, + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: TextButton( + style: TextButton.styleFrom( + backgroundColor: primaryRed, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(6), + ), + ), + child: Text( + appLocalizations.produceSound, + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + onPressed: () => {}, + ), + ), + const SizedBox(width: 4), + Expanded( + child: TextButton( + style: TextButton.styleFrom( + backgroundColor: provider + .waveGeneratorConstants + .modeSelected == + WaveConst.square + ? buttonEnabledColor + : buttonDisabledColor, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(6), + ), + ), + child: Text( + appLocalizations.analog, + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + onPressed: () => { + setState( + () { + provider.waveGeneratorConstants + .modeSelected = + WaveConst.square; + provider.propSelected = null; + provider.previewWave(); + }, + ), + }, + ), + ), + const SizedBox(width: 4), + Expanded( + child: TextButton( + style: TextButton.styleFrom( + backgroundColor: provider + .waveGeneratorConstants + .modeSelected == + WaveConst.pwm + ? buttonEnabledColor + : buttonDisabledColor, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(6), + ), + ), + child: Text( + appLocalizations.digital, + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + onPressed: () => { + setState( + () { + provider.waveGeneratorConstants + .modeSelected = WaveConst.pwm; + provider.propSelected = null; + provider.previewWave(); + }, + ), + }, + ), + ), + ], ), ), - child: Text( - appLocalizations.analog, - style: TextStyle( - color: Colors.white, - fontSize: 14, - ), - ), - onPressed: () => {}, - ), + ], ), - const SizedBox(width: 4), Expanded( - child: TextButton( - style: TextButton.styleFrom( - backgroundColor: primaryRed, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(6), - ), - ), - child: Text( - appLocalizations.digital, - style: TextStyle( - color: Colors.white, - fontSize: 14, - ), - ), - onPressed: () => {}, - ), + flex: 40, + child: WaveGeneratorMainControls(), ), ], ), ), - ], - ), - ), - Expanded( - flex: 40, - child: WaveGeneratorMainControls(), - ), - ], + actions: [ + IconButton( + icon: Icon(Icons.play_arrow, color: Colors.white), + onPressed: () {}, + ), + IconButton( + icon: Icon(Icons.save, color: Colors.white), + onPressed: () {}, + ), + PopupMenuButton( + icon: const Icon(Icons.more_vert, color: Colors.white), + onSelected: (value) { + if (value == appLocalizations.showGuide) { + setState(() { + _showGuide = !_showGuide; + }); + } + }, + itemBuilder: (BuildContext context) => [ + PopupMenuItem( + value: appLocalizations.showGuide, + child: Text(appLocalizations.showGuide), + ), + ], + ) + ], + ), + if (_showGuide) + InstrumentOverviewDrawer( + instrumentName: appLocalizations.waveGenerator, + content: _getWaveGeneratorContent(), + onHide: _hideInstrumentGuide, + ), + ], + ); + }, ), ), ); diff --git a/lib/view/widgets/analog_waveform_controls.dart b/lib/view/widgets/analog_waveform_controls.dart index 629c1b543..f5e3deca2 100644 --- a/lib/view/widgets/analog_waveform_controls.dart +++ b/lib/view/widgets/analog_waveform_controls.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'package:pslab/l10n/app_localizations.dart'; import 'package:pslab/providers/locator.dart'; +import 'package:pslab/providers/wave_generator_state_provider.dart'; import 'package:pslab/theme/colors.dart'; class AnalogWaveformControls extends StatefulWidget { @@ -16,6 +18,8 @@ class _AnalogWaveformControlsState extends State { String iconTriangular = "assets/icons/ic_triangular.png"; @override Widget build(BuildContext context) { + WaveGeneratorStateProvider waveGeneratorStateProvider = + Provider.of(context); return Stack( children: [ Container( @@ -27,96 +31,156 @@ class _AnalogWaveformControlsState extends State { ), child: Column( children: [ - Row( - children: [ - Expanded( - child: TextButton( - style: TextButton.styleFrom( - backgroundColor: primaryRed, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(6), - ), - ), - child: Text( - appLocalizations.wave1, - style: TextStyle( - color: Colors.white, - fontSize: 14, - ), - ), - onPressed: () => {}, - ), - ), - const SizedBox(width: 4), - Expanded( - child: TextButton( - style: TextButton.styleFrom( - backgroundColor: primaryRed, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(6), - ), - ), - child: Text( - appLocalizations.wave2, - style: TextStyle( - color: Colors.white, - fontSize: 14, - ), - ), - onPressed: () => {}, - ), - ), - ], - ), - IntrinsicHeight( + SizedBox( + height: 40, child: Row( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Expanded( - flex: 35, child: TextButton( style: TextButton.styleFrom( - backgroundColor: primaryRed, + backgroundColor: + waveGeneratorStateProvider.selectedAnalogWave == + WaveConst.wave1 + ? buttonEnabledColor + : buttonDisabledColor, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(6), ), ), child: Text( - appLocalizations.freq, + appLocalizations.wave1, style: TextStyle( color: Colors.white, fontSize: 14, ), ), - onPressed: () => {}, + onPressed: () => { + setState( + () { + waveGeneratorStateProvider + .setAnalogSelectedWave(WaveConst.wave1); + }, + ), + }, ), ), const SizedBox(width: 4), + Expanded( + child: TextButton( + style: TextButton.styleFrom( + backgroundColor: + waveGeneratorStateProvider.selectedAnalogWave == + WaveConst.wave2 + ? buttonEnabledColor + : buttonDisabledColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + ), + ), + child: Text( + appLocalizations.wave2, + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + onPressed: () => { + setState( + () { + waveGeneratorStateProvider + .setAnalogSelectedWave(WaveConst.wave2); + }, + ), + }, + ), + ), + ], + ), + ), + const SizedBox(height: 4), + SizedBox( + height: 40, + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ Expanded( flex: 35, child: TextButton( style: TextButton.styleFrom( - backgroundColor: primaryRed, + backgroundColor: + waveGeneratorStateProvider.propSelected == + WaveConst.frequency + ? buttonEnabledColor + : buttonDisabledColor, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(6), ), ), child: Text( - appLocalizations.phase, + appLocalizations.freq, style: TextStyle( color: Colors.white, fontSize: 14, ), ), - onPressed: () => {}, + onPressed: () => { + setState( + () { + waveGeneratorStateProvider + .setPropSelected(WaveConst.frequency); + }, + ), + }, ), ), const SizedBox(width: 4), + Expanded( + flex: 35, + child: waveGeneratorStateProvider.selectedAnalogWave == + WaveConst.wave2 + ? TextButton( + style: TextButton.styleFrom( + backgroundColor: + waveGeneratorStateProvider.propSelected == + WaveConst.phase + ? buttonEnabledColor + : buttonDisabledColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + ), + ), + child: Text( + appLocalizations.phase, + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + onPressed: () => { + setState( + () { + waveGeneratorStateProvider + .setPropSelected(WaveConst.phase); + }, + ), + }, + ) + : Container(), + ), + const SizedBox(width: 4), Expanded( flex: 15, child: IconButton( style: TextButton.styleFrom( - backgroundColor: primaryRed, + backgroundColor: waveGeneratorStateProvider + .waveGeneratorConstants.wave[ + waveGeneratorStateProvider + .selectedAnalogWave] + ?[WaveConst.waveType] == + WaveGeneratorStateProvider.sin + ? buttonEnabledColor + : buttonDisabledColor, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(6), ), @@ -125,7 +189,12 @@ class _AnalogWaveformControlsState extends State { iconSin, color: Colors.white, ), - onPressed: () => {}, + onPressed: () => { + setState( + () => waveGeneratorStateProvider.setAnalogWaveType( + WaveGeneratorStateProvider.sin), + ), + }, ), ), const SizedBox(width: 4), @@ -133,7 +202,14 @@ class _AnalogWaveformControlsState extends State { flex: 15, child: IconButton( style: TextButton.styleFrom( - backgroundColor: primaryRed, + backgroundColor: waveGeneratorStateProvider + .waveGeneratorConstants.wave[ + waveGeneratorStateProvider + .selectedAnalogWave] + ?[WaveConst.waveType] == + WaveGeneratorStateProvider.triangular + ? buttonEnabledColor + : buttonDisabledColor, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(6), ), @@ -142,7 +218,12 @@ class _AnalogWaveformControlsState extends State { iconTriangular, color: Colors.white, ), - onPressed: () => {}, + onPressed: () => { + setState( + () => waveGeneratorStateProvider.setAnalogWaveType( + WaveGeneratorStateProvider.triangular), + ), + }, ), ), ], diff --git a/lib/view/widgets/digital_waveform_controls.dart b/lib/view/widgets/digital_waveform_controls.dart index e16cb77a7..ee6950cc4 100644 --- a/lib/view/widgets/digital_waveform_controls.dart +++ b/lib/view/widgets/digital_waveform_controls.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'package:pslab/l10n/app_localizations.dart'; import 'package:pslab/providers/locator.dart'; +import 'package:pslab/providers/wave_generator_state_provider.dart'; import 'package:pslab/theme/colors.dart'; class DigitalWaveformControls extends StatefulWidget { @@ -14,6 +16,8 @@ class _DigitalWaveformControlsState extends State { AppLocalizations appLocalizations = getIt.get(); @override Widget build(BuildContext context) { + WaveGeneratorStateProvider waveGeneratorStateProvider = + Provider.of(context); return Stack( children: [ Container( @@ -25,134 +29,201 @@ class _DigitalWaveformControlsState extends State { ), child: Column( children: [ - Row( - children: [ - Expanded( - child: TextButton( - style: TextButton.styleFrom( - backgroundColor: primaryRed, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(6), - ), - ), - child: Text( - appLocalizations.sqr1.toUpperCase(), - style: TextStyle( - color: Colors.white, - fontSize: 14, - ), - ), - onPressed: () => {}, - ), - ), - const SizedBox(width: 4), - Expanded( - child: TextButton( - style: TextButton.styleFrom( - backgroundColor: primaryRed, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(6), + SizedBox( + height: 40, + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: TextButton( + style: TextButton.styleFrom( + backgroundColor: + waveGeneratorStateProvider.selectedDigitalWave == + WaveConst.sqr1 + ? buttonEnabledColor + : buttonDisabledColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + ), ), - ), - child: Text( - appLocalizations.sqr2.toUpperCase(), - style: TextStyle( - color: Colors.white, - fontSize: 14, + child: Text( + appLocalizations.sqr1.toUpperCase(), + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), ), + onPressed: () => { + setState(() { + waveGeneratorStateProvider + .setDigitalSelectedWave(WaveConst.sqr1); + }) + }, ), - onPressed: () => {}, ), - ), - const SizedBox(width: 4), - Expanded( - child: TextButton( - style: TextButton.styleFrom( - backgroundColor: primaryRed, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(6), + const SizedBox(width: 4), + Expanded( + child: TextButton( + style: TextButton.styleFrom( + backgroundColor: + waveGeneratorStateProvider.selectedDigitalWave == + WaveConst.sqr2 + ? buttonEnabledColor + : buttonDisabledColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + ), ), - ), - child: Text( - appLocalizations.sqr3.toUpperCase(), - style: TextStyle( - color: Colors.white, - fontSize: 14, + child: Text( + appLocalizations.sqr2.toUpperCase(), + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), ), + onPressed: () => { + setState(() { + waveGeneratorStateProvider + .setDigitalSelectedWave(WaveConst.sqr2); + }) + }, ), - onPressed: () => {}, ), - ), - const SizedBox(width: 4), - Expanded( - child: TextButton( - style: TextButton.styleFrom( - backgroundColor: primaryRed, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(6), + const SizedBox(width: 4), + Expanded( + child: TextButton( + style: TextButton.styleFrom( + backgroundColor: + waveGeneratorStateProvider.selectedDigitalWave == + WaveConst.sqr3 + ? buttonEnabledColor + : buttonDisabledColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + ), ), - ), - child: Text( - appLocalizations.sqr4.toUpperCase(), - style: TextStyle( - color: Colors.white, - fontSize: 14, + child: Text( + appLocalizations.sqr3.toUpperCase(), + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), ), + onPressed: () => { + setState(() { + waveGeneratorStateProvider + .setDigitalSelectedWave(WaveConst.sqr3); + }) + }, ), - onPressed: () => {}, ), - ), - ], - ), - IntrinsicHeight( - child: Row( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ + const SizedBox(width: 4), Expanded( - flex: 35, child: TextButton( style: TextButton.styleFrom( - backgroundColor: primaryRed, + backgroundColor: + waveGeneratorStateProvider.selectedDigitalWave == + WaveConst.sqr4 + ? buttonEnabledColor + : buttonDisabledColor, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(6), ), ), child: Text( - appLocalizations.freq, + appLocalizations.sqr4.toUpperCase(), style: TextStyle( color: Colors.white, fontSize: 14, ), ), - onPressed: () => {}, + onPressed: () => { + setState(() { + waveGeneratorStateProvider + .setDigitalSelectedWave(WaveConst.sqr4); + }) + }, ), ), - const SizedBox(width: 4), + ], + ), + ), + const SizedBox(height: 4), + SizedBox( + height: 40, + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ Expanded( flex: 35, child: TextButton( style: TextButton.styleFrom( - backgroundColor: primaryRed, + backgroundColor: + waveGeneratorStateProvider.propSelected == + WaveConst.frequency + ? buttonEnabledColor + : buttonDisabledColor, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(6), ), ), child: Text( - appLocalizations.phase, + appLocalizations.freq, style: TextStyle( color: Colors.white, fontSize: 14, ), ), - onPressed: () => {}, + onPressed: () => { + setState(() { + waveGeneratorStateProvider + .setPropSelected(WaveConst.frequency); + }) + }, ), ), const SizedBox(width: 4), + Expanded( + flex: 35, + child: waveGeneratorStateProvider.selectedDigitalWave != + WaveConst.sqr1 + ? TextButton( + style: TextButton.styleFrom( + backgroundColor: + waveGeneratorStateProvider.propSelected == + WaveConst.phase + ? buttonEnabledColor + : buttonDisabledColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + ), + ), + child: Text( + appLocalizations.phase, + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + onPressed: () => { + setState(() { + waveGeneratorStateProvider + .setPropSelected(WaveConst.phase); + }) + }, + ) + : Container(), + ), + const SizedBox(width: 4), Expanded( flex: 35, child: TextButton( style: TextButton.styleFrom( - backgroundColor: primaryRed, + backgroundColor: + waveGeneratorStateProvider.propSelected == + WaveConst.duty + ? buttonEnabledColor + : buttonDisabledColor, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(6), ), @@ -164,7 +235,12 @@ class _DigitalWaveformControlsState extends State { fontSize: 14, ), ), - onPressed: () => {}, + onPressed: () => { + setState(() { + waveGeneratorStateProvider + .setPropSelected(WaveConst.duty); + }) + }, ), ), ], diff --git a/lib/view/widgets/wave_generator_graph.dart b/lib/view/widgets/wave_generator_graph.dart index 3f8672416..628056226 100644 --- a/lib/view/widgets/wave_generator_graph.dart +++ b/lib/view/widgets/wave_generator_graph.dart @@ -1,5 +1,7 @@ import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/cupertino.dart'; +import 'package:provider/provider.dart'; +import 'package:pslab/providers/wave_generator_state_provider.dart'; import 'package:pslab/theme/colors.dart'; class WaveGeneratorGraph extends StatefulWidget { @@ -26,9 +28,12 @@ class _WaveGeneratorGraphState extends State { @override Widget build(BuildContext context) { + WaveGeneratorStateProvider waveGeneratorStateProvider = + Provider.of(context, listen: true); return Container( padding: const EdgeInsets.only(left: 20, right: 20, bottom: 20), child: LineChart( + duration: const Duration(milliseconds: 10), LineChartData( backgroundColor: chartBackgroundColor, titlesData: FlTitlesData( @@ -81,10 +86,11 @@ class _WaveGeneratorGraphState extends State { ), ), clipData: const FlClipData.all(), - maxY: 5.0, - minY: -5.0, + maxY: 7.0, + minY: -7.0, maxX: 5000.0, minX: 0.0, + lineBarsData: waveGeneratorStateProvider.createPlots(), ), ), ); diff --git a/lib/view/widgets/wave_generator_main_controls.dart b/lib/view/widgets/wave_generator_main_controls.dart index f3f3179f3..2b42ba2ca 100644 --- a/lib/view/widgets/wave_generator_main_controls.dart +++ b/lib/view/widgets/wave_generator_main_controls.dart @@ -1,4 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:pslab/l10n/app_localizations.dart'; +import 'package:pslab/providers/locator.dart'; +import 'package:pslab/providers/wave_generator_state_provider.dart'; import 'package:pslab/theme/colors.dart'; class WaveGeneratorMainControls extends StatefulWidget { @@ -9,113 +13,196 @@ class WaveGeneratorMainControls extends StatefulWidget { } class _WaveGeneratorMainControlsState extends State { + AppLocalizations appLocalizations = getIt.get(); String iconSin = "assets/icons/ic_sin.png"; String iconTriangular = "assets/icons/ic_triangular.png"; + String iconPwm = "assets/icons/ic_pwm_pic.png"; + var labelMap = {}; + var unitMap = {}; + final minValues = { + WaveConst.frequency: WaveData.freqMin.value, + WaveConst.phase: WaveData.phaseMin.value, + WaveConst.duty: WaveData.dutyMin.value, + }; + final maxValues = { + WaveConst.frequency: WaveData.freqMax.value, + WaveConst.phase: WaveData.phaseMax.value, + WaveConst.duty: WaveData.dutyMax.value, + }; @override Widget build(BuildContext context) { - return Column( - children: [ - Expanded( - flex: 75, - child: Container( - margin: const EdgeInsets.only(top: 4), - color: Colors.black, - child: Column( - children: [ - Expanded( - flex: 80, - child: IntrinsicHeight( - child: Row( - children: [ - Expanded( - flex: 20, - child: Container( - margin: const EdgeInsets.only(left: 8), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Image.asset( - iconSin, - height: 40, - width: 40, - ), - Text( - 'Sine', - style: TextStyle( - color: Colors.white, - fontSize: 13, - fontWeight: FontWeight.bold, - ), + labelMap = { + WaveConst.frequency: appLocalizations.frequency, + WaveConst.phase: appLocalizations.phaseOffset, + WaveConst.duty: appLocalizations.duty, + }; + unitMap = { + WaveConst.frequency: appLocalizations.unitHz, + WaveConst.phase: appLocalizations.unitDeg, + WaveConst.duty: appLocalizations.unitPercentage, + }; + return Consumer( + builder: (context, provider, _) { + return Column( + children: [ + Expanded( + flex: 75, + child: Container( + margin: const EdgeInsets.only(top: 4), + color: Colors.black, + child: Column( + children: [ + Expanded( + flex: 80, + child: IntrinsicHeight( + child: Row( + children: [ + Expanded( + flex: 20, + child: Container( + margin: const EdgeInsets.only(left: 8), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image.asset( + provider.waveGeneratorConstants + .modeSelected == + WaveConst.square + ? (provider.waveGeneratorConstants + .wave[ + provider + .selectedAnalogWave] + ?[WaveConst.waveType] == + WaveGeneratorStateProvider.sin + ? iconSin + : iconTriangular) + : iconPwm, + height: 40, + width: 40, + ), + Text( + provider.waveGeneratorConstants + .modeSelected == + WaveConst.square + ? (provider.waveGeneratorConstants + .wave[ + provider + .selectedAnalogWave] + ?[WaveConst.waveType] == + WaveGeneratorStateProvider.sin + ? appLocalizations.sine + : appLocalizations.tri) + : appLocalizations.pwm.toUpperCase(), + style: TextStyle( + color: Colors.white, + fontSize: 13, + fontWeight: FontWeight.bold, + ), + ), + ], ), - ], + ), ), - ), - ), - const VerticalDivider(), - Expanded( - flex: 80, - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Frequency:', - style: TextStyle( - color: Colors.white, - fontSize: 13, - fontWeight: FontWeight.bold, + const VerticalDivider(), + Expanded( + flex: 80, + child: Container( + margin: EdgeInsets.only( + right: 8, ), - ), - Text( - 'Phase:', - style: TextStyle( - color: Colors.white, - fontSize: 13, - fontWeight: FontWeight.bold, + child: Column( + mainAxisAlignment: + MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + provider.waveGeneratorConstants + .modeSelected == + WaveConst.square + ? '${appLocalizations.frequency}: ${provider.waveGeneratorConstants.wave[provider.selectedAnalogWave]?[WaveConst.frequency]} Hz' + : '${appLocalizations.frequency}: ${provider.waveGeneratorConstants.wave[WaveConst.sqr1]?[WaveConst.frequency]} Hz', + style: TextStyle( + color: Colors.white, + fontSize: 13, + fontWeight: FontWeight.bold, + ), + ), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + provider.waveGeneratorConstants + .modeSelected == + WaveConst.square + ? '${appLocalizations.phase}: ${provider.waveGeneratorConstants.wave[provider.selectedAnalogWave]?[WaveConst.phase] ?? '--'}°' + : '${appLocalizations.phase}: ${provider.waveGeneratorConstants.wave[provider.selectedDigitalWave]?[WaveConst.phase] ?? '--'}°', + style: TextStyle( + color: Colors.white, + fontSize: 13, + fontWeight: FontWeight.bold, + ), + ), + provider.waveGeneratorConstants + .modeSelected == + WaveConst.pwm + ? Text( + '${appLocalizations.duty}: ${provider.waveGeneratorConstants.wave[provider.selectedDigitalWave]?[WaveConst.duty]}%', + style: TextStyle( + color: Colors.white, + fontSize: 13, + fontWeight: FontWeight.bold, + ), + ) + : Container(), + ], + ), + ], ), ), - ], - ), + ), + ], ), - ], - ), - ), - ), - Transform.translate( - offset: const Offset(0, -8), - child: const Divider(), - ), - Expanded( - flex: 20, - child: Transform.translate( - offset: const Offset(0, -8), - child: Container( - margin: const EdgeInsets.only( - left: 32, ), - alignment: Alignment.centerLeft, - child: Text( - 'Phase Offset:', - style: TextStyle( - color: Colors.deepOrange, - fontSize: 16, + ), + Transform.translate( + offset: const Offset(0, -8), + child: const Divider(), + ), + Expanded( + flex: 20, + child: Transform.translate( + offset: const Offset(0, -8), + child: Container( + margin: const EdgeInsets.only( + left: 32, + ), + alignment: Alignment.centerLeft, + child: provider.propSelected != null + ? Text( + provider.waveGeneratorConstants + .modeSelected == + WaveConst.square + ? '${labelMap[provider.propSelected]}: ${provider.waveGeneratorConstants.wave[provider.selectedAnalogWave]?[provider.propSelected]}${unitMap[provider.propSelected]}' + : (provider.propSelected == + WaveConst.frequency + ? '${labelMap[provider.propSelected]}: ${provider.waveGeneratorConstants.wave[WaveConst.sqr1]?[provider.propSelected]}${unitMap[provider.propSelected]}' + : '${labelMap[provider.propSelected]}: ${provider.waveGeneratorConstants.wave[provider.selectedDigitalWave]?[provider.propSelected]}${unitMap[provider.propSelected]}'), + style: TextStyle( + color: waveGeneratorPropTextColor, + fontSize: 16, + ), + ) + : Container(), ), ), ), - ), + ], ), - ], - ), - ), - ), - Expanded( - flex: 25, - child: Container( - margin: const EdgeInsets.only( - bottom: 16, + ), ), - child: Row( + Row( children: [ SizedBox( height: 35, @@ -123,9 +210,15 @@ class _WaveGeneratorMainControlsState extends State { child: IconButton.filled( padding: EdgeInsets.zero, icon: Icon(Icons.chevron_left), - onPressed: () async {}, + onPressed: () async { + if (provider.propSelected != null) { + await provider.decrementValue(); + } + }, style: IconButton.styleFrom( - backgroundColor: primaryRed, + backgroundColor: provider.propSelected == null + ? buttonDisabledColor + : buttonEnabledColor, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(4), ), @@ -141,11 +234,39 @@ class _WaveGeneratorMainControlsState extends State { const RoundSliderThumbShape(enabledThumbRadius: 6), ), child: Slider( - activeColor: primaryRed, - min: 0, - max: 5000, - value: 0, - onChanged: (value) {}, + activeColor: provider.propSelected == null + ? buttonDisabledColor + : sliderActiveColor, + min: minValues[provider.propSelected]?.toDouble() ?? + WaveData.freqMin.value.toDouble(), + max: maxValues[provider.propSelected]?.toDouble() ?? + WaveData.freqMax.value.toDouble(), + value: provider.waveGeneratorConstants.modeSelected == + WaveConst.square + ? provider + .waveGeneratorConstants + .wave[provider.selectedAnalogWave]![ + provider.propSelected] + ?.toDouble() ?? + WaveData.freqMin.value.toDouble() + : (provider.propSelected == WaveConst.frequency + ? provider + .waveGeneratorConstants + .wave[WaveConst.sqr1]![ + provider.propSelected] + ?.toDouble() ?? + WaveData.freqMin.value.toDouble() + : provider + .waveGeneratorConstants + .wave[provider.selectedDigitalWave]![ + provider.propSelected] + ?.toDouble() ?? + WaveData.freqMin.value.toDouble()), + onChanged: (value) async { + if (provider.propSelected != null) { + await provider.setValue(value.round()); + } + }, ), ), ), @@ -155,9 +276,15 @@ class _WaveGeneratorMainControlsState extends State { child: IconButton.filled( padding: EdgeInsets.zero, icon: Icon(Icons.chevron_right), - onPressed: () async {}, + onPressed: () async { + if (provider.propSelected != null) { + await provider.incrementValue(); + } + }, style: IconButton.styleFrom( - backgroundColor: primaryRed, + backgroundColor: provider.propSelected == null + ? buttonDisabledColor + : buttonEnabledColor, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(4), ), @@ -166,9 +293,9 @@ class _WaveGeneratorMainControlsState extends State { ), ], ), - ), - ), - ], + ], + ); + }, ); } }