├── .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 | 9 | 13 | 14 | 15 | 19 | 23 | 24 | 25 | 29 | 33 | 34 | 35 | 39 | 43 | 44 | 45 | 49 | 53 | 54 | 55 | 59 | 63 | 64 | 65 | 69 | 70 |
6 |
7 | The home screen that showcases all our instruments. 8 |
10 |
11 | A four channel oscilloscope with analysis features. 12 |
16 |
17 | A four channel Logic Analyzer for digital outputs. 18 |
20 |
21 | A full fledged multimeter that can measure voltage, frequency, resistance etc. 22 |
26 |
27 | Instrument used to generate analoge and digital waves of different frequencies, phase and duty cycles. 28 |
30 |
31 | A programmable voltage and current source to drive your external circuits. 32 |
36 |
37 | An intutive interface to control 4 servo motor robot arm. 38 |
40 |
41 | Save data and config in CSV and retrieve them back later. 42 |
46 |
47 | The app drawer gives you access to many more options like the FAQ, Device Information Screen etc. 48 |
50 |
51 | A settings page for configuring your app as per your requirement. 52 |
56 |
57 | A device information screen that shows device information and guide to connect it to the app. 58 |
60 |
61 | A new FAQ screen that covers all generic as well as app specific questions that a user may have. 62 |
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 |
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 | 128 | {title && {title}} 129 | {renderDialogContent()} 130 | {renderDialogAction()} 131 | 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 | 15 | 16 | 20 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 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 | 15 | 16 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 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 | Oscilloscope 25 | ); 26 | }; 27 | 28 | export const LogicAnalyzerIcon = ({ size, color }) => { 29 | return ( 30 | Logic Analyzer 38 | ); 39 | }; 40 | 41 | export const PowerSourceIcon = ({ size, color }) => { 42 | return ( 43 | Power Source 51 | ); 52 | }; 53 | 54 | export const WaveGeneratorIcon = ({ size, color }) => { 55 | return ( 56 | Wave Generator 64 | ); 65 | }; 66 | 67 | export const MultimeterIcon = ({ size, color }) => { 68 | return ( 69 | Multimeter 77 | ); 78 | }; 79 | 80 | export const SensorsIcon = ({ size, color }) => { 81 | return ( 82 | Sensors 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 | 19 | 39 | 41 | 43 | 44 | 46 | image/svg+xml 47 | 49 | 50 | 51 | 52 | 53 | 58 | 63 | 68 | 73 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /src/resources/compass_red.svg: -------------------------------------------------------------------------------- 1 | 2 | 20 | 45 | 47 | 49 | 50 | 52 | image/svg+xml 53 | 55 | 56 | 57 | 58 | 59 | 63 | 72 | 78 | 85 | 92 | 100 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /src/resources/device_connected.svg: -------------------------------------------------------------------------------- 1 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /src/resources/device_disconnected.svg: -------------------------------------------------------------------------------- 1 | 5 | 10 | 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 | 19 | 37 | 39 | 41 | 42 | 44 | image/svg+xml 45 | 47 | 48 | 49 | 50 | 51 | 56 | 61 | 65 | 70 | 75 | 80 | 81 | 86 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/resources/oscilloscope_red.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 40 | 42 | 44 | 45 | 47 | image/svg+xml 48 | 50 | 51 | 52 | 53 | 54 | 59 | 63 | 69 | 74 | 79 | 84 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /src/resources/power_source.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 37 | 39 | 41 | 42 | 44 | image/svg+xml 45 | 47 | 48 | 49 | 50 | 51 | 55 | 62 | 66 | 71 | 72 | 77 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /src/resources/power_source_red.svg: -------------------------------------------------------------------------------- 1 | 2 | 20 | 45 | 47 | 49 | 50 | 52 | image/svg+xml 53 | 55 | 56 | 57 | 58 | 59 | 63 | 72 | 78 | 83 | 84 | 85 | 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 | App Icon 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 | 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 | 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 | 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 | 79 | 80 | 86 | 90 | Channel 2 91 | 92 | 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 | 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 | 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 | 88 | {this.dialogContentRenderer()} 89 | 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 | --------------------------------------------------------------------------------