From 7a881cd52a6977d0b6ad8dc789642b1bbbcb9507 Mon Sep 17 00:00:00 2001 From: a2nr Date: Sun, 15 Sep 2024 18:06:05 +0700 Subject: [PATCH] update check point --- Software/ALL-H3-CC-V1.0A Headers.ods | Bin 0 -> 42794 bytes Software/dashboard/Makefile | 18 +- Software/dashboard/lib/hal/X9C10X.dart | 54 ++++ Software/dashboard/lib/hal/adc1256.dart | 167 ----------- Software/dashboard/lib/hal/ads1256.dart | 278 ++++++++++++++++++ .../dashboard/lib/hardware/heartbeatmice.dart | 33 ++- Software/dashboard/lib/main.dart | 27 +- Software/dashboard/lib/ui/plot.dart | 171 +++++++++++ Software/dashboard/pubspec.lock | 24 ++ Software/dashboard/pubspec.yaml | 2 + 10 files changed, 587 insertions(+), 187 deletions(-) create mode 100644 Software/ALL-H3-CC-V1.0A Headers.ods create mode 100644 Software/dashboard/lib/hal/X9C10X.dart delete mode 100644 Software/dashboard/lib/hal/adc1256.dart create mode 100644 Software/dashboard/lib/hal/ads1256.dart create mode 100644 Software/dashboard/lib/ui/plot.dart diff --git a/Software/ALL-H3-CC-V1.0A Headers.ods b/Software/ALL-H3-CC-V1.0A Headers.ods new file mode 100644 index 0000000000000000000000000000000000000000..369c90759432f2970ef796483dc94c642669231a GIT binary patch literal 42794 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.write(false); + void releaseChip()=>ss?.write(true); + + void wipeUp(){ + onSelect.call(); + ud.write(true); + const Duration(microseconds: 3); + inc.write(false); + const Duration(microseconds: 1); + inc.write(true); + onRelease.call(); + } + + void wipeDown(){ + onSelect.call(); + ud.write(false); + const Duration(microseconds: 3); + inc.write(false); + const Duration(microseconds: 1); + inc.write(true); + onRelease.call(); + } + +} diff --git a/Software/dashboard/lib/hal/adc1256.dart b/Software/dashboard/lib/hal/adc1256.dart deleted file mode 100644 index 71f21cd..0000000 --- a/Software/dashboard/lib/hal/adc1256.dart +++ /dev/null @@ -1,167 +0,0 @@ -import 'package:dart_periphery/dart_periphery.dart'; - -class Ads1256 { - static const int RADD_STATUS = 0x00; - static const int RADD_MUX = 0x01; - static const int RADD_ADCON = 0x02; - static const int RADD_DRATE = 0x03; - static const int RADD_IO = 0x04; - static const int RADD_OFC0 = 0x05; - static const int RADD_OFC1 = 0x06; - static const int RADD_OFC2 = 0x07; - static const int RADD_FSC0 = 0x08; - static const int RADD_FSC1 = 0x09; - static const int RADD_FSC2 = 0x0A; - - static const int CMD_WAKEUP = 0x00; - static const int CMD_RDATA = 0x01; - static const int CMD_RDATAC = 0x03; - static const int CMD_SDATAC = 0x0f; - static const int CMD_RREG = 0x10; - static const int CMD_WREG = 0x50; - static const int CMD_SELFCAL = 0xF0; - static const int CMD_SELFOCAL = 0xF1; - static const int CMD_SELFGCAL = 0xF2; - static const int CMD_SYSOCAL = 0xF3; - static const int CMD_SYSGCAL = 0xF4; - static const int CMD_SYNC = 0xFC; - static const int CMD_STANDBY = 0xFD; - static const int CMD_RESET = 0xFE; - - static const int MUXP_AIN0 = 0x00; - static const int MUXP_AIN1 = 0x10; - static const int MUXP_AIN2 = 0x20; - static const int MUXP_AIN3 = 0x30; - static const int MUXP_AIN4 = 0x40; - static const int MUXP_AIN5 = 0x50; - static const int MUXP_AIN6 = 0x60; - static const int MUXP_AIN7 = 0x70; - static const int MUXP_AINCOM = 0x80; - - static const int MUXN_AIN0 = 0x00; - static const int MUXN_AIN1 = 0x01; - static const int MUXN_AIN2 = 0x02; - static const int MUXN_AIN3 = 0x03; - static const int MUXN_AIN4 = 0x04; - static const int MUXN_AIN5 = 0x05; - static const int MUXN_AIN6 = 0x06; - static const int MUXN_AIN7 = 0x07; - static const int MUXN_AINCOM = 0x08; - - static const int GAIN_1 = 0x00; - static const int GAIN_2 = 0x01; - static const int GAIN_4 = 0x02; - static const int GAIN_8 = 0x03; - static const int GAIN_16 = 0x04; - static const int GAIN_32 = 0x05; - static const int GAIN_64 = 0x06; - - static const int DRATE_30000SPS = 0xF0; - static const int DRATE_15000SPS = 0xE0; - static const int DRATE_7500SPS = 0xD0; - static const int DRATE_3750SPS = 0xC0; - static const int DRATE_2000SPS = 0xB0; - static const int DRATE_1000SPS = 0xA1; - static const int DRATE_500SPS = 0x92; - static const int DRATE_100SPS = 0x82; - static const int DRATE_60SPS = 0x72; - static const int DRATE_50SPS = 0x63; - static const int DRATE_30SPS = 0x53; - static const int DRATE_25SPS = 0x43; - static const int DRATE_15SPS = 0x33; - static const int DRATE_10SPS = 0x23; - static const int DRATE_5SPS = 0x13; - static const int DRATE_2_5SPS = 0x03; - - final SPI _spi; //SPI - final GPIO _drdy; //GPIO - final GPIO? _cs; //GPIO - - Ads1256(this._spi, this._drdy, this._cs) {} - - void _sendCommand(int reg) { - _CSON(); - _waitDRDY(); - _spi.transfer([reg], false); - _CSON(); - } - - void _writeRegister(int reg, int wdata) { - _CSON(); - _spi.transfer([CMD_WREG, reg, 0, wdata], false); - _CSOFF(); - } - - int _readRegister(int reg) { - var val = _spi.transfer([CMD_RREG, 0, 0], true); - return val[2]; - } - - void _waitDRDY() { - while (_drdy.read()){} - } - - void _CSON() { - //PORT_CS &= ~(1 << PINDEX_CS); - _cs?.write(false); - } - - void _CSOFF() { - //PORT_CS |= (1 << PINDEX_CS); - _cs?.write(true); - } - - void begin(int drate, int gain, bool buffen) { - _sendCommand( - CMD_SDATAC); // send out ADS1256_CMD_SDATAC command to stop continous reading mode. - _writeRegister(RADD_DRATE, drate); // write data rate register - int bytemask = 7; - int adcon = _readRegister(RADD_ADCON); - int byte2send = (adcon & ~bytemask) | gain; - _writeRegister(RADD_ADCON, byte2send); - if (buffen) { - setBufferEnable(_readRegister(RADD_STATUS)); - } - _sendCommand(CMD_SELFCAL); // perform self calibration - _waitDRDY(); - } - - /* STATUS : STATUS REGISTER (ADDRESS 00h) - Reset Value = x1h - Bit 3 ORDER: Data Output Bit Order - 0 = Most Significant Bit First (default) - 1 = Least Significant Bit First - Bit 2 ACAL: Auto-Calibration - 0 = Auto-Calibration Disabled (default) - 1 = Auto-Calibration Enabled - Bit 1 BUFEN: Analog Input Buffer Enable - 0 = Buffer Disabled (default) - 1 = Buffer Enabled - Bit 0 DRDY: Data Ready (Read Only) - */ - int getStatus() { - _sendCommand(CMD_SDATAC); - return _readRegister(RADD_STATUS); - } - - void setBufferEnable(int status) { - _writeRegister(RADD_STATUS, (status | (0x01 << 1))); - } - - void resetBufferEnable(int status) { - _writeRegister(RADD_STATUS, (status & ~(0x01 << 1))); - } - - void readTest() { - _CSON(); - var _hml = - _spi.transfer([CMD_RDATA, CMD_WAKEUP, CMD_WAKEUP, CMD_WAKEUP], false); - print(_hml); - _CSOFF(); - } - void dispose(){ - _spi.dispose(); //SPI - _drdy.dispose(); //GPIO - _cs?.dispose(); //GPIO - } -} diff --git a/Software/dashboard/lib/hal/ads1256.dart b/Software/dashboard/lib/hal/ads1256.dart new file mode 100644 index 0000000..d92a0cf --- /dev/null +++ b/Software/dashboard/lib/hal/ads1256.dart @@ -0,0 +1,278 @@ +import 'package:dart_periphery/dart_periphery.dart'; +import 'package:logging/logging.dart'; +import 'dart:io'; + +extension Ads1265Int on int { + double toMicroVolt(){ + int newThis = this; + if ((newThis >> 23) == 1) { + newThis = this - 16777216; + } + // 5000000.0 microVolt = Vref; 8388608 = 2^{23} -1 + // (5000000.0 / 8388608) = 0.5960464477539062; + return 0.5960464477539062 * newThis; + } + double toMilliVolt() => toMicroVolt() / 1000.0; + double toVolt() => toMicroVolt() / 1000000.0; +} +class Ads1256 { + late SPI spi; //SPI + late GPIO drdy; //GPIO + late GPIO? rst; + late GPIO? cs; //GPIO + late Logger log; + + Ads1256({ + required int spiBus, + required int spiChip, + required int gpioChip, + required int pinDrdy, + String? tag, + int? pinReset, + int? pinCS }){ + spi = SPI(spiBus, spiChip, SPImode.mode1, 50000); + drdy = GPIO(pinDrdy, GPIOdirection.gpioDirIn, gpioChip); + cs = pinCS != null ? GPIO(pinCS, GPIOdirection.gpioDirOutHigh, gpioChip): null; + rst = pinReset != null ? GPIO(pinReset, GPIOdirection.gpioDirOutHigh, gpioChip): null; + log = Logger("[Ads1256][$tag]"); + } + + void skipClk()=>sleep(const Duration(microseconds: 1 )); + + void writeRegister(int reg, int wdata) { + log.info(' writeRegistere() <${reg.toRadixString(16)}> <${wdata.toRadixString(8)}>'); + waitDRDY(); + spi.transfer([CMD_WREG | reg, 0, wdata], false); + skipClk(); + } + + int readRegister(int reg) { + waitDRDY(); + spi.transfer([CMD_RREG | reg], true); + skipClk(); + List val= spi.transfer([CMD_WAKEUP, CMD_WAKEUP], true); + log.info(' readRegister() <${reg.toRadixString(16)}>'); + log.info(' > ${val[1].toRadixString(16)}'); + skipClk(); + return val[1]; + } + + void sendCommand(int cmd){ + log.info(' sendCommand() <${cmd.toRadixString(16)}>'); + csOn(); + waitDRDY(); + spi.transfer([cmd], false); + csOff(); + } + int get offsetCalibration { + csOn(); + int x = readRegister(RADD_OFC0) | readRegister(RADD_OFC1) <<8 | readRegister(RADD_OFC2) <<16; + csOff(); + return x; + } + int get fullscaleCalibration { + csOn(); + int x = readRegister(RADD_FSC0) | readRegister(RADD_FSC1) <<8 | readRegister(RADD_FSC2) <<16; + csOff(); + return x; + } + void calibrateOffsetGain()=>sendCommand(CMD_SELFCAL); + void calibrateOffset()=>sendCommand(CMD_SELFOCAL); + void calibrateGain()=>sendCommand(CMD_SELFGCAL); + void calibrateSystemOffset()=>sendCommand(CMD_SYSOCAL); + void calibrateSystemGain()=>sendCommand(CMD_SYSGCAL); + void stopContinueRead()=>sendCommand(CMD_SDATAC); + void resetCommand()=>sendCommand(CMD_RESET); + + void doReset(){ + waitDRDY(); + rst?.write(false); + skipClk(); + skipClk(); + skipClk(); + rst?.write(true); + } + + void waitDRDY() { + while (drdy.read()){} + } + + void csOn(){ + cs?.write(false); + log.info("============== Start transfer"); + } + void csOff(){ + log.info("============== End transfer"); + cs?.write(true); + } + + void begin(int drate, int gain, bool buffen) { + log.info("begin() : <${drate.toRadixString(16)}> <${gain.toRadixString(8)}> <$buffen>"); + doReset(); + csOn(); + resetCommand(); + syncIt(); + buffer = buffen; + autoCalibration = true; + clockOutRate = CLKOUT_1; + programmableGain = gain; + dataRate = drate; + log.info(" status : ${status.toRadixString(16)}"); + log.info(" adcon : ${controlRegister.toRadixString(16)}"); + log.info(" drate : ${dataRate.toRadixString(16)}"); + csOff(); + calibrateOffsetGain(); + } + + void syncIt(){ + + spi.transfer([CMD_SYNC], false); + skipClk(); + skipClk(); + skipClk(); + spi.transfer([CMD_WAKEUP], false); + skipClk(); + } + + int get status => readRegister(RADD_STATUS); + + set buffer(bool isEnable){ + isEnable ? writeRegister(RADD_STATUS, (status | (0x01 << 1))): + writeRegister(RADD_STATUS, (status & ~(0x01 << 1))); + } + set autoCalibration(bool isEnable){ + isEnable ? writeRegister(RADD_STATUS, (status | (0x01 << 2))): + writeRegister(RADD_STATUS, (status & ~(0x01 << 2))); + } + + int get controlRegister => readRegister(RADD_ADCON); + set clockOutRate(int clk)=>writeRegister(RADD_ADCON, controlRegister | (clk << 5 )); + set programmableGain(int gain)=>writeRegister(RADD_ADCON, controlRegister | gain); + + + int get dataRate =>readRegister(RADD_DRATE); + set dataRate(int rate) => writeRegister(RADD_DRATE, rate); + + set ioDir(List dir){ + csOn(); + int d = dir[0] | dir[1]<<1 | dir[2]<<2 | dir[3]<<3; + writeRegister(RADD_IO, d <<4); + csOff(); + } + List get dIO { + csOn(); + int dio = readRegister(RADD_IO); + csOff(); + return [(dio & 0x01)==0x01?true:false, (dio &0x02)==0x02?true:false, (dio &0x04)==0x04?true:false, (dio &0x08)==0x08?true:false]; + } + set dIO(List io){ + csOn(); + writeRegister(RADD_IO, (io[0]?0x01:0x00) | (io[1]?0x02:0x00) | (io[2]?0x04:0x00) | (io[3]?0x08:0x00) ); + csOff(); + } + + set channel(int c) => writeRegister(RADD_MUX, c<<4); + + int analogRead(int channel){ + log.info(" analogRead() <${channel.toRadixString(16)}>"); + csOn(); + waitDRDY(); + this.channel = channel; + waitDRDY(); + spi.transfer([CMD_RDATA], false); + skipClk(); + List d = spi.transfer([0, 0, 0], true); + csOff(); + int v = d[0] << 16 | d[1] << 8 | d[2]; + log.info(" > $v"); + return v; + } + + void dispose(){ + spi.dispose(); //SPI + drdy.dispose(); //GPIO + cs?.dispose(); //GPIO + rst?.dispose(); //GPIO + } + + static const int RADD_STATUS = 0x00; + static const int RADD_MUX = 0x01; + static const int RADD_ADCON = 0x02; + static const int RADD_DRATE = 0x03; + static const int RADD_IO = 0x04; + static const int RADD_OFC0 = 0x05; + static const int RADD_OFC1 = 0x06; + static const int RADD_OFC2 = 0x07; + static const int RADD_FSC0 = 0x08; + static const int RADD_FSC1 = 0x09; + static const int RADD_FSC2 = 0x0A; + + static const int CMD_WAKEUP = 0x00; + static const int CMD_RDATA = 0x01; + static const int CMD_RDATAC = 0x03; + static const int CMD_SDATAC = 0x0f; + static const int CMD_RREG = 0x10; + static const int CMD_WREG = 0x50; + static const int CMD_SELFCAL = 0xF0; + static const int CMD_SELFOCAL = 0xF1; + static const int CMD_SELFGCAL = 0xF2; + static const int CMD_SYSOCAL = 0xF3; + static const int CMD_SYSGCAL = 0xF4; + static const int CMD_SYNC = 0xFC; + static const int CMD_STANDBY = 0xFD; + static const int CMD_RESET = 0xFE; + + static const int MUXP_AIN0 = 0x00; + static const int MUXP_AIN1 = 0x10; + static const int MUXP_AIN2 = 0x20; + static const int MUXP_AIN3 = 0x30; + static const int MUXP_AIN4 = 0x40; + static const int MUXP_AIN5 = 0x50; + static const int MUXP_AIN6 = 0x60; + static const int MUXP_AIN7 = 0x70; + static const int MUXP_AINCOM = 0x80; + + static const int MUXN_AIN0 = 0x00; + static const int MUXN_AIN1 = 0x01; + static const int MUXN_AIN2 = 0x02; + static const int MUXN_AIN3 = 0x03; + static const int MUXN_AIN4 = 0x04; + static const int MUXN_AIN5 = 0x05; + static const int MUXN_AIN6 = 0x06; + static const int MUXN_AIN7 = 0x07; + static const int MUXN_AINCOM = 0x08; + + static const int GAIN_1 = 0x00; + static const int GAIN_2 = 0x01; + static const int GAIN_4 = 0x02; + static const int GAIN_8 = 0x03; + static const int GAIN_16 = 0x04; + static const int GAIN_32 = 0x05; + static const int GAIN_64 = 0x06; + + static const int DRATE_30000SPS = 0xF0; + static const int DRATE_15000SPS = 0xE0; + static const int DRATE_7500SPS = 0xD0; + static const int DRATE_3750SPS = 0xC0; + static const int DRATE_2000SPS = 0xB0; + static const int DRATE_1000SPS = 0xA1; + static const int DRATE_500SPS = 0x92; + static const int DRATE_100SPS = 0x82; + static const int DRATE_60SPS = 0x72; + static const int DRATE_50SPS = 0x63; + static const int DRATE_30SPS = 0x53; + static const int DRATE_25SPS = 0x43; + static const int DRATE_15SPS = 0x33; + static const int DRATE_10SPS = 0x23; + static const int DRATE_5SPS = 0x13; + static const int DRATE_2_5SPS = 0x03; + + static const int CLKOUT_OFF = 0x00; + static const int CLKOUT_1 = 0x20; + static const int CLKOUT_HALF = 0x40; + static const int CLKOUT_QUARTER = 0x60; + + static const int IO_DIR_OUT = 0x00; + static const int IO_DIR_IN = 0x10; + +} diff --git a/Software/dashboard/lib/hardware/heartbeatmice.dart b/Software/dashboard/lib/hardware/heartbeatmice.dart index cff6cc7..45e6348 100644 --- a/Software/dashboard/lib/hardware/heartbeatmice.dart +++ b/Software/dashboard/lib/hardware/heartbeatmice.dart @@ -1,13 +1,30 @@ -import '../hal/adc1256.dart'; -import 'package:dart_periphery/dart_periphery.dart'; +import 'package:dashboard/hal/X9C10X.dart'; + +import '../hal/ads1256.dart'; class HeartBeatMice { - Ads1256 adc = Ads1256( - SPI(1, 0, SPImode.mode1, 50000), - GPIO(2, GPIOdirection.gpioDirIn, 1), - null - ); + Ads1256 adc1 = Ads1256(tag: "adc1", spiBus: 1, spiChip: 0, gpioChip: 1, pinDrdy: 202, pinCS: 13, pinReset: 20); + Ads1256 adc2 = Ads1256(tag: "adc2", spiBus: 1, spiChip: 0, gpioChip: 1, pinDrdy: 6, pinCS: 21); + X9c10x pot = X9c10x(ohm: 104000, gpioChip: 1, pinUd: 204, pinInc: 205); + + void potSelect(){ + + } + void potRelease(){ + + } + void init(){ + adc1.begin(Ads1256.DRATE_500SPS, Ads1256.GAIN_1, false); + adc2.begin(Ads1256.DRATE_500SPS, Ads1256.GAIN_1, false); + adc1.ioDir = [Ads1256.IO_DIR_OUT, Ads1256.IO_DIR_OUT, Ads1256.IO_DIR_OUT, Ads1256.IO_DIR_OUT]; + adc2.ioDir = [Ads1256.IO_DIR_OUT, Ads1256.IO_DIR_OUT, Ads1256.IO_DIR_OUT, Ads1256.IO_DIR_OUT]; + print('adc = ${adc1.analogRead(0).toVolt()}'); + } + void test(){ + print('adc = ${adc1.analogRead(0).toVolt()}'); + } void dispose(){ - adc.dispose(); + adc1.dispose(); + adc2.dispose(); } } diff --git a/Software/dashboard/lib/main.dart b/Software/dashboard/lib/main.dart index cc44ec4..843b12b 100644 --- a/Software/dashboard/lib/main.dart +++ b/Software/dashboard/lib/main.dart @@ -1,8 +1,15 @@ import 'package:dashboard/hardware/heartbeatmice.dart'; import 'package:flutter/material.dart'; import 'util/mouse_cursor.dart'; +import 'package:logging/logging.dart'; +import 'ui/plot.dart'; void main() { + Logger.root.level = Level.ALL; + Logger.root.onRecord.listen((record){ + + print('[${record.level.name}:${record.loggerName}][${record.time.hour}:${record.time.minute}:${record.time.second}] ${record.message}'); + }); runApp(const MyApp()); } @@ -12,16 +19,16 @@ class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { - return SoftwareMouseCursor( - child : MaterialApp( + //return SoftwareMouseCursor( + /*child :*/ return MaterialApp( title: 'Heart Beat Mice', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), - home: const MyHomePage(title: 'Heart Beat Mice'), - ) - ); + home: const MyHomePage(title: 'Heart Beat Mice Coba'), + ); + //); } } @@ -41,6 +48,7 @@ class _MyHomePageState extends State { @override void initState(){ super.initState(); + drive.init(); } @override @@ -53,6 +61,7 @@ class _MyHomePageState extends State { setState(() { _counter++; }); + drive.test(); } @override @@ -63,9 +72,8 @@ class _MyHomePageState extends State { title: Text(widget.title), ), body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ + child: Column ( + children: [ const Text( 'You have pushed the button this many times:', ), @@ -73,8 +81,9 @@ class _MyHomePageState extends State { '$_counter', style: Theme.of(context).textTheme.headlineMedium, ), + Container ( width: 350, child : const LineChartSample10()) ], - ), + ) ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, diff --git a/Software/dashboard/lib/ui/plot.dart b/Software/dashboard/lib/ui/plot.dart new file mode 100644 index 0000000..0f0c0a4 --- /dev/null +++ b/Software/dashboard/lib/ui/plot.dart @@ -0,0 +1,171 @@ +import 'dart:async'; +import 'dart:math' as math; + +import 'package:fl_chart/fl_chart.dart'; +//import 'package:fl_chart_app/presentation/resources/app_resources.dart'; +import 'package:flutter/material.dart'; + +class AppColors { + static const Color primary = contentColorCyan; + static const Color menuBackground = Color(0xFF090912); + static const Color itemsBackground = Color(0xFF1B2339); + static const Color pageBackground = Color(0xFF282E45); + static const Color mainTextColor1 = Colors.white; + static const Color mainTextColor2 = Colors.white70; + static const Color mainTextColor3 = Colors.white38; + static const Color mainGridLineColor = Colors.white10; + static const Color borderColor = Colors.white54; + static const Color gridLinesColor = Color(0x11FFFFFF); + + static const Color contentColorBlack = Colors.black; + static const Color contentColorWhite = Colors.white; + static const Color contentColorBlue = Color(0xFF2196F3); + static const Color contentColorYellow = Color(0xFFFFC300); + static const Color contentColorOrange = Color(0xFFFF683B); + static const Color contentColorGreen = Color(0xFF3BFF49); + static const Color contentColorPurple = Color(0xFF6E1BFF); + static const Color contentColorPink = Color(0xFFFF3AF2); + static const Color contentColorRed = Color(0xFFE80054); + static const Color contentColorCyan = Color(0xFF50E4FF); +} + +class LineChartSample10 extends StatefulWidget { + const LineChartSample10({super.key}); + + final Color sinColor = AppColors.contentColorBlue; + final Color cosColor = AppColors.contentColorPink; + + @override + State createState() => _LineChartSample10State(); +} + +class _LineChartSample10State extends State { + final limitCount = 100; + final sinPoints = []; + final cosPoints = []; + + double xValue = 0; + double step = 0.05; + + late Timer timer; + + @override + void initState() { + super.initState(); + timer = Timer.periodic(const Duration(milliseconds: 40), (timer) { + while (sinPoints.length > limitCount) { + sinPoints.removeAt(0); + cosPoints.removeAt(0); + } + setState(() { + sinPoints.add(FlSpot(xValue, math.sin(xValue))); + cosPoints.add(FlSpot(xValue, math.cos(xValue))); + }); + xValue += step; + }); + } + + @override + Widget build(BuildContext context) { + return cosPoints.isNotEmpty + ? Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(height: 12), + Text( + 'x: ${xValue.toStringAsFixed(1)}', + style: const TextStyle( + color: AppColors.mainTextColor2, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + Text( + 'sin: ${sinPoints.last.y.toStringAsFixed(1)}', + style: TextStyle( + color: widget.sinColor, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + Text( + 'cos: ${cosPoints.last.y.toStringAsFixed(1)}', + style: TextStyle( + color: widget.cosColor, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox( + height: 12, + ), + AspectRatio( + aspectRatio: 1.5, + child: Padding( + padding: const EdgeInsets.only(bottom: 24.0), + child: LineChart( + LineChartData( + minY: -1, + maxY: 1, + minX: sinPoints.first.x, + maxX: sinPoints.last.x, + lineTouchData: const LineTouchData(enabled: false), + clipData: const FlClipData.all(), + gridData: const FlGridData( + show: true, + drawVerticalLine: false, + ), + borderData: FlBorderData(show: false), + lineBarsData: [ + sinLine(sinPoints), + cosLine(cosPoints), + ], + titlesData: const FlTitlesData( + show: false, + ), + ), + ), + ), + ) + ], + ) + : Container(); + } + + LineChartBarData sinLine(List points) { + return LineChartBarData( + spots: points, + dotData: const FlDotData( + show: false, + ), + gradient: LinearGradient( + colors: [widget.sinColor.withOpacity(0), widget.sinColor], + stops: const [0.1, 1.0], + ), + barWidth: 4, + isCurved: false, + ); + } + + LineChartBarData cosLine(List points) { + return LineChartBarData( + spots: points, + dotData: const FlDotData( + show: false, + ), + gradient: LinearGradient( + colors: [widget.cosColor.withOpacity(0), widget.cosColor], + stops: const [0.1, 1.0], + ), + barWidth: 4, + isCurved: false, + ); + } + + @override + void dispose() { + timer.cancel(); + super.dispose(); + } +} + diff --git a/Software/dashboard/pubspec.lock b/Software/dashboard/pubspec.lock index 75d7ab4..799b261 100644 --- a/Software/dashboard/pubspec.lock +++ b/Software/dashboard/pubspec.lock @@ -57,6 +57,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.9.6" + equatable: + dependency: transitive + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" fake_async: dependency: transitive description: @@ -81,6 +89,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" + fl_chart: + dependency: "direct main" + description: + name: fl_chart + sha256: "94307bef3a324a0d329d3ab77b2f0c6e5ed739185ffc029ed28c0f9b019ea7ef" + url: "https://pub.dev" + source: hosted + version: "0.69.0" flutter: dependency: "direct main" description: flutter @@ -131,6 +147,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.0" + logging: + dependency: "direct main" + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.dev" + source: hosted + version: "1.2.0" matcher: dependency: transitive description: diff --git a/Software/dashboard/pubspec.yaml b/Software/dashboard/pubspec.yaml index 2217de6..829e2f7 100644 --- a/Software/dashboard/pubspec.yaml +++ b/Software/dashboard/pubspec.yaml @@ -31,6 +31,8 @@ dependencies: flutter: sdk: flutter dart_periphery: ^0.9.6 + logging: ^1.2.0 + fl_chart: ^0.69.0 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons.