├── .env
├── .eslintrc
├── .github
├── ISSUE_TEMPLATE.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ └── build.yml
├── .gitignore
├── .prettierrc
├── .storybook
├── main.js
└── preview.js
├── .travis.yml
├── LICENSE
├── README.md
├── background_tasks
├── linker.html
└── playback.html
├── docs
├── images
│ ├── 2019_09_01_20_59_07.jpg
│ ├── about_us.jpg
│ ├── data_logger.jpg
│ ├── device_screen.jpg
│ ├── drawer.jpg
│ ├── faq.jpg
│ ├── home_screen.png
│ ├── logic_analyzer.jpg
│ ├── multimeter.jpg
│ ├── oscilloscope.jpg
│ ├── power_source.jpg
│ ├── project_banner.jpg
│ ├── robot.jpg
│ ├── settings.jpg
│ └── wave_generator.jpg
└── screenshots-and-demos.md
├── package-lock.json
├── package.json
├── public
├── app_icon.png
├── electron.js
├── favicon.ico
├── index.html
└── manifest.json
├── scripts
├── analytics.py
├── bridge.py
├── device_detection.py
├── file_write.py
├── logic_analyser.py
├── multimeter.py
├── oscilloscope.py
├── playback_bridge.py
├── playback_la.py
├── playback_robot.py
├── power_source.py
├── robotic_arm.py
├── sensors.py
└── wave_generator.py
├── src
├── App.js
├── App.test.js
├── components
│ ├── Appshell
│ │ ├── Appshell.js
│ │ ├── Appshell.styles.js
│ │ └── index.js
│ ├── CustomCircularInput
│ │ ├── CustomCircularInput.js
│ │ └── index.js
│ ├── CustomDialog
│ │ ├── CustomDialog.js
│ │ ├── CustomDialog.stories.js
│ │ └── index.js
│ ├── CustomIcons
│ │ ├── CapacitorIcon.js
│ │ └── ResistorIcon.js
│ ├── CustomKnob
│ │ ├── CustomKnob.js
│ │ ├── index.js
│ │ └── skins
│ │ │ └── s3.js
│ ├── CustomSliderInput
│ │ ├── CustomSliderInput.js
│ │ └── index.js
│ ├── Decorators
│ │ └── Decorators.styles.js
│ ├── Display
│ │ ├── Display.js
│ │ ├── Display.styles.js
│ │ └── index.js
│ ├── GraphPanelLayout
│ │ ├── GraphPanelLayout.js
│ │ ├── GraphPanelLayout.styles.js
│ │ └── index.js
│ ├── HomeLayout
│ │ ├── HomeLayout.js
│ │ ├── HomeLayout.styles.js
│ │ └── index.js
│ ├── Icons
│ │ └── PSLabIcons.js
│ └── SimplePanelLayout
│ │ ├── SimplePanelLayout.js
│ │ ├── SimplePanelLayout.styles.js
│ │ └── index.js
├── index.css
├── index.js
├── redux
│ ├── actionTypes
│ │ └── app.js
│ ├── actions
│ │ ├── app.js
│ │ └── config.js
│ ├── reducers
│ │ ├── app.js
│ │ └── config.js
│ └── store.js
├── resources
│ ├── accelerometer.svg
│ ├── accelerometer_red.svg
│ ├── app_icon.png
│ ├── back_layout.png
│ ├── barometer.svg
│ ├── barometer_red.svg
│ ├── compass.svg
│ ├── compass_red.svg
│ ├── device_connected.svg
│ ├── device_disconnected.svg
│ ├── ds_con.png
│ ├── ds_discon.png
│ ├── fb.png
│ ├── front_layout.png
│ ├── git.png
│ ├── ic_pwm_pic.png
│ ├── ic_sin.png
│ ├── ic_square.png
│ ├── ic_triangular.png
│ ├── logic_analyzer.svg
│ ├── logic_analyzer_red.svg
│ ├── lux_meter.svg
│ ├── lux_meter_red.svg
│ ├── multimeter.svg
│ ├── multimeter_red.svg
│ ├── oscilloscope.svg
│ ├── oscilloscope_red.svg
│ ├── power_source.svg
│ ├── power_source_red.svg
│ ├── sensors.svg
│ ├── sensors_red.svg
│ ├── t.png
│ ├── wave_generator.svg
│ ├── wave_generator_red.svg
│ └── yt.png
├── screen
│ ├── AboutUs
│ │ ├── AboutUs.js
│ │ ├── AboutUs.styles.js
│ │ └── index.js
│ ├── DeviceScreen
│ │ ├── DeviceScreen.js
│ │ ├── DeviceScreen.styles.js
│ │ └── index.js
│ ├── FAQ
│ │ ├── FAQ.js
│ │ ├── FAQ.styles.js
│ │ └── index.js
│ ├── Home
│ │ ├── Home.js
│ │ ├── components
│ │ │ ├── InstrumentCard.js
│ │ │ ├── InstrumentCard.styles.js
│ │ │ ├── Tabs.js
│ │ │ ├── Tabs.styles.js
│ │ │ ├── Title.js
│ │ │ └── Title.styles.js
│ │ └── index.js
│ ├── Layout
│ │ ├── BackLayout.js
│ │ ├── FrontLayout.js
│ │ └── styles.js
│ ├── LoggedData
│ │ ├── LoggedData.js
│ │ ├── LoggedData.styles.js
│ │ └── index.js
│ ├── LogicAnalyzer
│ │ ├── LogicAnalyzer.js
│ │ ├── components
│ │ │ ├── ActionButtons.js
│ │ │ ├── ActionButtons.styles.js
│ │ │ ├── AnalysisParameters.js
│ │ │ ├── ChannelParameters.js
│ │ │ ├── Graph.js
│ │ │ ├── NumberParameter.js
│ │ │ ├── Settings.js
│ │ │ ├── Settings.styles.js
│ │ │ ├── TimeParameters.js
│ │ │ └── settingOptions.js
│ │ └── index.js
│ ├── Multimeter
│ │ ├── Multimeter.js
│ │ ├── components
│ │ │ ├── Dial.js
│ │ │ ├── Dial.styles.js
│ │ │ ├── InstrumentCluster.js
│ │ │ ├── InstrumentCluster.styles.js
│ │ │ ├── MeasurementDisplay.js
│ │ │ └── SettingOptions.js
│ │ └── index.js
│ ├── Oscilloscope
│ │ ├── Oscilloscope.js
│ │ ├── components
│ │ │ ├── ActionButtons.js
│ │ │ ├── ActionButtons.styles.js
│ │ │ ├── AnalysisParameters.js
│ │ │ ├── ChannelParameters.js
│ │ │ ├── FFTGraph.js
│ │ │ ├── FitPanel.js
│ │ │ ├── FitPanel.styles.js
│ │ │ ├── Graph.js
│ │ │ ├── PlotParameters.js
│ │ │ ├── Settings.js
│ │ │ ├── Settings.styles.js
│ │ │ ├── TimeParameters.js
│ │ │ ├── XYPlotGraph.js
│ │ │ └── settingOptions.js
│ │ └── index.js
│ ├── PowerSource
│ │ ├── PowerSource.js
│ │ ├── components
│ │ │ ├── InstrumentCluster.js
│ │ │ └── InstrumentCluster.styles.js
│ │ └── index.js
│ ├── RobotArm
│ │ ├── Components
│ │ │ ├── KnobControl.js
│ │ │ ├── PaintArea.js
│ │ │ └── styles.js
│ │ ├── RobotArm.js
│ │ ├── RobotArm.styles.js
│ │ └── index.js
│ ├── Sensors
│ │ ├── Sensors.js
│ │ ├── index.js
│ │ └── styles.js
│ ├── Settings
│ │ ├── Settings.js
│ │ ├── Settings.styles.js
│ │ └── index.js
│ └── WaveGenerator
│ │ ├── WaveGenerator.js
│ │ ├── components
│ │ ├── AnalogController.js
│ │ ├── DigitalController.js
│ │ ├── InstrumentCluster.js
│ │ ├── Settings.styles.js
│ │ ├── SineWaveParameters.js
│ │ ├── SquareWaveParameters.js
│ │ └── settingOptions.js
│ │ ├── index.js
│ │ └── styles.js
├── serviceWorker.js
├── theme.js
└── utils
│ ├── arithmetics.js
│ ├── fileNameProcessor.js
│ └── formStyles.js
└── utils
└── preProcessor.js
/.env:
--------------------------------------------------------------------------------
1 | SKIP_PREFLIGHT_CHECK=true
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | * **I'm submitting a ...**
2 | - [ ] bug report
3 | - [ ] feature request
4 |
5 | * **What is the current status?**
6 |
7 |
8 |
9 | * **If the current behavior is a bug, please provide the steps to reproduce it**
10 | > A screenshot of the issue may be sufficient for UI bugs
11 |
12 |
13 |
14 | * **What is the expected behavior?**
15 |
16 |
17 |
18 | * **Please tell us about your environment:**
19 | - OS with version number: [ Windows / OSX / Linux Distro ]
20 |
21 |
22 | * **Other information** ( Any research that you may have done which you think is the cause of problem )
23 |
24 |
25 | * **Would you like to work on it?**
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | * **Please check if the PR fulfills these requirements**
2 | - [ ] The commit message follows our guidelines
3 | - [ ] Tests for the changes have been added (for bug fixes / features)
4 | - [ ] Docs have been added / updated (for bug fixes / features)
5 |
6 |
7 | * **What kind of change does this PR introduce?** (Bug fix, feature, docs update, ...)
8 | - [ ] Bug fix
9 | - [ ] Feature implementation
10 | - [ ] Doc updates
11 |
12 |
13 | * **What changes have you introduced?**
14 |
15 |
16 |
17 | * **Does this PR introduce a breaking change?**
18 |
19 |
20 |
21 | * **Preview / Steps to verify your work**:
22 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build/release
2 |
3 | on: push
4 |
5 | jobs:
6 | release:
7 | runs-on: ${{ matrix.os }}
8 |
9 | strategy:
10 | matrix:
11 | os: [macos-latest, ubuntu-latest, windows-latest]
12 |
13 | steps:
14 | - name: Check out Git repository
15 | uses: actions/checkout@v1
16 |
17 | - name: Install Node.js, NPM and Yarn
18 | uses: actions/setup-node@v1
19 | with:
20 | node-version: 14
21 |
22 | - name: Build/release Electron app
23 | uses: samuelmeuli/action-electron-builder@v1
24 | with:
25 | # GitHub token, automatically provided to the action
26 | # (No need to define this secret in the repo settings)
27 | github_token: ${{ github.token }}
28 |
29 | # If the commit is tagged with a version (e.g. "v1.0.0"),
30 | # release the app after building
31 | release: ${{ startsWith(github.ref, 'refs/tags/v') }}
32 | args: '-c.extraMetadata.main=build/electron.js'
33 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 | *.pyc
3 | __pycache__
4 | PSL_Apps.egg-info
5 | build
6 | ui_*.py
7 | /venv
8 |
9 | #-------------------- Electron -----------------------------
10 | .DS_Store
11 | # See https://help.github.com/ignore-files/ for more about ignoring files.
12 |
13 | # dependencies
14 | /node_modules
15 |
16 | # testing
17 | /coverage
18 | /jest_0
19 |
20 | # production
21 | /build
22 | /dist
23 |
24 | # misc
25 | .DS_Store
26 | .env.local
27 | .env.development.local
28 | .env.test.local
29 | .env.production.local
30 |
31 | npm-debug.log*
32 | yarn-debug.log*
33 | yarn-error.log*
34 | /.idea
35 | yarn.lock
36 | .vscode/
37 |
38 | # Deploy keys should never be committed
39 | deploy_key
40 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all",
4 | "parser": "flow"
5 | }
6 |
--------------------------------------------------------------------------------
/.storybook/main.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "stories": [
3 | "../src/**/*.stories.mdx",
4 | "../src/**/*.stories.@(js|jsx|ts|tsx)"
5 | ],
6 | "addons": [
7 | "@storybook/addon-links",
8 | "@storybook/addon-actions",
9 | "@storybook/addon-essentials",
10 | "@storybook/preset-create-react-app"
11 | ]
12 | }
--------------------------------------------------------------------------------
/.storybook/preview.js:
--------------------------------------------------------------------------------
1 |
2 | export const parameters = {
3 | actions: { argTypesRegex: "^on[A-Z].*" },
4 | }
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | node_js:
4 | - node
5 |
6 | matrix:
7 | include:
8 | - name: "linux"
9 | os: linux
10 | script:
11 | - sudo apt-get install rpm -y
12 | - npm run build-linux
13 | - ls -al dist/
14 | - name: "windows"
15 | os: linux
16 | services: docker
17 | script:
18 | - >
19 | docker run --rm -e GH_TOKEN=$GITHUB_TOKEN
20 | -v $PWD:/project
21 | -v ~/.cache/electron:/root/.cache/electron
22 | -v ~/.cache/electron-builder:/root/.cache/electron-builder
23 | electronuserland/builder:wine /bin/bash -c "npm run build-windows"
24 | - ls -al dist/
25 |
26 | cache:
27 | npm: false
28 |
29 | before_script:
30 | - export CI=false
31 |
32 | script:
33 | - npm run lint
34 | - npm run fmt:check
35 |
36 | deploy:
37 | provider: releases
38 | api_key: "$GITHUB_TOKEN"
39 | # see .build.*.target in package.json
40 | # TODO: Be more specific? Files are named e.g. dist/pslab_2.1.0_amd64.deb
41 | file:
42 | - "dist/*.deb"
43 | - "dist/*.rpm"
44 | - "dist/*.tar.xz"
45 | - "dist/*.exe"
46 | skip_cleanup: true
47 | draft: true # TODO: remove when it works
48 | on:
49 | tags: true
50 | # all_branches: true
51 | # condition: $TRAVIS_BRANCH =~ ^(master|development)$
52 |
--------------------------------------------------------------------------------
/background_tasks/linker.html:
--------------------------------------------------------------------------------
1 |
98 |
--------------------------------------------------------------------------------
/background_tasks/playback.html:
--------------------------------------------------------------------------------
1 |
52 |
--------------------------------------------------------------------------------
/docs/images/2019_09_01_20_59_07.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/pslab-desktop/5f24d307cfae32c2d86f6fc222528cd26713b2a4/docs/images/2019_09_01_20_59_07.jpg
--------------------------------------------------------------------------------
/docs/images/about_us.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/pslab-desktop/5f24d307cfae32c2d86f6fc222528cd26713b2a4/docs/images/about_us.jpg
--------------------------------------------------------------------------------
/docs/images/data_logger.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/pslab-desktop/5f24d307cfae32c2d86f6fc222528cd26713b2a4/docs/images/data_logger.jpg
--------------------------------------------------------------------------------
/docs/images/device_screen.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/pslab-desktop/5f24d307cfae32c2d86f6fc222528cd26713b2a4/docs/images/device_screen.jpg
--------------------------------------------------------------------------------
/docs/images/drawer.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/pslab-desktop/5f24d307cfae32c2d86f6fc222528cd26713b2a4/docs/images/drawer.jpg
--------------------------------------------------------------------------------
/docs/images/faq.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/pslab-desktop/5f24d307cfae32c2d86f6fc222528cd26713b2a4/docs/images/faq.jpg
--------------------------------------------------------------------------------
/docs/images/home_screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/pslab-desktop/5f24d307cfae32c2d86f6fc222528cd26713b2a4/docs/images/home_screen.png
--------------------------------------------------------------------------------
/docs/images/logic_analyzer.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/pslab-desktop/5f24d307cfae32c2d86f6fc222528cd26713b2a4/docs/images/logic_analyzer.jpg
--------------------------------------------------------------------------------
/docs/images/multimeter.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/pslab-desktop/5f24d307cfae32c2d86f6fc222528cd26713b2a4/docs/images/multimeter.jpg
--------------------------------------------------------------------------------
/docs/images/oscilloscope.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/pslab-desktop/5f24d307cfae32c2d86f6fc222528cd26713b2a4/docs/images/oscilloscope.jpg
--------------------------------------------------------------------------------
/docs/images/power_source.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/pslab-desktop/5f24d307cfae32c2d86f6fc222528cd26713b2a4/docs/images/power_source.jpg
--------------------------------------------------------------------------------
/docs/images/project_banner.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/pslab-desktop/5f24d307cfae32c2d86f6fc222528cd26713b2a4/docs/images/project_banner.jpg
--------------------------------------------------------------------------------
/docs/images/robot.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/pslab-desktop/5f24d307cfae32c2d86f6fc222528cd26713b2a4/docs/images/robot.jpg
--------------------------------------------------------------------------------
/docs/images/settings.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/pslab-desktop/5f24d307cfae32c2d86f6fc222528cd26713b2a4/docs/images/settings.jpg
--------------------------------------------------------------------------------
/docs/images/wave_generator.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/pslab-desktop/5f24d307cfae32c2d86f6fc222528cd26713b2a4/docs/images/wave_generator.jpg
--------------------------------------------------------------------------------
/docs/screenshots-and-demos.md:
--------------------------------------------------------------------------------
1 | ## Screenshots
2 |
3 |
4 |
5 |
6 |
7 | The home screen that showcases all our instruments.
8 | |
9 |
10 |
11 | A four channel oscilloscope with analysis features.
12 | |
13 |
14 |
15 |
16 |
17 | A four channel Logic Analyzer for digital outputs.
18 | |
19 |
20 |
21 | A full fledged multimeter that can measure voltage, frequency, resistance etc.
22 | |
23 |
24 |
25 |
26 |
27 | Instrument used to generate analoge and digital waves of different frequencies, phase and duty cycles.
28 | |
29 |
30 |
31 | A programmable voltage and current source to drive your external circuits.
32 | |
33 |
34 |
35 |
36 |
37 | An intutive interface to control 4 servo motor robot arm.
38 | |
39 |
40 |
41 | Save data and config in CSV and retrieve them back later.
42 | |
43 |
44 |
45 |
46 |
47 | The app drawer gives you access to many more options like the FAQ, Device Information Screen etc.
48 | |
49 |
50 |
51 | A settings page for configuring your app as per your requirement.
52 | |
53 |
54 |
55 |
56 |
57 | A device information screen that shows device information and guide to connect it to the app.
58 | |
59 |
60 |
61 | A new FAQ screen that covers all generic as well as app specific questions that a user may have.
62 | |
63 |
64 |
65 |
66 |
67 | A page that has all the important links that can be used to reach out to us, or contribute to the project.
68 | |
69 |
70 |
71 |
72 | ## Project Videos
73 |
74 | [Project Overview](https://www.youtube.com/watch?v=0fVkTbNWDME)
75 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pslab",
3 | "description": "A desktop interface for the Pocket Science Lab open-harware device",
4 | "version": "2.8.1",
5 | "private": true,
6 | "author": {
7 | "name": "FOSSASIA",
8 | "email": "dev@fossasia.org",
9 | "url": "https://pslab.io/"
10 | },
11 | "main": "public/electron.js",
12 | "dependencies": {
13 | "@material-ui/core": "^4.10.2",
14 | "@material-ui/icons": "^4.9.1",
15 | "chokidar": "^3.4.3",
16 | "electron-load-balancer": "^3.0.0",
17 | "electron-log": "^3.0.5",
18 | "python-shell": "^1.0.7",
19 | "react": "^16.8.6",
20 | "react-circular-input": "^0.1.4",
21 | "react-custom-scrollbars": "^4.2.1",
22 | "react-dom": "^16.8.6",
23 | "react-redux": "^7.1.0",
24 | "react-rotary-knob": "^1.1.5",
25 | "react-router-dom": "^5.0.0",
26 | "react-spring": "^8.0.20",
27 | "recharts": "^2.0.8",
28 | "redux": "^4.0.1",
29 | "redux-thunk": "^2.3.0",
30 | "styled-components": "^4.2.0"
31 | },
32 | "devDependencies": {
33 | "@babel/core": "^7.12.3",
34 | "@storybook/addon-actions": "^6.0.27",
35 | "@storybook/addon-essentials": "^6.0.27",
36 | "@storybook/addon-links": "^6.0.27",
37 | "@storybook/node-logger": "^6.0.27",
38 | "@storybook/preset-create-react-app": "^3.1.4",
39 | "@storybook/react": "^6.0.27",
40 | "babel-eslint": "^10.0.1",
41 | "babel-loader": "^8.1.0",
42 | "concurrently": "^5.0.0",
43 | "cross-env": "^7.0.3",
44 | "devtron": "^1.4.0",
45 | "electron": "^10.4.2",
46 | "electron-builder": "^22.9.1",
47 | "electron-devtools-installer": "^3.0.0",
48 | "enzyme-adapter-react-16": "^1.12.1",
49 | "eslint": "^5.16.0",
50 | "eslint-config-prettier": "^4.1.0",
51 | "eslint-plugin-babel": "^5.3.0",
52 | "eslint-plugin-import": "^2.17.2",
53 | "eslint-plugin-jsx": "0.0.2",
54 | "eslint-plugin-jsx-a11y": "^6.2.1",
55 | "eslint-plugin-prettier": "^3.0.1",
56 | "eslint-plugin-react": "^7.20.0",
57 | "prettier": "^1.17.0",
58 | "react-is": "^17.0.1",
59 | "react-scripts": "^4.0.1",
60 | "wait-on": "^3.2.0"
61 | },
62 | "scripts": {
63 | "react-start": "cross-env BROWSER=NONE react-scripts start",
64 | "electron-start": "cross-env DEV=1 electron .",
65 | "start": "concurrently \"npm run react-start\" \"wait-on http://localhost:3000/ && npm run electron-start\"",
66 | "build": "react-scripts build",
67 | "build:electron": "electron-builder -c.extraMetadata.main=build/electron.js",
68 | "pack": "npm run build:electron -- --dir",
69 | "test": "react-scripts test",
70 | "eject": "react-scripts eject",
71 | "lint": "eslint src",
72 | "fmt": "prettier --write \"src/**/*.js\"",
73 | "fmt:check": "prettier --check \"src/**/*.js\"",
74 | "storybook": "start-storybook --ci -p 6006 -s public",
75 | "build-storybook": "build-storybook -s public"
76 | },
77 | "eslintConfig": {
78 | "extends": "react-app"
79 | },
80 | "homepage": "./",
81 | "browserslist": [
82 | ">0.2%",
83 | "not dead",
84 | "not ie <= 11",
85 | "not op_mini all"
86 | ],
87 | "build": {
88 | "appId": "FOSSASIA-PSLAB",
89 | "productName": "PSLab-Desktop",
90 | "copyright": "Copyright © 2019-2020 FOSSASIA",
91 | "asar": true,
92 | "asarUnpack": "scripts/",
93 | "linux": {
94 | "target": [
95 | "deb",
96 | "rpm",
97 | "tar.xz"
98 | ],
99 | "icon": "src/resources/app_icon.png",
100 | "category": "Utility"
101 | },
102 | "win": {
103 | "target": [
104 | "nsis"
105 | ],
106 | "publish": [
107 | "github"
108 | ],
109 | "icon": "src/resources/app_icon.png",
110 | "legalTrademarks": "Copyright © 2019-2020 FOSSASIA",
111 | "signingHashAlgorithms": [
112 | "sha1",
113 | "sha256"
114 | ]
115 | },
116 | "mac": {
117 | "category": "public.app-category.education",
118 | "target": [
119 | "default"
120 | ]
121 | },
122 | "nsis": {
123 | "oneClick": true
124 | },
125 | "files": [
126 | "build/**/*",
127 | "node_modules/**/*",
128 | "utils/**/*",
129 | "scripts/**/*",
130 | "background_tasks/**/*"
131 | ],
132 | "directories": {
133 | "buildResources": "assets"
134 | },
135 | "extends": null
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/public/app_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/pslab-desktop/5f24d307cfae32c2d86f6fc222528cd26713b2a4/public/app_icon.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/pslab-desktop/5f24d307cfae32c2d86f6fc222528cd26713b2a4/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 |
11 |
15 |
16 |
25 | PSLab
26 |
27 |
28 |
29 |
30 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/scripts/device_detection.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import time
3 | import threading
4 | import json
5 |
6 | from serial.tools import list_ports
7 |
8 | from pslab import ScienceLab
9 |
10 |
11 | class Device_detection:
12 | def __init__(self):
13 | self.device = None
14 | self.connected = False
15 | self.device_detection_thread = None
16 | pass
17 |
18 | def disconnect(self):
19 | self.connected = False
20 | self.device_detection_thread.join()
21 |
22 | def async_connect(self):
23 | # First connection attempt
24 | self.device = ScienceLab()
25 | output = None
26 | if not self.device.connected:
27 | # No device connected
28 | output = {'type': 'DEVICE_CONNECTION_STATUS',
29 | 'isConnected': False,
30 | 'message': 'Device not connected', }
31 | else:
32 | output = {'type': 'DEVICE_CONNECTION_STATUS',
33 | 'isConnected': True,
34 | 'message': 'Device connected',
35 | 'deviceName': str(self.device.get_version()),
36 | 'portName': self.device.interface.name
37 | }
38 | self.connected = True
39 | print(json.dumps(output))
40 | sys.stdout.flush()
41 |
42 | self.device_detection_thread = threading.Thread(
43 | target=self.check_connection,
44 | name='conn_check')
45 | self.device_detection_thread.start()
46 |
47 | def check_connection(self):
48 | while self.connected:
49 | ports = [p.device for p in list_ports.comports()]
50 | if self.device.interface.name not in ports:
51 | output = {'type': 'DEVICE_CONNECTION_STATUS',
52 | 'isConnected': False,
53 | 'message': 'Device disconnected'}
54 | print(json.dumps(output))
55 | sys.stdout.flush()
56 | self.disconnect()
57 | time.sleep(0.5)
58 |
--------------------------------------------------------------------------------
/scripts/logic_analyser.py:
--------------------------------------------------------------------------------
1 | import threading
2 | import sys
3 | import time
4 | import datetime
5 | import json
6 | import numpy as np
7 |
8 | from pslab.instrument.digital import MODES
9 |
10 | MODES = {v: k for k, v in MODES.items()}
11 |
12 |
13 | class LogicAnalyser:
14 | def __init__(self, I, file_write):
15 | self.file_write = file_write
16 | self.capture_time = 0.1
17 | self.device = I
18 | self.la_read_thread = None
19 | self.is_reading = False
20 | self.channel_mode = 3
21 | self.number_of_channels = 4
22 | self.trigger1_type = 1
23 | self.trigger2_type = 1
24 | self.trigger3_type = 1
25 | self.trigger4_type = 1
26 |
27 | def set_config(self, number_of_channels, trigger1_type, trigger2_type, trigger3_type, trigger4_type, capture_time):
28 | self.number_of_channels = int(number_of_channels)
29 | self.trigger1_type = int(trigger1_type)
30 | self.trigger2_type = int(trigger2_type)
31 | self.trigger3_type = int(trigger3_type)
32 | self.trigger4_type = int(trigger4_type)
33 | self.capture_time = int(capture_time)
34 |
35 | def get_config(self):
36 | output = {'type': 'GET_CONFIG_LA',
37 | 'numberOfChannels': self.number_of_channels,
38 | 'trigger1Type': self.trigger1_type,
39 | 'trigger2Type': self.trigger2_type,
40 | 'trigger3Type': self.trigger3_type,
41 | 'trigger4Type': self.trigger4_type,
42 | 'captureTime': self.capture_time
43 | }
44 | print(json.dumps(output))
45 | sys.stdout.flush()
46 |
47 | def start_read(self):
48 | self.la_read_thead = threading.Thread(
49 | target=self.capture,
50 | name='la')
51 | self.la_read_thead.start()
52 |
53 | def stop_read(self):
54 | if self.is_reading:
55 | self.is_reading = False
56 | self.device.logic_analyzer.stop()
57 | self.la_read_thead.join()
58 |
59 | def capture(self):
60 | self.is_reading = True
61 | self.device.logic_analyzer.capture(
62 | self.number_of_channels,
63 | modes=[
64 | MODES[self.trigger1_type],
65 | MODES[self.trigger2_type],
66 | MODES[self.trigger3_type],
67 | MODES[self.trigger4_type],
68 | ],
69 | block=False
70 | )
71 | time.sleep(self.capture_time / 1e3)
72 | self.device.logic_analyzer.stop()
73 | timestamps = self.device.logic_analyzer.fetch_data()
74 | xy = 8 * [np.array([])]
75 | xy[: self.number_of_channels * 2] = self.device.logic_analyzer.get_xy(timestamps)
76 | x1, y1, x2, y2, x3, y3, x4, y4 = xy
77 |
78 | output = {
79 | 'type': 'START_LA',
80 | 'time1': x1[1:].tolist(),
81 | 'voltage1': (y1[1:] + 0.).tolist(),
82 | 'time2': x2[1:].tolist(),
83 | 'voltage2': (y2[1:] + 2.).tolist(),
84 | 'time3': x3[1:].tolist(),
85 | 'voltage3': (y3[1:] + 4.).tolist(),
86 | 'time4': x4[1:].tolist(),
87 | 'voltage4': (y4[1:] + 6.).tolist(),
88 | 'numberOfChannels': self.number_of_channels,
89 | }
90 | if self.is_reading:
91 | print(json.dumps(output))
92 | sys.stdout.flush()
93 | self.is_reading = False
94 |
--------------------------------------------------------------------------------
/scripts/playback_bridge.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import time
3 | import json
4 | from playback_robot import PlaybackRobot
5 | from playback_la import LA
6 |
7 | playbackRobot = PlaybackRobot()
8 | la = LA()
9 |
10 |
11 | def main():
12 | while(True):
13 | in_stream_data = input()
14 | parsed_stream_data = json.loads(in_stream_data)
15 | command = parsed_stream_data['command']
16 |
17 | # # -------------------------- Script termination block ----------------------------
18 | if command == 'SAVE_CONFIG_ROB_ARM':
19 | output = {'type': 'FETCH_ROB_ARM'}
20 | print(json.dumps(output))
21 | sys.stdout.flush()
22 |
23 | if command == 'SAVE_DATA_LA':
24 | output = {'type': 'FETCH_LA'}
25 | print(json.dumps(output))
26 | sys.stdout.flush()
27 |
28 | if command == 'WRITE_ROB_ARM':
29 | servo1 = parsed_stream_data['servo1']
30 | servo2 = parsed_stream_data['servo2']
31 | servo3 = parsed_stream_data['servo3']
32 | servo4 = parsed_stream_data['servo4']
33 | data_path = parsed_stream_data['dataPath']
34 | playbackRobot.write(data_path, servo1, servo2, servo3, servo4)
35 |
36 | if command == 'WRITE_LA':
37 | la_1_voltage = parsed_stream_data['LA1Voltage']
38 | la_2_voltage = parsed_stream_data['LA2Voltage']
39 | la_3_voltage = parsed_stream_data['LA3Voltage']
40 | la_4_voltage = parsed_stream_data['LA4Voltage']
41 | la_1_time = parsed_stream_data['LA1Time']
42 | la_2_time = parsed_stream_data['LA2Time']
43 | la_3_time = parsed_stream_data['LA3Time']
44 | la_4_time = parsed_stream_data['LA4Time']
45 | number_of_channels = parsed_stream_data['numberOfChannels']
46 | data_path = parsed_stream_data['dataPath']
47 | la.write(data_path, number_of_channels, la_1_voltage, la_1_time,
48 | la_2_voltage, la_2_time, la_3_voltage, la_3_time, la_4_voltage, la_4_time,)
49 |
50 | if command == 'KILL':
51 | exit()
52 |
53 |
54 | if __name__ == '__main__':
55 | main()
56 |
--------------------------------------------------------------------------------
/scripts/playback_la.py:
--------------------------------------------------------------------------------
1 | import time
2 | import datetime
3 | import sys
4 | import json
5 |
6 |
7 | class LA:
8 | def write(self, data_path, number_of_channels, la_1_voltage, la_1_time,
9 | la_2_voltage, la_2_time, la_3_voltage, la_3_time, la_4_voltage, la_4_time):
10 | file_name = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S:000")
11 | file_pointed = open(data_path + '/' + file_name + '.csv', "w+")
12 | file_pointed.write("%s, %s \n\n" % (
13 | 'LogicAnalyzer', str(datetime.datetime.now())))
14 | file_pointed.write(
15 | "Number of Channels, Voltage 1, Time 1, Voltage 2, Time 2, Voltage 3, Time 3, Voltage 4, Time 4\n")
16 | file_pointed.write(
17 | "%s, %s, %s, %s, %s, %s, %s, %s, %s\n" % (
18 | str(number_of_channels),
19 | ' '.join(map(str, la_1_voltage)), ' '.join(
20 | map(str, la_1_time)),
21 | ' '.join(map(str, la_2_voltage)), ' '.join(
22 | map(str, la_2_time)),
23 | ' '.join(map(str, la_3_voltage)), ' '.join(
24 | map(str, la_3_time)),
25 | ' '.join(map(str, la_4_voltage)), ' '.join(map(str, la_4_time)),))
26 | file_pointed.close()
27 | print(json.dumps({'type': 'DATA_WRITING_STATUS',
28 | 'message': 'Data saved', }))
29 | sys.stdout.flush()
30 |
--------------------------------------------------------------------------------
/scripts/playback_robot.py:
--------------------------------------------------------------------------------
1 | import time
2 | import datetime
3 | import sys
4 | import json
5 |
6 |
7 | class PlaybackRobot:
8 | def write(self, data_path, servo1, servo2, servo3, servo4):
9 | file_name = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S:000")
10 | file_pointed = open(data_path + '/' + file_name + '.csv', "w+")
11 | file_pointed.write("%s, %s \n\n" % (
12 | 'RobotArm', str(datetime.datetime.now())))
13 | file_pointed.write(
14 | "Time, Servo1, Servo2, Servo3, Servo4, Latitude, Longitude\n")
15 | for i in range(len(servo1)):
16 | file_pointed.write(
17 | "%s, %s, %s, %s, %s\n" % (
18 | str(i + 1), str(servo1[i]), str(servo2[i]), str(servo3[i]), str(servo4[i])))
19 | file_pointed.close()
20 | print(json.dumps({'type': 'DATA_WRITING_STATUS',
21 | 'message': 'Config saved', }))
22 | sys.stdout.flush()
23 |
--------------------------------------------------------------------------------
/scripts/power_source.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import json
3 | import time
4 | import datetime
5 |
6 |
7 | class Power_source:
8 | def __init__(self, device, file_write):
9 | self.file_write = file_write
10 | self.power_supply = device.power_supply
11 |
12 | def set_config(self, pcs_value, pv1_value, pv2_value, pv3_value):
13 | self.power_supply.pcs = pcs_value / 1000 # mA
14 | self.power_supply.pv1 = pv1_value
15 | self.power_supply.pv2 = pv2_value
16 | self.power_supply.pv3 = pv3_value
17 |
18 | def get_config(self):
19 | output = {'type': 'GET_CONFIG_PWR_SRC',
20 | 'pcs': self.power_supply.pcs,
21 | 'pv1': self.power_supply.pv1,
22 | 'pv2': self.power_supply.pv2,
23 | 'pv3': self.power_supply.pv3}
24 | print(json.dumps(output))
25 | sys.stdout.flush()
26 |
27 | def get_config_from_file(self, data_path):
28 | self.file_write.get_config_from_file(data_path, "PowerSource")
29 |
30 | def save_config(self, data_path):
31 | datetime_data = datetime.datetime.now()
32 | timestamp = time.time()
33 | self.file_write.save_config(
34 | data_path, "PowerSource", timestamp=timestamp, datetime=datetime_data,
35 | pcs=self.power_supply.pcs, pv1=self.power_supply.pv1, pv2=self.power_supply.pv2,
36 | pv3=self.power_supply.pv3)
37 |
--------------------------------------------------------------------------------
/scripts/robotic_arm.py:
--------------------------------------------------------------------------------
1 | from pslab.instrument.digital import DIGITAL_OUTPUTS
2 | from pslab.external.motor import Servo
3 |
4 |
5 | class RoboticArm:
6 | def __init__(self, device, file_write):
7 | self.servos = [Servo(pin, device.pwm_generator) for pin in DIGITAL_OUTPUTS]
8 |
9 | def setServo(self, a1, a2, a3, a4):
10 | for servo, angle in zip(self.servos, [a1, a2, a3, a4]):
11 | if angle is not None:
12 | servo.angle = angle
13 |
--------------------------------------------------------------------------------
/scripts/sensors.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import json
3 | import sys
4 | import threading
5 | import time
6 |
7 |
8 | class Sensors:
9 | def __init__(self, I, file_write):
10 | self.file_write = file_write
11 |
12 | self.multimeter_data_read_thread = None
13 | self.device = I
14 | self.is_reading = False
15 | self.active_category = 'SCAN'
16 |
17 | def set_config(self, active_category):
18 | self.active_category = active_category
19 |
20 | def get_config(self):
21 | output = {'type': 'GET_CONFIG_SENSORS',
22 | 'activeCategory': self.active_category}
23 | print(json.dumps(output))
24 | sys.stdout.flush()
25 |
26 | def start_read(self):
27 | self.sensor_data_read_thread = threading.Thread(
28 | target=self.capture_loop,
29 | name='sensors')
30 | self.sensor_data_read_thread.start()
31 |
32 | def stop_read(self):
33 | self.is_reading = False
34 | self.sensor_data_read_thread.join()
35 |
36 | def capture_loop(self):
37 | self.is_reading = True
38 | while self.is_reading:
39 | if self.active_category == 'SCAN':
40 | self.scan()
41 |
42 | def scan(self):
43 | datetime_data = datetime.datetime.now()
44 | timestamp = time.time()
45 |
46 | data = self.device.i2c.scan()
47 | self.file_write.update_buffer(
48 | "SENSORS", timestamp=timestamp, datetime=datetime_data, data='scan', value=data)
49 | time.sleep(0.25)
50 |
51 | output = {'type': 'SENSORS_SCAN', 'data': data}
52 | print(json.dumps(output))
53 | sys.stdout.flush()
54 |
--------------------------------------------------------------------------------
/scripts/wave_generator.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import json
3 | import time
4 | import datetime
5 |
6 |
7 | class Wave_generator:
8 | def __init__(self, I, file_write):
9 | self.file_write = file_write
10 |
11 | self.wavegen = I.waveform_generator
12 | self.pwmgen = I.pwm_generator
13 | self.wave = True
14 | self.digital = False
15 | self.s1_frequency = 500
16 | self.s2_frequency = 500
17 | self.s2_phase = 90
18 | self.wave_form_s1 = 'sine'
19 | self.wave_form_s2 = 'sine'
20 | self.pwm_frequency = 500
21 | self.sqr1_duty_cycle = 50
22 | self.sqr2_duty_cycle = 50
23 | self.sqr2_phase = 90
24 | self.sqr3_duty_cycle = 50
25 | self.sqr3_phase = 90
26 | self.sqr4_duty_cycle = 50
27 | self.sqr4_phase = 90
28 | self.change_device_settings()
29 |
30 | def set_config(self, wave, digital,
31 | s1_frequency, s2_frequency, s2_phase, wave_form_s1,
32 | wave_form_s2, pwm_frequency, sqr1_duty_cycle,
33 | sqr2_duty_cycle, sqr2_phase,
34 | sqr3_duty_cycle, sqr3_phase,
35 | sqr4_duty_cycle, sqr4_phase):
36 | self.wave = wave
37 | self.digital = digital
38 | self.s1_frequency = s1_frequency
39 | self.s2_frequency = s2_frequency
40 | self.s2_phase = s2_phase
41 | self.wave_form_s1 = wave_form_s1
42 | self.wave_form_s2 = wave_form_s2
43 | self.pwm_frequency = pwm_frequency
44 | self.sqr1_duty_cycle = sqr1_duty_cycle
45 | self.sqr2_duty_cycle = sqr2_duty_cycle
46 | self.sqr2_phase = sqr2_phase
47 | self.sqr3_duty_cycle = sqr3_duty_cycle
48 | self.sqr3_phase = sqr3_phase
49 | self.sqr4_duty_cycle = sqr4_duty_cycle
50 | self.sqr4_phase = sqr4_phase
51 | self.change_device_settings()
52 |
53 | def change_device_settings(self):
54 | if self.wave:
55 | self.wavegen.load_function("SI1", self.wave_form_s1)
56 | self.wavegen.load_function("SI2", self.wave_form_s2)
57 | self.wavegen.generate(
58 | ["SI1", "SI2"],
59 | [self.s1_frequency, self.s2_frequency],
60 | self.s2_phase
61 | )
62 | else:
63 | duty_cycles = (
64 | self.sqr1_duty_cycle,
65 | self.sqr2_duty_cycle,
66 | self.sqr3_duty_cycle,
67 | self.sqr4_duty_cycle,
68 | )
69 | phases = (
70 | 0,
71 | self.sqr2_phase,
72 | self.sqr3_phase,
73 | self.sqr4_phase
74 | )
75 | self.pwmgen.generate(
76 | ["SQ1", "SQ2", "SQ3", "SQ4"],
77 | self.pwm_frequency,
78 | [dc / 100 for dc in duty_cycles],
79 | [ph / 360 for ph in phases],
80 | )
81 |
82 | def get_config(self):
83 | output = {'type': 'GET_CONFIG_WAV_GEN',
84 | 'wave': self.wave,
85 | 'digital': self.digital,
86 | 's1Frequency': self.s1_frequency,
87 | 's2Frequency': self.s2_frequency,
88 | 's2Phase': self.s2_phase,
89 | 'waveFormS1': self.wave_form_s1,
90 | 'waveFormS2': self.wave_form_s2,
91 | 'pwmFrequency': self.pwm_frequency,
92 | 'sqr1DutyCycle': self.sqr1_duty_cycle,
93 | 'sqr2DutyCycle': self.sqr2_duty_cycle,
94 | 'sqr2Phase': self.sqr2_phase,
95 | 'sqr3DutyCycle': self.sqr3_duty_cycle,
96 | 'sqr3Phase': self.sqr3_phase,
97 | 'sqr4DutyCycle': self.sqr4_duty_cycle,
98 | 'sqr4Phase': self.sqr4_phase,
99 | }
100 | print(json.dumps(output))
101 | sys.stdout.flush()
102 |
103 | def get_config_from_file(self, data_path):
104 | self.file_write.get_config_from_file(data_path, "WaveGenerator")
105 |
106 | def save_config(self, data_path):
107 | datetime_data = datetime.datetime.now()
108 | timestamp = time.time()
109 | self.file_write.save_config(
110 | data_path, "WaveGenerator", timestamp=timestamp, datetime=datetime_data,
111 | wave=self.wave, digital=self.digital, s1_f=self.s1_frequency, s1_shape=self.wave_form_s1,
112 | s2_f=self.s2_frequency, s2_p=self.s2_phase, s2_shape=self.wave_form_s2,
113 | pwm_f=self.pwm_frequency,
114 | dc_1=self.sqr1_duty_cycle,
115 | p2=self.sqr2_phase, dc_2=self.sqr2_duty_cycle,
116 | p3=self.sqr3_phase, dc_3=self.sqr3_duty_cycle,
117 | p4=self.sqr4_phase, dc_4=self.sqr4_duty_cycle)
118 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/src/components/Appshell/Appshell.styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { fade } from '@material-ui/core/styles/colorManipulator';
3 |
4 | export const AppshellContainer = styled.div`
5 | width: 100vw;
6 | height: 100vh;
7 | display: flex;
8 | flex-direction: row;
9 | overflow: hidden;
10 | `;
11 |
12 | export const ChildrenContainer = styled.div`
13 | width: 100%;
14 | height: 100%;
15 | `;
16 |
17 | export const ChildrenWrapper = styled.div`
18 | width: 100%;
19 | height: calc(100% - 4em);
20 | `;
21 |
22 | export const AppBar = styled.div`
23 | width: 100%;
24 | height: 4em;
25 | display: flex;
26 | background: ${props => props.theme.primary.main};
27 | box-shadow: 0px 2px 4px -1px rgba(0, 0, 0, 0.2),
28 | 0px 4px 5px 0px rgba(0, 0, 0, 0.14), 0px 1px 10px 0px rgba(0, 0, 0, 0.12);
29 | `;
30 |
31 | export const TitleContainer = styled.div`
32 | font-size: 24px;
33 | color: #ffffff;
34 | line-height: 48px;
35 | vertical-align: middle;
36 | `;
37 |
38 | export const ButtonContainer = styled.div`
39 | display: flex;
40 | flex-direction: row;
41 | justify-content: center;
42 | align-items: center;
43 | margin: 0px 16px 0px 16px;
44 | z-index: 999;
45 | `;
46 |
47 | export const NavigationContainer = styled.div`
48 | width: 4em;
49 | background-color: ${props => props.theme.navigationBackground};
50 | display: flex;
51 | flex-direction: column;
52 | z-index: 999;
53 | `;
54 |
55 | export const NavigationTab = styled.div`
56 | min-width: 100%;
57 | height: 4em;
58 | display: flex;
59 | align-items: center;
60 | justify-content: center;
61 | cursor: pointer;
62 | background-color: ${props => props.theme.navigationBackground};
63 | color: ${props => props =>
64 | props.selected ? props.theme.primary.main : props.theme.common.white};
65 | transition: all 0.5s ease;
66 |
67 | &:hover {
68 | background-color: ${props => fade(props.theme.primary.main, 0.1)};
69 | color: ${props => fade(props.theme.primary.main, 0.5)};
70 | }
71 | `;
72 |
73 | export const AppIconWrapper = styled.div`
74 | width: 100%;
75 | height: 4em;
76 | display: flex;
77 | align-items: center;
78 | justify-content: center;
79 | background-color: ${props => props.theme.iconBackground};
80 | `;
81 |
82 | export const TopNavigationWrapper = styled.div``;
83 |
84 | export const BottomNavigationWrapper = styled.div``;
85 |
86 | export const Spacer = styled.div`
87 | flex: 1;
88 | `;
89 |
90 | export const ButtonTextModifier = styled.div`
91 | display: flex;
92 | flex-direction: column;
93 | `;
94 |
95 | export const RecText = styled.div`
96 | font-size: 10px;
97 | `;
98 |
--------------------------------------------------------------------------------
/src/components/Appshell/index.js:
--------------------------------------------------------------------------------
1 | import Appshell from './Appshell';
2 |
3 | export default Appshell;
4 |
--------------------------------------------------------------------------------
/src/components/CustomCircularInput/CustomCircularInput.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | CircularInput,
4 | CircularTrack,
5 | CircularProgress,
6 | CircularThumb,
7 | } from 'react-circular-input';
8 | import { fade } from '@material-ui/core/styles/colorManipulator';
9 | import { Spring } from 'react-spring/renderprops';
10 | import { withTheme } from 'styled-components';
11 | import { connect } from 'react-redux';
12 | import { bindActionCreators } from 'redux';
13 | import { openDialog } from '../../redux/actions/app';
14 |
15 | const CustomCircularInput = ({
16 | setValue,
17 | value,
18 | min = 0,
19 | max = 100,
20 | step,
21 | radius,
22 | theme,
23 | selector = false,
24 | text = false,
25 | title,
26 | openDialog,
27 | }) => {
28 | const rangedValue = v => {
29 | return (v - min) / (max - min);
30 | };
31 |
32 | const stepValue = v => {
33 | if (!step) {
34 | return min + v * (max - min);
35 | }
36 | const increment = v * (max - min);
37 | const originalMultiplier = Math.round(increment / step);
38 | return min + originalMultiplier * step;
39 | };
40 |
41 | return (
42 |
43 | {props => (
44 | setValue(stepValue(value))}
48 | >
49 |
53 | {!selector && (
54 |
55 | )}
56 | {selector && }
57 | {text && (
58 | {
66 | e.stopPropagation();
67 | openDialog({
68 | variant: 'simple-input',
69 | title: title,
70 | textTitle: `Enter value ( ${min} to ${max} )`,
71 | onAccept: value => setValue(parseInt(value, 10)),
72 | onCheck: value => !(value >= min && value <= max),
73 | inputCheck: () => true,
74 | onCancel: () => {},
75 | });
76 | }}
77 | >
78 | {value}°
79 |
80 | )}
81 |
82 | )}
83 |
84 | );
85 | };
86 |
87 | const mapDispatchToProps = dispatch => ({
88 | ...bindActionCreators(
89 | {
90 | openDialog,
91 | },
92 | dispatch,
93 | ),
94 | });
95 |
96 | export default withTheme(
97 | connect(null, mapDispatchToProps)(CustomCircularInput),
98 | );
99 |
--------------------------------------------------------------------------------
/src/components/CustomCircularInput/index.js:
--------------------------------------------------------------------------------
1 | import CustomCircularInput from './CustomCircularInput';
2 |
3 | export default CustomCircularInput;
4 |
--------------------------------------------------------------------------------
/src/components/CustomDialog/CustomDialog.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | Dialog,
4 | DialogActions,
5 | DialogContent,
6 | DialogContentText,
7 | DialogTitle,
8 | Button,
9 | TextField,
10 | } from '@material-ui/core';
11 |
12 | const CustomDialog = ({
13 | title,
14 | isOpen,
15 | variant,
16 | hint,
17 | textTitle,
18 | onDialogClose,
19 | onCheck,
20 | inputCheck,
21 | onAccept,
22 | onCancel,
23 | }) => {
24 | const [values, setValues] = React.useState({
25 | textValue: '',
26 | isTextError: false,
27 | });
28 |
29 | const onTextFieldChange = fieldName => event => {
30 | if (inputCheck(event.target.value)) {
31 | setValues({
32 | ...values,
33 | [fieldName]: event.target.value,
34 | isTextError: false,
35 | });
36 | }
37 | };
38 |
39 | const onReset = () => {
40 | setValues({
41 | textValue: '',
42 | isTextError: false,
43 | });
44 | };
45 |
46 | const onHandleAccept = () => {
47 | if (inputCheck(values.textValue)) {
48 | const isError = onCheck ? onCheck(values.textValue) : false;
49 | if (isError) {
50 | setValues({
51 | ...values,
52 | isTextError: true,
53 | });
54 | } else {
55 | onAccept(values.textValue);
56 | onReset();
57 | onDialogClose();
58 | }
59 | }
60 | };
61 |
62 | const onHandleCancel = () => {
63 | onCancel && onCancel();
64 | onReset();
65 | onDialogClose();
66 | };
67 |
68 | const onHandleClose = () => {
69 | onReset();
70 | onDialogClose();
71 | };
72 |
73 | const renderDialogContent = () => {
74 | switch (variant) {
75 | case 'simple-input':
76 | return (
77 |
78 | {hint && {hint}}
79 | {
90 | if (ev.key === 'Enter') {
91 | // Do code here
92 | ev.preventDefault();
93 | onHandleAccept();
94 | }
95 | }}
96 | />
97 |
98 | );
99 | default:
100 | break;
101 | }
102 | };
103 |
104 | const renderDialogAction = () => {
105 | switch (variant) {
106 | case 'simple-input':
107 | return (
108 |
109 |
112 |
115 |
116 | );
117 | default:
118 | break;
119 | }
120 | };
121 |
122 | return (
123 |
132 | );
133 | };
134 |
135 | export default CustomDialog;
136 |
--------------------------------------------------------------------------------
/src/components/CustomDialog/CustomDialog.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { action, actions } from '@storybook/addon-actions';
3 |
4 | import CustomDialog from './CustomDialog';
5 |
6 | export default {
7 | title: 'Components/CustomDialog',
8 | component: CustomDialog,
9 | };
10 |
11 | export const SimpleDialog = () => (
12 |
13 | );
14 |
15 | export const InputDialog = () => (
16 | a.length > 0}
22 | {...actions('onAccept', 'onCancel', 'onDialogClose')}
23 | />
24 | );
25 |
--------------------------------------------------------------------------------
/src/components/CustomDialog/index.js:
--------------------------------------------------------------------------------
1 | import CustomDialog from './CustomDialog';
2 |
3 | export default CustomDialog;
4 |
--------------------------------------------------------------------------------
/src/components/CustomIcons/CapacitorIcon.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const CapacitorIcon = ({ size = '1.5em', color = 'rgba(0, 0, 0, 0.38)' }) => {
4 | return (
5 |
41 | );
42 | };
43 |
44 | export default CapacitorIcon;
45 |
--------------------------------------------------------------------------------
/src/components/CustomIcons/ResistorIcon.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const ResistorIcon = ({ size = '1.5em', color = 'rgba(0, 0, 0, 0.38)' }) => {
4 | return (
5 |
43 | );
44 | };
45 |
46 | export default ResistorIcon;
47 |
--------------------------------------------------------------------------------
/src/components/CustomKnob/CustomKnob.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import s3 from './skins/s3';
3 | import { Knob } from 'react-rotary-knob';
4 |
5 | const CustomKnob = ({ radius, onChangeDial, value, step, min, max }) => {
6 | return (
7 |
21 | );
22 | };
23 |
24 | export default CustomKnob;
25 |
--------------------------------------------------------------------------------
/src/components/CustomKnob/index.js:
--------------------------------------------------------------------------------
1 | import CustomKnob from './CustomKnob';
2 |
3 | export default CustomKnob;
4 |
--------------------------------------------------------------------------------
/src/components/CustomSliderInput/CustomSliderInput.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import { bindActionCreators } from 'redux';
4 | import Slider from '@material-ui/core/Slider';
5 | import { withStyles } from '@material-ui/core/styles';
6 | import { openDialog } from '../../redux/actions/app';
7 |
8 | const styles = () => ({
9 | slider: {
10 | margin: '0px 8px 0px 8px',
11 | },
12 | });
13 |
14 | const onCheck = (min, max) => value => {
15 | return !(value >= min && value <= max);
16 | };
17 |
18 | const CustomSliderInput = ({
19 | title,
20 | unit,
21 | onChangeSlider,
22 | value,
23 | min,
24 | max,
25 | inputCheck,
26 | step,
27 | disabled,
28 | minTitleWidth,
29 | minUnitWidth,
30 | display,
31 | classes,
32 | openDialog,
33 | }) => {
34 | return (
35 |
44 |
51 | {title}
52 |
53 |
62 | {
64 | !disabled &&
65 | openDialog({
66 | variant: 'simple-input',
67 | title: title,
68 | textTitle: `Enter value (${min} to ${max})`,
69 | onAccept: value => {
70 | onChangeSlider(undefined, Number(value));
71 | },
72 | onCheck: onCheck(min, max),
73 | inputCheck: inputCheck,
74 | onCancel: () => {},
75 | });
76 | }}
77 | style={{
78 | margin: '0px 0px 0px 8px',
79 | whiteSpace: 'nowrap',
80 | minWidth: minUnitWidth,
81 | textAlign: 'right',
82 | cursor: 'pointer',
83 | }}
84 | >{`${display ? display : value} ${unit}`}
85 |
86 | );
87 | };
88 |
89 | const mapDispatchToProps = dispatch => ({
90 | ...bindActionCreators(
91 | {
92 | openDialog,
93 | },
94 | dispatch,
95 | ),
96 | });
97 |
98 | export default withStyles(styles)(
99 | connect(null, mapDispatchToProps)(CustomSliderInput),
100 | );
101 |
--------------------------------------------------------------------------------
/src/components/CustomSliderInput/index.js:
--------------------------------------------------------------------------------
1 | import CustomSliderInput from './CustomSliderInput';
2 |
3 | export default CustomSliderInput;
4 |
--------------------------------------------------------------------------------
/src/components/Decorators/Decorators.styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const OptionHeading = styled.h3``;
4 |
--------------------------------------------------------------------------------
/src/components/Display/Display.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ValueWrapper } from './Display.styles';
3 |
4 | const Display = ({ fontSize, value, unit }) => (
5 |
6 | {value} {unit}
7 |
8 | );
9 |
10 | export default Display;
11 |
--------------------------------------------------------------------------------
/src/components/Display/Display.styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const ValueWrapper = styled.div`
4 | font-size: ${props => props.fontSize}em;
5 | color: ${props => props.theme.text.secondary};
6 | `;
7 |
--------------------------------------------------------------------------------
/src/components/Display/index.js:
--------------------------------------------------------------------------------
1 | import Display from './Display';
2 |
3 | export default Display;
4 |
--------------------------------------------------------------------------------
/src/components/GraphPanelLayout/GraphPanelLayout.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | LayoutContainer,
4 | ButtonWrapper,
5 | SettingsContainer,
6 | SettingsWrapper,
7 | GraphContainer,
8 | InformationContainer,
9 | } from './GraphPanelLayout.styles';
10 |
11 | const GraphPanelLayout = ({ actionButtons, settings, graph, information }) => {
12 | return (
13 |
14 | {/* */}
15 |
16 | {graph}
17 | {information}
18 |
19 |
20 | {actionButtons}
21 | {settings}
22 |
23 |
24 | );
25 | };
26 |
27 | export default GraphPanelLayout;
28 |
--------------------------------------------------------------------------------
/src/components/GraphPanelLayout/GraphPanelLayout.styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const LayoutContainer = styled.div`
4 | background-color: ${props => props.theme.background.default};
5 | height: 100%;
6 | width: 100%;
7 | z-index: 1;
8 | display: flex;
9 | `;
10 |
11 | export const ThickBar = styled.div`
12 | position: absolute;
13 | right: 0;
14 | top: 4em;
15 | height: calc((100% - 4em) * 0.2);
16 | width: calc(100% - 3.5em);
17 | background: ${props => props.theme.gradient1};
18 | z-index: 0;
19 | `;
20 |
21 | export const GraphContainer = styled.div`
22 | height: 80%;
23 | display: flex;
24 | transform: ${props =>
25 | props.information ? 'translateY(0%)' : 'translateY(-5%)'};
26 | `;
27 |
28 | export const SettingsContainer = styled.div`
29 | width: 28%;
30 | display: flex;
31 | flex-direction: column;
32 | z-index: 2;
33 | `;
34 |
35 | export const ButtonWrapper = styled.div`
36 | display: flex;
37 | `;
38 |
39 | export const SettingsWrapper = styled.div`
40 | flex: 1;
41 | display: flex;
42 | `;
43 |
44 | export const InformationContainer = styled.div`
45 | display: flex;
46 | flex-direction: column;
47 | flex: 1;
48 | justify-content: ${props => (props.information ? 'none' : 'center')};
49 | z-index: 2;
50 | `;
51 |
--------------------------------------------------------------------------------
/src/components/GraphPanelLayout/index.js:
--------------------------------------------------------------------------------
1 | import GraphPanelLayout from './GraphPanelLayout';
2 |
3 | export default GraphPanelLayout;
4 |
--------------------------------------------------------------------------------
/src/components/HomeLayout/HomeLayout.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { LayoutContainer } from './HomeLayout.styles';
3 |
4 | const HomeLayout = ({ title, tabs }) => {
5 | return {tabs};
6 | };
7 |
8 | export default HomeLayout;
9 |
--------------------------------------------------------------------------------
/src/components/HomeLayout/HomeLayout.styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const LayoutContainer = styled.div`
4 | display: flex;
5 | flex-direction: row;
6 | height: 100%;
7 | width: 100%;
8 | justify-content: center;
9 | align-items: center;
10 | background: ${props => props.theme.background.default};
11 | `;
12 |
--------------------------------------------------------------------------------
/src/components/HomeLayout/index.js:
--------------------------------------------------------------------------------
1 | import HomeLayout from './HomeLayout';
2 |
3 | export default HomeLayout;
4 |
--------------------------------------------------------------------------------
/src/components/Icons/PSLabIcons.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import OscilloscopeSvg from '../../resources/oscilloscope.svg';
3 | import LogicAnalyzerSvg from '../../resources/logic_analyzer.svg';
4 | import PowerSourceSvg from '../../resources/power_source.svg';
5 | import WaveGeneratorSvg from '../../resources/wave_generator.svg';
6 | import MultimeterSvg from '../../resources/multimeter.svg';
7 | import SensorsSvg from '../../resources/sensors.svg';
8 | import OscilloscopeRedSvg from '../../resources/oscilloscope_red.svg';
9 | import LogicAnalyzerRedSvg from '../../resources/logic_analyzer_red.svg';
10 | import PowerSourceRedSvg from '../../resources/power_source_red.svg';
11 | import WaveGeneratorRedSvg from '../../resources/wave_generator_red.svg';
12 | import MultimeterRedSvg from '../../resources/multimeter_red.svg';
13 | import SensorsRedSvg from '../../resources/sensors_red.svg';
14 |
15 | export const OscilloscopeIcon = ({ size, color }) => {
16 | return (
17 |
25 | );
26 | };
27 |
28 | export const LogicAnalyzerIcon = ({ size, color }) => {
29 | return (
30 |
38 | );
39 | };
40 |
41 | export const PowerSourceIcon = ({ size, color }) => {
42 | return (
43 |
51 | );
52 | };
53 |
54 | export const WaveGeneratorIcon = ({ size, color }) => {
55 | return (
56 |
64 | );
65 | };
66 |
67 | export const MultimeterIcon = ({ size, color }) => {
68 | return (
69 |
77 | );
78 | };
79 |
80 | export const SensorsIcon = ({ size, color }) => {
81 | return (
82 |
90 | );
91 | };
92 |
--------------------------------------------------------------------------------
/src/components/SimplePanelLayout/SimplePanelLayout.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { LayoutWrapper, LayoutContainer } from './SimplePanelLayout.styles';
3 |
4 | const SimplePanelLayout = props => {
5 | const { panel } = props;
6 |
7 | return (
8 |
9 | {/* */}
10 | {panel}
11 |
12 | );
13 | };
14 |
15 | export default SimplePanelLayout;
16 |
--------------------------------------------------------------------------------
/src/components/SimplePanelLayout/SimplePanelLayout.styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const ThickBar = styled.div`
4 | position: absolute;
5 | right: 0;
6 | top: 4em;
7 | height: calc((100% - 4em) * 0.2);
8 | width: calc(100% - 3.5em);
9 | background: ${props => props.theme.gradient1};
10 | z-index: 0;
11 | `;
12 |
13 | export const LayoutWrapper = styled.div`
14 | align-self: center;
15 | z-index: 999;
16 | `;
17 |
18 | export const LayoutContainer = styled.div`
19 | background-color: ${props => props.theme.background.default};
20 | height: 100%;
21 | width: 100%;
22 | z-index: 1;
23 | display: flex;
24 | flex-direction: column;
25 | justify-content: center;
26 | `;
27 |
--------------------------------------------------------------------------------
/src/components/SimplePanelLayout/index.js:
--------------------------------------------------------------------------------
1 | import SimplePanelLayout from './SimplePanelLayout';
2 |
3 | export default SimplePanelLayout;
4 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
5 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
6 | sans-serif;
7 | -webkit-font-smoothing: antialiased;
8 | -moz-osx-font-smoothing: grayscale;
9 | font-size: 12px;
10 | }
11 |
12 | code {
13 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
14 | monospace;
15 | }
16 |
17 | a {
18 | color: rgba(0, 0, 0, 0.87);
19 | text-decoration: none;
20 | }
21 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import { store } from './redux/store';
6 | import { Provider } from 'react-redux';
7 | import * as serviceWorker from './serviceWorker';
8 |
9 | ReactDOM.render(
10 |
11 |
12 | ,
13 | document.getElementById('root'),
14 | );
15 |
16 | // If you want your app to work offline and load faster, you can change
17 | // unregister() to register() below. Note this comes with some pitfalls.
18 | // Learn more about service workers: https://bit.ly/CRA-PWA
19 | serviceWorker.unregister();
20 |
--------------------------------------------------------------------------------
/src/redux/actionTypes/app.js:
--------------------------------------------------------------------------------
1 | export const DEVICE_CONNECTED = 'DEVICE_CONNECTED';
2 | export const DEVICE_DISCONNECTED = 'DEVICE_DISCONNECTED';
3 |
4 | export const OPEN_SNACKBAR = 'OPEN_SNACKBAR';
5 | export const CLOSE_SNACKBAR = 'CLOSE_SNACKBAR';
6 |
7 | export const OPEN_DIALOG = 'OPEN_DIALOG';
8 | export const CLOSE_DIALOG = 'CLOSE_DIALOG';
9 |
--------------------------------------------------------------------------------
/src/redux/actions/app.js:
--------------------------------------------------------------------------------
1 | import {
2 | OPEN_DIALOG,
3 | CLOSE_DIALOG,
4 | OPEN_SNACKBAR,
5 | CLOSE_SNACKBAR,
6 | DEVICE_CONNECTED,
7 | DEVICE_DISCONNECTED,
8 | } from '../actionTypes/app';
9 |
10 | export const deviceConnected = ({ deviceInformation }) => {
11 | return {
12 | type: DEVICE_CONNECTED,
13 | payload: { deviceInformation },
14 | };
15 | };
16 |
17 | export const deviceDisconnected = () => {
18 | return {
19 | type: DEVICE_DISCONNECTED,
20 | };
21 | };
22 |
23 | export const openSnackbar = ({ message = '', timeout = 4000 }) => {
24 | return {
25 | type: OPEN_SNACKBAR,
26 | payload: {
27 | message,
28 | timeout,
29 | },
30 | };
31 | };
32 |
33 | export const closeSnackbar = () => {
34 | return {
35 | type: CLOSE_SNACKBAR,
36 | };
37 | };
38 |
39 | export const openDialog = ({
40 | variant = null,
41 | title = null,
42 | hint = null,
43 | textTitle = null,
44 | onCheck = null,
45 | inputCheck = null,
46 | onAccept = null,
47 | onCancel = null,
48 | }) => {
49 | return {
50 | type: OPEN_DIALOG,
51 | payload: {
52 | variant,
53 | title,
54 | hint,
55 | textTitle,
56 | onCheck,
57 | inputCheck,
58 | onAccept,
59 | onCancel,
60 | },
61 | };
62 | };
63 |
64 | export const closeDialog = () => {
65 | return {
66 | type: CLOSE_DIALOG,
67 | };
68 | };
69 |
--------------------------------------------------------------------------------
/src/redux/actions/config.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/pslab-desktop/5f24d307cfae32c2d86f6fc222528cd26713b2a4/src/redux/actions/config.js
--------------------------------------------------------------------------------
/src/redux/reducers/app.js:
--------------------------------------------------------------------------------
1 | import {
2 | OPEN_SNACKBAR,
3 | CLOSE_SNACKBAR,
4 | DEVICE_CONNECTED,
5 | DEVICE_DISCONNECTED,
6 | OPEN_DIALOG,
7 | CLOSE_DIALOG,
8 | } from '../actionTypes/app';
9 |
10 | const initialState = {
11 | device: {
12 | isConnected: false,
13 | deviceInformation: null,
14 | },
15 | snackbar: {
16 | isOpen: false,
17 | message: '',
18 | timeout: 4000,
19 | },
20 | dialog: {
21 | isOpen: false,
22 | variant: null,
23 | title: null,
24 | hint: null,
25 | textTitle: null,
26 | onCheck: null,
27 | inputCheck: null,
28 | onAccept: null,
29 | onCancel: null,
30 | },
31 | };
32 |
33 | export const appReducer = (prevState = initialState, action) => {
34 | switch (action.type) {
35 | case OPEN_SNACKBAR: {
36 | const { message, timeout } = action.payload;
37 | return {
38 | ...prevState,
39 | snackbar: {
40 | isOpen: true,
41 | message,
42 | timeout,
43 | },
44 | };
45 | }
46 | case CLOSE_SNACKBAR: {
47 | return {
48 | ...prevState,
49 | snackbar: {
50 | ...initialState.snackbar,
51 | },
52 | };
53 | }
54 | case DEVICE_CONNECTED: {
55 | const { deviceInformation } = action.payload;
56 | return {
57 | ...prevState,
58 | device: {
59 | isConnected: true,
60 | deviceInformation: {
61 | ...initialState.device.deviceInformation,
62 | ...deviceInformation,
63 | },
64 | },
65 | };
66 | }
67 | case DEVICE_DISCONNECTED: {
68 | return {
69 | ...prevState,
70 | device: {
71 | ...initialState.device,
72 | },
73 | };
74 | }
75 | case OPEN_DIALOG: {
76 | const {
77 | variant,
78 | title,
79 | hint,
80 | textTitle,
81 | onCheck,
82 | inputCheck,
83 | onAccept,
84 | onCancel,
85 | } = action.payload;
86 | return {
87 | ...prevState,
88 | dialog: {
89 | isOpen: true,
90 | variant,
91 | title,
92 | hint,
93 | textTitle,
94 | onCheck,
95 | inputCheck,
96 | onAccept,
97 | onCancel,
98 | },
99 | };
100 | }
101 | case CLOSE_DIALOG: {
102 | return {
103 | ...prevState,
104 | dialog: {
105 | ...initialState.dialog,
106 | },
107 | };
108 | }
109 | default:
110 | return initialState;
111 | }
112 | };
113 |
--------------------------------------------------------------------------------
/src/redux/reducers/config.js:
--------------------------------------------------------------------------------
1 | const os = window.require('os');
2 | const path = window.require('path');
3 | const dataPath = path.join(os.homedir(), 'Documents', 'PSLab');
4 |
5 | // Create save path if not existing
6 |
7 | const initialState = {
8 | dataPath,
9 | };
10 |
11 | export const configReducer = (prevState = initialState, action) => {
12 | switch (action.type) {
13 | default:
14 | return initialState;
15 | }
16 | };
17 |
--------------------------------------------------------------------------------
/src/redux/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, combineReducers } from 'redux';
2 | import { appReducer } from './reducers/app';
3 | import { configReducer } from './reducers/config';
4 |
5 | const reducer = combineReducers({
6 | app: appReducer,
7 | config: configReducer,
8 | });
9 |
10 | export const store = createStore(
11 | reducer,
12 | window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
13 | );
14 |
--------------------------------------------------------------------------------
/src/resources/app_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/pslab-desktop/5f24d307cfae32c2d86f6fc222528cd26713b2a4/src/resources/app_icon.png
--------------------------------------------------------------------------------
/src/resources/back_layout.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/pslab-desktop/5f24d307cfae32c2d86f6fc222528cd26713b2a4/src/resources/back_layout.png
--------------------------------------------------------------------------------
/src/resources/compass.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
80 |
--------------------------------------------------------------------------------
/src/resources/compass_red.svg:
--------------------------------------------------------------------------------
1 |
2 |
110 |
--------------------------------------------------------------------------------
/src/resources/device_connected.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/src/resources/device_disconnected.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/src/resources/ds_con.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/pslab-desktop/5f24d307cfae32c2d86f6fc222528cd26713b2a4/src/resources/ds_con.png
--------------------------------------------------------------------------------
/src/resources/ds_discon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/pslab-desktop/5f24d307cfae32c2d86f6fc222528cd26713b2a4/src/resources/ds_discon.png
--------------------------------------------------------------------------------
/src/resources/fb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/pslab-desktop/5f24d307cfae32c2d86f6fc222528cd26713b2a4/src/resources/fb.png
--------------------------------------------------------------------------------
/src/resources/front_layout.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/pslab-desktop/5f24d307cfae32c2d86f6fc222528cd26713b2a4/src/resources/front_layout.png
--------------------------------------------------------------------------------
/src/resources/git.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/pslab-desktop/5f24d307cfae32c2d86f6fc222528cd26713b2a4/src/resources/git.png
--------------------------------------------------------------------------------
/src/resources/ic_pwm_pic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/pslab-desktop/5f24d307cfae32c2d86f6fc222528cd26713b2a4/src/resources/ic_pwm_pic.png
--------------------------------------------------------------------------------
/src/resources/ic_sin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/pslab-desktop/5f24d307cfae32c2d86f6fc222528cd26713b2a4/src/resources/ic_sin.png
--------------------------------------------------------------------------------
/src/resources/ic_square.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/pslab-desktop/5f24d307cfae32c2d86f6fc222528cd26713b2a4/src/resources/ic_square.png
--------------------------------------------------------------------------------
/src/resources/ic_triangular.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/pslab-desktop/5f24d307cfae32c2d86f6fc222528cd26713b2a4/src/resources/ic_triangular.png
--------------------------------------------------------------------------------
/src/resources/multimeter.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
93 |
--------------------------------------------------------------------------------
/src/resources/oscilloscope_red.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
92 |
--------------------------------------------------------------------------------
/src/resources/power_source.svg:
--------------------------------------------------------------------------------
1 |
2 |
84 |
--------------------------------------------------------------------------------
/src/resources/power_source_red.svg:
--------------------------------------------------------------------------------
1 |
2 |
86 |
--------------------------------------------------------------------------------
/src/resources/t.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/pslab-desktop/5f24d307cfae32c2d86f6fc222528cd26713b2a4/src/resources/t.png
--------------------------------------------------------------------------------
/src/resources/yt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/pslab-desktop/5f24d307cfae32c2d86f6fc222528cd26713b2a4/src/resources/yt.png
--------------------------------------------------------------------------------
/src/screen/AboutUs/AboutUs.styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { Card } from '@material-ui/core';
3 |
4 | export const Container = styled.div`
5 | height: 100%;
6 | width: 100%;
7 | display: flex;
8 | justify-content: center;
9 | overflow-y: auto;
10 | `;
11 |
12 | export const Wrapper = styled(Card)`
13 | margin: 16px 0px;
14 | height: 870px;
15 | width: 95%;
16 | display: flex;
17 | flex-direction: column;
18 | align-items: center;
19 | `;
20 |
--------------------------------------------------------------------------------
/src/screen/AboutUs/index.js:
--------------------------------------------------------------------------------
1 | import AboutUs from './AboutUs';
2 |
3 | export default AboutUs;
4 |
--------------------------------------------------------------------------------
/src/screen/DeviceScreen/DeviceScreen.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import { Container, Wrapper, Steps, Link, Hr } from './DeviceScreen.styles';
4 | import DisconnectedImage from '../../resources/ds_discon.png';
5 | import ConnectedImage from '../../resources/ds_con.png';
6 |
7 | const DeviceScreen = ({ isConnected, deviceInformation }) => {
8 | return isConnected ? (
9 |
10 |
11 |
20 |
27 | Device Connected Successfully
28 |
29 |
36 | {deviceInformation.deviceName}
37 |
38 |
39 | {
41 | window.open(
42 | 'https://pslab.io/',
43 | '_blank',
44 | 'height=650,width=1000,frame=true,show=true',
45 | );
46 | }}
47 | >
48 | What is PSLab Device?
49 |
50 |
51 |
52 | ) : (
53 |
54 |
55 |
64 |
71 | No USB Device Found
72 |
73 |
79 |
80 | Steps to connect the PSLab Device
81 |
82 |
83 |
84 |
89 | 1. Connect the other end of the micro USB cable to a USB adapter
90 |
91 |
96 | 2. Connect the adapter to your PC
97 |
98 |
103 | 3. Start the Desktop app
104 |
105 |
106 |
107 | {
109 | window.open(
110 | 'https://pslab.io/',
111 | '_blank',
112 | 'height=650,width=1000,frame=true,show=true',
113 | );
114 | }}
115 | >
116 | What is PSLab Device?
117 |
118 |
119 |
120 | );
121 | };
122 |
123 | const mapStateToProps = state => ({
124 | isConnected: state.app.device.isConnected,
125 | deviceInformation: state.app.device.deviceInformation,
126 | });
127 |
128 | export default connect(mapStateToProps, null)(DeviceScreen);
129 |
--------------------------------------------------------------------------------
/src/screen/DeviceScreen/DeviceScreen.styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Container = styled.div`
4 | width: 100%;
5 | height: calc(100vh - 52px);
6 | display: flex;
7 | justify-content: center;
8 | align-items: center;
9 | `;
10 |
11 | export const Wrapper = styled.div`
12 | display: flex;
13 | flex-direction: column;
14 | align-items: center;
15 | `;
16 |
17 | export const Steps = styled.div`
18 | display: flex;
19 | flex-direction: column;
20 | font-size: 16px;
21 | margin: 0px 0px 16px 0px;
22 | width: 310px;
23 | `;
24 |
25 | export const Hr = styled.hr`
26 | width: 35%;
27 | `;
28 |
29 | export const Link = styled.div`
30 | color: red;
31 | font-size: 24px;
32 | margin: 20px 0px 0px 0px;
33 |
34 | &:hover {
35 | cursor: pointer;
36 | }
37 | `;
38 |
--------------------------------------------------------------------------------
/src/screen/DeviceScreen/index.js:
--------------------------------------------------------------------------------
1 | import DeviceScreen from './DeviceScreen';
2 |
3 | export default DeviceScreen;
4 |
--------------------------------------------------------------------------------
/src/screen/FAQ/FAQ.styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Container = styled.div`
4 | width: 100%;
5 | display: flex;
6 | justify-content: center;
7 | height: calc(100vh - 52px);
8 | overflow: auto;
9 | z-index: 0;
10 | `;
11 |
12 | export const Wrapper = styled.div`
13 | width: 50%;
14 | margin: 16px 0px 0px 0px;
15 | `;
16 |
17 | export const Link = styled.span`
18 | color: #e12b00;
19 |
20 | &:hover {
21 | cursor: pointer;
22 | }
23 | `;
24 |
--------------------------------------------------------------------------------
/src/screen/FAQ/index.js:
--------------------------------------------------------------------------------
1 | import FAQ from './FAQ';
2 |
3 | export default FAQ;
4 |
--------------------------------------------------------------------------------
/src/screen/Home/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import HomeLayout from '../../components/HomeLayout';
3 | import Tabs from './components/Tabs';
4 |
5 | const Home = props => {
6 | return } />;
7 | };
8 |
9 | export default Home;
10 |
--------------------------------------------------------------------------------
/src/screen/Home/components/InstrumentCard.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 | import {
4 | CustomCard,
5 | ImageWrapper,
6 | ContentContainer,
7 | Title,
8 | Description,
9 | HorizontalBar,
10 | VerticalBar,
11 | } from './InstrumentCard.styles';
12 |
13 | const InstrumentCard = ({ icon, title, description, redirectPath }) => {
14 | return (
15 |
19 |
20 |
21 | {title}
22 | {description}
23 |
24 | {icon}
25 |
26 |
27 |
28 |
29 | );
30 | };
31 |
32 | export default InstrumentCard;
33 |
--------------------------------------------------------------------------------
/src/screen/Home/components/InstrumentCard.styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const CustomCard = styled.div`
4 | position: relative;
5 | display: flex;
6 | width: 48em;
7 | height: 26em;
8 | margin-right: 1em;
9 | margin-bottom: 1em;
10 | transition-timing-function: ease-in-out;
11 | transition-duration: 200ms;
12 | border-radius: 8px;
13 | background-color: ${props => props.theme.primary.main};
14 | box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.2),
15 | 0px 1px 1px 0px rgba(0, 0, 0, 0.14), 0px 2px 1px -1px rgba(0, 0, 0, 0.12);
16 |
17 | &:hover {
18 | cursor: pointer;
19 | box-shadow: 0px 3px 5px -1px rgba(0, 0, 0, 0.2),
20 | 0px 6px 10px 0px rgba(0, 0, 0, 0.14), 0px 1px 18px 0px rgba(0, 0, 0, 0.12);
21 | }
22 | `;
23 |
24 | export const ImageWrapper = styled.div`
25 | display: flex;
26 | justify-content: flex-end;
27 | align-items: flex-end;
28 | width: 40em;
29 | margin: 0px 48px 48px 0px;
30 | `;
31 |
32 | export const ContentContainer = styled.div`
33 | display: flex;
34 | flex-direction: column;
35 | margin: 32px 0px 0px 32px;
36 | `;
37 |
38 | export const Title = styled.div`
39 | font-size: 2.8em;
40 | font-weight: 400;
41 | color: ${props => props.theme.common.white};
42 | `;
43 |
44 | export const Description = styled.div`
45 | font-size: 1.6em;
46 | font-weight: 350;
47 | margin: 32px 0px 0px 0px;
48 | color: ${props => props.theme.common.white};
49 | `;
50 |
51 | export const HorizontalBar = styled.div`
52 | width: 74%;
53 | height: 2em;
54 | z-index: 1;
55 | bottom: 5.5em;
56 | left: 0px;
57 | position: absolute;
58 | border-style: solid;
59 | border-color: #fff;
60 | border-width: 4px 0px 4px 0px;
61 | clip-path: polygon(0 0, 96% 0, 100% 100%, 0 100%);
62 | `;
63 |
64 | export const VerticalBar = styled.div`
65 | width: 2em;
66 | height: 52.5%;
67 | z-index: 1;
68 | top: 0px;
69 | right: 5.5em;
70 | position: absolute;
71 | border-style: solid;
72 | border-color: #fff;
73 | border-width: 0px 4px 0px 4px;
74 | clip-path: polygon(0 0, 100% 0, 100% 100%, 0 89%);
75 | `;
76 |
--------------------------------------------------------------------------------
/src/screen/Home/components/Tabs.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Scrollbars } from 'react-custom-scrollbars';
3 | import InstrumentCard from './InstrumentCard';
4 | import { TabsContainer, TabsContainerInner } from './Tabs.styles';
5 | import {
6 | OscilloscopeIcon,
7 | LogicAnalyzerIcon,
8 | WaveGeneratorIcon,
9 | PowerSourceIcon,
10 | MultimeterIcon,
11 | SensorsIcon,
12 | } from '../../../components/Icons/PSLabIcons';
13 |
14 | const Tabs = () => {
15 | return (
16 |
17 |
18 |
19 | }
21 | title={'Oscilloscope'}
22 | description={'Allows observation of varying signal voltages'}
23 | redirectPath={'/oscilloscope'}
24 | />
25 | }
27 | title={'Logic Analyzer'}
28 | description={'Captures and displays signals from digital systems'}
29 | redirectPath={'/logicanalyzer'}
30 | />
31 | }
33 | title={'Wave Generator'}
34 | description={'Generates arbitrary analog and digital waveforms'}
35 | redirectPath={'/wavegenerator'}
36 | />
37 | }
39 | title={'Power Source'}
40 | description={'Generates programmable voltage and currents'}
41 | redirectPath={'/powersource'}
42 | />
43 | }
45 | title={'Multimeter'}
46 | description={
47 | 'Measure voltage, current, resistance and capacitance.'
48 | }
49 | redirectPath={'/multimeter'}
50 | />
51 | }
53 | title={'Sensors'}
54 | description={'Allows logging of data returned by sensor connected.'}
55 | redirectPath={'/sensors'}
56 | />
57 | }
59 | title={'Robotic Arm'}
60 | description={'To control servo motors using a timeline.'}
61 | redirectPath={'/robotarm'}
62 | />
63 |
64 |
65 |
66 | );
67 | };
68 |
69 | export default Tabs;
70 |
--------------------------------------------------------------------------------
/src/screen/Home/components/Tabs.styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const TabsContainer = styled.div`
4 | height: calc(100% - 16px);
5 | width: 100%;
6 | `;
7 | export const TabsContainerInner = styled.div`
8 | width: 100%;
9 | display: flex;
10 | flex-wrap: wrap;
11 | margin: 1em auto;
12 | justify-content: center;
13 | padding-left: 1em;
14 | @media only screen and (min-width: 1176px) {
15 | justify-content: start;
16 | }
17 | `;
18 | export const TabsWrapper = styled.div`
19 | margin: 16px 16px 0px 0px;
20 | width: 98em;
21 | display: flex;
22 | justify-content: flex-start;
23 | align-items: center;
24 | `;
25 | export const TabsRow = styled.div`
26 | display: flex;
27 | justify-content: center;
28 | align-items: center;
29 | `;
30 |
--------------------------------------------------------------------------------
/src/screen/Home/components/Title.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import AppIcon from '../../../resources/app_icon.svg';
3 | import { TitleContainer, TitleWrapper, TextLabel } from './Title.styles';
4 |
5 | const Title = () => {
6 | return (
7 |
8 |
9 |
17 | Pocket Science Lab
18 |
19 |
20 | );
21 | };
22 |
23 | export default Title;
24 |
--------------------------------------------------------------------------------
/src/screen/Home/components/Title.styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const TitleContainer = styled.div`
4 | display: flex;
5 | align-items: center;
6 | justify-content: center;
7 | height: 100%;
8 | width: 100%;
9 | `;
10 |
11 | export const TitleWrapper = styled.div`
12 | display: flex;
13 | `;
14 |
15 | export const TextLabel = styled.div`
16 | display: flex;
17 | flex: 1;
18 | margin: 0px 0px 0px 16px;
19 | font-size: 4em;
20 | font-weight: 450;
21 | align-items: center;
22 | justify-content: center;
23 | `;
24 |
--------------------------------------------------------------------------------
/src/screen/Home/index.js:
--------------------------------------------------------------------------------
1 | import Home from './Home';
2 |
3 | export default Home;
4 |
--------------------------------------------------------------------------------
/src/screen/Layout/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const ImageContainer = styled.div`
4 | position: relative;
5 | `;
6 |
7 | export const Marker = styled.div`
8 | position: absolute;
9 | top: ${props => props.top}px;
10 | left: ${props => props.left}px;
11 | height: 20px;
12 | width: 20px;
13 | `;
14 |
--------------------------------------------------------------------------------
/src/screen/LoggedData/LoggedData.styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import Card from '@material-ui/core/Card';
3 |
4 | export const Container = styled.div`
5 | display: flex;
6 | width: 100%;
7 | height: 100%;
8 | justify-content: center;
9 | `;
10 |
11 | export const Wrapper = styled.div`
12 | width: 100%;
13 | margin: 16px 0px;
14 | display: flex;
15 | flex-direction: column;
16 | align-items: center;
17 |
18 | & > * + * {
19 | margin: 16px 0px 0px 0px;
20 | }
21 | `;
22 |
23 | export const CustomCard = styled(Card)`
24 | width: calc(50% - 32px);
25 | `;
26 |
27 | export const ContentWrapper = styled.div`
28 | width: 100%;
29 | z-index: 1;
30 | display: flex;
31 | justify-content: center;
32 | cursor: pointer;
33 | `;
34 |
35 | export const ButtonContainer = styled.div`
36 | margin: 0px 16px 0px 0px;
37 | z-index: 4;
38 | display: flex;
39 | justify-content: center;
40 | align-items: center;
41 | height: 9.2em;
42 | width: 9.2em;
43 | `;
44 |
45 | export const Spacer = styled.div`
46 | flex: 1;
47 | `;
48 |
49 | export const TextContainer = styled.div`
50 | height: 9.2em;
51 | display: flex;
52 | flex-direction: column;
53 | margin: 0px 0px 0px 48px;
54 | `;
55 |
56 | export const TitleWrapper = styled.div`
57 | color: ${props => props.theme.text.primary};
58 | font-size: 1.5em;
59 | font-weight: 600;
60 | margin: 16px 0px 0px 0px;
61 | `;
62 |
63 | export const InstrumentWrapper = styled.div`
64 | color: ${props => props.theme.text.secondary};
65 | font-size: 1.1em;
66 | font-weight: 600;
67 | margin: 16px 0px 0px 0px;
68 | `;
69 |
70 | export const InfoContainer = styled.div`
71 | display: flex;
72 | color: ${props => props.theme.text.secondary};
73 | margin: 16px 0px 16px 0px;
74 |
75 | & > * + * {
76 | margin: 0px 0px 0px 32px;
77 | }
78 | `;
79 |
80 | export const EmptyWrapper = styled.div`
81 | display: flex;
82 | width: 100%;
83 | height: calc(100vh - 48px);
84 | justify-content: center;
85 | align-items: center;
86 | font-size: 18px;
87 | `;
88 |
--------------------------------------------------------------------------------
/src/screen/LoggedData/index.js:
--------------------------------------------------------------------------------
1 | import LoggedData from './LoggedData';
2 |
3 | export default LoggedData;
4 |
--------------------------------------------------------------------------------
/src/screen/LogicAnalyzer/components/ActionButtons.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Checkbox, FormControlLabel, Button } from '@material-ui/core';
3 | import { withStyles } from '@material-ui/core/styles';
4 | import {
5 | ActionButtonsWrapper,
6 | ButtonWrapper,
7 | CheckboxWrapper,
8 | } from './ActionButtons.styles';
9 |
10 | const styles = () => ({
11 | buttonMargin: {
12 | margin: '0px 0px 0px 16px',
13 | },
14 | });
15 |
16 | const ActionButtons = ({
17 | isConnected,
18 | isReading,
19 | toggleRead,
20 | isAutoReading,
21 | toggleAutoRead,
22 | classes,
23 | }) => {
24 | return (
25 |
26 |
27 |
37 |
38 |
39 |
42 | }
43 | label="Auto Recapture"
44 | />
45 |
46 |
47 | );
48 | };
49 |
50 | export default withStyles(styles)(ActionButtons);
51 |
--------------------------------------------------------------------------------
/src/screen/LogicAnalyzer/components/ActionButtons.styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { Card } from '@material-ui/core';
3 |
4 | export const ActionButtonsWrapper = styled(Card)`
5 | width: calc(100% - 16px);
6 | margin: 16px;
7 | `;
8 |
9 | export const ButtonWrapper = styled.div`
10 | display: flex;
11 | margin: 16px 16px 0px 16px;
12 | width: calc(100% - 32px);
13 | justify-content: center;
14 | align-items: center;
15 | `;
16 |
17 | export const CheckboxWrapper = styled.div`
18 | display: flex;
19 | justify-content: center;
20 | align-items: center;
21 | `;
22 |
--------------------------------------------------------------------------------
/src/screen/LogicAnalyzer/components/NumberParameter.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import {
3 | Select,
4 | MenuItem,
5 | OutlinedInput,
6 | FormControl,
7 | InputLabel,
8 | } from '@material-ui/core';
9 | import { withStyles } from '@material-ui/core/styles';
10 | import { SettingsWrapper, OptionsRowWrapper } from './Settings.styles';
11 | import { options } from './settingOptions';
12 | import formStyles from '../../../utils/formStyles';
13 |
14 | class NumberParameters extends Component {
15 | render() {
16 | const { numberOfChannels, changeNumberOfChannels, classes } = this.props;
17 |
18 | return (
19 |
20 |
21 |
22 |
26 | Number of Channels
27 |
28 |
37 | }
38 | >
39 | {Object.entries(options.NumberOfChannels).map((item, index) => {
40 | const key = item[0];
41 | const value = item[1];
42 | return (
43 |
46 | );
47 | })}
48 |
49 |
50 |
51 |
52 | );
53 | }
54 | }
55 |
56 | export default withStyles(formStyles)(NumberParameters);
57 |
--------------------------------------------------------------------------------
/src/screen/LogicAnalyzer/components/Settings.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Scrollbars } from 'react-custom-scrollbars';
3 | import {
4 | SettingsContainer,
5 | FixedWrapper,
6 | ScrollWrapper,
7 | } from './Settings.styles';
8 | import NumberParameter from './NumberParameter';
9 | import ChannelParameters from './ChannelParameters';
10 | import TimeParameters from './TimeParameters';
11 | const Settings = ({
12 | numberOfChannels,
13 | channel1Map,
14 | channel2Map,
15 | trigger1Type,
16 | trigger2Type,
17 | trigger3Type,
18 | trigger4Type,
19 | changeNumberOfChannels,
20 | changeChannelMap,
21 | changeTriggerType,
22 | onChangeCaptureTime,
23 | captureTime,
24 | maxCaptureTime,
25 | }) => (
26 |
27 |
28 |
32 |
33 |
34 |
35 |
46 |
51 |
52 |
53 |
54 | );
55 |
56 | export default Settings;
57 |
--------------------------------------------------------------------------------
/src/screen/LogicAnalyzer/components/Settings.styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { Card } from '@material-ui/core';
3 |
4 | export const SettingsContainer = styled.div`
5 | width: 100%;
6 | height: 100%;
7 | display: flex;
8 | flex-direction: column;
9 | margin: 0px 0px 0px 16px;
10 | `;
11 |
12 | export const SettingsWrapper = styled(Card)`
13 | width: calc(100% - 16px);
14 | margin: 8px 0px;
15 | `;
16 |
17 | export const OptionsRowWrapper = styled.div`
18 | display: flex;
19 | align-items: center;
20 | margin: 16px 16px 16px 16px;
21 | min-width: 80%;
22 | `;
23 |
24 | export const GraphWrapper = styled.div`
25 | margin: 16px 0px 0px 16px;
26 | display: flex;
27 | flex-direction: column;
28 | height: calc(100% - 16px);
29 | width: calc(100% - 16px);
30 | background-color: #ffffff;
31 | border: 1px solid #e8e8e8;
32 | color: rgba(0, 0, 0, 0.65);
33 | border-radius: 2px;
34 | `;
35 |
36 | export const ProgressWrapper = styled.div`
37 | width: 100%;
38 | height: 4px;
39 | `;
40 |
41 | export const FixedWrapper = styled.div`
42 | display: flex;
43 | `;
44 |
45 | export const ScrollWrapper = styled.div`
46 | display: flex;
47 | flex: 1;
48 | `;
49 |
--------------------------------------------------------------------------------
/src/screen/LogicAnalyzer/components/TimeParameters.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Typography, Divider } from '@material-ui/core';
3 | import CustomSliderInput from '../../../components/CustomSliderInput';
4 | import { SettingsWrapper, OptionsRowWrapper } from './Settings.styles';
5 |
6 | const inputCheck = value => {
7 | let regex = /(^([0-9]+([.][0-9]*)?|[.][0-9]+)$|^$)/; // non-negative float or blank
8 | return regex.test(value);
9 | };
10 |
11 | class TimeParameters extends Component {
12 | render() {
13 | const { onChangeCaptureTime, captureTime, maxCaptureTime } = this.props;
14 | return (
15 |
16 |
17 | Capture Time
18 |
19 |
20 |
21 |
33 |
34 |
35 | );
36 | }
37 | }
38 | export default TimeParameters;
39 |
--------------------------------------------------------------------------------
/src/screen/LogicAnalyzer/components/settingOptions.js:
--------------------------------------------------------------------------------
1 | export const options = {
2 | NumberOfChannels: {
3 | 1: '1',
4 | 2: '2',
5 | 3: '3',
6 | 4: '4',
7 | },
8 | ChannelMap: {
9 | ID1: 'LA1',
10 | ID2: 'LA2',
11 | ID3: 'LA3',
12 | ID4: 'LA4',
13 | SEN: 'SEN',
14 | EXT: 'EXT',
15 | CNTR: 'CNTR',
16 | },
17 | ChannelTrigger: {
18 | 0: 'Disabled',
19 | 1: 'Every Edge',
20 | 2: 'Falling Edge',
21 | 3: 'Rising Edge',
22 | 4: 'Every 4 Rising Edge',
23 | },
24 | TimeMeasureTrigger: {
25 | 1: 'Rising Edge',
26 | 2: 'Falling Edge',
27 | 3: 'Every 4 Rising Edges',
28 | },
29 | Write: {
30 | 1: '1',
31 | 2: '2',
32 | 3: '3',
33 | 4: '4',
34 | },
35 | };
36 |
--------------------------------------------------------------------------------
/src/screen/LogicAnalyzer/index.js:
--------------------------------------------------------------------------------
1 | import LogicAnalyzer from './LogicAnalyzer';
2 |
3 | export default LogicAnalyzer;
4 |
--------------------------------------------------------------------------------
/src/screen/Multimeter/Multimeter.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import SimplePanelLayout from '../../components/SimplePanelLayout';
4 | import InstrumentCluster from './components/InstrumentCluster';
5 | import debounce from 'lodash/debounce';
6 | import { optionMap } from './components/SettingOptions';
7 | const electron = window.require('electron');
8 | const { ipcRenderer } = electron;
9 | const loadBalancer = window.require('electron-load-balancer');
10 |
11 | class Multimeter extends Component {
12 | constructor(props) {
13 | super(props);
14 | this.state = {
15 | isWriting: false,
16 | isReading: false,
17 | unit: 'V',
18 | activeCategory: 'VOLTAGE',
19 | activeSubType: 'CH1',
20 | parameter: 'PULSE_FREQUENCY',
21 | dialValue: 0,
22 | };
23 | }
24 |
25 | componentDidMount() {
26 | const { startRead, stopRead } = this.props;
27 | ipcRenderer.on('CONNECTION_STATUS_MUL_MET', (event, args) => {
28 | const { isConnected } = args;
29 | isConnected && this.getConfigFromDevice();
30 | isConnected ? startRead('START_MUL_MET') : stopRead('STOP_MUL_MET');
31 | });
32 | ipcRenderer.on('MUL_MET_CONFIG', (event, args) => {
33 | const { activeCategory, activeSubType, parameter } = args;
34 | const dialValue = optionMap[activeSubType].angle;
35 | const unit =
36 | activeCategory === 'PULSE'
37 | ? optionMap[activeSubType].unit[parameter]
38 | : optionMap[activeSubType].unit;
39 | this.setState({
40 | activeCategory,
41 | activeSubType,
42 | parameter,
43 | dialValue,
44 | unit,
45 | });
46 | });
47 | this.getConfigFromDevice();
48 | startRead('START_MUL_MET');
49 | }
50 |
51 | componentWillUnmount() {
52 | const { stopRead, stopWriting } = this.props;
53 | stopRead('STOP_MUL_MET');
54 | stopWriting();
55 | ipcRenderer.removeAllListeners('MUL_MET_CONFIG');
56 | ipcRenderer.removeAllListeners('CONNECTION_STATUS_MUL_MET');
57 | }
58 |
59 | getConfigFromDevice = debounce(() => {
60 | const { isConnected } = this.props;
61 | isConnected &&
62 | loadBalancer.sendData(ipcRenderer, 'linker', {
63 | command: 'GET_CONFIG_MUL_MET',
64 | });
65 | }, 500);
66 |
67 | sendConfigToDevice = debounce(() => {
68 | const { isConnected } = this.props;
69 | const { activeCategory, activeSubType, parameter } = this.state;
70 | isConnected &&
71 | loadBalancer.sendData(ipcRenderer, 'linker', {
72 | command: 'SET_CONFIG_MUL_MET',
73 | activeCategory,
74 | activeSubType,
75 | parameter,
76 | });
77 | }, 500);
78 |
79 | onTogglePulseUnit = () => {
80 | let { activeSubType, parameter } = this.state;
81 | parameter =
82 | parameter === 'PULSE_FREQUENCY' ? 'PULSE_COUNT' : 'PULSE_FREQUENCY';
83 | const unit = optionMap[activeSubType].unit[parameter];
84 | this.setState(
85 | prevState => ({
86 | parameter,
87 | unit,
88 | }),
89 | () => {
90 | this.sendConfigToDevice();
91 | },
92 | );
93 | };
94 |
95 | onClickButton = activeSubType => () => {
96 | this.changeOption(activeSubType);
97 | };
98 |
99 | changeDialValue = dialValue => {
100 | this.setState({
101 | dialValue,
102 | });
103 | };
104 |
105 | changeOption = activeSubType => {
106 | const { parameter } = this.state;
107 | const activeCategory = optionMap[activeSubType].category;
108 | const dialValue = optionMap[activeSubType].angle;
109 | const unit =
110 | activeCategory === 'PULSE'
111 | ? optionMap[activeSubType].unit[parameter]
112 | : optionMap[activeSubType].unit;
113 | this.setState(
114 | {
115 | data: 0,
116 | activeCategory,
117 | activeSubType,
118 | parameter,
119 | dialValue,
120 | unit,
121 | },
122 | () => {
123 | this.sendConfigToDevice();
124 | },
125 | );
126 | };
127 |
128 | render() {
129 | const { activeSubType, data, unit, dialValue, activeCategory } = this.state;
130 | const { isReading } = this.props;
131 | return (
132 |
146 | }
147 | />
148 | );
149 | }
150 | }
151 |
152 | const mapStateToProps = state => ({
153 | isConnected: state.app.device.isConnected,
154 | });
155 |
156 | export default connect(mapStateToProps, null)(Multimeter);
157 |
--------------------------------------------------------------------------------
/src/screen/Multimeter/components/Dial.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { IconButton } from '@material-ui/core';
3 | import { DialContainer, IconWrapper, DialWrapper } from './Dial.styles';
4 | import { withTheme } from 'styled-components';
5 | import CustomKnob from '../../../components/CustomKnob';
6 | import { iconMap, optionsOrder, angleMap } from './SettingOptions';
7 |
8 | const Dial = ({
9 | value,
10 | onClickButton,
11 | changeDialValue,
12 | changeOption,
13 | activeSubType,
14 | theme,
15 | }) => {
16 | const onChangeDial = value => {
17 | let dialValue = Math.round(value);
18 | Object.keys(angleMap).map(angle => {
19 | const compAngle = parseInt(angle, 10);
20 | if (compAngle - 5 <= dialValue && dialValue < compAngle + 5) {
21 | dialValue = compAngle;
22 | }
23 | return null;
24 | });
25 | const activeSubType = angleMap[dialValue];
26 | if (activeSubType) {
27 | changeOption(activeSubType);
28 | } else {
29 | changeDialValue(dialValue);
30 | }
31 | };
32 |
33 | const itemList = iconMap(activeSubType, theme);
34 |
35 | return (
36 |
37 | {optionsOrder.map((subType, index) => {
38 | const item = itemList[subType];
39 | return (
40 |
46 |
47 | {item.icon}
48 |
49 |
50 | );
51 | })}
52 |
53 |
61 |
62 |
63 | );
64 | };
65 |
66 | export default withTheme(Dial);
67 |
--------------------------------------------------------------------------------
/src/screen/Multimeter/components/Dial.styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const DialContainer = styled.div`
4 | position: relative;
5 | margin: 16px;
6 | display: flex;
7 | width: 400px;
8 | height: 400px;
9 | z-index: 999;
10 |
11 | & > * {
12 | position: absolute;
13 | top: 50%;
14 | left: 50%;
15 | transform: translateX(-50%) translateY(-50%);
16 | z-index: 9999;
17 | }
18 | `;
19 |
20 | export const DialWrapper = styled.div`
21 | z-index: 999;
22 | background: white;
23 | border-radius: 50%;
24 | `;
25 |
26 | export const IconWrapper = styled.div`
27 | min-width: 58px;
28 | min-height: 58px;
29 | `;
30 |
--------------------------------------------------------------------------------
/src/screen/Multimeter/components/InstrumentCluster.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Switch, Card } from '@material-ui/core';
3 | import {
4 | InstrumentContainer,
5 | DisplayContainer,
6 | SwitchWrapper,
7 | Backdrop,
8 | TopSection,
9 | BottomSection,
10 | ResSection,
11 | WaveSection,
12 | } from './InstrumentCluster.styles';
13 | import Dial from './Dial';
14 | import { withTheme, withStyles } from '@material-ui/core/styles';
15 | import MeasurementDisplay from './MeasurementDisplay';
16 |
17 | const styles = () => ({
18 | cardMargin: {
19 | display: 'flex',
20 | flexDirection: 'column',
21 | justifyContent: 'center',
22 | },
23 | });
24 |
25 | const InstrumentCluster = ({
26 | activeSubType,
27 | onClickButton,
28 | changeDialValue,
29 | changeOption,
30 | dialValue,
31 | unit,
32 | isReading,
33 | onTogglePulseUnit,
34 | classes,
35 | activeCategory,
36 | }) => {
37 | return (
38 |
39 |
40 |
41 |
48 |
49 | Voltage
50 |
51 |
52 | Measure
53 |
54 |
55 |
62 |
63 | Hz
64 |
69 | Count Pulse
70 |
71 |
72 |
73 |
74 | );
75 | };
76 |
77 | export default withTheme(withStyles(styles)(InstrumentCluster));
78 |
--------------------------------------------------------------------------------
/src/screen/Multimeter/components/InstrumentCluster.styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const InstrumentContainer = styled.div`
4 | display: flex;
5 | flex-direction: column;
6 | `;
7 |
8 | export const DisplayContainer = styled.div`
9 | margin: 0px 0px 8px 0px;
10 | `;
11 |
12 | export const DisplayWrapper = styled.div`
13 | display: flex;
14 | justify-content: flex-end;
15 | align-items: center;
16 | padding: 0px 16px 0px 0px;
17 | `;
18 |
19 | export const TextIcon = styled.div`
20 | font-size: 16px;
21 | font-weight: 900;
22 | height: 34px;
23 | width: 34px;
24 | vertical-align: middle;
25 | line-height: 34px;
26 | color: ${props =>
27 | props.active ? props.theme.secondary.dark : props.theme.text.hint};
28 | `;
29 |
30 | export const SwitchWrapper = styled.div`
31 | display: flex;
32 | align-items: center;
33 | justify-content: flex-start;
34 | margin: 0px 0px 0px 32px;
35 | `;
36 |
37 | export const Backdrop = styled.div`
38 | position: absolute;
39 | height: 590px;
40 | width: 432px;
41 | display: flex;
42 | flex-direction: column;
43 | `;
44 |
45 | export const TopSection = styled.div`
46 | height: 226px;
47 | margin: 16px 16px 4px 16px;
48 | border: 3px solid red;
49 | border-radius: 4px;
50 | display: flex;
51 | justify-content: center;
52 | align-items: flex-start;
53 | font-size: 18px;
54 | color: red;
55 | padding: 8px 0px 0px 0px;
56 | `;
57 |
58 | export const BottomSection = styled.div`
59 | flex: 1;
60 | display: flex;
61 | `;
62 |
63 | export const ResSection = styled.div`
64 | margin: 4px 16px 16px 4px;
65 | border: 3px solid black;
66 | border-radius: 4px;
67 | width: 100px;
68 | display: flex;
69 | justify-content: center;
70 | align-items: flex-end;
71 | font-size: 18px;
72 | color: black;
73 | padding: 0px 0px 8px 0px;
74 | `;
75 |
76 | export const WaveSection = styled.div`
77 | margin: 4px 4px 16px 16px;
78 | border: 3px solid black;
79 | border-radius: 4px;
80 | flex: 1;
81 | `;
82 |
--------------------------------------------------------------------------------
/src/screen/Multimeter/components/MeasurementDisplay.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Card, LinearProgress } from '@material-ui/core';
3 | import { DisplayContainer, DisplayWrapper } from './InstrumentCluster.styles';
4 | import Display from '../../../components/Display';
5 | const electron = window.require('electron');
6 | const { ipcRenderer } = electron;
7 |
8 | class MeasurementDisplay extends Component {
9 | constructor(props) {
10 | super(props);
11 | this.state = {
12 | prefix: null,
13 | data: 0,
14 | };
15 | }
16 |
17 | componentDidMount() {
18 | ipcRenderer.on('MUL_MET_DATA', (event, args) => {
19 | const { isReading } = this.props;
20 | isReading &&
21 | this.setState({
22 | ...args,
23 | });
24 | });
25 | }
26 |
27 | componentWillUnmount() {
28 | ipcRenderer.removeAllListeners('MUL_MET_DATA');
29 | }
30 |
31 | render() {
32 | const { isReading, unit } = this.props;
33 | const { data, prefix } = this.state;
34 |
35 | return (
36 |
37 |
38 | {isReading && }
39 |
40 |
45 |
46 |
47 |
48 | );
49 | }
50 | }
51 |
52 | export default MeasurementDisplay;
53 |
--------------------------------------------------------------------------------
/src/screen/Multimeter/index.js:
--------------------------------------------------------------------------------
1 | import Multimeter from './Multimeter';
2 |
3 | export default Multimeter;
4 |
--------------------------------------------------------------------------------
/src/screen/Oscilloscope/components/ActionButtons.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | Pause as StopReadIcon,
4 | PlayCircleFilled as ReadIcon,
5 | RadioButtonChecked as RecordIcon,
6 | Stop as StopIcon,
7 | } from '@material-ui/icons';
8 | import { Button } from '@material-ui/core';
9 | import { withStyles } from '@material-ui/core/styles';
10 | import { ButtonWrapper } from './ActionButtons.styles';
11 |
12 | const styles = () => ({
13 | buttonMargin: {
14 | margin: '0px 0px 0px 16px',
15 | },
16 | });
17 |
18 | const ActionButtons = ({
19 | isConnected,
20 | isReading,
21 | isWriting,
22 | onToggleRead,
23 | onToggleWrite,
24 | classes,
25 | }) => {
26 | return (
27 |
28 |
42 |
57 |
58 | );
59 | };
60 |
61 | export default withStyles(styles)(ActionButtons);
62 |
--------------------------------------------------------------------------------
/src/screen/Oscilloscope/components/ActionButtons.styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const ButtonWrapper = styled.div`
4 | display: flex;
5 | margin: 16px;
6 | width: calc(100% - 32px);
7 | justify-content: center;
8 | align-items: center;
9 | `;
10 |
--------------------------------------------------------------------------------
/src/screen/Oscilloscope/components/FFTGraph.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import {
3 | ResponsiveContainer,
4 | Line,
5 | LineChart,
6 | XAxis,
7 | YAxis,
8 | CartesianGrid,
9 | Tooltip,
10 | Legend,
11 | Label,
12 | } from 'recharts';
13 | import { withTheme } from 'styled-components';
14 | import { GraphWrapper } from './Settings.styles';
15 |
16 | const electron = window.require('electron');
17 | const { ipcRenderer } = electron;
18 |
19 | class Graph extends Component {
20 | constructor(props) {
21 | super(props);
22 | this.state = {
23 | fftData: [
24 | {
25 | ch1: 0,
26 | ch2: 0,
27 | ch3: 0,
28 | ch4: 0,
29 | frequency: 0,
30 | },
31 | ],
32 | };
33 | }
34 |
35 | componentDidMount() {
36 | ipcRenderer.on('OSC_FFT_DATA', (event, args) => {
37 | const { isReading } = this.props;
38 | isReading &&
39 | this.setState({
40 | fftData: args.data,
41 | });
42 | });
43 | }
44 |
45 | componentWillUnmount() {
46 | ipcRenderer.removeAllListeners('OSC_FFT_DATA');
47 | }
48 |
49 | render() {
50 | const { activeChannels, theme } = this.props;
51 | const { fftData } = this.state;
52 |
53 | return (
54 |
55 |
56 |
65 |
66 |
67 |
68 |
69 |
70 |
75 |
76 |
77 | {activeChannels.ch1 && (
78 |
86 | )}
87 | {activeChannels.ch2 && (
88 |
96 | )}
97 | {activeChannels.ch3 && (
98 |
106 | )}
107 | {activeChannels.mic && (
108 |
116 | )}
117 |
118 |
119 |
120 | );
121 | }
122 | }
123 |
124 | export default withTheme(Graph);
125 |
--------------------------------------------------------------------------------
/src/screen/Oscilloscope/components/FitPanel.styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const PanelContainer = styled.div`
4 | display: flex;
5 | margin: 16px 0px 0px 16px;
6 | width: calc(100% - 16px);
7 |
8 | & > * + * {
9 | margin: 0px 0px 0px 16px;
10 | }
11 | `;
12 |
13 | export const ValueWrapper = styled.div`
14 | min-width: 5.8em;
15 | display: flex;
16 | flex-direction: column;
17 | margin: 16px 8px;
18 | align-items: center;
19 | justify-content: center;
20 | cursor: pointer;
21 |
22 | & > * + * {
23 | margin: 8px 0px 0px 0px;
24 | }
25 | `;
26 |
27 | export const DisplayContainer = styled.div`
28 | display: flex;
29 | `;
30 |
--------------------------------------------------------------------------------
/src/screen/Oscilloscope/components/Graph.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import {
3 | ResponsiveContainer,
4 | Line,
5 | LineChart,
6 | XAxis,
7 | YAxis,
8 | CartesianGrid,
9 | Tooltip,
10 | Legend,
11 | Label,
12 | } from 'recharts';
13 | import { withTheme } from 'styled-components';
14 | import { GraphWrapper } from './Settings.styles';
15 |
16 | const electron = window.require('electron');
17 | const { ipcRenderer } = electron;
18 |
19 | class Graph extends Component {
20 | constructor(props) {
21 | super(props);
22 | this.state = {
23 | oscData: [
24 | {
25 | ch1: 0,
26 | ch2: 0,
27 | ch3: 0,
28 | ch4: 0,
29 | time: 0,
30 | },
31 | ],
32 | };
33 | }
34 |
35 | componentDidMount() {
36 | ipcRenderer.on('OSC_VOLTAGE_DATA', (event, args) => {
37 | const { isReading } = this.props;
38 | isReading &&
39 | this.setState({
40 | oscData: args.data,
41 | });
42 | });
43 | }
44 |
45 | componentWillUnmount() {
46 | ipcRenderer.removeAllListeners('OSC_VOLTAGE_DATA');
47 | }
48 |
49 | render() {
50 | const { activeChannels, timeBase, channelRanges, theme } = this.props;
51 | const { oscData } = this.state;
52 |
53 | return (
54 |
55 |
56 |
65 |
66 |
72 |
73 |
74 |
83 |
92 |
93 |
94 | {activeChannels.ch1 && (
95 |
103 | )}
104 | {activeChannels.ch2 && (
105 |
113 | )}
114 | {activeChannels.ch3 && (
115 |
123 | )}
124 | {activeChannels.mic && (
125 |
133 | )}
134 |
135 |
136 |
137 | );
138 | }
139 | }
140 |
141 | export default withTheme(Graph);
142 |
--------------------------------------------------------------------------------
/src/screen/Oscilloscope/components/PlotParameters.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import {
3 | Checkbox,
4 | Select,
5 | Typography,
6 | Divider,
7 | FormControlLabel,
8 | MenuItem,
9 | OutlinedInput,
10 | FormControl,
11 | InputLabel,
12 | } from '@material-ui/core';
13 | import { withStyles } from '@material-ui/core/styles';
14 | import { SettingsWrapper, OptionsRowWrapper } from './Settings.styles';
15 | import { options } from './settingOptions';
16 | import formStyles from '../../../utils/formStyles';
17 |
18 | class AnalysisParameters extends Component {
19 | render() {
20 | const {
21 | isXYPlotActive,
22 | plotChannel1,
23 | plotChannel2,
24 | onToggleCheckBox,
25 | onChangePlotChannel,
26 | classes,
27 | } = this.props;
28 |
29 | return (
30 |
31 |
32 | XY Plot
33 |
34 |
35 |
36 |
42 | }
43 | label="Enable XY Plot"
44 | />
45 |
46 |
47 |
52 |
56 | Channel 1
57 |
58 |
67 | }
68 | >
69 | {Object.entries(options.Select).map((item, index) => {
70 | const key = item[0];
71 | const value = item[1];
72 | return (
73 |
76 | );
77 | })}
78 |
79 |
80 |
86 |
90 | Channel 2
91 |
92 |
101 | }
102 | >
103 | {Object.entries(options.Select).map((item, index) => {
104 | const key = item[0];
105 | const value = item[1];
106 | return (
107 |
110 | );
111 | })}
112 |
113 |
114 |
115 |
116 | );
117 | }
118 | }
119 |
120 | export default withStyles(formStyles)(AnalysisParameters);
121 |
--------------------------------------------------------------------------------
/src/screen/Oscilloscope/components/Settings.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Scrollbars } from 'react-custom-scrollbars';
3 | import {
4 | SettingsContainer,
5 | FixedWrapper,
6 | ScrollWrapper,
7 | } from './Settings.styles';
8 | import ChannelParameters from './ChannelParameters';
9 | import TimeParameters from './TimeParameters';
10 | import AnalysisParameters from './AnalysisParameters';
11 | import PlotParameters from './PlotParameters';
12 |
13 | const Settings = ({
14 | timeBaseIndex,
15 | timeBase,
16 | activeChannels,
17 | channelRanges,
18 | isTriggerActive,
19 | channelMaps,
20 | triggerVoltage,
21 | triggerChannel,
22 | isFourierTransformActive,
23 | fitType,
24 | fitChannel1,
25 | fitChannel2,
26 | isXYPlotActive,
27 | plotChannel1,
28 | plotChannel2,
29 | onToggleChannel,
30 | onChangeChannelRange,
31 | onChangeChannelMap,
32 | onToggleCheckBox,
33 | onChangeTriggerVoltage,
34 | onChangeTriggerChannel,
35 | onChangeTimeBaseIndex,
36 | timeBaseListLength,
37 | onChangeFitType,
38 | onChangeFitChannel,
39 | onChangePlotChannel,
40 | }) => (
41 |
42 |
43 |
51 |
52 |
53 |
54 |
66 |
75 |
82 |
83 |
84 |
85 | );
86 |
87 | export default Settings;
88 |
--------------------------------------------------------------------------------
/src/screen/Oscilloscope/components/Settings.styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { Card } from '@material-ui/core';
3 |
4 | export const SettingsContainer = styled.div`
5 | width: 100%;
6 | height: 100%;
7 | display: flex;
8 | flex-direction: column;
9 | margin: 0px 0px 0px 16px;
10 | `;
11 |
12 | export const SettingsWrapper = styled(Card)`
13 | width: calc(100% - 16px);
14 | margin: 8px 0px;
15 | `;
16 |
17 | export const OptionsRowWrapper = styled.div`
18 | display: flex;
19 | align-items: center;
20 | margin: 16px 16px 16px 16px;
21 | min-width: 80%;
22 | `;
23 |
24 | export const GraphWrapper = styled.div`
25 | margin: 16px 0px 0px 16px;
26 | display: flex;
27 | height: calc(100% - 16px);
28 | width: calc(100% - 16px);
29 | background-color: #ffffff;
30 | border: 1px solid #e8e8e8;
31 | color: rgba(0, 0, 0, 0.65);
32 | border-radius: 2px;
33 | `;
34 |
35 | export const FixedWrapper = styled.div`
36 | display: flex;
37 | `;
38 |
39 | export const ScrollWrapper = styled.div`
40 | display: flex;
41 | flex: 1;
42 | `;
43 |
--------------------------------------------------------------------------------
/src/screen/Oscilloscope/components/TimeParameters.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import {
3 | Checkbox,
4 | Select,
5 | Typography,
6 | Divider,
7 | FormControlLabel,
8 | MenuItem,
9 | OutlinedInput,
10 | FormControl,
11 | InputLabel,
12 | } from '@material-ui/core';
13 | import { withStyles } from '@material-ui/core/styles';
14 | import CustomSliderInput from '../../../components/CustomSliderInput';
15 | import { SettingsWrapper, OptionsRowWrapper } from './Settings.styles';
16 | import { options } from './settingOptions';
17 | import formStyles from '../../../utils/formStyles';
18 |
19 | class TimeParameters extends Component {
20 | render() {
21 | const {
22 | triggerVoltage,
23 | timeBaseListLength,
24 | timeBaseIndex,
25 | timeBase,
26 | triggerChannel,
27 | isTriggerActive,
28 | onToggleCheckBox,
29 | onChangeTriggerVoltage,
30 | onChangeTriggerChannel,
31 | onChangeTimeBaseIndex,
32 | classes,
33 | } = this.props;
34 | return (
35 |
36 |
37 | Timebase and Trigger
38 |
39 |
40 |
41 |
47 | }
48 | label="Trigger"
49 | />
50 |
55 |
59 | Channel
60 |
61 |
70 | }
71 | >
72 | {Object.entries(options.Select).map((item, index) => {
73 | const key = item[0];
74 | const value = item[1];
75 | return (
76 |
79 | );
80 | })}
81 |
82 |
83 |
84 |
85 |
97 |
98 |
99 |
111 |
112 |
113 | );
114 | }
115 | }
116 |
117 | export default withStyles(formStyles)(TimeParameters);
118 |
--------------------------------------------------------------------------------
/src/screen/Oscilloscope/components/XYPlotGraph.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import {
3 | ResponsiveContainer,
4 | Line,
5 | LineChart,
6 | XAxis,
7 | YAxis,
8 | CartesianGrid,
9 | Label,
10 | } from 'recharts';
11 | import { withTheme } from 'styled-components';
12 | import { GraphWrapper } from './Settings.styles';
13 |
14 | const electron = window.require('electron');
15 | const { ipcRenderer } = electron;
16 |
17 | class XYPlotGraph extends Component {
18 | constructor(props) {
19 | super(props);
20 | this.state = {
21 | xyPlotData: [
22 | {
23 | plotChannel1: 0,
24 | plotChannel2: 0,
25 | },
26 | ],
27 | };
28 | }
29 |
30 | componentDidMount() {
31 | ipcRenderer.on('OSC_XY_PLOT_DATA', (event, args) => {
32 | const { isReading } = this.props;
33 | isReading &&
34 | args.data &&
35 | this.setState({
36 | xyPlotData: args.data,
37 | });
38 | });
39 | }
40 |
41 | componentWillUnmount() {
42 | ipcRenderer.removeAllListeners('OSC_XY_PLOT_DATA');
43 | }
44 |
45 | render() {
46 | const { theme, plotChannel1, plotChannel2 } = this.props;
47 | const { xyPlotData } = this.state;
48 |
49 | return (
50 |
51 |
52 |
61 |
62 |
68 |
69 |
70 |
76 |
77 |
85 |
86 |
87 |
88 | );
89 | }
90 | }
91 |
92 | export default withTheme(XYPlotGraph);
93 |
--------------------------------------------------------------------------------
/src/screen/Oscilloscope/components/settingOptions.js:
--------------------------------------------------------------------------------
1 | export const options = {
2 | Range1: {
3 | '16': '+/-16V',
4 | '8': '+/-8V',
5 | '4': '+/-4V',
6 | '3': '+/-3V',
7 | '2': '+/-2V',
8 | '1': '+/-1V',
9 | '0.5': '+/-500V',
10 | '160': '+/-160V',
11 | },
12 | Map1: {
13 | CH1: 'CH1',
14 | CH2: 'CH2',
15 | CH3: 'CH3',
16 | MIC: 'MIC',
17 | CAP: 'CAP',
18 | SEN: 'SEN',
19 | AN8: 'AN8',
20 | },
21 | Mic: {
22 | Microphone: 'Microphone',
23 | Inbuilt: 'In-Built Mic',
24 | },
25 | Select: {
26 | CH1: 'CH1',
27 | CH2: 'CH2',
28 | CH3: 'CH3',
29 | MIC: 'MIC',
30 | },
31 | DataAnalysisSelect: {
32 | None: 'None',
33 | CH1: 'CH1',
34 | CH2: 'CH2',
35 | CH3: 'CH3',
36 | MIC: 'MIC',
37 | },
38 | FitSelect: {
39 | Sine: 'Sine Fit',
40 | Square: 'Square Fit',
41 | },
42 | };
43 |
--------------------------------------------------------------------------------
/src/screen/Oscilloscope/index.js:
--------------------------------------------------------------------------------
1 | import Oscilloscope from './Oscilloscope';
2 |
3 | export default Oscilloscope;
4 |
--------------------------------------------------------------------------------
/src/screen/PowerSource/PowerSource.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import { withRouter } from 'react-router-dom';
4 | import SimplePanelLayout from '../../components/SimplePanelLayout';
5 | import InstrumentCluster from './components/InstrumentCluster';
6 | import roundOff from '../../utils/arithmetics';
7 | import debounce from 'lodash/debounce';
8 | const electron = window.require('electron');
9 | const { ipcRenderer } = electron;
10 | const loadBalancer = window.require('electron-load-balancer');
11 |
12 | class PowerSouce extends Component {
13 | constructor(props) {
14 | super(props);
15 | this.state = {
16 | pv1: -5,
17 | pv2: -3.3,
18 | pv3: 0,
19 | pcs: 0,
20 | };
21 | }
22 |
23 | componentDidMount() {
24 | ipcRenderer.on('CONNECTION_STATUS', (event, args) => {
25 | const { isConnected } = args;
26 | isConnected && this.getConfigFromDevice();
27 | });
28 | ipcRenderer.on('PWR_SRC_CONFIG', (event, args) => {
29 | const { pv1, pv2, pv3, pcs } = args;
30 | this.setState({
31 | pv1: roundOff(pv1),
32 | pv2: roundOff(pv2),
33 | pv3: roundOff(pv3),
34 | pcs: roundOff(pcs),
35 | });
36 | });
37 |
38 | const { filePath } = this.props.match.params;
39 | filePath ? this.getConfigFromFile() : this.getConfigFromDevice();
40 | }
41 |
42 | componentWillUnmount() {
43 | ipcRenderer.removeAllListeners('PWR_SRC_CONFIG');
44 | }
45 |
46 | getConfigFromFile = debounce(() => {
47 | const { filePath } = this.props.match.params;
48 | const { isConnected, dataPath } = this.props;
49 | isConnected &&
50 | loadBalancer.sendData(ipcRenderer, 'linker', {
51 | command: 'GET_CONFIG_PWR_SRC_FILE',
52 | dataPath: `${dataPath}/${filePath}`,
53 | });
54 | }, 500);
55 |
56 | getConfigFromDevice = debounce(() => {
57 | const { isConnected } = this.props;
58 | isConnected &&
59 | loadBalancer.sendData(ipcRenderer, 'linker', {
60 | command: 'GET_CONFIG_PWR_SRC',
61 | });
62 | }, 500);
63 |
64 | sendConfigToDevice = debounce(() => {
65 | const { isConnected } = this.props;
66 | isConnected &&
67 | loadBalancer.sendData(ipcRenderer, 'linker', {
68 | command: 'SET_CONFIG_PWR_SRC',
69 | ...this.state,
70 | });
71 | }, 500);
72 |
73 | onChangeSlider = pinType => value => {
74 | this.setState(
75 | {
76 | [pinType]: roundOff(parseFloat(value)),
77 | },
78 | () => {
79 | this.sendConfigToDevice();
80 | },
81 | );
82 | };
83 |
84 | onPressButton = (pinType, isPositive) => event => {
85 | this.setState(
86 | {
87 | [pinType]: roundOff(this.state[pinType] + (isPositive ? 0.01 : -0.01)),
88 | },
89 | () => {
90 | this.sendConfigToDevice();
91 | },
92 | );
93 | };
94 |
95 | render() {
96 | const { pv1, pv2, pv3, pcs } = this.state;
97 |
98 | return (
99 |
109 | }
110 | />
111 | );
112 | }
113 | }
114 |
115 | const mapStateToProps = state => ({
116 | isConnected: state.app.device.isConnected,
117 | });
118 |
119 | export default withRouter(connect(mapStateToProps, null)(PowerSouce));
120 |
--------------------------------------------------------------------------------
/src/screen/PowerSource/components/InstrumentCluster.styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const CardContainer = styled.div`
4 | display: flex;
5 | flex-direction: row;
6 |
7 | & > * + * {
8 | margin: 0px 0px 0px 32px;
9 | }
10 | `;
11 |
12 | export const CardColumnWrapper = styled.div`
13 | display: flex;
14 | flex-direction: column;
15 |
16 | & > * + * {
17 | margin: 32px 0px 0px 0px;
18 | }
19 | `;
20 |
21 | export const ButtonContainer = styled.div`
22 | display: flex;
23 | flex-direction: row;
24 | align-items: center;
25 | justify-content: center;
26 | margin: 16px 16px 16px 16px;
27 | `;
28 |
29 | export const DisplayContainer = styled.div`
30 | display: flex;
31 | flex-direction: column;
32 | `;
33 |
34 | export const CircularInputContainer = styled.div`
35 | display: flex;
36 | margin: 16px 0px 24px 24px;
37 | `;
38 |
39 | export const InstrumentContainer = styled.div`
40 | display: flex;
41 | flex-direction: row;
42 | margin: 16px;
43 | `;
44 |
45 | export const ValueWrapper = styled.div`
46 | min-width: 24em;
47 | display: flex;
48 | flex-direction: column;
49 | margin: 16px 16px 16px 16px;
50 | align-items: center;
51 | justify-content: center;
52 | cursor: pointer;
53 | `;
54 |
--------------------------------------------------------------------------------
/src/screen/PowerSource/index.js:
--------------------------------------------------------------------------------
1 | import PowerSource from './PowerSource';
2 |
3 | export default PowerSource;
4 |
--------------------------------------------------------------------------------
/src/screen/RobotArm/Components/PaintArea.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | PaintContainer,
4 | PaintRow,
5 | PaintCell,
6 | ValueWrapper,
7 | IndexWrapper,
8 | } from './styles';
9 |
10 | const PaintArea = ({
11 | brush1,
12 | brush2,
13 | brush3,
14 | brush4,
15 | servo1,
16 | servo2,
17 | servo3,
18 | servo4,
19 | activeBrush,
20 | setServoValues,
21 | timeLine,
22 | }) => {
23 | return (
24 |
25 |
26 | {servo1.map((item, index) => {
27 | return (
28 |
31 | activeBrush === 'servo1' &&
32 | setServoValues(brush1, 'servo1', index)
33 | }
34 | >
35 |
39 | {item !== null ? `${item}°` : ' '}
40 | {`${index + 1}s`}
41 |
42 |
43 | );
44 | })}
45 |
46 |
47 | {servo2.map((item, index) => {
48 | return (
49 |
52 | activeBrush === 'servo2' &&
53 | setServoValues(brush2, 'servo2', index)
54 | }
55 | >
56 |
57 | {item !== null ? `${item}°` : ' '}
58 | {`${index + 1}s`}
59 |
60 |
61 | );
62 | })}
63 |
64 |
65 | {servo3.map((item, index) => {
66 | return (
67 |
70 | activeBrush === 'servo3' &&
71 | setServoValues(brush3, 'servo3', index)
72 | }
73 | >
74 |
75 | {item !== null ? `${item}°` : ' '}
76 | {`${index + 1}s`}
77 |
78 |
79 | );
80 | })}
81 |
82 |
83 | {servo4.map((item, index) => {
84 | return (
85 |
88 | activeBrush === 'servo4' &&
89 | setServoValues(brush4, 'servo4', index)
90 | }
91 | >
92 |
93 | {item !== null ? `${item}°` : ' '}
94 | {`${index + 1}s`}
95 |
96 |
97 | );
98 | })}
99 |
100 |
101 | );
102 | };
103 |
104 | export default PaintArea;
105 |
--------------------------------------------------------------------------------
/src/screen/RobotArm/Components/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const KnobCellWrapper = styled.div`
4 | width: 100%;
5 | display: flex;
6 | `;
7 |
8 | export const KnobCell = styled.div`
9 | flex: 1;
10 | border: 1px solid #d9dadb;
11 | display: flex;
12 | flex-direction: column;
13 | `;
14 |
15 | export const Title = styled.div`
16 | width: 100%;
17 | display: flex;
18 | justify-content: center;
19 | `;
20 |
21 | export const TitleText = styled.div`
22 | margin: 16px 0px 16px 16px;
23 | font-size: 18px;
24 | color: ${props => props.theme.text.secondary};
25 | `;
26 |
27 | export const ButtonWrapper = styled.div`
28 | margin: 16px 16px 16px 0px;
29 |
30 | &:hover {
31 | cursor: pointer;
32 | }
33 | `;
34 |
35 | export const Spacer = styled.div`
36 | flex: 1;
37 | `;
38 |
39 | export const InputWrapper = styled.div`
40 | display: flex;
41 | justify-content: center;
42 | align-items: center;
43 | flex: 1;
44 | margin: 16px 16px 32px 16px;
45 | `;
46 |
47 | export const PaintContainer = styled.div`
48 | display: flex;
49 | flex-direction: column;
50 | overflow-x: auto;
51 | `;
52 |
53 | export const PaintRow = styled.div`
54 | display: flex;
55 | width: 9600px;
56 | `;
57 |
58 | export const PaintCell = styled.div`
59 | background: #000000;
60 | border: 2px solid #ffffff;
61 | border-top-color: ${props =>
62 | props.active ? props.theme.primary.main : '#fff'};
63 | width: 160px;
64 | height: 90px;
65 | display: flex;
66 | flex-direction: column;
67 | `;
68 |
69 | export const ValueWrapper = styled.div`
70 | width: calc(100% - 32px);
71 | height: 21px;
72 | margin: 16px;
73 | font-size: 28px;
74 | color: ${props => props.theme.common.white};
75 | `;
76 |
77 | export const IndexWrapper = styled.div`
78 | width: calc(100% - 32px);
79 | margin: 16px;
80 | font-size: 14px;
81 | color: ${props => props.theme.common.white};
82 | text-align: right;
83 | `;
84 |
85 | export const TimeControlPanel = styled.div`
86 | margin: 4px 0px 4px 0px;
87 | width: 64px;
88 | background: ${props => props.theme.primary.main};
89 | display: flex;
90 | flex-direction: column;
91 | justify-content: center;
92 | `;
93 |
--------------------------------------------------------------------------------
/src/screen/RobotArm/RobotArm.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import { Container, KnobWrapper, PaintWrapper } from './RobotArm.styles';
4 | import PaintArea from './Components/PaintArea';
5 | import KnobControl from './Components/KnobControl';
6 | import range from 'lodash/range';
7 |
8 | const electron = window.require('electron');
9 | const { ipcRenderer } = electron;
10 | const loadBalancer = window.require('electron-load-balancer');
11 |
12 | class RobotArm extends Component {
13 | constructor(props) {
14 | super(props);
15 | this.state = {
16 | brush1: 0,
17 | brush2: 0,
18 | brush3: 0,
19 | brush4: 0,
20 | servo1: range(60).map(item => null),
21 | servo2: range(60).map(item => null),
22 | servo3: range(60).map(item => null),
23 | servo4: range(60).map(item => null),
24 | activeBrush: null,
25 | active: false,
26 | timeLine: 0,
27 | };
28 | this.timer = null;
29 | }
30 |
31 | componentDidMount() {
32 | ipcRenderer.on('FETCH_ROB_ARM', (event, args) => {
33 | const { servo1, servo2, servo3, servo4 } = this.state;
34 | const { dataPath } = this.props;
35 | loadBalancer.sendData(ipcRenderer, 'playback', {
36 | command: 'WRITE_ROB_ARM',
37 | servo1,
38 | servo2,
39 | servo3,
40 | servo4,
41 | dataPath,
42 | });
43 | setTimeout(() => {
44 | loadBalancer.stop(ipcRenderer, 'playback');
45 | }, 500);
46 | });
47 | }
48 |
49 | componentWillUnmount() {
50 | ipcRenderer.removeAllListeners('FETCH_ROB_ARM');
51 | }
52 |
53 | sendCommand = () => {
54 | const { active, timeLine, servo1, servo2, servo3, servo4 } = this.state;
55 | if (timeLine < 60) {
56 | active &&
57 | this.setState(
58 | prevState => ({ timeLine: prevState.timeLine + 1 }),
59 | () => {
60 | loadBalancer.sendData(ipcRenderer, 'linker', {
61 | command: 'SET_ROBO_ARM',
62 | angle1: servo1[timeLine],
63 | angle2: servo2[timeLine],
64 | angle3: servo3[timeLine],
65 | angle4: servo4[timeLine],
66 | });
67 | },
68 | );
69 | } else {
70 | this.stop();
71 | }
72 | };
73 |
74 | start = () => {
75 | this.setState(
76 | {
77 | active: true,
78 | },
79 | () => {
80 | if (this.timer === null) {
81 | this.timer = setInterval(this.sendCommand, 1000);
82 | }
83 | },
84 | );
85 | };
86 |
87 | pause = () => {
88 | this.setState({
89 | active: false,
90 | });
91 | };
92 |
93 | stop = () => {
94 | clearInterval(this.timer);
95 | this.timer = null;
96 | this.setState({
97 | active: false,
98 | timeLine: 0,
99 | });
100 | };
101 |
102 | changeBrushValue = brushNumber => value => {
103 | this.setState({
104 | [brushNumber]: value,
105 | });
106 | };
107 |
108 | setActiveBrush = value => {
109 | this.setState({
110 | activeBrush: value,
111 | });
112 | };
113 |
114 | setServoValues = (value, servoType, index) => {
115 | let newArray = [...this.state[servoType]];
116 | newArray[index] = value;
117 | this.setState({
118 | [servoType]: newArray,
119 | });
120 | };
121 |
122 | render() {
123 | const {
124 | brush1,
125 | brush2,
126 | brush3,
127 | brush4,
128 | servo1,
129 | servo2,
130 | servo3,
131 | servo4,
132 | activeBrush,
133 | timeLine,
134 | active,
135 | } = this.state;
136 |
137 | const { isConnected } = this.props;
138 |
139 | return (
140 |
141 |
142 |
155 |
156 |
157 |
170 |
171 |
172 | );
173 | }
174 | }
175 |
176 | const mapStateToProps = state => ({
177 | isConnected: state.app.device.isConnected,
178 | });
179 |
180 | export default connect(mapStateToProps, null)(RobotArm);
181 |
--------------------------------------------------------------------------------
/src/screen/RobotArm/RobotArm.styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Container = styled.div`
4 | width: 100%;
5 | height: 100%;
6 | display: flex;
7 | flex-direction: column;
8 | justify-content: center;
9 | `;
10 |
11 | export const KnobWrapper = styled.div`
12 | width: 100%;
13 | `;
14 |
15 | export const PaintWrapper = styled.div`
16 | width: 100%;
17 | `;
18 |
--------------------------------------------------------------------------------
/src/screen/RobotArm/index.js:
--------------------------------------------------------------------------------
1 | import RobotArm from './RobotArm';
2 |
3 | export default RobotArm;
4 |
--------------------------------------------------------------------------------
/src/screen/Sensors/Sensors.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { withRouter } from 'react-router-dom';
3 | import { connect } from 'react-redux';
4 | import debounce from 'lodash/debounce';
5 | import Button from '@material-ui/core/Button';
6 | import {
7 | Container,
8 | Wrapper,
9 | HelpText,
10 | SecondaryContentWrapper,
11 | TitleWrapper,
12 | ScrollWrapper,
13 | SensorTab,
14 | SensorTitle,
15 | } from './styles';
16 |
17 | const electron = window.require('electron');
18 | const { ipcRenderer } = electron;
19 | const loadBalancer = window.require('electron-load-balancer');
20 |
21 | // see PSL/sensorlist.py for the mapping
22 | const knownSensors = [
23 | {
24 | id: 0x48, // TODO: check duplicate definitions
25 | name: 'ADS1115',
26 | },
27 | {
28 | id: 0x77,
29 | name: 'BMP180',
30 | },
31 | {
32 | id: 0x5a,
33 | name: 'MLX90614',
34 | },
35 | {
36 | id: 0x1e,
37 | name: 'HMC5883L',
38 | },
39 | {
40 | id: 0x68,
41 | name: 'MPU6050',
42 | },
43 | {
44 | id: 0x40,
45 | name: 'SHT21',
46 | },
47 | {
48 | id: 0x49,
49 | name: 'TSL2561',
50 | },
51 | ];
52 |
53 | const filterKnownSensors = (detectedSensors = []) => {
54 | // PSL always lists 0 and 96, regardless of whether a sensor is present
55 | const filtered = detectedSensors.filter(s => s !== 96 && s !== 0);
56 | return knownSensors.filter(k => filtered.includes(k.id));
57 | };
58 |
59 | const SensorList = ({ sensors = [], onReadSensor = () => {} }) => (
60 |
61 | {sensors.map((item, index) => {
62 | return (
63 | onReadSensor(item)}>
64 | {item.name}
65 |
66 | );
67 | })}
68 |
69 | );
70 |
71 | // TODO: implement reading from sensor
72 |
73 | class Sensors extends Component {
74 | constructor(props) {
75 | super(props);
76 | this.state = {
77 | isScanned: false,
78 | sensorList: [],
79 | data: null,
80 | };
81 | }
82 |
83 | componentDidMount() {
84 | // TODO: Other components are implemented this way. Check how it applies
85 | // here.
86 | /*
87 | ipcRenderer.on('CONNECTION_STATUS', (event, args) => {
88 | const { isConnected } = args;
89 | isConnected && this.getConfigFromDevice();
90 | });
91 | */
92 | ipcRenderer.on('SENSORS_SCAN', (event, args) => {
93 | this.setState({
94 | data: args.data,
95 | isScanned: true,
96 | sensorList: filterKnownSensors(args.data),
97 | });
98 | });
99 | // this.getConfigFromDevice();
100 | }
101 |
102 | componentWillUnmount() {
103 | ipcRenderer.removeAllListeners('SENSORS_SCAN');
104 | }
105 |
106 | getConfigFromDevice = debounce(() => {
107 | const { isConnected } = this.props;
108 | isConnected &&
109 | loadBalancer.sendData(ipcRenderer, 'linker', {
110 | command: 'GET_CONFIG_SENSORS',
111 | });
112 | }, 500);
113 |
114 | scan = debounce(() => {
115 | const { isConnected } = this.props;
116 | isConnected &&
117 | loadBalancer.sendData(ipcRenderer, 'linker', {
118 | command: 'SENSORS_SCAN',
119 | });
120 | }, 500);
121 | render() {
122 | const { data, isScanned, sensorList } = this.state;
123 |
124 | return (
125 |
126 |
127 |
130 |
131 | {!isScanned && sensorList.length === 0
132 | ? 'Use Autoscan button to find connected sensors to PSLab device'
133 | : 'Scan complete'}
134 |
135 |
136 |
137 | {isScanned &&
138 | (sensorList.length > 0 ? (
139 | <>
140 | Detected sensors
141 | console.info({ sensor })}
144 | />
145 | >
146 | ) : (
147 | No sensors detected
148 | ))}
149 | Known sensors
150 |
151 | {JSON.stringify(data)}
152 |
153 |
154 | );
155 | }
156 | }
157 |
158 | const mapStateToProps = state => ({
159 | isConnected: state.app.device.isConnected,
160 | });
161 |
162 | export default withRouter(connect(mapStateToProps, null)(Sensors));
163 |
--------------------------------------------------------------------------------
/src/screen/Sensors/index.js:
--------------------------------------------------------------------------------
1 | import Sensors from './Sensors';
2 |
3 | export default Sensors;
4 |
--------------------------------------------------------------------------------
/src/screen/Sensors/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Container = styled.div`
4 | height: 100%;
5 | display: flex;
6 | flex-direction: column;
7 | align-items: center;
8 | `;
9 |
10 | export const Wrapper = styled.div`
11 | width: 60%;
12 | flex: 1;
13 | display: flex;
14 | flex-direction: column;
15 | align-items: center;
16 | overflow-y: auto;
17 | `;
18 |
19 | export const HelpText = styled.div`
20 | padding: 16px;
21 | border: 1px solid grey;
22 | border-radius: 4px;
23 | margin: 16px 0px 0px 0px;
24 | `;
25 |
26 | export const SecondaryContentWrapper = styled.div`
27 | margin: 16px 0px 0px 0px;
28 | display: flex;
29 | flex-direction: column;
30 | align-items: center;
31 | `;
32 |
33 | export const TitleWrapper = styled.div`
34 | margin: 32px 0px 0px 0px;
35 | font-size: 1.5em;
36 | color: ${props => props.theme.text.primary};
37 | `;
38 |
39 | export const ScrollWrapper = styled.div`
40 | margin: 16px 0px 0px 0px;
41 | `;
42 |
43 | export const SensorTab = styled.div`
44 | margin: 16px;
45 | height: 42px;
46 | width: 340px;
47 | display: flex;
48 | justify-content: center;
49 | transition-timing-function: ease-in-out;
50 | transition-duration: 200ms;
51 | border-radius: 2px;
52 | background-color: ${props => props.theme.primary.main};
53 | box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.2),
54 | 0px 1px 1px 0px rgba(0, 0, 0, 0.14), 0px 2px 1px -1px rgba(0, 0, 0, 0.12);
55 |
56 | &:hover {
57 | cursor: pointer;
58 | box-shadow: 0px 3px 5px -1px rgba(0, 0, 0, 0.2),
59 | 0px 6px 10px 0px rgba(0, 0, 0, 0.14), 0px 1px 18px 0px rgba(0, 0, 0, 0.12);
60 | }
61 | `;
62 |
63 | export const SensorTitle = styled.div`
64 | color: ${props => props.theme.common.white};
65 | font-size: 1.2em;
66 | font-weight: 600;
67 | vertical-align: center;
68 | height: 42px;
69 | line-height: 42px;
70 | `;
71 |
--------------------------------------------------------------------------------
/src/screen/Settings/Settings.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Dialog from '@material-ui/core/Dialog';
3 | import DialogTitle from '@material-ui/core/DialogTitle';
4 | import Radio from '@material-ui/core/Radio';
5 | import RadioGroup from '@material-ui/core/RadioGroup';
6 | import FormControlLabel from '@material-ui/core/FormControlLabel';
7 | import FormControl from '@material-ui/core/FormControl';
8 | import {
9 | Container,
10 | Wrapper,
11 | CustomCard,
12 | ContentWrapper,
13 | SettingMain,
14 | SettingSub,
15 | } from './Settings.styles';
16 | import { Scrollbars } from 'react-custom-scrollbars';
17 |
18 | class Settings extends Component {
19 | constructor(props) {
20 | super(props);
21 | this.state = {
22 | isDialogOpen: false,
23 | dataFormat: 'csv',
24 | };
25 | this.activeSetting = null;
26 | }
27 |
28 | handleChangeDataFormat = event => {
29 | this.setState({
30 | dataFormat: event.target.value,
31 | });
32 | };
33 |
34 | dialogContentRenderer = () => {
35 | const { dataFormat } = this.state;
36 | switch (this.activeSetting) {
37 | case 'WRITE_TYPE':
38 | return (
39 |
40 |
41 | Export Data Format
42 |
43 |
49 |
58 | } label="CSV" />
59 |
60 |
61 |
62 | );
63 |
64 | default:
65 | break;
66 | }
67 | };
68 |
69 | handleOpen = type => () => {
70 | this.setState({
71 | isDialogOpen: true,
72 | });
73 | this.activeSetting = 'WRITE_TYPE';
74 | };
75 |
76 | handleClose = () => {
77 | this.setState({
78 | isDialogOpen: false,
79 | });
80 | this.activeSetting = null;
81 | };
82 |
83 | render() {
84 | const { isDialogOpen } = this.state;
85 | return (
86 |
87 |
90 |
91 |
92 |
93 |
94 | Export Data Format
95 | Current format is CSV format
96 |
97 |
98 |
99 |
100 |
101 | );
102 | }
103 | }
104 |
105 | export default Settings;
106 |
--------------------------------------------------------------------------------
/src/screen/Settings/Settings.styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { Card } from '@material-ui/core';
3 |
4 | export const Container = styled.div`
5 | display: flex;
6 | width: 100%;
7 | height: 100%;
8 | justify-content: center;
9 | `;
10 |
11 | export const Wrapper = styled.div`
12 | width: 100%;
13 | margin: 16px 0px;
14 | display: flex;
15 | flex-direction: column;
16 | align-items: center;
17 | `;
18 |
19 | export const CustomCard = styled(Card)`
20 | width: calc(50% - 32px);
21 | `;
22 |
23 | export const ContentWrapper = styled.div`
24 | width: 100%;
25 | z-index: 1;
26 | display: flex;
27 | flex-direction: column;
28 | margin: 16px 16px 16px 16px;
29 | cursor: pointer;
30 | `;
31 |
32 | export const SettingMain = styled.div`
33 | font-size: 16px;
34 | margin: 0px 0px 4px 0px;
35 | `;
36 |
37 | export const SettingSub = styled.div`
38 | font-size: 12px;
39 | color: gray;
40 | `;
41 |
--------------------------------------------------------------------------------
/src/screen/Settings/index.js:
--------------------------------------------------------------------------------
1 | import Settings from './Settings';
2 |
3 | export default Settings;
4 |
--------------------------------------------------------------------------------
/src/screen/WaveGenerator/components/InstrumentCluster.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import SineWavePanel from './SineWavePanel';
3 | import SquareWavePanel from './SquareWavePanel';
4 | import { SettingsContainer } from './InstrumentCluster.styles';
5 |
6 | const InstrumentCluster = () => {
7 | return (
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | );
17 | };
18 |
19 | export default InstrumentCluster;
20 |
--------------------------------------------------------------------------------
/src/screen/WaveGenerator/components/Settings.styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { Card } from '@material-ui/core';
3 |
4 | export const SettingsContainer = styled.div`
5 | width: 100%;
6 | height: calc(100% - 16px);
7 | display: flex;
8 | flex-direction: column;
9 | margin: 8px 0px 0px 16px;
10 | `;
11 |
12 | export const SettingsWrapper = styled(Card)`
13 | width: calc(100% - 16px);
14 | margin: 8px 0px;
15 | `;
16 |
17 | export const OptionsRowWrapper = styled.div`
18 | display: flex;
19 | align-items: center;
20 | margin: 16px 16px 16px 16px;
21 | min-width: 80%;
22 | `;
23 |
24 | export const TitleWrapper = styled.div`
25 | display: flex;
26 | `;
27 |
28 | export const Spacer = styled.div`
29 | flex: 1;
30 | `;
31 |
32 | export const Wrapper = styled.div`
33 | width: 1208px;
34 | `;
35 |
36 | export const MainContainer = styled.div`
37 | display: flex;
38 | `;
39 |
40 | export const DisplayWrapper = styled.div`
41 | width: 650px;
42 | height: 500px;
43 | margin: 16px 0px 0px 16px;
44 | background: #000;
45 | display: flex;
46 | flex-direction: column;
47 | `;
48 |
49 | export const InformationRow1 = styled.div`
50 | display: flex;
51 | border-bottom: 1px solid #fff;
52 | `;
53 |
54 | export const InformationRow2 = styled.div`
55 | display: flex;
56 | flex: 1;
57 | `;
58 |
59 | export const InformationRow3 = styled.div`
60 | display: flex;
61 | border-top: 1px solid #fff;
62 | `;
63 |
64 | export const WaveMarker = styled.div`
65 | height: 100px;
66 | color: ${props => (props.active ? '#ffcc80' : '#fff')};
67 | flex: 1;
68 | text-align: center;
69 | font-size: 18px;
70 | line-height: 100px;
71 | vertical-align: middle;
72 | `;
73 |
74 | export const WaveDetails = styled.div`
75 | height: 100px;
76 | color: #ffcc80;
77 | flex: 1;
78 | text-align: center;
79 | font-size: 24px;
80 | line-height: 100px;
81 | vertical-align: middle;
82 | `;
83 |
84 | export const WaveType = styled.div`
85 | width: 250px;
86 | display: flex;
87 | justify-content: center;
88 | align-items: center;
89 | font-size: 24px;
90 | margin: 0px 16px;
91 | `;
92 |
93 | export const InfoList = styled.div`
94 | display: flex;
95 | flex-direction: column;
96 | flex: 1;
97 | border-left: 1px solid #fff;
98 | font-size: 22px;
99 | justify-content: center;
100 | `;
101 |
102 | export const InfoText = styled.div`
103 | color: #fff;
104 | text-align: left;
105 | `;
106 |
107 | export const ControllerWrapper = styled.div`
108 | display: flex;
109 | flex-direction: column;
110 | margin: 16px;
111 | `;
112 |
113 | export const ButtonRow = styled.div`
114 | display: flex;
115 | margin: 16px 16px;
116 | `;
117 |
118 | export const TextWrapper = styled.div`
119 | display: flex;
120 | justify-content: center;
121 | align-items: center;
122 | `;
123 |
124 | export const Title = styled.div`
125 | color: ${props => props.theme.primary.main};
126 | font-size: 24px;
127 | text-align: center;
128 | `;
129 |
130 | export const BorderMaker = styled.div`
131 | border: 2px solid;
132 | border-color: ${props => props.theme.primary.main};
133 | border-radius: 4px;
134 | padding: 16px 8px;
135 | height: 138px;
136 | width: 510px;
137 | `;
138 |
139 | export const SliderContainer = styled.div`
140 | height: 40px;
141 | margin: 16px;
142 | display: flex;
143 | align-items: center;
144 | `;
145 |
146 | export const SliderWrapper = styled.div`
147 | height: 10px;
148 | margin: 0px 0px 0px 16px;
149 | flex: 1;
150 | `;
151 |
152 | export const ButtonContainer = styled.div`
153 | margin: 0px 0px 0px 16px;
154 | `;
155 |
--------------------------------------------------------------------------------
/src/screen/WaveGenerator/components/settingOptions.js:
--------------------------------------------------------------------------------
1 | export const options = {
2 | WaveForm: {
3 | sine: 'Sinusoidal',
4 | tria: 'Triangular',
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/src/screen/WaveGenerator/index.js:
--------------------------------------------------------------------------------
1 | import WaveGenerator from './WaveGenerator';
2 |
3 | export default WaveGenerator;
4 |
--------------------------------------------------------------------------------
/src/screen/WaveGenerator/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Container = styled.div`
4 | width: 100%;
5 | height: 100%;
6 | display: flex;
7 | justify-content: center;
8 | align-items: center;
9 | background: ${props => props.theme.background.default};
10 | `;
11 |
12 | export const Wrapper = styled.div``;
13 |
--------------------------------------------------------------------------------
/src/theme.js:
--------------------------------------------------------------------------------
1 | import { createMuiTheme } from '@material-ui/core/styles';
2 |
3 | const theme = createMuiTheme({
4 | palette: {
5 | common: {
6 | black: '#000',
7 | white: '#fff',
8 | },
9 | background: {
10 | paper: 'rgba(255, 255, 255, 1)',
11 | default: 'rgba(181, 181, 181, 0.2)',
12 | },
13 | primary: {
14 | light: '#ff6659',
15 | main: '#d32f2f',
16 | dark: '#9a0007',
17 | contrastText: '#fff',
18 | },
19 | secondary: {
20 | light: '#757de8',
21 | main: '#3f51b5',
22 | dark: '#002984',
23 | contrastText: '#fff',
24 | },
25 | error: {
26 | light: '#e57373',
27 | main: '#f44336',
28 | dark: '#d32f2f',
29 | contrastText: '#fff',
30 | },
31 | text: {
32 | primary: 'rgba(0, 0, 0, 0.87)',
33 | secondary: 'rgba(0, 0, 0, 0.54)',
34 | disabled: 'rgba(0, 0, 0, 0.38)',
35 | hint: 'rgba(0, 0, 0, 0.38)',
36 | },
37 | linkColor: '#1890ff',
38 | headingColor: 'rgba(0, 0, 0, .85)',
39 | disabledColor: 'rgba(0, 0, 0, .25)',
40 | gradient1:
41 | 'linear-gradient(90deg, rgba(207,72,139,1) 5%, rgba(101,101,163,1) 48%, rgba(79,77,175,1) 100%)',
42 | iconBackground: '#ffffff',
43 | navigationBackground: '#bdbdbd',
44 | ch1Color: '#ff9800',
45 | ch2Color: '#03a9f4',
46 | ch3Color: '#4caf50',
47 | micColor: '#5e35b1',
48 | s1Color: '#ff9800',
49 | s2Color: '#03a9f4',
50 | sqr1Color: '#4caf50',
51 | sqr2Color: '#5e35b1',
52 | sqr3Color: '#ff9800',
53 | sqr4Color: '#03a9f4',
54 | },
55 | });
56 |
57 | export default theme;
58 |
--------------------------------------------------------------------------------
/src/utils/arithmetics.js:
--------------------------------------------------------------------------------
1 | export default function roundOff(num, roundTo = 2) {
2 | num = parseFloat(num);
3 | return +num.toFixed(roundTo);
4 | }
5 |
--------------------------------------------------------------------------------
/src/utils/fileNameProcessor.js:
--------------------------------------------------------------------------------
1 | exports.fileNameTrimmer = (n, len) => {
2 | const ext = n.substring(n.lastIndexOf('.') + 1, n.length).toLowerCase();
3 | let filename = n.replace('.' + ext, '');
4 | if (filename.length <= len) {
5 | return n;
6 | }
7 | filename = filename.substr(0, len) + (n.length > len ? '...' : '');
8 | return filename + '.' + ext;
9 | };
10 |
--------------------------------------------------------------------------------
/src/utils/formStyles.js:
--------------------------------------------------------------------------------
1 | // The label background color is a workaround for issues with line through text
2 | // see https://github.com/mui-org/material-ui/issues/14530#issuecomment-463576879
3 | // The padding and negative margin allow for a little spacing whilst retaining
4 | // the alignment.
5 | export default theme => ({
6 | formControl: {
7 | margin: '0px 0px 0px 16px',
8 | },
9 | label: {
10 | margin: '0 -4px',
11 | padding: '0 4px',
12 | backgroundColor: 'white',
13 | },
14 | });
15 |
--------------------------------------------------------------------------------
/utils/preProcessor.js:
--------------------------------------------------------------------------------
1 | exports.oscilloscopeVoltageProcessor = parsedJSON => {
2 | const { data } = parsedJSON;
3 | const { keys } = parsedJSON;
4 | const { numberOfChannels } = parsedJSON;
5 | const numberOfDataPoints = data.length;
6 |
7 | let parsedOutput = [];
8 |
9 | switch (numberOfChannels) {
10 | case 1:
11 | try {
12 | for (let index = 0; index < numberOfDataPoints; index += 1) {
13 | parsedOutput = [
14 | ...parsedOutput,
15 | {
16 | [keys[0]]: data[index][0],
17 | [keys[1]]: data[index][1],
18 | },
19 | ];
20 | }
21 | } catch (error) {
22 | return false;
23 | }
24 | break;
25 | case 2:
26 | try {
27 | for (let index = 0; index < numberOfDataPoints; index += 1) {
28 | parsedOutput = [
29 | ...parsedOutput,
30 | {
31 | [keys[0]]: data[index][0],
32 | [keys[1]]: data[index][1],
33 | [keys[2]]: data[index][2],
34 | },
35 | ];
36 | }
37 | } catch (error) {
38 | return false;
39 | }
40 |
41 | break;
42 | case 3:
43 | try {
44 | for (let index = 0; index < numberOfDataPoints; index += 1) {
45 | parsedOutput = [
46 | ...parsedOutput,
47 | {
48 | [keys[0]]: data[index][0],
49 | [keys[1]]: data[index][1],
50 | [keys[2]]: data[index][2],
51 | [keys[3]]: data[index][3],
52 | },
53 | ];
54 | }
55 | } catch (error) {
56 | return false;
57 | }
58 |
59 | break;
60 | case 4:
61 | try {
62 | for (let index = 0; index < numberOfDataPoints; index += 1) {
63 | parsedOutput = [
64 | ...parsedOutput,
65 | {
66 | [keys[0]]: data[index][0],
67 | [keys[1]]: data[index][1],
68 | [keys[2]]: data[index][2],
69 | [keys[3]]: data[index][3],
70 | [keys[4]]: data[index][4],
71 | },
72 | ];
73 | }
74 | } catch (error) {
75 | return false;
76 | }
77 |
78 | break;
79 | default:
80 | break;
81 | }
82 |
83 | return parsedOutput;
84 | };
85 |
86 | exports.oscilloscopeXYProcessor = parsedJSON => {
87 | const { data } = parsedJSON;
88 | const { keys } = parsedJSON;
89 | const numberOfDataPoints = data.length;
90 |
91 | let parsedOutput = [];
92 | if (data[0][0] === undefined || data[0][1] === undefined) {
93 | return false;
94 | }
95 | for (let index = 0; index < numberOfDataPoints; index += 1) {
96 | parsedOutput = [
97 | ...parsedOutput,
98 | {
99 | [keys[0]]: data[index][0],
100 | [keys[1]]: data[index][1],
101 | },
102 | ];
103 | }
104 |
105 | return parsedOutput;
106 | };
107 |
108 | exports.LAProcessor = parsedJSON => {
109 | const {
110 | time1,
111 | voltage1,
112 | time2,
113 | voltage2,
114 | time3,
115 | voltage3,
116 | time4,
117 | voltage4,
118 | } = parsedJSON;
119 | const { numberOfChannels } = parsedJSON;
120 |
121 | let parsedOutput = {
122 | LA1Data: null,
123 | LA2Data: null,
124 | LA3Data: null,
125 | LA4Data: null,
126 | };
127 | if (numberOfChannels >= 1) {
128 | parsedOutput.LA1Data = [];
129 | time1.map((time, index) => {
130 | parsedOutput.LA1Data = [
131 | ...parsedOutput.LA1Data,
132 | {
133 | voltage: voltage1[index],
134 | time,
135 | },
136 | ];
137 | });
138 | }
139 | if (numberOfChannels >= 2) {
140 | parsedOutput.LA2Data = [];
141 | time2.map((time, index) => {
142 | parsedOutput.LA2Data = [
143 | ...parsedOutput.LA2Data,
144 | {
145 | voltage: voltage2[index],
146 | time,
147 | },
148 | ];
149 | });
150 | }
151 | if (numberOfChannels >= 3) {
152 | parsedOutput.LA3Data = [];
153 | time3.map((time, index) => {
154 | parsedOutput.LA3Data = [
155 | ...parsedOutput.LA3Data,
156 | {
157 | voltage: voltage3[index],
158 | time,
159 | },
160 | ];
161 | });
162 | }
163 | if (numberOfChannels >= 4) {
164 | parsedOutput.LA4Data = [];
165 | time4.map((time, index) => {
166 | parsedOutput.LA4Data = [
167 | ...parsedOutput.LA4Data,
168 | {
169 | voltage: voltage4[index],
170 | time,
171 | },
172 | ];
173 | });
174 | }
175 |
176 | return parsedOutput;
177 | };
178 |
--------------------------------------------------------------------------------