Comparar commits
309 Commits
development
...
patch-1
| Autor | SHA1 | Data | |
|---|---|---|---|
| 4a1b0a8028 | |||
| 16129254c3 | |||
| dd1eab12cc | |||
| 72877da087 | |||
| 47ce0d9910 | |||
| 3a6283b440 | |||
| b43720113a | |||
| a44f76a101 | |||
| 4c4171bc3a | |||
| f2095a0614 | |||
| 8dd516ae73 | |||
| 790a1e49ff | |||
| 84544eac61 | |||
| 3653a15a8a | |||
| e251c865c0 | |||
| 1336c31441 | |||
| 9b0953ad2d | |||
| 856e584eb3 | |||
| e15ac89ce3 | |||
| 72adb8071a | |||
| e478ae2489 | |||
| 016c702662 | |||
| b3b8c6dd0c | |||
| 05cbf3acbf | |||
| 4538e42a6b | |||
| f05fffbd52 | |||
| 05f3156659 | |||
| 41ea23ee54 | |||
| ecd2b7f613 | |||
| 5ac8adb629 | |||
| 028fe0964b | |||
| 7d90ad3906 | |||
| 86bac7de19 | |||
| e7b5c71cfb | |||
| 979ce4dc36 | |||
| 3557a20e6e | |||
| 96778b75af | |||
| bc8ad100dc | |||
| ede04a9beb | |||
| 3e51be21df | |||
| 650087c9ec | |||
| 3964bb8258 | |||
| ad039f382d | |||
| 017a040107 | |||
| 0c6ea968d9 | |||
| d23eb24222 | |||
| edcb42b1e0 | |||
| 91e1a3be34 | |||
| 08ee608d91 | |||
| 59e7788a0e | |||
| eec308c23c | |||
| aa8c8a2c0e | |||
| 30bf9cd9a1 | |||
| d1b717d171 | |||
| 8dd02edc67 | |||
| 3625dc9d04 | |||
| 8fff2a7633 | |||
| dcca513d35 | |||
| a8a1cfe91e | |||
| bc47e6e2dc | |||
| 559ab19549 | |||
| 3252b15f55 | |||
| a095ed157b | |||
| cb8a093a0f | |||
| 522ffe36ce | |||
| d8060e7343 | |||
| 5f9d781c2b | |||
| 51402ddc88 | |||
| 765111b22f | |||
| 8e88f7aecc | |||
| 554d909b4a | |||
| 0004277117 | |||
| 832671dee1 | |||
| 7b14419438 | |||
| 8ae2fb04d9 | |||
| 94f4ff817e | |||
| d7612f0b8f | |||
| e6cf7829f1 | |||
| 6fcec99b8b | |||
| 81ac1521bf | |||
| 1a0dd293fc | |||
| ee3efbd973 | |||
| 96063a95d2 | |||
| 119984281f | |||
| 3f0eea8867 | |||
| 10dd400626 | |||
| 2c1b839fa8 | |||
| 6ad99d3c09 | |||
| 6828d01923 | |||
| e09e25ff49 | |||
| 307ead8c72 | |||
| cf7cba0e72 | |||
| 82a1443981 | |||
| 25e0653123 | |||
| 5b7629de84 | |||
| 8cdea473c1 | |||
| 6d40dbe1e7 | |||
| 75a6d4f68d | |||
| 8f238f9651 | |||
| ab942ae21f | |||
| 113ff47473 | |||
| b6b536dea7 | |||
| c878731fe1 | |||
| e8251669c3 | |||
| 9c545e06b4 | |||
| 4333132e44 | |||
| 732a77b78d | |||
| 2677da9810 | |||
| 80a51bde35 | |||
| 5b51197e1a | |||
| 1ceb4ba659 | |||
| 26c57c4775 | |||
| 99d1457b5e | |||
| 3896379e29 | |||
| e7a5fd85f8 | |||
| 00891188a6 | |||
| b436531e65 | |||
| c1bd680c98 | |||
| 6400a6be7f | |||
| d81baae274 | |||
| be25f63b15 | |||
| 304a4ab681 | |||
| ca156b7282 | |||
| d180bebd74 | |||
| 17fd478e20 | |||
| a09a88656e | |||
| ef467cc543 | |||
| 492a68b84b | |||
| 89f5871e3c | |||
| 5c7d042605 | |||
| abe7fd6a5f | |||
| 0ceca9e882 | |||
| f503d429b5 | |||
| 7b288eca63 | |||
| c2726b1f24 | |||
| 979f887b57 | |||
| a77533fd95 | |||
| 55b8ba73fa | |||
| 9bb260e953 | |||
| 3778156ffd | |||
| 6ec5f51e08 | |||
| bc89658985 | |||
| 41a62d379b | |||
| 3fc7c8371b | |||
| dc8d8b6546 | |||
| 7720932e99 | |||
| d202038e37 | |||
| 71cd18e4d7 | |||
| bb4ccf58e7 | |||
| 56330dcec3 | |||
| d01b6c0335 | |||
| 331455f367 | |||
| 029ad9e0da | |||
| af79980050 | |||
| fe5ddb61ff | |||
| f21e433ace | |||
| 5ea33b6e47 | |||
| 22461d331c | |||
| 38729797d9 | |||
| 37e7d33275 | |||
| dff20cf12b | |||
| 6ed0753b1d | |||
| 182af94b03 | |||
| 9a3e77ce35 | |||
| 791b82675a | |||
| 2c0d2d8076 | |||
| 55a6265cf0 | |||
| 9f194060e3 | |||
| 9ac95ae367 | |||
| 21d6322402 | |||
| b2c2accded | |||
| 2a2398b34e | |||
| ef99b600d6 | |||
| 496e735495 | |||
| beb6882d0a | |||
| 58bfc2db64 | |||
| 496cd2d377 | |||
| 9a2b29e33a | |||
| df78dffa25 | |||
| 98160d075d | |||
| f64fa740d0 | |||
| ab5cdc39bb | |||
| 57b2553881 | |||
| 31eff8d58a | |||
| 5123f20c99 | |||
| e48363d64d | |||
| 23a3d76ac7 | |||
| 538875a95d | |||
| 1247eb16d5 | |||
| 4f45eabdca | |||
| 267dc92f55 | |||
| 6cc544b1d2 | |||
| 1b4cb2af68 | |||
| 96b000e2f5 | |||
| efe5dafee4 | |||
| 819805b634 | |||
| e1ccf7876f | |||
| 4a5e8d3c42 | |||
| 478d024edc | |||
| 39c7c9682b | |||
| 8b598d4770 | |||
| 646d2076a8 | |||
| 2aa68cc3b7 | |||
| 5890ed4c06 | |||
| 39c84d129b | |||
| 0a8e78a0a2 | |||
| 2de3a83440 | |||
| 778f246411 | |||
| 6d14f9ebda | |||
| d2f91236b4 | |||
| ff6ba26587 | |||
| 17e3b368ab | |||
| 9346c25d82 | |||
| 62da5ed336 | |||
| 3ce0b92324 | |||
| dd944eeb3c | |||
| 2bc0878a08 | |||
| bd3aa02e57 | |||
| 7333b12c33 | |||
| 18c6b8f136 | |||
| 84459f63b2 | |||
| 5a63fa7e68 | |||
| aac74d46dd | |||
| db359f3546 | |||
| d77950ae8f | |||
| a49641865b | |||
| 9130978167 | |||
| 34fb04c144 | |||
| 8568411b47 | |||
| 9f104a6d42 | |||
| 1a17087d5a | |||
| 46d2f51e52 | |||
| b4c53f6caa | |||
| 51975449af | |||
| 3f75affd3d | |||
| d32f71978f | |||
| 350bbb0bef | |||
| 3d017f2a7f | |||
| 8df2422528 | |||
| 379806e850 | |||
| ec4ef76d99 | |||
| 67b4d05dd3 | |||
| 97c4df2cd5 | |||
| d033a562ac | |||
| cd2bef7495 | |||
| ea73d69b6f | |||
| 675f2c6fac | |||
| 3f4c8770fe | |||
| 0a1d66f4f4 | |||
| 6dec81e5ac | |||
| 8f3abb2e67 | |||
| 731f5d55bd | |||
| 656561e906 | |||
| b709b67219 | |||
| 3355d89c10 | |||
| a44abd5cfb | |||
| 3849f6313c | |||
| 0b30e219be | |||
| 0566b2e657 | |||
| 0f8932cf2f | |||
| d6cfc23ec8 | |||
| 4059179a78 | |||
| 08f447ce18 | |||
| 9b5af48c9a | |||
| 600ec189aa | |||
| 3ea77b89de | |||
| 616da6c757 | |||
| 3d490214f3 | |||
| cb1bb1b148 | |||
| 95d43542ba | |||
| 913b356034 | |||
| adfbd45a60 | |||
| ca6d978a03 | |||
| cf6db129ee | |||
| eff2079b18 | |||
| c3fe433dcf | |||
| bd199f512a | |||
| dbfbf693be | |||
| 8d89574498 | |||
| 59e7f6704d | |||
| 19b0773bfb | |||
| fe8cf65b08 | |||
| 93ad739add | |||
| e2f0e6f27b | |||
| 66637e4d52 | |||
| 0f35c75b10 | |||
| ade69319a4 | |||
| 6a2c03bf45 | |||
| 581c186c53 | |||
| 8c9ce74533 | |||
| 7e8542b11a | |||
| 7a368e6d83 | |||
| 9e184723ab | |||
| 04c106f0ee | |||
| 9821f9424f | |||
| bff5821bfe | |||
| 8105336dd5 | |||
| 61caee3400 | |||
| a5ea844469 | |||
| d66f90ad97 | |||
| 042ab47246 | |||
| 8e09cf5070 | |||
| e77d4ad127 | |||
| 5d9ea0ea98 | |||
| 49e2bc7c20 | |||
| 6f62a1d048 | |||
| 205e23b13c | |||
| 81e18f7ae9 | |||
| 9beb219720 |
@@ -6,3 +6,9 @@ OpenBCI_GUI/temp.txt
|
||||
OpenBCI_GUI/t-temp.txt
|
||||
OpenBCI_GUI/build/source/*.java
|
||||
*.app
|
||||
OpenBCI_GUI/SavedData
|
||||
OpenBCI_GUI/SavedData/EEG_Data/SDconverted-*
|
||||
OpenBCI_GUI/data/EEG_Data/*
|
||||
OpenBCI_GUI/application.*.zip
|
||||
OpenBCI_GUI/application.*
|
||||
*.autosave
|
||||
|
||||
@@ -1,3 +1,209 @@
|
||||
# v3.3.1
|
||||
|
||||
Use OpenBCIHub v1.4.2 please.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fixed bug where SD files were called csv
|
||||
* Fixed bug where SD files could not be played back in gui
|
||||
* Fixed bug where SD files could freeze the GUI
|
||||
* Fixed bug where GUI crashed on windows and linux (thanks @chrisjz) #331
|
||||
* Added a bunch of checking to avoid exception errors when not running as mac to prevent BLED112
|
||||
* Fixed a bug where the GUI did not work with processing 3.3.7 #316
|
||||
|
||||
## Beta 1
|
||||
|
||||
Initial release with bug fixes!
|
||||
|
||||
# v3.3.0
|
||||
|
||||
Use OpenBCIHub v1.4.2 please.
|
||||
|
||||
### New Features
|
||||
|
||||
* Add support for BLED112
|
||||
* Add support for static IP for wifi
|
||||
|
||||
## Beta 5
|
||||
|
||||
* Fixes a bunch of spacing and layout issues found when switching between the different interfaces
|
||||
|
||||
## Beta 4
|
||||
|
||||
* There was a problem with the release
|
||||
|
||||
## Beta 3
|
||||
|
||||
* Add support for static IP for wifi
|
||||
* Bump Hub to v1.4.2
|
||||
|
||||
## Beta 2
|
||||
|
||||
* Fix bug with Hub, bump hub to v1.4.1
|
||||
|
||||
## Beta 1
|
||||
|
||||
* Initial release
|
||||
|
||||
# v3.2.0
|
||||
|
||||
Use OpenBCIHub v1.3.9 please.
|
||||
|
||||
### New Features
|
||||
|
||||
* Add the Digital Read widget
|
||||
* Add the Analog Read widget
|
||||
* Add the Marker Mode widget
|
||||
* Add info, warn, success, and error types to output function to alert user in help widget.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Did not write aux values for Cyton with Daisy #272
|
||||
* Did not write to the SD card #277
|
||||
* Add button to accelerometer widget to turn accel mode on if the user was just using digital, analog, or marker mode.
|
||||
* Fixes #192 with drop down of different color themes.
|
||||
* Fixes #285 by moving the wifi options to the right of the drop down pane.
|
||||
* Fixes #270 where macOS 10.13 could not connect to Ganglion
|
||||
* Fixes #247 where the timer series graph looked strange with 7 seconds.
|
||||
|
||||
## rc2
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Added stability for ganglion bluetooth #270
|
||||
|
||||
## rc1
|
||||
|
||||
* Update the hub to 1.3.7 to catch more wifi errors
|
||||
* Finished the udp/tcp
|
||||
* Add colors to help widget.
|
||||
|
||||
## Beta 3
|
||||
|
||||
Finished Analog and Digital read widgets.
|
||||
|
||||
## Beta 2
|
||||
|
||||
Initial release
|
||||
|
||||
# v3.1.0
|
||||
|
||||
Use hub v1.3.4 please.
|
||||
|
||||
### New Features
|
||||
|
||||
* Added new files for Contributing, code of conduct and roadmap
|
||||
* Refactored readme with banner image, and all in all made it sweet.
|
||||
* Added 500Hz sample rate option for WiFi Shield Cyton
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* SD Converted file goes into `data/SavedData` instead of `data/EED_Data`. #267
|
||||
* Sending data over UDP produced unreadable raw format. Switched to JSON output.
|
||||
* All UDP output sends a serialized json packet ending with `\r\n`
|
||||
* Data files are now saved with `.csv` instead of `.txt`
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* "Data stream stopped" would be shown to users even if no data stream was stopped #263
|
||||
* Accel did not work for wifi Daisy #265
|
||||
* Users would have to close the GUI before restarting after cyton or ganglion session #262
|
||||
* Design your own widget link #261
|
||||
|
||||
## Beta 2
|
||||
|
||||
Implement overhaul of GUI docs.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* #261 #267
|
||||
|
||||
## Beta 1
|
||||
|
||||
Initial release.
|
||||
|
||||
# v3.0.1
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* FIX: #254 LSL, UDP, OSC ArrayIndexOutOfBoundsException Stream with 4 or 16 channels
|
||||
|
||||
# v3.0.0
|
||||
|
||||
v3.0.0 set out to move **all** of the data collection to the electron hub. This means moving serial port parsing as well.
|
||||
|
||||
### New Features
|
||||
|
||||
* Able to use wifi shield with GUI. Streams in at 1000Hz for Cyton and 1600Hz for Ganglion.
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* Dependent on electron hub for all data streaming activity.
|
||||
|
||||
## Release Candidate 5
|
||||
|
||||
Uses OpenBCIHub v1.3.0
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Closes: #191
|
||||
|
||||
## Release Candidate 4
|
||||
|
||||
Uses OpenBCIHub v1.2.0
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Closes: #208 - ganglion not using correct scale factor when on wifi high resolution mode
|
||||
* Fixes bug where gui started in 45 fps frame rate
|
||||
|
||||
## Release Candidate 2/3
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Critical windows hub patches
|
||||
|
||||
## Release Candidate 1
|
||||
|
||||
Initial RC
|
||||
|
||||
## Beta 6
|
||||
|
||||
* Closes #202 #205 #207
|
||||
|
||||
## Beta 4
|
||||
|
||||
* Closes #203
|
||||
|
||||
## Beta 2-3
|
||||
|
||||
Required a lot of work on the hub. But none the less, this seems to be working decently.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Closes #196 #195 #194 #193 #190 #188 #187 #186 #189
|
||||
|
||||
## Beta 1
|
||||
|
||||
The first beta to be released. There are some [minor issues](https://github.com/OpenBCI/OpenBCI_GUI/issues), but if any are encountered, please [open an issue](https://github.com/OpenBCI/OpenBCI_GUI/issues/new) on the [github page](https://github.com/OpenBCI/OpenBCI_GUI/issues).
|
||||
|
||||
# 2.2.1
|
||||
|
||||
### Bug Fixes
|
||||
* Addresses #121 - `.edf` incompatible changed ending to `.bdf`
|
||||
* Closes #148 - LSL does not stream correctly
|
||||
|
||||
# 2.2.0
|
||||
|
||||
### Bug Fixes
|
||||
* Fix #151 - Incorrect number of channels on playback caused index out of bounds errors.
|
||||
* Addresses #149 - Allows for proper scaling of channels with four thanks to #151 #157
|
||||
|
||||
### New Features
|
||||
* Band power widget #153 (thanks @sunwangshu)
|
||||
* Closes #138 - Able to drag and drop the electrodes on the head map (thanks @liqwid)
|
||||
* Closes #142 - GUI needs to pass key strokes to Ganglion
|
||||
|
||||
# 2.1.2
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
# OpenBCI GUI Code of Conduct
|
||||
|
||||
## Purpose
|
||||
|
||||
It is our hope that any one is able to contribute to OpenBCI GUI regardless of their background. Thus, we hope to provide a safe, welcoming, and warmly geeky environment for everybody, regardless of gender, sexual orientation, ability, ethnicity, socioeconomic status, and religion (or lack thereof).
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at [contact@openbci.com](mailto:contact@openbci.com). All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
@@ -0,0 +1,37 @@
|
||||
# Contributing
|
||||
|
||||
:tada::clinking_glasses: First off, thanks for taking the time to contribute! :tada::clinking_glasses:
|
||||
|
||||
Contributions are always welcome, no matter how small.
|
||||
|
||||
The following is a small set of guidelines for how to contribute to the project
|
||||
|
||||
## Where to start
|
||||
|
||||
### Code of Conduct
|
||||
This project adheres to the Contributor Covenant [Code of Conduct](CODE_OF_CONDUCT.md).
|
||||
By participating you are expected to adhere to these expectations. Please report unacceptable behaviour to [info@pushtheworld.us](mailto:info@pushtheworld.us)
|
||||
|
||||
### Contributing on Github
|
||||
|
||||
If you're new to Git and want to learn how to fork this repo, make your own additions, and include those additions in the master version of this project, check out this [great tutorial](http://blog.davidecoppola.com/2016/11/howto-contribute-to-open-source-project-on-github/).
|
||||
|
||||
### Community
|
||||
|
||||
This project is maintained by the [OpenBCI](www.openbci.com) community. Join the [OpenBCI Forum](http://openbci.com/index.php/forum/), where discussions about OpenBCI takes place.
|
||||
|
||||
## How can I contribute?
|
||||
|
||||
This is currently a small, humble project so our contribution process is rather casual. If there's a feature you'd be interested in building, go ahead! Let us know on the [OpenBCI Forum](http://openbci.com/index.php/forum/) or [open an issue](../../issues) so others can follow along and we'll support you as much as we can. When you're finished submit a pull request to the master branch referencing the specific issue you addressed.
|
||||
|
||||
If you find a bug, or have a suggestion on how to improve the project, just fill out a [Github issue](../../issues)
|
||||
|
||||
### Steps to Contribute
|
||||
|
||||
1. Fork it!
|
||||
2. Branch off of `development`: `git checkout development`
|
||||
2. Create your feature branch: `git checkout -b my-new-feature`
|
||||
3. Make changes
|
||||
4. Commit your changes: `git commit -m 'Add some feature'`
|
||||
5. Push to the branch: `git push origin my-new-feature`
|
||||
6. Submit a pull request. Make sure it is based off of the `development` branch when submitting! :D
|
||||
@@ -0,0 +1,27 @@
|
||||
## Problem
|
||||
|
||||
Explain the problem
|
||||
|
||||
## Expected
|
||||
|
||||
In a perfect world, what do you expect to happen.
|
||||
|
||||
## Operating System and Version
|
||||
|
||||
macOS/Windows/Linux
|
||||
|
||||
## GUI Version
|
||||
|
||||
The version is displayed on startup.
|
||||
|
||||
## Running standalone app
|
||||
|
||||
Are you running the downloaded app or are you running from Processing 3
|
||||
|
||||
## Type of OpenBCI Board
|
||||
|
||||
Cyton/Cyton+Daisy/Ganglion
|
||||
|
||||
## Are you using a WiFi Shield?
|
||||
|
||||
Yes/No
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 OpenBCI
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,115 @@
|
||||
import argparse
|
||||
import time
|
||||
import atexit
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
if sys.version_info.major == 3:
|
||||
from pythonosc import dispatcher
|
||||
from pythonosc import osc_server
|
||||
elif sys.version_info.major == 2:
|
||||
import OSC
|
||||
|
||||
|
||||
# Print received message to console
|
||||
def print_message(*args):
|
||||
try:
|
||||
current = time.time()
|
||||
if sys.version_info.major == 2:
|
||||
print("(%f) RECEIVED MESSAGE: %s %s" % (current, args[0], ",".join(str(x) for x in args[2:])))
|
||||
elif sys.version_info.major == 3:
|
||||
print("(%f) RECEIVED MESSAGE: %s %s" % (current, args[0], ",".join(str(x) for x in args[1:])))
|
||||
|
||||
except ValueError: pass
|
||||
|
||||
# Clean exit from print mode
|
||||
def exit_print(signal, frame):
|
||||
print("Closing listener")
|
||||
sys.exit(0)
|
||||
|
||||
# Record received message in text file
|
||||
def record_to_file(*args):
|
||||
textfile.write(str(time.time()) + ",")
|
||||
textfile.write(",".join(str(x) for x in args))
|
||||
textfile.write("\n")
|
||||
|
||||
# Save recording, clean exit from record mode
|
||||
def close_file(*args):
|
||||
print("\nFILE SAVED")
|
||||
textfile.close()
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Collect command line arguments
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--ip",
|
||||
default="localhost", help="The ip to listen on")
|
||||
parser.add_argument("--port",
|
||||
type=int, default=12345, help="The port to listen on")
|
||||
parser.add_argument("--address",default="/openbci", help="address to listen to")
|
||||
parser.add_argument("--option",default="print",help="Debugger option")
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
if sys.version_info.major == 3:
|
||||
# Set up necessary parameters from command line
|
||||
dispatcher = dispatcher.Dispatcher()
|
||||
if args.option=="print":
|
||||
dispatcher.map("/openbci", print_message)
|
||||
signal.signal(signal.SIGINT, exit_print)
|
||||
|
||||
elif args.option=="record":
|
||||
i = 0
|
||||
while os.path.exists("osc_test%s.txt" % i):
|
||||
i += 1
|
||||
filename = "osc_test%i.txt" % i
|
||||
textfile = open(filename, "w")
|
||||
textfile.write("time,address,messages\n")
|
||||
textfile.write("-------------------------\n")
|
||||
print("Recording to %s" % filename)
|
||||
dispatcher.map("/openbci", record_to_file)
|
||||
signal.signal(signal.SIGINT, close_file)
|
||||
|
||||
# Display server attributes
|
||||
print('--------------------')
|
||||
print("-- OSC LISTENER -- ")
|
||||
print('--------------------')
|
||||
print("IP:", args.ip)
|
||||
print("PORT:", args.port)
|
||||
print("ADDRESS:", args.address)
|
||||
print('--------------------')
|
||||
print("%s option selected" % args.option)
|
||||
|
||||
|
||||
# connect server
|
||||
server = osc_server.ThreadingOSCUDPServer(
|
||||
(args.ip, args.port), dispatcher)
|
||||
server.serve_forever()
|
||||
|
||||
elif sys.version_info.major == 2:
|
||||
s = OSC.OSCServer((args.ip, args.port)) # listen on localhost, port 57120
|
||||
if args.option=="print":
|
||||
s.addMsgHandler(args.address, print_message)
|
||||
elif args.option=="record":
|
||||
i = 0
|
||||
while os.path.exists("osc_test%s.txt" % i):
|
||||
i += 1
|
||||
filename = "osc_test%i.txt" % i
|
||||
textfile = open(filename, "w")
|
||||
textfile.write("time,address,messages\n")
|
||||
textfile.write("-------------------------\n")
|
||||
print("Recording to %s" % filename)
|
||||
signal.signal(signal.SIGINT, close_file)
|
||||
# Display server attributes
|
||||
print('--------------------')
|
||||
print("-- OSC LISTENER -- ")
|
||||
print('--------------------')
|
||||
print("IP:", args.ip)
|
||||
print("PORT:", args.port)
|
||||
print("ADDRESS:", args.address)
|
||||
print('--------------------')
|
||||
print("%s option selected" % args.option)
|
||||
print("Listening...")
|
||||
|
||||
s.serve_forever()
|
||||
@@ -0,0 +1,59 @@
|
||||
import argparse
|
||||
import random
|
||||
import time
|
||||
import sys
|
||||
|
||||
if sys.version_info.major == 3:
|
||||
from pythonosc import osc_message_builder
|
||||
from pythonosc import udp_client
|
||||
elif sys.version_info.major == 2:
|
||||
import OSC
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
# Collect command line arguments
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--ip", default="127.0.0.1",
|
||||
help="The ip of the OSC server")
|
||||
parser.add_argument("--port", type=int, default=12345,
|
||||
help="The port the OSC server is listening on")
|
||||
parser.add_argument("--address", default="/openbci",
|
||||
help="The address the OSC server is sending to")
|
||||
args = parser.parse_args()
|
||||
|
||||
# Display socket attributes
|
||||
print('--------------------')
|
||||
print("-- OSC SIMULATION -- ")
|
||||
print('--------------------')
|
||||
print("IP:", args.ip)
|
||||
print("PORT:", args.port)
|
||||
print("ADDRESS:", args.address)
|
||||
print('--------------------')
|
||||
|
||||
# Establish UDP client (for OSC)
|
||||
if sys.version_info.major == 3:
|
||||
client = udp_client.SimpleUDPClient("127.0.0.1", 12345 )
|
||||
|
||||
|
||||
|
||||
# Send test data
|
||||
while (1):
|
||||
msg = [random.random() for x in range(8)]
|
||||
print("SENT MESSAGE: ", msg )
|
||||
client.send_message(args.address, msg)
|
||||
time.sleep(.25)
|
||||
elif sys.version_info.major == 2:
|
||||
client = OSC.OSCClient()
|
||||
client.connect((args.ip,args.port))
|
||||
while (1):
|
||||
msg = [random.random() for x in range(8)]
|
||||
oscmsg = OSC.OSCMessage()
|
||||
oscmsg.setAddress(args.address)
|
||||
oscmsg.append(msg)
|
||||
print("SENT MESSAGE: ", msg )
|
||||
try:
|
||||
client.send(oscmsg)
|
||||
except:
|
||||
pass
|
||||
time.sleep(.25)
|
||||
@@ -0,0 +1,86 @@
|
||||
import socket
|
||||
import sys
|
||||
import time
|
||||
import argparse
|
||||
import signal
|
||||
import struct
|
||||
import os
|
||||
import json
|
||||
|
||||
# Print received message to console
|
||||
def print_message(*args):
|
||||
try:
|
||||
obj = json.loads(args[0])
|
||||
print obj.get('data')
|
||||
except BaseException as e:
|
||||
print e
|
||||
# print("(%s) RECEIVED MESSAGE: " % time.time() +
|
||||
# ''.join(str(struct.unpack('>%df' % int(length), args[0]))))
|
||||
|
||||
# Clean exit from print mode
|
||||
def exit_print(signal, frame):
|
||||
print("Closing listener")
|
||||
sys.exit(0)
|
||||
|
||||
# Record received message in text file
|
||||
def record_to_file(*args):
|
||||
textfile.write(str(time.time()) + ",")
|
||||
textfile.write(''.join(str(struct.unpack('>%df' % length,args[0]))))
|
||||
textfile.write("\n")
|
||||
|
||||
# Save recording, clean exit from record mode
|
||||
def close_file(*args):
|
||||
print("\nFILE SAVED")
|
||||
textfile.close()
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Collect command line arguments
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--ip",
|
||||
default="127.0.0.1", help="The ip to listen on")
|
||||
parser.add_argument("--port",
|
||||
type=int, default=12345, help="The port to listen on")
|
||||
parser.add_argument("--address",default="/openbci", help="address to listen to")
|
||||
parser.add_argument("--option",default="print",help="Debugger option")
|
||||
parser.add_argument("--len",default=8,help="Debugger option")
|
||||
args = parser.parse_args()
|
||||
|
||||
# Set up necessary parameters from command line
|
||||
length = args.len
|
||||
if args.option=="print":
|
||||
signal.signal(signal.SIGINT, exit_print)
|
||||
elif args.option=="record":
|
||||
i = 0
|
||||
while os.path.exists("udp_test%s.txt" % i):
|
||||
i += 1
|
||||
filename = "udp_test%i.txt" % i
|
||||
textfile = open(filename, "w")
|
||||
textfile.write("time,address,messages\n")
|
||||
textfile.write("-------------------------\n")
|
||||
print("Recording to %s" % filename)
|
||||
signal.signal(signal.SIGINT, close_file)
|
||||
|
||||
# Connect to socket
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
server_address = (args.ip, args.port)
|
||||
sock.bind(server_address)
|
||||
|
||||
# Display socket attributes
|
||||
print('--------------------')
|
||||
print("-- UDP LISTENER -- ")
|
||||
print('--------------------')
|
||||
print("IP:", args.ip)
|
||||
print("PORT:", args.port)
|
||||
print('--------------------')
|
||||
print("%s option selected" % args.option)
|
||||
|
||||
# Receive messages
|
||||
print("Listening...")
|
||||
while True:
|
||||
data, addr = sock.recvfrom(20000) # buffer size is 20000 bytes
|
||||
if args.option=="print":
|
||||
print_message(data)
|
||||
elif args.option=="record":
|
||||
record_to_file(data)
|
||||
@@ -0,0 +1,42 @@
|
||||
import socket
|
||||
import random
|
||||
import struct
|
||||
import time
|
||||
import argparse
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
# Collect command line arguments
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--ip", default="127.0.0.1",
|
||||
help="The ip of the OSC server")
|
||||
parser.add_argument("--port", type=int, default=12345,
|
||||
help="The port the OSC server is listening on")
|
||||
parser.add_argument("--address", default="/openbci",
|
||||
help="The address the OSC server is sending to")
|
||||
args = parser.parse_args()
|
||||
|
||||
# Establish UDP socket
|
||||
UDP_IP = args.ip
|
||||
UDP_PORT = args.port
|
||||
sock = socket.socket(socket.AF_INET, # Internet
|
||||
socket.SOCK_DGRAM) # UDP
|
||||
|
||||
# Display socket attributes
|
||||
print('--------------------')
|
||||
print("-- UDP SIMULATION -- ")
|
||||
print('--------------------')
|
||||
print("IP:", args.ip)
|
||||
print("PORT:", args.port)
|
||||
print('--------------------')
|
||||
|
||||
# Send test data
|
||||
while (1):
|
||||
# generate random list
|
||||
ar = [random.random() for x in range(8)]
|
||||
print("SENT MESSAGE: ",ar)
|
||||
# package as byte array
|
||||
msg = struct.pack('>8f', *ar)
|
||||
# send through socket
|
||||
sock.sendto(msg, (UDP_IP, UDP_PORT))
|
||||
time.sleep(.25)
|
||||
@@ -0,0 +1,158 @@
|
||||
# Networking Test Kit
|
||||
|
||||
This repository contains a collection of scripts for testing networking functionality for OSC, UDP, and LSL. This is intended for use with OpenBCI networking functionality, but could work for other tasks as well.
|
||||
|
||||
## Contents
|
||||
+ [Installation](#Installation)
|
||||
+ [Usage](#Use)
|
||||
+ [OSC](#OSC)
|
||||
+ [Send](#OSC-Sending)
|
||||
+ [Receive](#OSC-Receiving)
|
||||
+ [UDP](#UDP)
|
||||
+ [Send](#UDP-Sending)
|
||||
2. [Receive](#UDP-Receiving)
|
||||
|
||||
<a name="Usage"/>
|
||||
## Installation
|
||||
The scripts should work for Python 2 and Python 3. Install a different OSC package depending on your version of python.
|
||||
|
||||
Python 2:
|
||||
```
|
||||
pip install pyosc
|
||||
```
|
||||
Python 3:
|
||||
```
|
||||
pip install python-osc
|
||||
```
|
||||
|
||||
<a name="Usage"/>
|
||||
## Usage
|
||||
|
||||
This kit is provided to help you localize and diagnose any issues while using the OpenBCI networking functionality. It is helpful to run scripts that allow you to send and receive data in order to determine if your issue is with the program sending data, the program receiving data, or the network in-between.
|
||||
|
||||
If you are using the OpenBCI GUI, the first step might be to check if the GUI is correctly sending data. To do this, check the settings you entered into the Networking Widget, and run the appropriate **receive** networking script with those arguments. If you can correctly receive data, the issue is not with the GUI, but possibly with the 3rd party program or network.
|
||||
|
||||
Similarly, the **send** scripts can be used to determine if you can receive data in this 3rd party program. If you are sending data from this script and cannot receive it in a 3rd party program, check to see if your program is correctly configured to accept incoming connections. If you are sending data with the script and can receive it with the 3rd party program, but not with data sent from the OpenBCI GUI, check to see if your setup in the GUI is correct.
|
||||
|
||||
The "send" and "receive" scripts can also be used together to test your network, or as a sanity check for the settings your are using.
|
||||
___
|
||||
<a name="OSC"/>
|
||||
## OSC
|
||||
<a name="OSC-Receiving"/>
|
||||
### Receiving
|
||||
Run the script **osc_receive.py** to test listening to OSC messages. Use this to ensure that your program (such as the OpenBCI GUI) is sending messages correctly to the right location.
|
||||
|
||||
**Optional Arguments**:
|
||||
```
|
||||
--ip - specify an IP address [Default = 127.0.0.1]
|
||||
--port - specify the port number [Default = 12345]
|
||||
--address - specify an OSC message address [Default = \openbci]
|
||||
--option - specify a debugger option [Default = print]
|
||||
|
||||
```
|
||||
|
||||
**Debugger Options**
|
||||
|
||||
There are two debugger options: **print** or **record**. **Print** outputs the messages receives to the console, while **record** saves a text file to this directory with the messages the debugger has received.
|
||||
|
||||
|
||||
**Examples:**
|
||||
|
||||
Listen to and print messages from IP 127.0.0.1, port 12345, address "\openbci".
|
||||
```
|
||||
python osc_receive.py
|
||||
```
|
||||
|
||||
Listen to and record messages from IP 127.0.0.1, port 12345, address "\openbci"
|
||||
```
|
||||
python osc_receive.py --option=record
|
||||
```
|
||||
|
||||
Listen to and print messages from IP 137.110.96.253, port 8888, address "\accel"
|
||||
```
|
||||
python osc_receive.py --ip=137.110.96.253 --port=8888 --address=\accel
|
||||
```
|
||||
|
||||
|
||||
<a name="OSC-Sending"/>
|
||||
### Sending
|
||||
Run the script **osc_send.py** to test sending to OSC messages. Use this to ensure that your program is configured to receive messages correctly.
|
||||
|
||||
**Optional Arguments**:
|
||||
```
|
||||
--ip - specify an IP address [Default = 127.0.0.1]
|
||||
--port - specify the port number [Default = 12345]
|
||||
--address - specify an OSC message address [Default = \openbci]
|
||||
```
|
||||
**Examples:**
|
||||
|
||||
Send messages to IP 127.0.0.1, port 12345, address "\openbci".
|
||||
```
|
||||
python osc_send.py
|
||||
```
|
||||
|
||||
Send messages to IP 137.110.96.253, port 8888, address "\accel"
|
||||
```
|
||||
python osc_send.py --ip=137.110.96.253 --port=8888 --address=\accel
|
||||
```
|
||||
___
|
||||
<a name="UDP"/>
|
||||
## UDP
|
||||
<a name="UDP-Receiving"/>
|
||||
### Receiving
|
||||
Run the script **udp_receive.py** to test listening to UDP messages. Use this to ensure that your program (such as the OpenBCI GUI) is sending messages correctly to the right location.
|
||||
|
||||
**Optional Arguments**:
|
||||
```
|
||||
--ip - specify an IP address [Default = 127.0.0.1]
|
||||
--port - specify the port number [Default = 12345]
|
||||
--len - specify the length of message [Default = 8]
|
||||
--option - specify a debugger option [Default = print]
|
||||
```
|
||||
|
||||
**Len**
|
||||
If you are receiving data other than 8 channel Time Series data (i.e. FFT or triggers), you *must* specify a length. The length is usually the number of channels you are sending (4 for the Ganglion, 16 for the Cyton with Daisy, 1 for a marker stream, etc). If you are sending FFT data, this must be 126.
|
||||
|
||||
**Debugger Options**
|
||||
|
||||
There are two debugger options: **print** or **record**. **Print** outputs the messages receives to the console, while **record** saves a text file to this directory with the messages the debugger has received.
|
||||
|
||||
|
||||
|
||||
**Examples:**
|
||||
|
||||
Listen to and print messages from IP 127.0.0.1, port 12345 (defaults).
|
||||
```
|
||||
python udp_receive.py
|
||||
```
|
||||
|
||||
Listen to and record messages from IP 127.0.0.1, port 12345.
|
||||
```
|
||||
python udp_receive.py --option=record
|
||||
```
|
||||
|
||||
Listen to and print messages from IP 137.110.96.253, port 8888.
|
||||
```
|
||||
python udp_receive.py --ip=137.110.96.253 --port=8888
|
||||
```
|
||||
|
||||
<a name="UDP-Sending"/>
|
||||
### Sending
|
||||
Run the script **udp_send.py** to test sending to UDP messages. Use this to ensure that your program is configured to receive messages correctly.
|
||||
|
||||
**Optional Arguments**:
|
||||
```
|
||||
--ip - specify an IP address [Default = 127.0.0.1]
|
||||
--port - specify the port number [Default = 12345]
|
||||
```
|
||||
**Examples:**
|
||||
|
||||
Send messages to IP 127.0.0.1, port 12345 (defaults).
|
||||
```
|
||||
python udp_send.py
|
||||
```
|
||||
|
||||
Send messages to IP 137.110.96.253, port 8888.
|
||||
```
|
||||
python udp_send.py --ip=137.110.96.253 --port=8888
|
||||
```
|
||||
@@ -0,0 +1,701 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// This class configures and manages the connection to the OpenBCI shield for
|
||||
// the Arduino. The connection is implemented via a Serial connection.
|
||||
// The OpenBCI is configured using single letter text commands sent from the
|
||||
// PC to the Arduino. The EEG data streams back from the Arduino to the PC
|
||||
// continuously (once started). This class defaults to using binary transfer
|
||||
// for normal operation.
|
||||
//
|
||||
// Created: Chip Audette, Oct 2013
|
||||
// Modified: through April 2014
|
||||
// Modified again: Conor Russomanno Sept-Oct 2014
|
||||
// Modified for Daisy (16-chan) OpenBCI V3: Conor Russomanno Nov 2014
|
||||
// Modified Daisy Behaviors: Chip Audette Dec 2014
|
||||
//
|
||||
// Note: this class now expects the data format produced by OpenBCI V3.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
import java.io.OutputStream; //for logging raw bytes to an output file
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Global Variables & Instances
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
final char command_stop = 's';
|
||||
// final String command_startText = "x";
|
||||
final char command_startBinary = 'b';
|
||||
final char command_startBinary_wAux = 'n'; // already doing this with 'b' now
|
||||
final char command_startBinary_4chan = 'v'; // not necessary now
|
||||
final char command_activateFilters = 'f'; // swithed from 'F' to 'f' ... but not necessary because taken out of hardware code
|
||||
final char command_deactivateFilters = 'g'; // not necessary anymore
|
||||
|
||||
final String command_setMode = "/"; // this is used to set the board into different modes
|
||||
|
||||
final char[] command_deactivate_channel = {'1', '2', '3', '4', '5', '6', '7', '8', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i'};
|
||||
final char[] command_activate_channel = {'!', '@', '#', '$', '%', '^', '&', '*', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I'};
|
||||
|
||||
int channelDeactivateCounter = 0; //used for re-deactivating channels after switching settings...
|
||||
|
||||
final int BOARD_MODE_DEFAULT = 0;
|
||||
final int BOARD_MODE_DEBUG = 1;
|
||||
final int BOARD_MODE_ANALOG = 2;
|
||||
final int BOARD_MODE_DIGITAL = 3;
|
||||
final int BOARD_MODE_MARKER = 4;
|
||||
|
||||
//everything below is now deprecated...
|
||||
// final String[] command_activate_leadoffP_channel = {'!', '@', '#', '$', '%', '^', '&', '*'}; //shift + 1-8
|
||||
// final String[] command_deactivate_leadoffP_channel = {'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I'}; //letters (plus shift) right below 1-8
|
||||
// final String[] command_activate_leadoffN_channel = {'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K'}; //letters (plus shift) below the letters below 1-8
|
||||
// final String[] command_deactivate_leadoffN_channel = {'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<'}; //letters (plus shift) below the letters below the letters below 1-8
|
||||
// final String command_biasAuto = "`";
|
||||
// final String command_biasFixed = "~";
|
||||
|
||||
// ArrayList defaultChannelSettings;
|
||||
|
||||
//here is the routine that listens to the serial port.
|
||||
//if any data is waiting, get it, parse it, and stuff it into our vector of
|
||||
//pre-allocated dataPacketBuff
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Classes
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
class Cyton {
|
||||
|
||||
private int nEEGValuesPerPacket = 8; //defined by the data format sent by cyton boards
|
||||
private int nAuxValuesPerPacket = 3; //defined by the data format sent by cyton boards
|
||||
private DataPacket_ADS1299 rawReceivedDataPacket;
|
||||
private DataPacket_ADS1299 missedDataPacket;
|
||||
private DataPacket_ADS1299 dataPacket;
|
||||
// public int [] validAuxValues = {0, 0, 0};
|
||||
// public boolean[] freshAuxValuesAvailable = {false, false, false};
|
||||
// public boolean freshAuxValues = false;
|
||||
//DataPacket_ADS1299 prevDataPacket;
|
||||
|
||||
private int nAuxValues;
|
||||
private boolean isNewDataPacketAvailable = false;
|
||||
private OutputStream output; //for debugging WEA 2014-01-26
|
||||
private int prevSampleIndex = 0;
|
||||
private int serialErrorCounter = 0;
|
||||
|
||||
private final int fsHzSerialCyton = 250; //sample rate used by OpenBCI board...set by its Arduino code
|
||||
private final int fsHzSerialCytonDaisy = 125; //sample rate used by OpenBCI board...set by its Arduino code
|
||||
private final int fsHzWifi = 1000; //sample rate used by OpenBCI board...set by its Arduino code
|
||||
private final int NfftSerialCyton = 256;
|
||||
private final int NfftSerialCytonDaisy = 256;
|
||||
private final int NfftWifi = 1024;
|
||||
private final float ADS1299_Vref = 4.5f; //reference voltage for ADC in ADS1299. set by its hardware
|
||||
private float ADS1299_gain = 24.0; //assumed gain setting for ADS1299. set by its Arduino code
|
||||
private float openBCI_series_resistor_ohms = 2200; // Ohms. There is a series resistor on the 32 bit board.
|
||||
private float scale_fac_uVolts_per_count = ADS1299_Vref / ((float)(pow(2, 23)-1)) / ADS1299_gain * 1000000.f; //ADS1299 datasheet Table 7, confirmed through experiment
|
||||
//float LIS3DH_full_scale_G = 4; // +/- 4G, assumed full scale setting for the accelerometer
|
||||
private final float scale_fac_accel_G_per_count = 0.002 / ((float)pow(2, 4)); //assume set to +/4G, so 2 mG per digit (datasheet). Account for 4 bits unused
|
||||
//private final float scale_fac_accel_G_per_count = 1.0; //to test stimulations //final float scale_fac_accel_G_per_count = 1.0;
|
||||
private final float leadOffDrive_amps = 6.0e-9; //6 nA, set by its Arduino code
|
||||
|
||||
boolean isBiasAuto = true; //not being used?
|
||||
|
||||
private int curBoardMode = BOARD_MODE_DEFAULT;
|
||||
|
||||
//data related to Conor's setup for V3 boards
|
||||
final char[] EOT = {'$', '$', '$'};
|
||||
char[] prev3chars = {'#', '#', '#'};
|
||||
public String potentialFailureMessage = "";
|
||||
public String defaultChannelSettings = "";
|
||||
public String daisyOrNot = "";
|
||||
public int hardwareSyncStep = 0; //start this at 0...
|
||||
private long timeOfLastCommand = 0; //used when sync'ing to hardware
|
||||
|
||||
private int curInterface = INTERFACE_SERIAL;
|
||||
private int sampleRate = fsHzWifi;
|
||||
PApplet mainApplet;
|
||||
|
||||
//some get methods
|
||||
public float getSampleRate() {
|
||||
if (isSerial()) {
|
||||
if (nchan == NCHAN_CYTON_DAISY) {
|
||||
return fsHzSerialCytonDaisy;
|
||||
} else {
|
||||
return fsHzSerialCyton;
|
||||
}
|
||||
} else {
|
||||
return hub.getSampleRate();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: ADJUST getNfft for new sample variable sample rates
|
||||
public int getNfft() {
|
||||
if (isWifi()) {
|
||||
if (sampleRate == fsHzSerialCyton) {
|
||||
return NfftSerialCyton;
|
||||
} else {
|
||||
return NfftWifi;
|
||||
}
|
||||
} else {
|
||||
if (nchan == NCHAN_CYTON_DAISY) {
|
||||
return NfftSerialCytonDaisy;
|
||||
} else {
|
||||
return NfftSerialCyton;
|
||||
}
|
||||
}
|
||||
}
|
||||
public int getBoardMode() {
|
||||
return curBoardMode;
|
||||
}
|
||||
public int getInterface() {
|
||||
return curInterface;
|
||||
}
|
||||
public float get_Vref() {
|
||||
return ADS1299_Vref;
|
||||
}
|
||||
public void set_ADS1299_gain(float _gain) {
|
||||
ADS1299_gain = _gain;
|
||||
scale_fac_uVolts_per_count = ADS1299_Vref / ((float)(pow(2, 23)-1)) / ADS1299_gain * 1000000.0; //ADS1299 datasheet Table 7, confirmed through experiment
|
||||
}
|
||||
public float get_ADS1299_gain() {
|
||||
return ADS1299_gain;
|
||||
}
|
||||
public float get_series_resistor() {
|
||||
return openBCI_series_resistor_ohms;
|
||||
}
|
||||
public float get_scale_fac_uVolts_per_count() {
|
||||
return scale_fac_uVolts_per_count;
|
||||
}
|
||||
public float get_scale_fac_accel_G_per_count() {
|
||||
return scale_fac_accel_G_per_count;
|
||||
}
|
||||
public float get_leadOffDrive_amps() {
|
||||
return leadOffDrive_amps;
|
||||
}
|
||||
public String get_defaultChannelSettings() {
|
||||
return defaultChannelSettings;
|
||||
}
|
||||
|
||||
public void setBoardMode(int boardMode) {
|
||||
hub.sendCommand("/" + boardMode);
|
||||
curBoardMode = boardMode;
|
||||
print("Cyton: setBoardMode to :" + curBoardMode);
|
||||
}
|
||||
|
||||
public void setSampleRate(int _sampleRate) {
|
||||
sampleRate = _sampleRate;
|
||||
output("Setting sample rate for Cyton to " + sampleRate + "Hz");
|
||||
println("Setting sample rate for Cyton to " + sampleRate + "Hz");
|
||||
hub.setSampleRate(sampleRate);
|
||||
}
|
||||
|
||||
public boolean setInterface(int _interface) {
|
||||
curInterface = _interface;
|
||||
// println("current interface: " + curInterface);
|
||||
println("setInterface: curInterface: " + getInterface());
|
||||
if (isWifi()) {
|
||||
setSampleRate((int)fsHzWifi);
|
||||
hub.setProtocol(PROTOCOL_WIFI);
|
||||
} else if (isSerial()) {
|
||||
setSampleRate((int)fsHzSerialCyton);
|
||||
hub.setProtocol(PROTOCOL_SERIAL);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//constructors
|
||||
Cyton() {
|
||||
}; //only use this if you simply want access to some of the constants
|
||||
Cyton(PApplet applet, String comPort, int baud, int nEEGValuesPerOpenBCI, boolean useAux, int nAuxValuesPerOpenBCI, int _interface) {
|
||||
curInterface = _interface;
|
||||
|
||||
initDataPackets(nEEGValuesPerOpenBCI, nAuxValuesPerOpenBCI);
|
||||
|
||||
if (isSerial()) {
|
||||
hub.connectSerial(comPort);
|
||||
} else if (isWifi()) {
|
||||
hub.connectWifi(comPort);
|
||||
}
|
||||
}
|
||||
|
||||
public void initDataPackets(int _nEEGValuesPerPacket, int _nAuxValuesPerPacket) {
|
||||
nEEGValuesPerPacket = _nEEGValuesPerPacket;
|
||||
nAuxValuesPerPacket = _nAuxValuesPerPacket;
|
||||
//allocate space for data packet
|
||||
rawReceivedDataPacket = new DataPacket_ADS1299(nEEGValuesPerPacket, nAuxValuesPerPacket); //this should always be 8 channels
|
||||
missedDataPacket = new DataPacket_ADS1299(nEEGValuesPerPacket, nAuxValuesPerPacket); //this should always be 8 channels
|
||||
dataPacket = new DataPacket_ADS1299(nEEGValuesPerPacket, nAuxValuesPerPacket); //this could be 8 or 16 channels
|
||||
//set all values to 0 so not null
|
||||
|
||||
for (int i = 0; i < nEEGValuesPerPacket; i++) {
|
||||
rawReceivedDataPacket.values[i] = 0;
|
||||
//prevDataPacket.values[i] = 0;
|
||||
}
|
||||
|
||||
for (int i=0; i < nEEGValuesPerPacket; i++) {
|
||||
dataPacket.values[i] = 0;
|
||||
missedDataPacket.values[i] = 0;
|
||||
}
|
||||
for (int i = 0; i < nAuxValuesPerPacket; i++) {
|
||||
rawReceivedDataPacket.auxValues[i] = 0;
|
||||
dataPacket.auxValues[i] = 0;
|
||||
missedDataPacket.auxValues[i] = 0;
|
||||
//prevDataPacket.auxValues[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public int closeSDandPort() {
|
||||
closeSDFile();
|
||||
return closePort();
|
||||
}
|
||||
|
||||
public int closePort() {
|
||||
if (isSerial()) {
|
||||
return hub.disconnectSerial();
|
||||
} else {
|
||||
return hub.disconnectWifi();
|
||||
}
|
||||
}
|
||||
|
||||
public int closeSDFile() {
|
||||
println("Closing any open SD file. Writing 'j' to OpenBCI.");
|
||||
if (isPortOpen()) write('j'); // tell the SD file to close if one is open...
|
||||
delay(100); //make sure 'j' gets sent to the board
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void syncWithHardware(int sdSetting) {
|
||||
switch (hardwareSyncStep) {
|
||||
case 1: //send # of channels (8 or 16) ... (regular or daisy setup)
|
||||
println("Cyton: syncWithHardware: [1] Sending channel count (" + nchan + ") to OpenBCI...");
|
||||
if (nchan == 8) {
|
||||
write('c');
|
||||
}
|
||||
if (nchan == 16) {
|
||||
write('C', false);
|
||||
}
|
||||
break;
|
||||
case 2: //reset hardware to default registers
|
||||
println("Cyton: syncWithHardware: [2] Reseting OpenBCI registers to default... writing \'d\'...");
|
||||
write('d'); // TODO: Why does this not get a $$$ readyToSend = false?
|
||||
break;
|
||||
case 3: //ask for series of channel setting ASCII values to sync with channel setting interface in GUI
|
||||
println("Cyton: syncWithHardware: [3] Retrieving OpenBCI's channel settings to sync with GUI... writing \'D\'... waiting for $$$...");
|
||||
write('D', false); //wait for $$$ to iterate... applies to commands expecting a response
|
||||
break;
|
||||
case 4: //check existing registers
|
||||
println("Cyton: syncWithHardware: [4] Retrieving OpenBCI's full register map for verification... writing \'?\'... waiting for $$$...");
|
||||
write('?', false); //wait for $$$ to iterate... applies to commands expecting a response
|
||||
break;
|
||||
case 5:
|
||||
// write("j"); // send OpenBCI's 'j' commaned to make sure any already open SD file is closed before opening another one...
|
||||
switch (sdSetting) {
|
||||
case 1: //"5 min max"
|
||||
write('A', false); //wait for $$$ to iterate... applies to commands expecting a response
|
||||
break;
|
||||
case 2: //"5 min max"
|
||||
write('S', false); //wait for $$$ to iterate... applies to commands expecting a response
|
||||
break;
|
||||
case 3: //"5 min max"
|
||||
write('F', false); //wait for $$$ to iterate... applies to commands expecting a response
|
||||
break;
|
||||
case 4: //"5 min max"
|
||||
write('G', false); //wait for $$$ to iterate... applies to commands expecting a response
|
||||
break;
|
||||
case 5: //"5 min max"
|
||||
write('H', false); //wait for $$$ to iterate... applies to commands expecting a response
|
||||
break;
|
||||
case 6: //"5 min max"
|
||||
write('J', false); //wait for $$$ to iterate... applies to commands expecting a response
|
||||
break;
|
||||
case 7: //"5 min max"
|
||||
write('K', false); //wait for $$$ to iterate... applies to commands expecting a response
|
||||
break;
|
||||
case 8: //"5 min max"
|
||||
write('L', false); //wait for $$$ to iterate... applies to commands expecting a response
|
||||
break;
|
||||
default:
|
||||
break; // Do Nothing
|
||||
}
|
||||
println("Cyton: syncWithHardware: [5] Writing selected SD setting (" + sdSettingString + ") to OpenBCI...");
|
||||
//final hacky way of abandoning initiation if someone selected daisy but doesn't have one connected.
|
||||
if(abandonInit){
|
||||
haltSystem();
|
||||
output("No daisy board present. Make sure you selected the correct number of channels.");
|
||||
controlPanel.open();
|
||||
abandonInit = false;
|
||||
}
|
||||
break;
|
||||
case 6:
|
||||
output("Cyton: syncWithHardware: The GUI is done intializing. Click outside of the control panel to interact with the GUI.");
|
||||
hub.changeState(STATE_STOPPED);
|
||||
systemMode = 10;
|
||||
controlPanel.close();
|
||||
topNav.controlPanelCollapser.setIsActive(false);
|
||||
//renitialize GUI if nchan has been updated... needs to be built
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void writeCommand(String val) {
|
||||
if (hub.isHubRunning()) {
|
||||
hub.write(String.valueOf(val));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean write(char val) {
|
||||
if (hub.isHubRunning()) {
|
||||
hub.sendCommand(val);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean write(char val, boolean _readyToSend) {
|
||||
// if (isSerial()) {
|
||||
// iSerial.setReadyToSend(_readyToSend);
|
||||
// }
|
||||
return write(val);
|
||||
}
|
||||
|
||||
public boolean write(String out, boolean _readyToSend) {
|
||||
// if (isSerial()) {
|
||||
// iSerial.setReadyToSend(_readyToSend);
|
||||
// }
|
||||
return write(out);
|
||||
}
|
||||
|
||||
public boolean write(String out) {
|
||||
if (hub.isHubRunning()) {
|
||||
hub.write(out);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isSerial () {
|
||||
// println("My interface is " + curInterface);
|
||||
return curInterface == INTERFACE_SERIAL;
|
||||
}
|
||||
|
||||
private boolean isWifi () {
|
||||
return curInterface == INTERFACE_HUB_WIFI;
|
||||
}
|
||||
|
||||
public void startDataTransfer() {
|
||||
if (isPortOpen()) {
|
||||
// Now give the command to start binary data transmission
|
||||
if (isSerial()) {
|
||||
hub.changeState(STATE_NORMAL); // make sure it's now interpretting as binary
|
||||
println("Cyton: startDataTransfer(): writing \'" + command_startBinary + "\' to the serial port...");
|
||||
// if (isSerial()) iSerial.clear(); // clear anything in the com port's buffer
|
||||
write(command_startBinary);
|
||||
} else if (isWifi()) {
|
||||
println("Cyton: startDataTransfer(): writing \'" + command_startBinary + "\' to the wifi shield...");
|
||||
write(command_startBinary);
|
||||
}
|
||||
|
||||
} else {
|
||||
println("port not open");
|
||||
}
|
||||
}
|
||||
|
||||
public void stopDataTransfer() {
|
||||
if (isPortOpen()) {
|
||||
hub.changeState(STATE_STOPPED); // make sure it's now interpretting as binary
|
||||
println("Cyton: startDataTransfer(): writing \'" + command_stop + "\' to the serial port...");
|
||||
write(command_stop);// + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
public void printRegisters() {
|
||||
if (isPortOpen()) {
|
||||
println("Cyton: printRegisters(): Writing ? to OpenBCI...");
|
||||
write('?');
|
||||
}
|
||||
}
|
||||
|
||||
/* **** Borrowed from Chris Viegl from his OpenBCI parser for BrainBay
|
||||
Modified by Joel Murphy and Conor Russomanno to read OpenBCI data
|
||||
Packet Parser for OpenBCI (1-N channel binary format):
|
||||
3-byte data values are stored in 'little endian' formant in AVRs
|
||||
so this protocol parser expects the lower bytes first.
|
||||
Start Indicator: 0xA0
|
||||
EXPECTING STANDARD PACKET LENGTH DON'T NEED: Packet_length : 1 byte (length = 4 bytes framenumber + 4 bytes per active channel + (optional) 4 bytes for 1 Aux value)
|
||||
Framenumber : 1 byte (Sequential counter of packets)
|
||||
Channel 1 data : 3 bytes
|
||||
...
|
||||
Channel 8 data : 3 bytes
|
||||
Aux Values : UP TO 6 bytes
|
||||
End Indcator : 0xC0
|
||||
TOTAL OF 33 bytes ALL DAY
|
||||
********************************************************************* */
|
||||
private int nDataValuesInPacket = 0;
|
||||
private int localByteCounter=0;
|
||||
private int localChannelCounter=0;
|
||||
private int PACKET_readstate = 0;
|
||||
// byte[] localByteBuffer = {0,0,0,0};
|
||||
private byte[] localAdsByteBuffer = {0, 0, 0};
|
||||
private byte[] localAccelByteBuffer = {0, 0};
|
||||
|
||||
private boolean isPortOpen() {
|
||||
if (isWifi() || isSerial()) {
|
||||
return hub.isPortOpen();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//activate or deactivate an EEG channel...channel counting is zero through nchan-1
|
||||
public void changeChannelState(int Ichan, boolean activate) {
|
||||
if (isPortOpen()) {
|
||||
// if ((Ichan >= 0) && (Ichan < command_activate_channel.length)) {
|
||||
if ((Ichan >= 0)) {
|
||||
if (activate) {
|
||||
// write(command_activate_channel[Ichan]);
|
||||
// gui.cc.powerUpChannel(Ichan);
|
||||
w_timeSeries.hsc.powerUpChannel(Ichan);
|
||||
} else {
|
||||
// write(command_deactivate_channel[Ichan]);
|
||||
// gui.cc.powerDownChannel(Ichan);
|
||||
w_timeSeries.hsc.powerDownChannel(Ichan);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//deactivate an EEG channel...channel counting is zero through nchan-1
|
||||
public void deactivateChannel(int Ichan) {
|
||||
if (isPortOpen()) {
|
||||
if ((Ichan >= 0) && (Ichan < command_deactivate_channel.length)) {
|
||||
write(command_deactivate_channel[Ichan]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//activate an EEG channel...channel counting is zero through nchan-1
|
||||
public void activateChannel(int Ichan) {
|
||||
if (isPortOpen()) {
|
||||
if ((Ichan >= 0) && (Ichan < command_activate_channel.length)) {
|
||||
write(command_activate_channel[Ichan]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//return the state
|
||||
public boolean isStateNormal() {
|
||||
if (hub.get_state() == STATE_NORMAL) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private int copyRawDataToFullData() {
|
||||
//Prior to the 16-chan OpenBCI, we did NOT have rawReceivedDataPacket along with dataPacket...we just had dataPacket.
|
||||
//With the 16-chan OpenBCI, where the first 8 channels are sent and then the second 8 channels are sent, we introduced
|
||||
//this extra structure so that we could alternate between them.
|
||||
//
|
||||
//This function here decides how to join the latest data (rawReceivedDataPacket) into the full dataPacket
|
||||
|
||||
if (dataPacket.values.length < 2*rawReceivedDataPacket.values.length) {
|
||||
//this is an 8 channel board, so simply copy the data
|
||||
return rawReceivedDataPacket.copyTo(dataPacket);
|
||||
} else {
|
||||
//this is 16-channels, so copy the raw data into the correct channels of the new data
|
||||
int offsetInd_values = 0; //this is correct assuming we just recevied a "board" packet (ie, channels 1-8)
|
||||
int offsetInd_aux = 0; //this is correct assuming we just recevied a "board" packet (ie, channels 1-8)
|
||||
if (rawReceivedDataPacket.sampleIndex % 2 == 0) { // even data packets are from the daisy board
|
||||
offsetInd_values = rawReceivedDataPacket.values.length; //start copying to the 8th slot
|
||||
//offsetInd_aux = rawReceivedDataPacket.auxValues.length; //start copying to the 3rd slot
|
||||
offsetInd_aux = 0;
|
||||
}
|
||||
return rawReceivedDataPacket.copyTo(dataPacket, offsetInd_values, offsetInd_aux);
|
||||
}
|
||||
}
|
||||
|
||||
public int copyDataPacketTo(DataPacket_ADS1299 target) {
|
||||
return dataPacket.copyTo(target);
|
||||
}
|
||||
|
||||
|
||||
private long timeOfLastChannelWrite = 0;
|
||||
private int channelWriteCounter = 0;
|
||||
private boolean isWritingChannel = false;
|
||||
|
||||
public void configureAllChannelsToDefault() {
|
||||
write('d');
|
||||
};
|
||||
|
||||
public void initChannelWrite(int _numChannel) { //numChannel counts from zero
|
||||
timeOfLastChannelWrite = millis();
|
||||
isWritingChannel = true;
|
||||
}
|
||||
|
||||
public void syncChannelSettings() {
|
||||
write("r,start" + TCP_STOP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to convert a gain from the hub back into local codes.
|
||||
*/
|
||||
public char getCommandForGain(int gain) {
|
||||
switch (gain) {
|
||||
case 1:
|
||||
return '0';
|
||||
case 2:
|
||||
return '1';
|
||||
case 4:
|
||||
return '2';
|
||||
case 6:
|
||||
return '3';
|
||||
case 8:
|
||||
return '4';
|
||||
case 12:
|
||||
return '5';
|
||||
case 24:
|
||||
default:
|
||||
return '6';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to convert raw code to hub code
|
||||
* @param inputType {String} - The input from a hub sync channel with register settings
|
||||
*/
|
||||
public char getCommandForInputType(String inputType) {
|
||||
if (inputType.equals("normal")) return '0';
|
||||
if (inputType.equals("shorted")) return '1';
|
||||
if (inputType.equals("biasMethod")) return '2';
|
||||
if (inputType.equals("mvdd")) return '3';
|
||||
if (inputType.equals("temp")) return '4';
|
||||
if (inputType.equals("testsig")) return '5';
|
||||
if (inputType.equals("biasDrp")) return '6';
|
||||
if (inputType.equals("biasDrn")) return '7';
|
||||
return '0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to convert a local channel code into a hub gain which is human
|
||||
* readable and in scientific values.
|
||||
*/
|
||||
public int getGainForCommand(char cmd) {
|
||||
switch (cmd) {
|
||||
case '0':
|
||||
return 1;
|
||||
case '1':
|
||||
return 2;
|
||||
case '2':
|
||||
return 4;
|
||||
case '3':
|
||||
return 6;
|
||||
case '4':
|
||||
return 8;
|
||||
case '5':
|
||||
return 12;
|
||||
case '6':
|
||||
default:
|
||||
return 24;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used right before a channel setting command is sent to the hub to convert
|
||||
* local values into the expected form for the hub.
|
||||
*/
|
||||
public String getInputTypeForCommand(char cmd) {
|
||||
final String inputTypeShorted = "shorted";
|
||||
final String inputTypeBiasMethod = "biasMethod";
|
||||
final String inputTypeMvdd = "mvdd";
|
||||
final String inputTypeTemp = "temp";
|
||||
final String inputTypeTestsig = "testsig";
|
||||
final String inputTypeBiasDrp = "biasDrp";
|
||||
final String inputTypeBiasDrn = "biasDrn";
|
||||
final String inputTypeNormal = "normal";
|
||||
switch (cmd) {
|
||||
case '1':
|
||||
return inputTypeShorted;
|
||||
case '2':
|
||||
return inputTypeBiasMethod;
|
||||
case '3':
|
||||
return inputTypeMvdd;
|
||||
case '4':
|
||||
return inputTypeTemp;
|
||||
case '5':
|
||||
return inputTypeTestsig;
|
||||
case '6':
|
||||
return inputTypeBiasDrp;
|
||||
case '7':
|
||||
return inputTypeBiasDrn;
|
||||
case '0':
|
||||
default:
|
||||
return inputTypeNormal;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to convert a local index number to a hub human readable sd setting
|
||||
* command.
|
||||
*/
|
||||
public String getSDSettingForSetting(int setting) {
|
||||
switch (setting) {
|
||||
case 1:
|
||||
return "5min";
|
||||
case 2:
|
||||
return "15min";
|
||||
case 3:
|
||||
return "30min";
|
||||
case 4:
|
||||
return "1hour";
|
||||
case 5:
|
||||
return "2hour";
|
||||
case 6:
|
||||
return "4hour";
|
||||
case 7:
|
||||
return "12hour";
|
||||
case 8:
|
||||
return "24hour";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// FULL DISCLAIMER: this method is messy....... very messy... we had to brute force a firmware miscue
|
||||
public void writeChannelSettings(int _numChannel, char[][] channelSettingValues) { //numChannel counts from zero
|
||||
String output = "r,set,";
|
||||
output += Integer.toString(_numChannel) + ","; // 0 indexed channel number
|
||||
output += channelSettingValues[_numChannel][0] + ","; // power down
|
||||
output += getGainForCommand(channelSettingValues[_numChannel][1]) + ","; // gain
|
||||
output += getInputTypeForCommand(channelSettingValues[_numChannel][2]) + ",";
|
||||
output += channelSettingValues[_numChannel][3] + ",";
|
||||
output += channelSettingValues[_numChannel][4] + ",";
|
||||
output += channelSettingValues[_numChannel][5] + TCP_STOP;
|
||||
write(output);
|
||||
// verbosePrint("done writing channel.");
|
||||
isWritingChannel = false;
|
||||
}
|
||||
|
||||
private long timeOfLastImpWrite = 0;
|
||||
private int impWriteCounter = 0;
|
||||
private boolean isWritingImp = false;
|
||||
public boolean get_isWritingImp() {
|
||||
return isWritingImp;
|
||||
}
|
||||
|
||||
// public void initImpWrite(int _numChannel) { //numChannel counts from zero
|
||||
// timeOfLastImpWrite = millis();
|
||||
// isWritingImp = true;
|
||||
// }
|
||||
|
||||
public void writeImpedanceSettings(int _numChannel, char[][] impedanceCheckValues) { //numChannel counts from zero
|
||||
String output = "i,set,";
|
||||
if (_numChannel < 8) {
|
||||
output += (char)('0'+(_numChannel+1)) + ",";
|
||||
} else { //(_numChannel >= 8) {
|
||||
//command_activate_channel holds non-daisy and daisy values
|
||||
output += command_activate_channel[_numChannel] + ",";
|
||||
}
|
||||
output += impedanceCheckValues[_numChannel][0] + ",";
|
||||
output += impedanceCheckValues[_numChannel][1] + TCP_STOP;
|
||||
write(output);
|
||||
isWritingImp = false;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,321 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// This class configures and manages the connection to the OpenBCI Ganglion.
|
||||
// The connection is implemented via a TCP connection to a TCP port.
|
||||
// The Gagnlion is configured using single letter text commands sent from the
|
||||
// PC to the TCP server. The EEG data streams back from the Ganglion, to the
|
||||
// TCP server and back to the PC continuously (once started).
|
||||
//
|
||||
// Created: AJ Keller, August 2016
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// import java.io.OutputStream; //for logging raw bytes to an output file
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Global Functions
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
class Ganglion {
|
||||
final static String TCP_CMD_ACCEL = "a";
|
||||
final static String TCP_CMD_CONNECT = "c";
|
||||
final static String TCP_CMD_COMMAND = "k";
|
||||
final static String TCP_CMD_DISCONNECT = "d";
|
||||
final static String TCP_CMD_DATA= "t";
|
||||
final static String TCP_CMD_ERROR = "e"; //<>//
|
||||
final static String TCP_CMD_IMPEDANCE = "i";
|
||||
final static String TCP_CMD_LOG = "l";
|
||||
final static String TCP_CMD_SCAN = "s";
|
||||
final static String TCP_CMD_STATUS = "q";
|
||||
final static String TCP_STOP = ",;\n";
|
||||
|
||||
final static String TCP_ACTION_START = "start";
|
||||
final static String TCP_ACTION_STATUS = "status";
|
||||
final static String TCP_ACTION_STOP = "stop";
|
||||
|
||||
final static String GANGLION_BOOTLOADER_MODE = ">";
|
||||
|
||||
final static int NUM_ACCEL_DIMS = 3;
|
||||
|
||||
final static int RESP_ERROR_UNKNOWN = 499;
|
||||
final static int RESP_ERROR_BAD_PACKET = 500;
|
||||
final static int RESP_ERROR_BAD_NOBLE_START = 501;
|
||||
final static int RESP_ERROR_ALREADY_CONNECTED = 408;
|
||||
final static int RESP_ERROR_COMMAND_NOT_RECOGNIZED = 406;
|
||||
final static int RESP_ERROR_DEVICE_NOT_FOUND = 405;
|
||||
final static int RESP_ERROR_NO_OPEN_BLE_DEVICE = 400;
|
||||
final static int RESP_ERROR_UNABLE_TO_CONNECT = 402;
|
||||
final static int RESP_ERROR_UNABLE_TO_DISCONNECT = 401;
|
||||
final static int RESP_ERROR_SCAN_ALREADY_SCANNING = 409;
|
||||
final static int RESP_ERROR_SCAN_NONE_FOUND = 407;
|
||||
final static int RESP_ERROR_SCAN_NO_SCAN_TO_STOP = 410;
|
||||
final static int RESP_ERROR_SCAN_COULD_NOT_START = 412;
|
||||
final static int RESP_ERROR_SCAN_COULD_NOT_STOP = 411;
|
||||
final static int RESP_GANGLION_FOUND = 201;
|
||||
final static int RESP_SUCCESS = 200;
|
||||
final static int RESP_SUCCESS_DATA_ACCEL = 202;
|
||||
final static int RESP_SUCCESS_DATA_IMPEDANCE = 203;
|
||||
final static int RESP_SUCCESS_DATA_SAMPLE = 204;
|
||||
final static int RESP_STATUS_CONNECTED = 300;
|
||||
final static int RESP_STATUS_DISCONNECTED = 301;
|
||||
final static int RESP_STATUS_SCANNING = 302;
|
||||
final static int RESP_STATUS_NOT_SCANNING = 303;
|
||||
|
||||
private int nEEGValuesPerPacket = NCHAN_GANGLION; // Defined by the data format sent by cyton boards
|
||||
private int nAuxValuesPerPacket = NUM_ACCEL_DIMS; // Defined by the arduino code
|
||||
|
||||
private final float fsHzBLE = 200.0f; //sample rate used by OpenBCI Ganglion board... set by its Arduino code
|
||||
private final float fsHzWifi = 1600.0f; //sample rate used by OpenBCI Ganglion board on wifi, set by hub
|
||||
private final int NfftBLE = 256;
|
||||
private final int NfftWifi = 2048;
|
||||
private final float MCP3912_Vref = 1.2f; // reference voltage for ADC in MCP3912 set in hardware
|
||||
private float MCP3912_gain = 1.0; //assumed gain setting for MCP3912. NEEDS TO BE ADJUSTABLE JM
|
||||
private float scale_fac_uVolts_per_count = (MCP3912_Vref * 1000000.f) / (8388607.0 * MCP3912_gain * 1.5 * 51.0); //MCP3912 datasheet page 34. Gain of InAmp = 80
|
||||
// private float scale_fac_accel_G_per_count = 0.032;
|
||||
private float scale_fac_accel_G_per_count_ble = 0.016;
|
||||
private float scale_fac_accel_G_per_count_wifi = 0.001;
|
||||
// private final float scale_fac_accel_G_per_count = 0.002 / ((float)pow(2,4)); //assume set to +/4G, so 2 mG per digit (datasheet). Account for 4 bits unused
|
||||
// private final float leadOffDrive_amps = 6.0e-9; //6 nA, set by its Arduino code
|
||||
|
||||
private int curInterface = INTERFACE_NONE;
|
||||
|
||||
private DataPacket_ADS1299 dataPacket;
|
||||
|
||||
private boolean connected = false;
|
||||
|
||||
public int numberOfDevices = 0;
|
||||
public int maxNumberOfDevices = 10;
|
||||
|
||||
private boolean checkingImpedance = false;
|
||||
private boolean accelModeActive = false;
|
||||
|
||||
public boolean impedanceUpdated = false;
|
||||
public int[] impedanceArray = new int[NCHAN_GANGLION + 1];
|
||||
|
||||
private int sampleRate = (int)fsHzWifi;
|
||||
|
||||
// Getters
|
||||
public float getSampleRate() {
|
||||
if (isBLE()) {
|
||||
return fsHzBLE;
|
||||
} else {
|
||||
return hub.getSampleRate();
|
||||
}
|
||||
}
|
||||
public int getNfft() {
|
||||
if (isWifi()) {
|
||||
if (hub.getSampleRate() == (int)fsHzBLE) {
|
||||
return NfftBLE;
|
||||
} else {
|
||||
return NfftWifi;
|
||||
}
|
||||
} else {
|
||||
return NfftBLE;
|
||||
}
|
||||
}
|
||||
public float get_scale_fac_uVolts_per_count() { return scale_fac_uVolts_per_count; }
|
||||
public float get_scale_fac_accel_G_per_count() {
|
||||
if (isWifi()) {
|
||||
return scale_fac_accel_G_per_count_wifi;
|
||||
} else {
|
||||
return scale_fac_accel_G_per_count_ble;
|
||||
}
|
||||
}
|
||||
public boolean isCheckingImpedance() { return checkingImpedance; }
|
||||
public boolean isAccelModeActive() { return accelModeActive; }
|
||||
public void overrideCheckingImpedance(boolean val) { checkingImpedance = val; }
|
||||
public int getInterface() {
|
||||
return curInterface;
|
||||
}
|
||||
public boolean isBLE () {
|
||||
return curInterface == INTERFACE_HUB_BLE || curInterface == INTERFACE_HUB_BLED112;
|
||||
}
|
||||
|
||||
public boolean isWifi () {
|
||||
return curInterface == INTERFACE_HUB_WIFI;
|
||||
}
|
||||
|
||||
public boolean isPortOpen() {
|
||||
return hub.isPortOpen();
|
||||
}
|
||||
|
||||
private PApplet mainApplet;
|
||||
|
||||
//constructors
|
||||
Ganglion() {}; //only use this if you simply want access to some of the constants
|
||||
Ganglion(PApplet applet) {
|
||||
mainApplet = applet;
|
||||
|
||||
initDataPackets(nEEGValuesPerPacket, nAuxValuesPerPacket);
|
||||
}
|
||||
|
||||
public void initDataPackets(int _nEEGValuesPerPacket, int _nAuxValuesPerPacket) {
|
||||
nEEGValuesPerPacket = _nEEGValuesPerPacket;
|
||||
nAuxValuesPerPacket = _nAuxValuesPerPacket;
|
||||
// For storing data into
|
||||
dataPacket = new DataPacket_ADS1299(nEEGValuesPerPacket, nAuxValuesPerPacket); //this should always be 8 channels
|
||||
for(int i = 0; i < nEEGValuesPerPacket; i++) {
|
||||
dataPacket.values[i] = 0;
|
||||
}
|
||||
for(int i = 0; i < nAuxValuesPerPacket; i++){
|
||||
dataPacket.auxValues[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleError(int code, String msg) {
|
||||
output("Code " + code + "Error: " + msg);
|
||||
println("Code " + code + "Error: " + msg);
|
||||
}
|
||||
|
||||
public void processImpedance(String msg) {
|
||||
String[] list = split(msg, ',');
|
||||
if (Integer.parseInt(list[1]) == RESP_SUCCESS_DATA_IMPEDANCE) {
|
||||
int channel = Integer.parseInt(list[2]);
|
||||
if (channel < 5) { //<>//
|
||||
int value = Integer.parseInt(list[3]);
|
||||
impedanceArray[channel] = value;
|
||||
if (channel == 0) {
|
||||
impedanceUpdated = true;
|
||||
println("Impedance for channel reference is " + value + " ohms.");
|
||||
} else {
|
||||
println("Impedance for channel " + channel + " is " + value + " ohms.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setSampleRate(int _sampleRate) {
|
||||
sampleRate = _sampleRate;
|
||||
hub.setSampleRate(sampleRate);
|
||||
output("Setting sample rate for Ganglion to " + sampleRate + "Hz");
|
||||
}
|
||||
|
||||
public void setInterface(int _interface) {
|
||||
curInterface = _interface;
|
||||
if (isBLE()) {
|
||||
setSampleRate((int)fsHzBLE);
|
||||
if (_interface == INTERFACE_HUB_BLE) {
|
||||
hub.setProtocol(PROTOCOL_BLE);
|
||||
} else {
|
||||
hub.setProtocol(PROTOCOL_BLED112);
|
||||
}
|
||||
// hub.searchDeviceStart();
|
||||
} else if (isWifi()) {
|
||||
setSampleRate((int)fsHzWifi);
|
||||
hub.setProtocol(PROTOCOL_WIFI);
|
||||
hub.searchDeviceStart();
|
||||
}
|
||||
}
|
||||
|
||||
public int copyDataPacketTo(DataPacket_ADS1299 target) {
|
||||
return dataPacket.copyTo(target);
|
||||
}
|
||||
|
||||
// SCANNING/SEARHING FOR DEVICES
|
||||
public int closePort() {
|
||||
if (isBLE()) {
|
||||
hub.disconnectBLE();
|
||||
} else if (isWifi()) {
|
||||
hub.disconnectWifi();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Sends a start streaming command to the Ganglion Node module.
|
||||
*/
|
||||
void startDataTransfer(){
|
||||
hub.changeState(STATE_NORMAL); // make sure it's now interpretting as binary
|
||||
println("Ganglion: startDataTransfer(): sending \'" + command_startBinary);
|
||||
if (checkingImpedance) {
|
||||
impedanceStop();
|
||||
delay(100);
|
||||
hub.sendCommand('b');
|
||||
} else {
|
||||
hub.sendCommand('b');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Sends a stop streaming command to the Ganglion Node module.
|
||||
*/
|
||||
public void stopDataTransfer() {
|
||||
hub.changeState(STATE_STOPPED); // make sure it's now interpretting as binary
|
||||
println("Ganglion: stopDataTransfer(): sending \'" + command_stop);
|
||||
hub.sendCommand('s');
|
||||
}
|
||||
|
||||
private void printGanglion(String msg) {
|
||||
print("Ganglion: "); println(msg);
|
||||
}
|
||||
|
||||
// Channel setting
|
||||
//activate or deactivate an EEG channel...channel counting is zero through nchan-1
|
||||
public void changeChannelState(int Ichan, boolean activate) {
|
||||
if (isPortOpen()) {
|
||||
if ((Ichan >= 0)) {
|
||||
if (activate) {
|
||||
println("Ganglion: changeChannelState(): activate: sending " + command_activate_channel[Ichan]);
|
||||
hub.sendCommand(command_activate_channel[Ichan]);
|
||||
w_timeSeries.hsc.powerUpChannel(Ichan);
|
||||
} else {
|
||||
println("Ganglion: changeChannelState(): deactivate: sending " + command_deactivate_channel[Ichan]);
|
||||
hub.sendCommand(command_deactivate_channel[Ichan]);
|
||||
w_timeSeries.hsc.powerDownChannel(Ichan);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to start accel data mode. Accel arrays will arrive asynchronously!
|
||||
*/
|
||||
public void accelStart() {
|
||||
println("Ganglion: accell: START");
|
||||
hub.write(TCP_CMD_ACCEL + "," + TCP_ACTION_START + TCP_STOP);
|
||||
accelModeActive = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to stop accel data mode. Some accel arrays may arrive after stop command
|
||||
* was sent by this function.
|
||||
*/
|
||||
public void accelStop() {
|
||||
println("Ganglion: accel: STOP");
|
||||
hub.write(TCP_CMD_ACCEL + "," + TCP_ACTION_STOP + TCP_STOP);
|
||||
accelModeActive = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to start impedance testing. Impedances will arrive asynchronously!
|
||||
*/
|
||||
public void impedanceStart() {
|
||||
println("Ganglion: impedance: START");
|
||||
hub.write(TCP_CMD_IMPEDANCE + "," + TCP_ACTION_START + TCP_STOP);
|
||||
checkingImpedance = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to stop impedance testing. Some impedances may arrive after stop command
|
||||
* was sent by this function.
|
||||
*/
|
||||
public void impedanceStop() {
|
||||
println("Ganglion: impedance: STOP");
|
||||
hub.write(TCP_CMD_IMPEDANCE + "," + TCP_ACTION_STOP + TCP_STOP);
|
||||
checkingImpedance = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the ganglion in bootloader mode.
|
||||
*/
|
||||
public void enterBootloaderMode() {
|
||||
println("Ganglion: Entering Bootloader Mode");
|
||||
hub.sendCommand(GANGLION_BOOTLOADER_MODE.charAt(0));
|
||||
delay(500);
|
||||
closePort();
|
||||
haltSystem();
|
||||
initSystemButton.setString("START SYSTEM");
|
||||
controlPanel.open();
|
||||
output("Ganglion now in bootloader mode! Enjoy!");
|
||||
}
|
||||
};
|
||||
@@ -7,19 +7,19 @@
|
||||
// Ex: container[1] is the upper left corner of the large rectangle between [0] & [10]
|
||||
// Ex 2: container[6] is the entire right half of the same rectangle.
|
||||
//
|
||||
// ------------------------------------------------
|
||||
// +----------------------------------------------+
|
||||
// | [0] |
|
||||
// ------------------------------------------------
|
||||
// +----------------------------------------------+
|
||||
// | | [11] |
|
||||
// | [1] [2]---[15]--[3]---[16]--|
|
||||
// | [1] [2]---[15]--[3]---[16]--+
|
||||
// | | [12] |
|
||||
// |---------[4]----------[5]---------[6]---------|
|
||||
// +---------[4]----------[5]---------[6]---------+
|
||||
// | | [13] |
|
||||
// | [7] [8]---[17]--[9]---[18]--|
|
||||
// | [7] [8]---[17]--[9]---[18]--+
|
||||
// | | [14] |
|
||||
// ------------------------------------------------
|
||||
// +----------------------------------------------+
|
||||
// | [10] |
|
||||
// ------------------------------------------------
|
||||
// +----------------------------------------------+
|
||||
//
|
||||
// Created by: Conor Russomanno (May 2016)
|
||||
//
|
||||
|
||||
@@ -49,14 +49,11 @@ void openNewLogFileBDF(String _fileName) {
|
||||
closeLogFile();
|
||||
}
|
||||
//open the new file
|
||||
if (eegDataSource == DATASOURCE_GANGLION) {
|
||||
fileoutput_bdf = new OutputFile_BDF(ganglion.get_fs_Hz(), nchan, _fileName);
|
||||
} else {
|
||||
fileoutput_bdf = new OutputFile_BDF(openBCI.get_fs_Hz(), nchan, _fileName);
|
||||
}
|
||||
fileoutput_bdf = new OutputFile_BDF(getSampleRateSafe(), nchan, _fileName);
|
||||
|
||||
output_fname = fileoutput_bdf.fname;
|
||||
println("openBCI: openNewLogFile: opened BDF output file: " + output_fname);
|
||||
output("openBCI: openNewLogFile: opened BDF output file: " + output_fname);
|
||||
println("cyton: openNewLogFile: opened BDF output file: " + output_fname);
|
||||
output("cyton: openNewLogFile: opened BDF output file: " + output_fname);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,14 +67,11 @@ void openNewLogFileODF(String _fileName) {
|
||||
closeLogFile();
|
||||
}
|
||||
//open the new file
|
||||
if (eegDataSource == DATASOURCE_GANGLION) {
|
||||
fileoutput_odf = new OutputFile_rawtxt(ganglion.get_fs_Hz(), _fileName);
|
||||
} else {
|
||||
fileoutput_odf = new OutputFile_rawtxt(openBCI.get_fs_Hz(), _fileName);
|
||||
}
|
||||
fileoutput_odf = new OutputFile_rawtxt(getSampleRateSafe(), _fileName);
|
||||
|
||||
output_fname = fileoutput_odf.fname;
|
||||
println("openBCI: openNewLogFile: opened ODF output file: " + output_fname);
|
||||
output("openBCI: openNewLogFile: opened ODF output file: " + output_fname);
|
||||
println("cyton: openNewLogFile: opened ODF output file: " + output_fname);
|
||||
output("cyton: openNewLogFile: opened ODF output file: " + output_fname);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,9 +152,15 @@ String getDateString() {
|
||||
|
||||
//these functions are relevant to convertSDFile
|
||||
void createPlaybackFileFromSD() {
|
||||
logFileName = "data/EEG_Data/SDconverted-"+getDateString()+".txt";
|
||||
logFileName = "SavedData/SDconverted-"+getDateString()+".csv";
|
||||
dataWriter = createWriter(logFileName);
|
||||
dataWriter.println("%OBCI Data Log - " + getDateString());
|
||||
dataWriter.println("%OBCI SD Convert - " + getDateString());
|
||||
dataWriter.println("%");
|
||||
dataWriter.println("%Sample Rate = 250.0 Hz");
|
||||
dataWriter.println("%First Column = SampleIndex");
|
||||
dataWriter.println("%Last Column = Timestamp");
|
||||
dataWriter.println("%Other Columns = EEG data in microvolts followed by Accel Data (in G) interleaved with Aux Data");
|
||||
|
||||
}
|
||||
|
||||
void sdFileSelected(File selection) {
|
||||
@@ -239,17 +239,22 @@ public class OutputFile_rawtxt {
|
||||
output.flush();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void writeRawData_dataPacket(DataPacket_ADS1299 data, float scale_to_uV, float scale_for_aux) {
|
||||
|
||||
public void writeRawData_dataPacket(DataPacket_ADS1299 data, float scale_to_uV, float scale_for_aux, int stopByte) {
|
||||
//get current date time with Date()
|
||||
Date date = new Date();
|
||||
|
||||
if (output != null) {
|
||||
output.print(Integer.toString(data.sampleIndex));
|
||||
writeValues(data.values,scale_to_uV);
|
||||
writeAccValues(data.auxValues,scale_for_aux);
|
||||
if (eegDataSource == DATASOURCE_GANGLION) {
|
||||
writeAccValues(data.auxValues, scale_for_aux);
|
||||
} else {
|
||||
if (stopByte == 0xC1) {
|
||||
writeAuxValues(data);
|
||||
} else {
|
||||
writeAccValues(data.auxValues, scale_for_aux);
|
||||
}
|
||||
}
|
||||
output.print( ", " + dateFormat.format(date));
|
||||
output.println(); rowsWritten++;
|
||||
//output.flush();
|
||||
@@ -272,6 +277,51 @@ public class OutputFile_rawtxt {
|
||||
}
|
||||
}
|
||||
|
||||
private void writeAuxValues(DataPacket_ADS1299 data) {
|
||||
if (eegDataSource == DATASOURCE_CYTON) {
|
||||
// println("board mode: " + cyton.getBoardMode());
|
||||
if (cyton.getBoardMode() == BOARD_MODE_DIGITAL) {
|
||||
if (cyton.isWifi()) {
|
||||
output.print(", " + ((data.auxValues[0] & 0xFF00) >> 8));
|
||||
output.print(", " + (data.auxValues[0] & 0xFF));
|
||||
output.print(", " + data.auxValues[1]);
|
||||
} else {
|
||||
output.print(", " + ((data.auxValues[0] & 0xFF00) >> 8));
|
||||
output.print(", " + (data.auxValues[0] & 0xFF));
|
||||
output.print(", " + ((data.auxValues[1] & 0xFF00) >> 8));
|
||||
output.print(", " + (data.auxValues[1] & 0xFF));
|
||||
output.print(", " + data.auxValues[2]);
|
||||
}
|
||||
} else if (cyton.getBoardMode() == BOARD_MODE_ANALOG) {
|
||||
if (cyton.isWifi()) {
|
||||
output.print(", " + data.auxValues[0]);
|
||||
output.print(", " + data.auxValues[1]);
|
||||
} else {
|
||||
output.print(", " + data.auxValues[0]);
|
||||
output.print(", " + data.auxValues[1]);
|
||||
output.print(", " + data.auxValues[2]);
|
||||
}
|
||||
} else if (cyton.getBoardMode() == BOARD_MODE_MARKER) {
|
||||
output.print(", " + data.auxValues[0]);
|
||||
if ( data.auxValues[0] > 0) {
|
||||
hub.validLastMarker = data.auxValues[0];
|
||||
}
|
||||
|
||||
} else {
|
||||
for (int Ival = 0; Ival < 3; Ival++) {
|
||||
output.print(", " + data.auxValues[Ival]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
output.print(", " + (data.auxValues[i] & 0xFF));
|
||||
output.print(", " + ((data.auxValues[i] & 0xFF00) >> 8));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void closeFile() {
|
||||
output.flush();
|
||||
output.close();
|
||||
@@ -510,14 +560,14 @@ public class OutputFile_BDF {
|
||||
}
|
||||
|
||||
writeChannelDataValues(data.rawValues);
|
||||
if (eegDataSource == DATASOURCE_NORMAL_W_AUX) {
|
||||
if (eegDataSource == DATASOURCE_CYTON) {
|
||||
writeAuxDataValues(data.rawAuxValues);
|
||||
}
|
||||
samplesInDataRecord++;
|
||||
// writeValues(data.auxValues,scale_for_aux);
|
||||
if (samplesInDataRecord >= fs_Hz) {
|
||||
arrayCopy(chanValBuf,chanValBuf_buffer);
|
||||
if (eegDataSource == DATASOURCE_NORMAL_W_AUX) {
|
||||
if (eegDataSource == DATASOURCE_CYTON) {
|
||||
arrayCopy(auxValBuf,auxValBuf_buffer);
|
||||
}
|
||||
|
||||
@@ -535,7 +585,7 @@ public class OutputFile_BDF {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (eegDataSource == DATASOURCE_NORMAL_W_AUX) {
|
||||
if (eegDataSource == DATASOURCE_CYTON) {
|
||||
for (int i = 0; i < nbAux; i++) {
|
||||
for (int j = 0; j < fs_Hz; j++) {
|
||||
for (int k = 0; k < 3; k++) {
|
||||
@@ -940,9 +990,9 @@ public class OutputFile_BDF {
|
||||
* @returns {String} - A fully qualified name of an output file with `str`.
|
||||
*/
|
||||
private String getFileName(String s) {
|
||||
String output = "SavedData"+System.getProperty("file.separator")+"OpenBCI-EDF-";
|
||||
String output = "SavedData"+System.getProperty("file.separator")+"OpenBCI-BDF-";
|
||||
output += s;
|
||||
output += ".edf";
|
||||
output += ".bdf";
|
||||
return output;
|
||||
}
|
||||
|
||||
@@ -952,7 +1002,7 @@ public class OutputFile_BDF {
|
||||
* @returns {int} - The number of signals in the header.
|
||||
*/
|
||||
private int getNbSignals() {
|
||||
if (eegDataSource == DATASOURCE_NORMAL_W_AUX) {
|
||||
if (eegDataSource == DATASOURCE_CYTON) {
|
||||
return nbChan + nbAux + nbAnnotations;
|
||||
} else {
|
||||
return nbChan + nbAnnotations;
|
||||
@@ -1229,43 +1279,43 @@ public class OutputFile_BDF {
|
||||
writeString(padStringRight(str(getNbSignals()),BDF_HEADER_SIZE_NUMBER_SIGNALS), o);
|
||||
|
||||
writeStringArrayWithPaddingTimes(labelsEEG, BDF_HEADER_NS_SIZE_LABEL, o);
|
||||
if (eegDataSource == DATASOURCE_NORMAL_W_AUX) writeStringArrayWithPaddingTimes(labelsAux, BDF_HEADER_NS_SIZE_LABEL, o);
|
||||
if (eegDataSource == DATASOURCE_CYTON) writeStringArrayWithPaddingTimes(labelsAux, BDF_HEADER_NS_SIZE_LABEL, o);
|
||||
writeStringArrayWithPaddingTimes(labelsAnnotations, BDF_HEADER_NS_SIZE_LABEL, o);
|
||||
|
||||
writeStringArrayWithPaddingTimes(transducerEEG, BDF_HEADER_NS_SIZE_TRANSDUCER_TYPE, o);
|
||||
if (eegDataSource == DATASOURCE_NORMAL_W_AUX) writeStringArrayWithPaddingTimes(transducerAux, BDF_HEADER_NS_SIZE_TRANSDUCER_TYPE, o);
|
||||
if (eegDataSource == DATASOURCE_CYTON) writeStringArrayWithPaddingTimes(transducerAux, BDF_HEADER_NS_SIZE_TRANSDUCER_TYPE, o);
|
||||
writeStringArrayWithPaddingTimes(transducerAnnotations, BDF_HEADER_NS_SIZE_TRANSDUCER_TYPE, o);
|
||||
|
||||
writeStringArrayWithPaddingTimes(physicalDimensionEEG, BDF_HEADER_NS_SIZE_PHYSICAL_DIMENSION, o);
|
||||
if (eegDataSource == DATASOURCE_NORMAL_W_AUX) writeStringArrayWithPaddingTimes(physicalDimensionAux, BDF_HEADER_NS_SIZE_PHYSICAL_DIMENSION, o);
|
||||
if (eegDataSource == DATASOURCE_CYTON) writeStringArrayWithPaddingTimes(physicalDimensionAux, BDF_HEADER_NS_SIZE_PHYSICAL_DIMENSION, o);
|
||||
writeStringArrayWithPaddingTimes(physicalDimensionAnnotations, BDF_HEADER_NS_SIZE_PHYSICAL_DIMENSION, o);
|
||||
|
||||
writeStringArrayWithPaddingTimes(physicalMinimumEEG, BDF_HEADER_NS_SIZE_PHYSICAL_MINIMUM, o);
|
||||
if (eegDataSource == DATASOURCE_NORMAL_W_AUX) writeStringArrayWithPaddingTimes(physicalMinimumAux, BDF_HEADER_NS_SIZE_PHYSICAL_MINIMUM, o);
|
||||
if (eegDataSource == DATASOURCE_CYTON) writeStringArrayWithPaddingTimes(physicalMinimumAux, BDF_HEADER_NS_SIZE_PHYSICAL_MINIMUM, o);
|
||||
writeStringArrayWithPaddingTimes(physicalMinimumAnnotations, BDF_HEADER_NS_SIZE_PHYSICAL_MINIMUM, o);
|
||||
|
||||
writeStringArrayWithPaddingTimes(physicalMaximumEEG, BDF_HEADER_NS_SIZE_PHYSICAL_MAXIMUM, o);
|
||||
if (eegDataSource == DATASOURCE_NORMAL_W_AUX) writeStringArrayWithPaddingTimes(physicalMaximumAux, BDF_HEADER_NS_SIZE_PHYSICAL_MAXIMUM, o);
|
||||
if (eegDataSource == DATASOURCE_CYTON) writeStringArrayWithPaddingTimes(physicalMaximumAux, BDF_HEADER_NS_SIZE_PHYSICAL_MAXIMUM, o);
|
||||
writeStringArrayWithPaddingTimes(physicalMaximumAnnotations, BDF_HEADER_NS_SIZE_PHYSICAL_MAXIMUM, o);
|
||||
|
||||
writeStringArrayWithPaddingTimes(digitalMinimumEEG, BDF_HEADER_NS_SIZE_DIGITAL_MINIMUM, o);
|
||||
if (eegDataSource == DATASOURCE_NORMAL_W_AUX) writeStringArrayWithPaddingTimes(digitalMinimumAux, BDF_HEADER_NS_SIZE_DIGITAL_MINIMUM, o);
|
||||
if (eegDataSource == DATASOURCE_CYTON) writeStringArrayWithPaddingTimes(digitalMinimumAux, BDF_HEADER_NS_SIZE_DIGITAL_MINIMUM, o);
|
||||
writeStringArrayWithPaddingTimes(digitalMinimumAnnotations, BDF_HEADER_NS_SIZE_DIGITAL_MINIMUM, o);
|
||||
|
||||
writeStringArrayWithPaddingTimes(digitalMaximumEEG, BDF_HEADER_NS_SIZE_DIGITAL_MAXIMUM, o);
|
||||
if (eegDataSource == DATASOURCE_NORMAL_W_AUX) writeStringArrayWithPaddingTimes(digitalMaximumAux, BDF_HEADER_NS_SIZE_DIGITAL_MAXIMUM, o);
|
||||
if (eegDataSource == DATASOURCE_CYTON) writeStringArrayWithPaddingTimes(digitalMaximumAux, BDF_HEADER_NS_SIZE_DIGITAL_MAXIMUM, o);
|
||||
writeStringArrayWithPaddingTimes(digitalMaximumAnnotations, BDF_HEADER_NS_SIZE_DIGITAL_MAXIMUM, o);
|
||||
|
||||
writeStringArrayWithPaddingTimes(prefilteringEEG, BDF_HEADER_NS_SIZE_PREFILTERING, o);
|
||||
if (eegDataSource == DATASOURCE_NORMAL_W_AUX) writeStringArrayWithPaddingTimes(prefilteringAux, BDF_HEADER_NS_SIZE_PREFILTERING, o);
|
||||
if (eegDataSource == DATASOURCE_CYTON) writeStringArrayWithPaddingTimes(prefilteringAux, BDF_HEADER_NS_SIZE_PREFILTERING, o);
|
||||
writeStringArrayWithPaddingTimes(prefilteringAnnotations, BDF_HEADER_NS_SIZE_PREFILTERING, o);
|
||||
|
||||
writeStringArrayWithPaddingTimes(nbSamplesPerDataRecordEEG, BDF_HEADER_NS_SIZE_NR, o);
|
||||
if (eegDataSource == DATASOURCE_NORMAL_W_AUX) writeStringArrayWithPaddingTimes(nbSamplesPerDataRecordAux, BDF_HEADER_NS_SIZE_NR, o);
|
||||
if (eegDataSource == DATASOURCE_CYTON) writeStringArrayWithPaddingTimes(nbSamplesPerDataRecordAux, BDF_HEADER_NS_SIZE_NR, o);
|
||||
writeStringArrayWithPaddingTimes(nbSamplesPerDataRecordAnnotations, BDF_HEADER_NS_SIZE_NR, o);
|
||||
|
||||
writeStringArrayWithPaddingTimes(reservedEEG, BDF_HEADER_NS_SIZE_RESERVED, o);
|
||||
if (eegDataSource == DATASOURCE_NORMAL_W_AUX) writeStringArrayWithPaddingTimes(reservedAux, BDF_HEADER_NS_SIZE_RESERVED, o);
|
||||
if (eegDataSource == DATASOURCE_CYTON) writeStringArrayWithPaddingTimes(reservedAux, BDF_HEADER_NS_SIZE_RESERVED, o);
|
||||
writeStringArrayWithPaddingTimes(reservedAnnotations, BDF_HEADER_NS_SIZE_RESERVED, o);
|
||||
|
||||
// println("writeHeader: done...");
|
||||
@@ -1322,6 +1372,8 @@ public class OutputFile_BDF {
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
class Table_CSV extends Table {
|
||||
private int sampleRate;
|
||||
public int getSampleRate() { return sampleRate; }
|
||||
Table_CSV(String fname) throws IOException {
|
||||
init();
|
||||
readCSV(PApplet.createReader(createInput(fname)));
|
||||
@@ -1342,7 +1394,24 @@ class Table_CSV extends Table {
|
||||
while ( (line = reader.readLine ()) != null) {
|
||||
//added by Chip, May 2, 2014 to ignore lines that are comments
|
||||
if (line.charAt(0) == '%') {
|
||||
//println("Table_CSV: readCSV: ignoring commented line...");
|
||||
if (line.length() > 18) {
|
||||
if (line.charAt(1) == 'S') {
|
||||
// println(line.substring(15, 18));
|
||||
sampleRate = Integer.parseInt(line.substring(15, 18));
|
||||
if (sampleRate == 100 || sampleRate == 160) {
|
||||
sampleRate = Integer.parseInt(line.substring(15, 19));
|
||||
}
|
||||
println("Sample rate set to " + sampleRate);
|
||||
// String[] m = match(line, "\\d+");
|
||||
// if (m != null) {
|
||||
// println("Found '" + m[1] + "' inside the line");
|
||||
// }
|
||||
}
|
||||
}
|
||||
println(line);
|
||||
// if (line.charAt(1) == 'S') {
|
||||
// println("sampel rarteakjdsf;ldj");
|
||||
// }
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1394,6 +1463,7 @@ class Table_CSV extends Table {
|
||||
// This collection of functions/methods - convertSDFile, createPlaybackFileFromSD, & sdFileSelected - contains code
|
||||
// used to convert HEX files (stored by OpenBCI on the local SD) into text files that can be used for PLAYBACK mode.
|
||||
// Created: Conor Russomanno - 10/22/14 (based on code written by Joel Murphy summer 2014)
|
||||
// Updated: Joel Murphy - 6/26/17
|
||||
//
|
||||
//////////////////////////////////
|
||||
|
||||
@@ -1404,13 +1474,16 @@ PrintWriter dataWriter;
|
||||
String convertedLine;
|
||||
String thisLine;
|
||||
String h;
|
||||
float[] floatData = new float[20];
|
||||
float[] intData = new float[20];
|
||||
String logFileName;
|
||||
String[] hexNums;
|
||||
long thisTime;
|
||||
long thatTime;
|
||||
boolean printNextLine = false;
|
||||
|
||||
public void convertSDFile() {
|
||||
println("");
|
||||
// println("");
|
||||
try {
|
||||
dataLine = dataReader.readLine();
|
||||
}
|
||||
@@ -1425,54 +1498,215 @@ public void convertSDFile() {
|
||||
controlPanel.convertingSD = false;
|
||||
println("nothing left in file");
|
||||
println("SD file conversion took "+thisTime+" mS");
|
||||
outputSuccess("SD file converted to " + logFileName);
|
||||
dataWriter.flush();
|
||||
dataWriter.close();
|
||||
} else {
|
||||
// println(dataLine);
|
||||
String[] hexNums = splitTokens(dataLine, ",");
|
||||
}
|
||||
else
|
||||
{
|
||||
hexNums = splitTokens(dataLine, ",");
|
||||
|
||||
if (hexNums[0].charAt(0) == '%') {
|
||||
// println(dataLine);
|
||||
dataWriter.println(dataLine);
|
||||
// dataWriter.println(dataLine);
|
||||
println(dataLine);
|
||||
printNextLine = true;
|
||||
} else {
|
||||
for (int i=0; i<hexNums.length; i++) {
|
||||
h = hexNums[i];
|
||||
if (i > 0) {
|
||||
if (h.charAt(0) > '7') { // if the number is negative
|
||||
h = "FF" + hexNums[i]; // keep it negative
|
||||
} else { // if the number is positive
|
||||
h = "00" + hexNums[i]; // keep it positive
|
||||
}
|
||||
if (i > 8) { // accelerometer data needs another byte
|
||||
if (h.charAt(0) == 'F') {
|
||||
h = "FF" + h;
|
||||
} else {
|
||||
h = "00" + h;
|
||||
}
|
||||
}
|
||||
}
|
||||
// println(h); // use for debugging
|
||||
if (h.length()%2 == 0) { // make sure this is a real number
|
||||
intData[i] = unhex(h);
|
||||
} else {
|
||||
intData[i] = 0;
|
||||
}
|
||||
|
||||
//if not first column(sample #) or columns 9-11 (accelerometer), convert to uV
|
||||
if (i>=1 && i<=8) {
|
||||
intData[i] *= openBCI.get_scale_fac_uVolts_per_count();
|
||||
}
|
||||
|
||||
//print the current channel value
|
||||
dataWriter.print(intData[i]);
|
||||
if (i < hexNums.length-1) {
|
||||
//print "," separator
|
||||
dataWriter.print(",");
|
||||
}
|
||||
if (hexNums.length < 13){
|
||||
convert8channelLine();
|
||||
} else {
|
||||
convert16channelLine();
|
||||
}
|
||||
if(printNextLine){
|
||||
printNextLine = false;
|
||||
}
|
||||
//println();
|
||||
dataWriter.println();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void convert16channelLine() {
|
||||
if(printNextLine){
|
||||
for(int i=0; i<hexNums.length; i++){
|
||||
h = hexNums[i];
|
||||
if (h.length()%2 == 0) { // make sure this is a real number
|
||||
intData[i] = unhex(h);
|
||||
} else {
|
||||
intData[i] = 0;
|
||||
}
|
||||
dataWriter.print(intData[i]);
|
||||
print(intData[i]);
|
||||
if(hexNums.length > 1){
|
||||
dataWriter.print(", ");
|
||||
print(", ");
|
||||
}
|
||||
}
|
||||
dataWriter.println();
|
||||
println();
|
||||
return;
|
||||
}
|
||||
for (int i=0; i<hexNums.length; i++) {
|
||||
h = hexNums[i];
|
||||
if (i > 0) {
|
||||
if (h.charAt(0) > '7') { // if the number is negative
|
||||
h = "FF" + hexNums[i]; // keep it negative
|
||||
} else { // if the number is positive
|
||||
h = "00" + hexNums[i]; // keep it positive
|
||||
}
|
||||
if (i > 16) { // accelerometer data needs another byte
|
||||
if (h.charAt(0) == 'F') {
|
||||
h = "FF" + h;
|
||||
} else {
|
||||
h = "00" + h;
|
||||
}
|
||||
}
|
||||
}
|
||||
// println(h); // use for debugging
|
||||
if (h.length()%2 == 0) { // make sure this is a real number
|
||||
floatData[i] = unhex(h);
|
||||
} else {
|
||||
floatData[i] = 0;
|
||||
}
|
||||
|
||||
if (i>=1 && i<=16) {
|
||||
floatData[i] *= cyton.get_scale_fac_uVolts_per_count();
|
||||
}else if(i != 0){
|
||||
floatData[i] *= cyton.get_scale_fac_accel_G_per_count();
|
||||
}
|
||||
|
||||
if(i == 0){
|
||||
dataWriter.print(int(floatData[i])); // print the sample counter
|
||||
}else{
|
||||
dataWriter.print(floatData[i]); // print the current channel value
|
||||
}
|
||||
if (i < hexNums.length-1) { // print the current channel value
|
||||
dataWriter.print(","); // print "," separator
|
||||
}
|
||||
}
|
||||
dataWriter.println();
|
||||
}
|
||||
|
||||
void convert8channelLine() {
|
||||
if(printNextLine){
|
||||
for(int i=0; i<hexNums.length; i++){
|
||||
h = hexNums[i];
|
||||
if (h.length()%2 == 0) { // make sure this is a real number
|
||||
intData[i] = unhex(h);
|
||||
} else {
|
||||
intData[i] = 0;
|
||||
}
|
||||
print(intData[i]);
|
||||
dataWriter.print(intData[i]);
|
||||
if(hexNums.length > 1){
|
||||
dataWriter.print(", ");
|
||||
print(", ");
|
||||
}
|
||||
}
|
||||
dataWriter.println();
|
||||
println();
|
||||
return;
|
||||
}
|
||||
for (int i=0; i<hexNums.length; i++) {
|
||||
h = hexNums[i];
|
||||
if (i > 0) {
|
||||
if (h.charAt(0) > '7') { // if the number is negative
|
||||
h = "FF" + hexNums[i]; // keep it negative
|
||||
} else { // if the number is positive
|
||||
h = "00" + hexNums[i]; // keep it positive
|
||||
}
|
||||
if (i > 8) { // accelerometer data needs another byte
|
||||
if (h.charAt(0) == 'F') {
|
||||
h = "FF" + h;
|
||||
} else {
|
||||
h = "00" + h;
|
||||
}
|
||||
}
|
||||
}
|
||||
// println(h + " " + h.length()); // use for debugging
|
||||
if (h.length() > 8) {
|
||||
break;
|
||||
}
|
||||
if (h.length()%2 == 0) { // make sure this is a real number
|
||||
floatData[i] = unhex(h);
|
||||
} else {
|
||||
floatData[i] = 0;
|
||||
}
|
||||
|
||||
if (i>=1 && i<=8) {
|
||||
floatData[i] *= cyton.get_scale_fac_uVolts_per_count();
|
||||
}else if(i != 0){
|
||||
floatData[i] *= cyton.get_scale_fac_accel_G_per_count();
|
||||
}
|
||||
|
||||
if(i == 0){
|
||||
dataWriter.print(int(floatData[i])); // print the sample counter
|
||||
}else{
|
||||
dataWriter.print(floatData[i]); // print the current channel value
|
||||
}
|
||||
if (i < hexNums.length-1) {
|
||||
dataWriter.print(","); // print "," separator
|
||||
}
|
||||
}
|
||||
dataWriter.println();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// BEWARE: Old Stuff Below
|
||||
//
|
||||
// // println(dataLine);
|
||||
// String[] hexNums = splitTokens(dataLine, ",");
|
||||
//
|
||||
// if (hexNums[0].charAt(0) == '%') {
|
||||
// // println(dataLine);
|
||||
// dataWriter.println(dataLine);
|
||||
// println(dataLine);
|
||||
// printNextLine = true;
|
||||
// } else {
|
||||
// for (int i=0; i<hexNums.length; i++) {
|
||||
// h = hexNums[i];
|
||||
// if (i > 0) {
|
||||
// if (h.charAt(0) > '7') { // if the number is negative
|
||||
// h = "FF" + hexNums[i]; // keep it negative
|
||||
// } else { // if the number is positive
|
||||
// h = "00" + hexNums[i]; // keep it positive
|
||||
// }
|
||||
// if (i > 8) { // accelerometer data needs another byte
|
||||
// if (h.charAt(0) == 'F') {
|
||||
// h = "FF" + h;
|
||||
// } else {
|
||||
// h = "00" + h;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// // println(h); // use for debugging
|
||||
// if (h.length()%2 == 0) { // make sure this is a real number
|
||||
// intData[i] = unhex(h);
|
||||
// } else {
|
||||
// intData[i] = 0;
|
||||
// }
|
||||
//
|
||||
// //if not first column(sample #) or columns 9-11 (accelerometer), convert to uV
|
||||
// if (i>=1 && i<=8) {
|
||||
// intData[i] *= openBCI.get_scale_fac_uVolts_per_count();
|
||||
// }
|
||||
//
|
||||
// //print the current channel value
|
||||
// dataWriter.print(intData[i]);
|
||||
// if (i < hexNums.length-1) {
|
||||
// //print "," separator
|
||||
// dataWriter.print(",");
|
||||
// }
|
||||
// }
|
||||
// //println();
|
||||
// dataWriter.println();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -11,6 +11,12 @@ HashMap<String,float[][]> processed_file;
|
||||
HashMap<Integer,String> index_of_times;
|
||||
HashMap<String,Integer> index_of_times_rev;
|
||||
|
||||
// indexs
|
||||
final int DELTA = 0; // 1-4 Hz
|
||||
final int THETA = 1; // 4-8 Hz
|
||||
final int ALPHA = 2; // 8-13 Hz
|
||||
final int BETA = 3; // 13-30 Hz
|
||||
final int GAMMA = 4; // 30-55 Hz
|
||||
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
@@ -26,11 +32,11 @@ void process_input_file() throws Exception {
|
||||
|
||||
try {
|
||||
while (!hasRepeated) {
|
||||
currentTableRowIndex=getPlaybackDataFromTable(playbackData_table, currentTableRowIndex, openBCI.get_scale_fac_uVolts_per_count(), openBCI.get_scale_fac_accel_G_per_count(), dataPacketBuff[lastReadDataPacketInd]);
|
||||
currentTableRowIndex=getPlaybackDataFromTable(playbackData_table, currentTableRowIndex, cyton.get_scale_fac_uVolts_per_count(), cyton.get_scale_fac_accel_G_per_count(), dataPacketBuff[lastReadDataPacketInd]);
|
||||
|
||||
for (int Ichan=0; Ichan < nchan; Ichan++) {
|
||||
//scale the data into engineering units..."microvolts"
|
||||
localLittleBuff[Ichan][pointCounter] = dataPacketBuff[lastReadDataPacketInd].values[Ichan]* openBCI.get_scale_fac_uVolts_per_count();
|
||||
localLittleBuff[Ichan][pointCounter] = dataPacketBuff[lastReadDataPacketInd].values[Ichan]* cyton.get_scale_fac_uVolts_per_count();
|
||||
}
|
||||
processed_file.put(curTimestamp, localLittleBuff);
|
||||
index_of_times.put(indices,curTimestamp);
|
||||
@@ -50,14 +56,14 @@ void process_input_file() throws Exception {
|
||||
/*************************/
|
||||
int getDataIfAvailable(int pointCounter) {
|
||||
|
||||
if (eegDataSource == DATASOURCE_NORMAL_W_AUX) {
|
||||
if (eegDataSource == DATASOURCE_CYTON) {
|
||||
//get data from serial port as it streams in
|
||||
//next, gather any new data into the "little buffer"
|
||||
while ( (curDataPacketInd != lastReadDataPacketInd) && (pointCounter < nPointsPerUpdate)) {
|
||||
lastReadDataPacketInd = (lastReadDataPacketInd+1) % dataPacketBuff.length; //increment to read the next packet
|
||||
for (int Ichan=0; Ichan < nchan; Ichan++) { //loop over each cahnnel
|
||||
//scale the data into engineering units ("microvolts") and save to the "little buffer"
|
||||
yLittleBuff_uV[Ichan][pointCounter] = dataPacketBuff[lastReadDataPacketInd].values[Ichan] * openBCI.get_scale_fac_uVolts_per_count();
|
||||
yLittleBuff_uV[Ichan][pointCounter] = dataPacketBuff[lastReadDataPacketInd].values[Ichan] * cyton.get_scale_fac_uVolts_per_count();
|
||||
}
|
||||
for (int auxChan=0; auxChan < 3; auxChan++) auxBuff[auxChan][pointCounter] = dataPacketBuff[lastReadDataPacketInd].auxValues[auxChan];
|
||||
pointCounter++; //increment counter for "little buffer"
|
||||
@@ -81,7 +87,7 @@ int getDataIfAvailable(int pointCounter) {
|
||||
int current_millis = millis();
|
||||
if (current_millis >= nextPlayback_millis) {
|
||||
//prepare for next time
|
||||
int increment_millis = int(round(float(nPointsPerUpdate)*1000.f/get_fs_Hz_safe())/playback_speed_fac);
|
||||
int increment_millis = int(round(float(nPointsPerUpdate)*1000.f/getSampleRateSafe())/playback_speed_fac);
|
||||
if (nextPlayback_millis < 0) nextPlayback_millis = current_millis;
|
||||
nextPlayback_millis += increment_millis;
|
||||
|
||||
@@ -92,10 +98,10 @@ int getDataIfAvailable(int pointCounter) {
|
||||
dataPacketBuff[lastReadDataPacketInd].sampleIndex++;
|
||||
switch (eegDataSource) {
|
||||
case DATASOURCE_SYNTHETIC: //use synthetic data (for GUI debugging)
|
||||
synthesizeData(nchan, get_fs_Hz_safe(), openBCI.get_scale_fac_uVolts_per_count(), dataPacketBuff[lastReadDataPacketInd]);
|
||||
synthesizeData(nchan, getSampleRateSafe(), cyton.get_scale_fac_uVolts_per_count(), dataPacketBuff[lastReadDataPacketInd]);
|
||||
break;
|
||||
case DATASOURCE_PLAYBACKFILE:
|
||||
currentTableRowIndex=getPlaybackDataFromTable(playbackData_table, currentTableRowIndex, openBCI.get_scale_fac_uVolts_per_count(), openBCI.get_scale_fac_accel_G_per_count(), dataPacketBuff[lastReadDataPacketInd]);
|
||||
currentTableRowIndex=getPlaybackDataFromTable(playbackData_table, currentTableRowIndex, cyton.get_scale_fac_uVolts_per_count(), cyton.get_scale_fac_accel_G_per_count(), dataPacketBuff[lastReadDataPacketInd]);
|
||||
break;
|
||||
default:
|
||||
//no action
|
||||
@@ -103,7 +109,7 @@ int getDataIfAvailable(int pointCounter) {
|
||||
//gather the data into the "little buffer"
|
||||
for (int Ichan=0; Ichan < nchan; Ichan++) {
|
||||
//scale the data into engineering units..."microvolts"
|
||||
yLittleBuff_uV[Ichan][pointCounter] = dataPacketBuff[lastReadDataPacketInd].values[Ichan]* openBCI.get_scale_fac_uVolts_per_count();
|
||||
yLittleBuff_uV[Ichan][pointCounter] = dataPacketBuff[lastReadDataPacketInd].values[Ichan]* cyton.get_scale_fac_uVolts_per_count();
|
||||
}
|
||||
|
||||
pointCounter++;
|
||||
@@ -166,9 +172,9 @@ void processNewData() {
|
||||
//compute the electrode impedance. Do it in a very simple way [rms to amplitude, then uVolt to Volt, then Volt/Amp to Ohm]
|
||||
for (int Ichan=0; Ichan < nchan; Ichan++) {
|
||||
// Calculate the impedance
|
||||
float impedance = (sqrt(2.0)*dataProcessing.data_std_uV[Ichan]*1.0e-6) / openBCI.get_leadOffDrive_amps();
|
||||
float impedance = (sqrt(2.0)*dataProcessing.data_std_uV[Ichan]*1.0e-6) / cyton.get_leadOffDrive_amps();
|
||||
// Subtract the 2.2kOhm resistor
|
||||
impedance -= openBCI.get_series_resistor();
|
||||
impedance -= cyton.get_series_resistor();
|
||||
// Verify the impedance is not less than 0
|
||||
if (impedance < 0) {
|
||||
// Incase impedance some how dipped below 2.2kOhm
|
||||
@@ -248,7 +254,7 @@ void prepareData(float[] dataBuffX, float[][] dataBuffY_uV, float fs_Hz) {
|
||||
}
|
||||
|
||||
|
||||
void initializeFFTObjects(FFT[] fftBuff, float[][] dataBuffY_uV, int N, float fs_Hz) {
|
||||
void initializeFFTObjects(FFT[] fftBuff, float[][] dataBuffY_uV, int Nfft, float fs_Hz) {
|
||||
|
||||
float[] fooData;
|
||||
for (int Ichan=0; Ichan < nchan; Ichan++) {
|
||||
@@ -335,12 +341,14 @@ int getPlaybackDataFromTable(Table datatable, int currentTableRowIndex, float sc
|
||||
|
||||
if(!isRunning){
|
||||
try{
|
||||
if(!isOldData) row.getString(nchan+4);
|
||||
else row.getString(nchan+3);
|
||||
row.getString(nchan+3);
|
||||
|
||||
nchan = 16;
|
||||
// nchan = 16; AJK 5/31/17 see issue #151
|
||||
}
|
||||
catch (ArrayIndexOutOfBoundsException e){
|
||||
println(e);
|
||||
println("8 Channel");
|
||||
}
|
||||
catch (ArrayIndexOutOfBoundsException e){ println("8 Channel");}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -374,13 +382,6 @@ class DataProcessing {
|
||||
float headWidePower[];
|
||||
int numBins;
|
||||
|
||||
// indexs
|
||||
final int DELTA = 0; // 1-4 Hz
|
||||
final int THETA = 1; // 4-8 Hz
|
||||
final int ALPHA = 2; // 8-13 Hz
|
||||
final int BETA = 3; // 13-30 Hz
|
||||
final int GAMMA = 4; // 30-55 Hz
|
||||
|
||||
DataProcessing(int NCHAN, float sample_rate_Hz) {
|
||||
nchan = NCHAN;
|
||||
fs_Hz = sample_rate_Hz;
|
||||
@@ -390,46 +391,87 @@ class DataProcessing {
|
||||
avgPowerInBins = new float[nchan][processing_band_low_Hz.length];
|
||||
headWidePower = new float[processing_band_low_Hz.length];
|
||||
|
||||
//check to make sure the sample rate is acceptable and then define the filters
|
||||
if (abs(fs_Hz-250.0f) < 1.0) {
|
||||
defineFilters(0);
|
||||
} else if (abs(fs_Hz-200.0f) < 1.0) {
|
||||
defineFilters(1);
|
||||
} else {
|
||||
println("EEG_Processing: *** ERROR *** Filters can currently only work at 250 Hz or 200 Hz");
|
||||
defineFilters(0); //define the filters anyway just so that the code doesn't bomb
|
||||
}
|
||||
defineFilters(); //define the filters anyway just so that the code doesn't bomb
|
||||
}
|
||||
|
||||
public float getSampleRateHz() {
|
||||
return fs_Hz;
|
||||
};
|
||||
|
||||
//define filters...assumes sample rate of 250 Hz !!!!!
|
||||
private void defineFilters(int _mode) {
|
||||
int mode = _mode; // 0 means classic OpenBCI board, 1 means ganglion
|
||||
//define filters depending on the sampling rate
|
||||
private void defineFilters() {
|
||||
int n_filt;
|
||||
double[] b, a, b2, a2;
|
||||
String filt_txt, filt_txt2;
|
||||
String short_txt, short_txt2;
|
||||
|
||||
switch(mode) {
|
||||
// classic OpenBCI board, sampling rate 250 Hz
|
||||
case 0:
|
||||
//loop over all of the pre-defined filter types
|
||||
n_filt = filtCoeff_notch.length;
|
||||
for (int Ifilt=0; Ifilt < n_filt; Ifilt++) {
|
||||
switch (Ifilt) {
|
||||
//------------ loop over all of the pre-defined filter types -----------
|
||||
//------------ notch filters ------------
|
||||
n_filt = filtCoeff_notch.length;
|
||||
for (int Ifilt=0; Ifilt < n_filt; Ifilt++) {
|
||||
switch (Ifilt) {
|
||||
case 0:
|
||||
//60 Hz notch filter, assumed fs = 250 Hz. 2nd Order Butterworth: b, a = signal.butter(2,[59.0 61.0]/(fs_Hz / 2.0), 'bandstop')
|
||||
b2 = new double[] { 9.650809863447347e-001, -2.424683201757643e-001, 1.945391494128786e+000, -2.424683201757643e-001, 9.650809863447347e-001 };
|
||||
a2 = new double[] { 1.000000000000000e+000, -2.467782611297853e-001, 1.944171784691352e+000, -2.381583792217435e-001, 9.313816821269039e-001 };
|
||||
//60 Hz notch filter, 2nd Order Butterworth: [b, a] = butter(2,[59.0 61.0]/(fs_Hz / 2.0), 'stop') %matlab command
|
||||
switch(int(fs_Hz)) {
|
||||
case 125:
|
||||
b2 = new double[] { 0.931378858122982, 3.70081291785747, 5.53903191270520, 3.70081291785747, 0.931378858122982 };
|
||||
a2 = new double[] { 1, 3.83246204081167, 5.53431749515949, 3.56916379490328, 0.867472133791669 };
|
||||
break;
|
||||
case 200:
|
||||
b2 = new double[] { 0.956543225556877, 1.18293615779028, 2.27881429174348, 1.18293615779028, 0.956543225556877 };
|
||||
a2 = new double[] { 1, 1.20922304075909, 2.27692490805580, 1.15664927482146, 0.914975834801436 };
|
||||
break;
|
||||
case 250:
|
||||
b2 = new double[] { 0.965080986344733, -0.242468320175764, 1.94539149412878, -0.242468320175764, 0.965080986344733 };
|
||||
a2 = new double[] { 1, -0.246778261129785, 1.94417178469135, -0.238158379221743, 0.931381682126902 };
|
||||
break;
|
||||
case 500:
|
||||
b2 = new double[] { 0.982385438526095, -2.86473884662109, 4.05324051877773, -2.86473884662109, 0.982385438526095};
|
||||
a2 = new double[] { 1, -2.89019558531207, 4.05293022193077, -2.83928210793009, 0.965081173899134 };
|
||||
break;
|
||||
case 1000:
|
||||
b2 = new double[] { 0.991153595101611, -3.68627799048791, 5.40978944177152, -3.68627799048791, 0.991153595101611 };
|
||||
a2 = new double[] { 1, -3.70265590760266, 5.40971118136100, -3.66990007337352, 0.982385450614122 };
|
||||
break;
|
||||
case 1600:
|
||||
b2 = new double[] { 0.994461788958027, -3.86796874670208, 5.75004904085114, -3.86796874670208, 0.994461788958027 };
|
||||
a2 = new double[] { 1, -3.87870938463296, 5.75001836883538, -3.85722810877252, 0.988954249933128 };
|
||||
break;
|
||||
default:
|
||||
println("EEG_Processing: *** ERROR *** Filters can only work at 125Hz, 200Hz, 250 Hz, 1000Hz or 1600Hz");
|
||||
b2 = new double[] { 1.0 };
|
||||
a2 = new double[] { 1.0 };
|
||||
}
|
||||
filtCoeff_notch[Ifilt] = new FilterConstants(b2, a2, "Notch 60Hz", "60Hz");
|
||||
break;
|
||||
case 1:
|
||||
//50 Hz notch filter, assumed fs = 250 Hz. 2nd Order Butterworth: b, a = signal.butter(2,[49.0 51.0]/(fs_Hz / 2.0), 'bandstop')
|
||||
b2 = new double[] { 0.96508099, -1.19328255, 2.29902305, -1.19328255, 0.96508099 };
|
||||
a2 = new double[] { 1.0, -1.21449348, 2.29780334, -1.17207163, 0.93138168 };
|
||||
//50 Hz notch filter, 2nd Order Butterworth: [b, a] = butter(2,[49.0 51.0]/(fs_Hz / 2.0), 'stop')
|
||||
switch(int(fs_Hz)) {
|
||||
case 125:
|
||||
b2 = new double[] { 0.931378858122983, 3.01781693143160, 4.30731047590091, 3.01781693143160, 0.931378858122983 };
|
||||
a2 = new double[] { 1, 3.12516981877757, 4.30259605835520, 2.91046404408562, 0.867472133791670 };
|
||||
break;
|
||||
case 200:
|
||||
b2 = new double[] { 0.956543225556877, -2.34285519884863e-16, 1.91308645111375, -2.34285519884863e-16, 0.956543225556877 };
|
||||
a2 = new double[] { 1, -1.41553435639707e-15, 1.91119706742607, -1.36696209906972e-15, 0.914975834801435 };
|
||||
break;
|
||||
case 250:
|
||||
b2 = new double[] { 0.965080986344734, -1.19328255433335, 2.29902305135123, -1.19328255433335, 0.965080986344734 };
|
||||
a2 = new double[] { 1, -1.21449347931898, 2.29780334191380, -1.17207162934771, 0.931381682126901 };
|
||||
break;
|
||||
case 500:
|
||||
b2 = new double[] { 0.982385438526090, -3.17931708468811, 4.53709552901242, -3.17931708468811, 0.982385438526090 };
|
||||
a2 = new double[] { 1, -3.20756923909868, 4.53678523216547, -3.15106493027754, 0.965081173899133 };
|
||||
break;
|
||||
case 1000:
|
||||
b2 = new double[] { 0.991153595101607, -3.77064677042206, 5.56847615976560, -3.77064677042206, 0.991153595101607 };
|
||||
a2 = new double[] { 1, -3.78739953308251, 5.56839789935513, -3.75389400776205, 0.982385450614127 };
|
||||
break;
|
||||
case 1600:
|
||||
b2 = new double[] { 0.994461788958316, -3.90144402068168, 5.81543195046478, -3.90144402068168, 0.994461788958316 };
|
||||
a2 = new double[] { 1, -3.91227761329151, 5.81540127844733, -3.89061042807090, 0.988954249933127 };
|
||||
break;
|
||||
default:
|
||||
println("EEG_Processing: *** ERROR *** Filters can only work at 125Hz, 200Hz, 250 Hz, 1000Hz or 1600Hz");
|
||||
b2 = new double[] { 1.0 };
|
||||
a2 = new double[] { 1.0 };
|
||||
}
|
||||
filtCoeff_notch[Ifilt] = new FilterConstants(b2, a2, "Notch 50Hz", "50Hz");
|
||||
break;
|
||||
case 2:
|
||||
@@ -439,168 +481,166 @@ class DataProcessing {
|
||||
filtCoeff_notch[Ifilt] = new FilterConstants(b2, a2, "No Notch", "None");
|
||||
break;
|
||||
}
|
||||
} // end loop over notch filters
|
||||
}// end loop over notch filters
|
||||
|
||||
//------------ bandpass filters ------------
|
||||
n_filt = filtCoeff_bp.length;
|
||||
for (int Ifilt=0; Ifilt<n_filt; Ifilt++) {
|
||||
//define bandpass filter
|
||||
switch (Ifilt) {
|
||||
case 0:
|
||||
//butter(2,[1 50]/(250/2)); %bandpass filter
|
||||
b = new double[] {
|
||||
2.001387256580675e-001, 0.0f, -4.002774513161350e-001, 0.0f, 2.001387256580675e-001
|
||||
};
|
||||
a = new double[] {
|
||||
1.0f, -2.355934631131582e+000, 1.941257088655214e+000, -7.847063755334187e-001, 1.999076052968340e-001
|
||||
};
|
||||
//1-50 Hz band pass filter, 2nd Order Butterworth: [b, a] = butter(2,[1.0 50.0]/(fs_Hz / 2.0))
|
||||
switch(int(fs_Hz)) {
|
||||
case 125:
|
||||
b = new double[] { 0.615877232553135, 0, -1.23175446510627, 0, 0.615877232553135 };
|
||||
a = new double[] { 1, -0.789307541613509, -0.853263915766877, 0.263710995896442, 0.385190413112446 };
|
||||
break;
|
||||
case 200:
|
||||
b = new double[] { 0.283751216219319, 0, -0.567502432438638, 0, 0.283751216219319 };
|
||||
a = new double[] { 1, -1.97380379923172, 1.17181238127012, -0.368664525962831, 0.171812381270120 };
|
||||
break;
|
||||
case 250:
|
||||
b = new double[] { 0.200138725658073, 0, -0.400277451316145, 0, 0.200138725658073 };
|
||||
a = new double[] { 1, -2.35593463113158, 1.94125708865521, -0.784706375533419, 0.199907605296834 };
|
||||
break;
|
||||
case 500:
|
||||
b = new double[] { 0.0652016551604422, 0, -0.130403310320884, 0, 0.0652016551604422 };
|
||||
a = new double[] { 1, -3.14636562553919, 3.71754597063790, -1.99118301927812, 0.420045500522989 };
|
||||
break;
|
||||
case 1000:
|
||||
b = new double[] { 0.0193615659240911, 0, -0.0387231318481823, 0, 0.0193615659240911 };
|
||||
a = new double[] { 1, -3.56607203834158, 4.77991824545949, -2.86091191298975, 0.647068888346475 };
|
||||
break;
|
||||
case 1600:
|
||||
b = new double[] { 0.00812885687466408, 0, -0.0162577137493282, 0, 0.00812885687466408 };
|
||||
a = new double[] { 1, -3.72780746887970, 5.21756471024747, -3.25152171857009, 0.761764999239264 };
|
||||
break;
|
||||
default:
|
||||
println("EEG_Processing: *** ERROR *** Filters can only work at 125Hz, 200Hz, 250 Hz, 1000Hz or 1600Hz");
|
||||
b = new double[] { 1.0 };
|
||||
a = new double[] { 1.0 };
|
||||
}
|
||||
filt_txt = "Bandpass 1-50Hz";
|
||||
short_txt = "1-50 Hz";
|
||||
break;
|
||||
case 1:
|
||||
//butter(2,[7 13]/(250/2));
|
||||
b = new double[] {
|
||||
5.129268366104263e-003, 0.0f, -1.025853673220853e-002, 0.0f, 5.129268366104263e-003
|
||||
};
|
||||
a = new double[] {
|
||||
1.0f, -3.678895469764040e+000, 5.179700413522124e+000, -3.305801890016702e+000, 8.079495914209149e-001
|
||||
};
|
||||
//7-13 Hz band pass filter, 2nd Order Butterworth: [b, a] = butter(2,[7.0 13.0]/(fs_Hz / 2.0))
|
||||
switch(int(fs_Hz)) {
|
||||
case 125:
|
||||
b = new double[] { 0.0186503962278349, 0, -0.0373007924556699, 0, 0.0186503962278349 };
|
||||
a = new double[] { 1, -3.17162467236842, 4.11670870329067, -2.55619949640702, 0.652837763407545 };
|
||||
break;
|
||||
case 200:
|
||||
b = new double[] { 0.00782020803349772, 0, -0.0156404160669954, 0, 0.00782020803349772 };
|
||||
a = new double[] { 1, -3.56776916484310, 4.92946172209398, -3.12070317627516, 0.766006600943265 };
|
||||
break;
|
||||
case 250:
|
||||
b = new double[] { 0.00512926836610803, 0, -0.0102585367322161, 0, 0.00512926836610803 };
|
||||
a = new double[] { 1, -3.67889546976404, 5.17970041352212, -3.30580189001670, 0.807949591420914 };
|
||||
break;
|
||||
case 500:
|
||||
b = new double[] { 0.00134871194834618, 0, -0.00269742389669237, 0, 0.00134871194834618 };
|
||||
a = new double[] { 1, -3.86550956895320, 5.63152598761351, -3.66467991638185, 0.898858994155253 };
|
||||
break;
|
||||
case 1000:
|
||||
b = new double[] { 0.000346041337684191, 0, -0.000692082675368382, 0, 0.000346041337684191 };
|
||||
a = new double[] { 1, -3.93960949694447, 5.82749974685320, -3.83595939375067, 0.948081706106736 };
|
||||
break;
|
||||
case 1600:
|
||||
b = new double[] { 0.000136510722194708, 0, -0.000273021444389417, 0, 0.000136510722194708 };
|
||||
a = new double[] { 1, -3.96389829181139, 5.89507193593518, -3.89839913574117, 0.967227428151860 };
|
||||
break;
|
||||
default:
|
||||
println("EEG_Processing: *** ERROR *** Filters can only work at 125Hz, 200Hz, 250 Hz, 1000Hz or 1600Hz");
|
||||
b = new double[] { 1.0 };
|
||||
a = new double[] { 1.0 };
|
||||
}
|
||||
filt_txt = "Bandpass 7-13Hz";
|
||||
short_txt = "7-13 Hz";
|
||||
break;
|
||||
case 2:
|
||||
//[b,a]=butter(2,[15 50]/(250/2)); %matlab command
|
||||
b = new double[] {
|
||||
1.173510367246093e-001, 0.0f, -2.347020734492186e-001, 0.0f, 1.173510367246093e-001
|
||||
};
|
||||
a = new double[] {
|
||||
1.0f, -2.137430180172061e+000, 2.038578008108517e+000, -1.070144399200925e+000, 2.946365275879138e-001
|
||||
};
|
||||
//15-50 Hz band pass filter, 2nd Order Butterworth: [b, a] = butter(2,[15.0 50.0]/(fs_Hz / 2.0))
|
||||
switch(int(fs_Hz)) {
|
||||
case 125:
|
||||
b = new double[] { 0.350346377855414, 0, -0.700692755710828, 0, 0.350346377855414 };
|
||||
a = new double[] { 1, 0.175228265043619, -0.211846955102387, 0.0137230352398757, 0.180232073898346 };
|
||||
break;
|
||||
case 200:
|
||||
b = new double[] { 0.167483800127017, 0, -0.334967600254034, 0, 0.167483800127017 };
|
||||
a = new double[] { 1, -1.56695061045088, 1.22696619781982, -0.619519163981229, 0.226966197819818 };
|
||||
break;
|
||||
case 250:
|
||||
b = new double[] { 0.117351036724609, 0, -0.234702073449219, 0, 0.117351036724609 };
|
||||
a = new double[] { 1, -2.13743018017206, 2.03857800810852, -1.07014439920093, 0.294636527587914 };
|
||||
break;
|
||||
case 500:
|
||||
b = new double[] { 0.0365748358439273, 0, -0.0731496716878546, 0, 0.0365748358439273 };
|
||||
a = new double[] { 1, -3.18880661866679, 3.98037203788323, -2.31835989524663, 0.537194624801103 };
|
||||
break;
|
||||
case 1000:
|
||||
b = new double[] { 0.0104324133710872, 0, -0.0208648267421744, 0, 0.0104324133710872 };
|
||||
a = new double[] { 1, -3.63626742713985, 5.01393973667604, -3.10964559897057, 0.732726030371817 };
|
||||
break;
|
||||
case 1600:
|
||||
b = new double[] { 0.00429884732196394, 0, -0.00859769464392787, 0, 0.00429884732196394 };
|
||||
a = new double[] { 1, -3.78412985599134, 5.39377521548486, -3.43287342581222, 0.823349595537562 };
|
||||
break;
|
||||
default:
|
||||
println("EEG_Processing: *** ERROR *** Filters can only work at 125Hz, 200Hz, 250 Hz, 1000Hz or 1600Hz");
|
||||
b = new double[] { 1.0 };
|
||||
a = new double[] { 1.0 };
|
||||
}
|
||||
filt_txt = "Bandpass 15-50Hz";
|
||||
short_txt = "15-50 Hz";
|
||||
break;
|
||||
case 3:
|
||||
//[b,a]=butter(2,[5 50]/(250/2)); %matlab command
|
||||
b = new double[] {
|
||||
1.750876436721012e-001, 0.0f, -3.501752873442023e-001, 0.0f, 1.750876436721012e-001
|
||||
};
|
||||
a = new double[] {
|
||||
1.0f, -2.299055356038497e+000, 1.967497759984450e+000, -8.748055564494800e-001, 2.196539839136946e-001
|
||||
};
|
||||
//5-50 Hz band pass filter, 2nd Order Butterworth: [b, a] = butter(2,[5.0 50.0]/(fs_Hz / 2.0))
|
||||
switch(int(fs_Hz)) {
|
||||
case 125:
|
||||
b = new double[] { 0.529967227069348, 0, -1.05993445413870, 0, 0.529967227069348 };
|
||||
a = new double[] { 1, -0.517003774490767, -0.734318454224823, 0.103843398397761, 0.294636527587914 };
|
||||
break;
|
||||
case 200:
|
||||
b = new double[] { 0.248341078962541, 0, -0.496682157925081, 0, 0.248341078962541 };
|
||||
a = new double[] { 1, -1.86549482213123, 1.17757811892770, -0.460665534278457, 0.177578118927698 };
|
||||
break;
|
||||
case 250:
|
||||
b = new double[] { 0.175087643672101, 0, -0.350175287344202, 0, 0.175087643672101 };
|
||||
a = new double[] { 1, -2.29905535603850, 1.96749775998445, -0.874805556449481, 0.219653983913695 };
|
||||
break;
|
||||
case 500:
|
||||
b = new double[] { 0.0564484622607352, 0, -0.112896924521470, 0, 0.0564484622607352 };
|
||||
a = new double[] { 1, -3.15946330211917, 3.79268442285094, -2.08257331718360, 0.450445430056042 };
|
||||
break;
|
||||
case 1000:
|
||||
b = new double[] { 0.0165819316692804, 0, -0.0331638633385608, 0, 0.0165819316692804 };
|
||||
a = new double[] { 1, -3.58623980811691, 4.84628980428803, -2.93042721682014, 0.670457905953175 };
|
||||
break;
|
||||
case 1600:
|
||||
b = new double[] { 0.00692579317243661, 0, -0.0138515863448732, 0, 0.00692579317243661 };
|
||||
a = new double[] { 1, -3.74392328264678, 5.26758817627966, -3.30252568902969, 0.778873972655117 };
|
||||
break;
|
||||
default:
|
||||
println("EEG_Processing: *** ERROR *** Filters can only work at 125Hz, 200Hz, 250 Hz, 1000Hz or 1600Hz");
|
||||
b = new double[] { 1.0 };
|
||||
a = new double[] { 1.0 };
|
||||
}
|
||||
filt_txt = "Bandpass 5-50Hz";
|
||||
short_txt = "5-50 Hz";
|
||||
break;
|
||||
default:
|
||||
//no filtering
|
||||
b = new double[] {
|
||||
1.0
|
||||
};
|
||||
a = new double[] {
|
||||
1.0
|
||||
};
|
||||
b = new double[] { 1.0 };
|
||||
a = new double[] { 1.0 };
|
||||
filt_txt = "No BP Filter";
|
||||
short_txt = "No Filter";
|
||||
} //end switch block
|
||||
|
||||
//create the bandpass filter
|
||||
filtCoeff_bp[Ifilt] = new FilterConstants(b, a, filt_txt, short_txt);
|
||||
} //end loop over band pass filters
|
||||
|
||||
break;
|
||||
|
||||
// Ganglion board, sampling rate 200 Hz
|
||||
case 1:
|
||||
//loop over all of the pre-defined filter types
|
||||
n_filt = filtCoeff_notch.length;
|
||||
for (int Ifilt=0; Ifilt < n_filt; Ifilt++) {
|
||||
switch (Ifilt) {
|
||||
case 0:
|
||||
//60 Hz notch filter, assumed fs = 200 Hz. 2nd Order Butterworth: b, a = signal.butter(2,[59.0 61.0]/(fs_Hz / 2.0), 'bandstop')
|
||||
b2 = new double[] { 0.956543225556876, 1.18293615779028, 2.27881429174347, 1.18293615779028, 0.956543225556876 };
|
||||
a2 = new double[] { 1, 1.20922304075909, 2.27692490805579, 1.15664927482146, 0.914975834801432 };
|
||||
filtCoeff_notch[Ifilt] = new FilterConstants(b2, a2, "Notch 60Hz", "60Hz");
|
||||
break;
|
||||
case 1:
|
||||
//50 Hz notch filter, assumed fs = 200 Hz. 2nd Order Butterworth: b, a = signal.butter(2,[49.0 51.0]/(fs_Hz / 2.0), 'bandstop')
|
||||
b2 = new double[] { 0.956543225556877, -2.34285519884863e-16, 1.91308645111375, -2.34285519884863e-16, 0.956543225556877};
|
||||
a2 = new double[] { 1, -1.02695629777827e-15, 1.91119706742607, -1.01654795692241e-15, 0.914975834801435};
|
||||
filtCoeff_notch[Ifilt] = new FilterConstants(b2, a2, "Notch 50Hz", "50Hz");
|
||||
break;
|
||||
case 2:
|
||||
//no notch filter
|
||||
b2 = new double[] { 1.0 };
|
||||
a2 = new double[] { 1.0 };
|
||||
filtCoeff_notch[Ifilt] = new FilterConstants(b2, a2, "No Notch", "None");
|
||||
break;
|
||||
}
|
||||
} // end loop over notch filters
|
||||
|
||||
n_filt = filtCoeff_bp.length;
|
||||
for (int Ifilt=0; Ifilt<n_filt; Ifilt++) {
|
||||
//define bandpass filter
|
||||
switch (Ifilt) {
|
||||
case 0:
|
||||
//butter(2,[1 50]/(200/2)); %bandpass filter
|
||||
b = new double[] {
|
||||
0.283751216219318, 0, -0.567502432438636, 0, 0.283751216219318
|
||||
};
|
||||
a = new double[] {
|
||||
1, -1.97380379923172, 1.17181238127012, -0.368664525962831, 0.171812381270120
|
||||
};
|
||||
filt_txt = "Bandpass 1-50Hz";
|
||||
short_txt = "1-50 Hz";
|
||||
break;
|
||||
case 1:
|
||||
//butter(2,[7 13]/(200/2));
|
||||
b = new double[] {
|
||||
0.00782020803349883, 0, -0.0156404160669977, 0, 0.00782020803349883
|
||||
};
|
||||
a = new double[] {
|
||||
1, -3.56776916484310, 4.92946172209398, -3.12070317627516, 0.766006600943266
|
||||
};
|
||||
filt_txt = "Bandpass 7-13Hz";
|
||||
short_txt = "7-13 Hz";
|
||||
break;
|
||||
case 2:
|
||||
//[b,a]=butter(2,[15 50]/(200/2)); %matlab command
|
||||
b = new double[] {
|
||||
0.167483800127017, 0, -0.334967600254034, 0, 0.167483800127017
|
||||
};
|
||||
a = new double[] {
|
||||
1, -1.56695061045088, 1.22696619781982, -0.619519163981230, 0.226966197819818
|
||||
};
|
||||
filt_txt = "Bandpass 15-50Hz";
|
||||
short_txt = "15-50 Hz";
|
||||
break;
|
||||
case 3:
|
||||
//[b,a]=butter(2,[5 50]/(200/2)); %matlab command
|
||||
b = new double[] {
|
||||
0.248341078962540, 0, -0.496682157925080, 0, 0.248341078962540
|
||||
};
|
||||
a = new double[] {
|
||||
1, -1.86549482213123, 1.17757811892770, -0.460665534278457, 0.177578118927698
|
||||
};
|
||||
filt_txt = "Bandpass 5-50Hz";
|
||||
short_txt = "5-50 Hz";
|
||||
break;
|
||||
default:
|
||||
//no filtering
|
||||
b = new double[] {
|
||||
1.0
|
||||
};
|
||||
a = new double[] {
|
||||
1.0
|
||||
};
|
||||
filt_txt = "No BP Filter";
|
||||
short_txt = "No Filter";
|
||||
} //end switch block
|
||||
|
||||
//create the bandpass filter
|
||||
filtCoeff_bp[Ifilt] = new FilterConstants(b, a, filt_txt, short_txt);
|
||||
} //end loop over band pass filters
|
||||
|
||||
break;
|
||||
}
|
||||
} //end defineFilters method
|
||||
} //end loop over band pass filters
|
||||
}
|
||||
//end defineFilters method
|
||||
|
||||
public String getFilterDescription() {
|
||||
return filtCoeff_bp[currentFilt_ind].name + ", " + filtCoeff_notch[currentNotch_ind].name;
|
||||
@@ -628,7 +668,7 @@ class DataProcessing {
|
||||
float[][] data_long_uV, //holds a longer piece of buffered EEG data, of same length as will be plotted on the screen
|
||||
float[][] data_forDisplay_uV, //put data here that should be plotted on the screen
|
||||
FFT[] fftData) { //holds the FFT (frequency spectrum) of the latest data
|
||||
|
||||
int Nfft = getNfftSafe();
|
||||
//loop over each EEG channel
|
||||
for (int Ichan=0; Ichan < nchan; Ichan++) {
|
||||
|
||||
@@ -674,8 +714,15 @@ class DataProcessing {
|
||||
|
||||
//convert to uV_per_bin...still need to confirm the accuracy of this code.
|
||||
//Do we need to account for the power lost in the windowing function? CHIP 2014-10-24
|
||||
for (int I=0; I < fftBuff[Ichan].specSize(); I++) { //loop over each FFT bin
|
||||
fftBuff[Ichan].setBand(I, (float)(fftBuff[Ichan].getBand(I) / fftBuff[Ichan].specSize()));
|
||||
|
||||
// FFT ref: https://www.mathworks.com/help/matlab/ref/fft.html
|
||||
// first calculate double-sided FFT amplitude spectrum
|
||||
for (int I=0; I <= Nfft/2; I++) {
|
||||
fftBuff[Ichan].setBand(I, (float)(fftBuff[Ichan].getBand(I) / Nfft));
|
||||
}
|
||||
// then convert into single-sided FFT spectrum: DC & Nyquist (i=0 & i=N/2) remain the same, others multiply by two.
|
||||
for (int I=1; I < Nfft/2; I++) {
|
||||
fftBuff[Ichan].setBand(I, (float)(fftBuff[Ichan].getBand(I) * 2));
|
||||
}
|
||||
|
||||
//average the FFT with previous FFT data so that it makes it smoother in time
|
||||
@@ -698,13 +745,34 @@ class DataProcessing {
|
||||
foo = java.lang.Math.sqrt(foo);
|
||||
}
|
||||
fftBuff[Ichan].setBand(I, (float)foo); //put the smoothed data back into the fftBuff data holder for use by everyone else
|
||||
// fftBuff[Ichan].setBand(I, 1.0f); // test
|
||||
} //end loop over FFT bins
|
||||
|
||||
// calculate single-sided psd by single-sided FFT amplitude spectrum
|
||||
// PSD ref: https://www.mathworks.com/help/dsp/ug/estimate-the-power-spectral-density-in-matlab.html
|
||||
// when i = 1 ~ (N/2-1), psd = (N / fs) * mag(i)^2 / 4
|
||||
// when i = 0 or i = N/2, psd = (N / fs) * mag(i)^2
|
||||
|
||||
for (int i = 0; i < processing_band_low_Hz.length; i++) {
|
||||
float sum = 0;
|
||||
for (int j = processing_band_low_Hz[i]; j < processing_band_high_Hz[i]; j++) {
|
||||
sum += fftBuff[Ichan].getBand(j);
|
||||
// int binNum = 0;
|
||||
for (int Ibin = 0; Ibin <= Nfft/2; Ibin ++) { // loop over FFT bins
|
||||
float FFT_freq_Hz = fftBuff[Ichan].indexToFreq(Ibin); // center frequency of this bin
|
||||
float psdx = 0;
|
||||
// if the frequency matches a band
|
||||
if (FFT_freq_Hz >= processing_band_low_Hz[i] && FFT_freq_Hz < processing_band_high_Hz[i]) {
|
||||
if (Ibin != 0 && Ibin != Nfft/2) {
|
||||
psdx = fftBuff[Ichan].getBand(Ibin) * fftBuff[Ichan].getBand(Ibin) * Nfft/getSampleRateSafe() / 4;
|
||||
}
|
||||
else {
|
||||
psdx = fftBuff[Ichan].getBand(Ibin) * fftBuff[Ichan].getBand(Ibin) * Nfft/getSampleRateSafe();
|
||||
}
|
||||
sum += psdx;
|
||||
// binNum ++;
|
||||
}
|
||||
}
|
||||
avgPowerInBins[Ichan][i] = sum;
|
||||
avgPowerInBins[Ichan][i] = sum; // total power in a band
|
||||
// println(i, binNum, sum);
|
||||
}
|
||||
} //end the loop over channels.
|
||||
for (int i = 0; i < processing_band_low_Hz.length; i++) {
|
||||
@@ -713,7 +781,7 @@ class DataProcessing {
|
||||
for (int j = 0; j < nchan; j++) {
|
||||
sum += avgPowerInBins[j][i];
|
||||
}
|
||||
headWidePower[i] = sum/nchan;
|
||||
headWidePower[i] = sum/nchan; // averaging power over all channels
|
||||
}
|
||||
|
||||
//delta in channel 2 ... avgPowerInBins[1][DELTA];
|
||||
|
||||
@@ -25,6 +25,12 @@ boolean printSignPosts = true;
|
||||
float millisOfLastSignPost = 0.0;
|
||||
float millisSinceLastSignPost = 0.0;
|
||||
|
||||
final static int OUTPUT_LEVEL_DEFAULT = 0;
|
||||
final static int OUTPUT_LEVEL_INFO = 1;
|
||||
final static int OUTPUT_LEVEL_SUCCESS = 2;
|
||||
final static int OUTPUT_LEVEL_WARN = 3;
|
||||
final static int OUTPUT_LEVEL_ERROR = 4;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Global Functions
|
||||
//------------------------------------------------------------------------
|
||||
@@ -49,9 +55,13 @@ class HelpWidget {
|
||||
public float x, y, w, h;
|
||||
// ArrayList<String> prevOutputs; //growing list of all previous system interactivity
|
||||
|
||||
String currentOutput = "..."; //current text shown in help widget, based on most recent command
|
||||
String currentOutput = "Learn how to use this application and more at docs.openbci.com/OpenBCI%20Software/01-OpenBCI_GUI"; //current text shown in help widget, based on most recent command
|
||||
|
||||
int padding = 5;
|
||||
int outputStart = 0;
|
||||
int outputDurationMs = 3000;
|
||||
boolean animatingMessage = false;
|
||||
int curOutputLevel = OUTPUT_LEVEL_DEFAULT;
|
||||
|
||||
HelpWidget(float _xPos, float _yPos, float _width, float _height) {
|
||||
x = _xPos;
|
||||
@@ -61,7 +71,12 @@ class HelpWidget {
|
||||
}
|
||||
|
||||
public void update() {
|
||||
//nothing needed here
|
||||
if (animatingMessage) {
|
||||
if (millis() > outputStart + outputDurationMs) {
|
||||
animatingMessage = false;
|
||||
curOutputLevel = OUTPUT_LEVEL_DEFAULT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void draw() {
|
||||
@@ -95,32 +110,93 @@ class HelpWidget {
|
||||
|
||||
//draw bg of text field of widget
|
||||
strokeWeight(1);
|
||||
stroke(color(0, 5, 11));
|
||||
fill(200);
|
||||
fill(255);
|
||||
stroke(getBackgroundColor());
|
||||
// fill(200);
|
||||
// fill(255);
|
||||
fill(getBackgroundColor());
|
||||
// fill(57,128,204);
|
||||
rect(x + padding, height-h + padding, width - padding*2, h - padding *2);
|
||||
|
||||
textFont(p4);
|
||||
textSize(14);
|
||||
fill(bgColor);
|
||||
// fill(bgColor);
|
||||
fill(getTextColor());
|
||||
// fill(57,128,204);
|
||||
// fill(openbciBlue);
|
||||
textAlign(LEFT, TOP);
|
||||
text(currentOutput, padding*2, height - h + padding);
|
||||
}
|
||||
|
||||
|
||||
popStyle();
|
||||
}
|
||||
|
||||
public void output(String _output) {
|
||||
private color getTextColor() {
|
||||
switch (curOutputLevel) {
|
||||
case OUTPUT_LEVEL_INFO:
|
||||
return #00529B;
|
||||
case OUTPUT_LEVEL_SUCCESS:
|
||||
return #4F8A10;
|
||||
case OUTPUT_LEVEL_WARN:
|
||||
return #9F6000;
|
||||
case OUTPUT_LEVEL_ERROR:
|
||||
return #D8000C;
|
||||
case OUTPUT_LEVEL_DEFAULT:
|
||||
default:
|
||||
return color(0, 5, 11);
|
||||
}
|
||||
}
|
||||
|
||||
private color getBackgroundColor() {
|
||||
switch (curOutputLevel) {
|
||||
case OUTPUT_LEVEL_INFO:
|
||||
return #BDE5F8;
|
||||
case OUTPUT_LEVEL_SUCCESS:
|
||||
return #DFF2BF;
|
||||
case OUTPUT_LEVEL_WARN:
|
||||
return #FEEFB3;
|
||||
case OUTPUT_LEVEL_ERROR:
|
||||
return #FFD2D2;
|
||||
case OUTPUT_LEVEL_DEFAULT:
|
||||
default:
|
||||
return color(255);
|
||||
}
|
||||
}
|
||||
|
||||
public void output(String _output, int level) {
|
||||
if (OUTPUT_LEVEL_DEFAULT == level) {
|
||||
animatingMessage = false;
|
||||
} else {
|
||||
animatingMessage = true;
|
||||
outputStart = millis();
|
||||
}
|
||||
curOutputLevel = level;
|
||||
currentOutput = _output;
|
||||
// prevOutputs.add(_output);
|
||||
}
|
||||
};
|
||||
|
||||
public void output(String _output) {
|
||||
helpWidget.output(_output);
|
||||
output(_output, OUTPUT_LEVEL_DEFAULT);
|
||||
}
|
||||
|
||||
public void output(String _output, int level) {
|
||||
helpWidget.output(_output, level);
|
||||
}
|
||||
|
||||
public void outputError(String _output) {
|
||||
output(_output, OUTPUT_LEVEL_ERROR);
|
||||
}
|
||||
|
||||
public void outputInfo(String _output) {
|
||||
output(_output, OUTPUT_LEVEL_INFO);
|
||||
}
|
||||
|
||||
public void outputSuccess(String _output) {
|
||||
output(_output, OUTPUT_LEVEL_SUCCESS);
|
||||
}
|
||||
|
||||
public void outputWarn(String _output) {
|
||||
output(_output, OUTPUT_LEVEL_WARN);
|
||||
}
|
||||
|
||||
// created 2/10/16 by Conor Russomanno to dissect the aspects of the GUI that are slowing it down
|
||||
|
||||
@@ -1,721 +0,0 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// This class configures and manages the connection to the OpenBCI Ganglion.
|
||||
// The connection is implemented via a TCP connection to a TCP port.
|
||||
// The Gagnlion is configured using single letter text commands sent from the
|
||||
// PC to the TCP server. The EEG data streams back from the Ganglion, to the
|
||||
// TCP server and back to the PC continuously (once started).
|
||||
//
|
||||
// Created: AJ Keller, August 2016
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// import java.io.OutputStream; //for logging raw bytes to an output file
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Global Functions
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
boolean werePacketsDroppedGang = false;
|
||||
int numPacketsDroppedGang = 0;
|
||||
|
||||
void clientEvent(Client someClient) {
|
||||
// print("Server Says: ");
|
||||
|
||||
int p = ganglion.tcpBufferPositon;
|
||||
ganglion.tcpBuffer[p] = ganglion.tcpClient.readChar();
|
||||
ganglion.tcpBufferPositon++;
|
||||
|
||||
if(p > 2) {
|
||||
String posMatch = new String(ganglion.tcpBuffer, p - 2, 3);
|
||||
if (posMatch.equals(ganglion.TCP_STOP)) {
|
||||
if (!ganglion.nodeProcessHandshakeComplete) {
|
||||
ganglion.nodeProcessHandshakeComplete = true;
|
||||
ganglion.setHubIsRunning(true);
|
||||
println("GanglionSync: clientEvent: handshake complete");
|
||||
}
|
||||
// Get a string from the tcp buffer
|
||||
String msg = new String(ganglion.tcpBuffer, 0, p);
|
||||
// Send the new string message to be processed
|
||||
ganglion.parseMessage(msg);
|
||||
// Check to see if the ganglion ble list needs to be updated
|
||||
if (ganglion.deviceListUpdated) {
|
||||
ganglion.deviceListUpdated = false;
|
||||
controlPanel.bleBox.refreshBLEList();
|
||||
}
|
||||
// Reset the buffer position
|
||||
ganglion.tcpBufferPositon = 0;
|
||||
}
|
||||
} //<>//
|
||||
}
|
||||
|
||||
class OpenBCI_Ganglion {
|
||||
final static String TCP_CMD_ACCEL = "a";
|
||||
final static String TCP_CMD_CONNECT = "c";
|
||||
final static String TCP_CMD_COMMAND = "k";
|
||||
final static String TCP_CMD_DISCONNECT = "d";
|
||||
final static String TCP_CMD_DATA= "t";
|
||||
final static String TCP_CMD_ERROR = "e"; //<>//
|
||||
final static String TCP_CMD_IMPEDANCE = "i";
|
||||
final static String TCP_CMD_LOG = "l";
|
||||
final static String TCP_CMD_SCAN = "s";
|
||||
final static String TCP_CMD_STATUS = "q";
|
||||
final static String TCP_STOP = ",;\n";
|
||||
|
||||
final static String TCP_ACTION_START = "start";
|
||||
final static String TCP_ACTION_STATUS = "status";
|
||||
final static String TCP_ACTION_STOP = "stop";
|
||||
|
||||
final static String GANGLION_BOOTLOADER_MODE = ">";
|
||||
|
||||
final static byte BYTE_START = (byte)0xA0;
|
||||
final static byte BYTE_END = (byte)0xC0;
|
||||
|
||||
// States For Syncing with the hardware
|
||||
final static int STATE_NOCOM = 0;
|
||||
final static int STATE_COMINIT = 1;
|
||||
final static int STATE_SYNCWITHHARDWARE = 2;
|
||||
final static int STATE_NORMAL = 3;
|
||||
final static int STATE_STOPPED = 4;
|
||||
final static int COM_INIT_MSEC = 3000; //you may need to vary this for your computer or your Arduino
|
||||
|
||||
final static int NUM_ACCEL_DIMS = 3;
|
||||
|
||||
final static int RESP_ERROR_UNKNOWN = 499;
|
||||
final static int RESP_ERROR_BAD_PACKET = 500;
|
||||
final static int RESP_ERROR_BAD_NOBLE_START = 501;
|
||||
final static int RESP_ERROR_ALREADY_CONNECTED = 408;
|
||||
final static int RESP_ERROR_COMMAND_NOT_RECOGNIZED = 406;
|
||||
final static int RESP_ERROR_DEVICE_NOT_FOUND = 405;
|
||||
final static int RESP_ERROR_NO_OPEN_BLE_DEVICE = 400;
|
||||
final static int RESP_ERROR_UNABLE_TO_CONNECT = 402;
|
||||
final static int RESP_ERROR_UNABLE_TO_DISCONNECT = 401;
|
||||
final static int RESP_ERROR_SCAN_ALREADY_SCANNING = 409;
|
||||
final static int RESP_ERROR_SCAN_NONE_FOUND = 407;
|
||||
final static int RESP_ERROR_SCAN_NO_SCAN_TO_STOP = 410;
|
||||
final static int RESP_ERROR_SCAN_COULD_NOT_START = 412;
|
||||
final static int RESP_ERROR_SCAN_COULD_NOT_STOP = 411;
|
||||
final static int RESP_GANGLION_FOUND = 201;
|
||||
final static int RESP_SUCCESS = 200;
|
||||
final static int RESP_SUCCESS_DATA_ACCEL = 202;
|
||||
final static int RESP_SUCCESS_DATA_IMPEDANCE = 203;
|
||||
final static int RESP_SUCCESS_DATA_SAMPLE = 204;
|
||||
final static int RESP_STATUS_CONNECTED = 300;
|
||||
final static int RESP_STATUS_DISCONNECTED = 301;
|
||||
final static int RESP_STATUS_SCANNING = 302;
|
||||
final static int RESP_STATUS_NOT_SCANNING = 303;
|
||||
|
||||
private int state = STATE_NOCOM;
|
||||
int prevState_millis = 0; // Used for calculating connect time out
|
||||
|
||||
private int nEEGValuesPerPacket = NCHAN_GANGLION; // Defined by the data format sent by openBCI boards
|
||||
private int nAuxValuesPerPacket = NUM_ACCEL_DIMS; // Defined by the arduino code
|
||||
|
||||
private int tcpGanglionPort = 10996;
|
||||
private String tcpGanglionIP = "127.0.0.1";
|
||||
private String tcpGanglionFull = tcpGanglionIP + ":" + tcpGanglionPort;
|
||||
private boolean tcpClientActive = false;
|
||||
private int tcpTimeout = 1000;
|
||||
|
||||
private final float fs_Hz = 200.0f; //sample rate used by OpenBCI Ganglion board... set by its Arduino code
|
||||
private final float MCP3912_Vref = 1.2f; // reference voltage for ADC in MCP3912 set in hardware
|
||||
private float MCP3912_gain = 1.0; //assumed gain setting for MCP3912. NEEDS TO BE ADJUSTABLE JM
|
||||
private float scale_fac_uVolts_per_count = (MCP3912_Vref * 1000000.f) / (8388607.0 * MCP3912_gain * 1.5 * 51.0); //MCP3912 datasheet page 34. Gain of InAmp = 80
|
||||
// private float scale_fac_accel_G_per_count = 0.032;
|
||||
private float scale_fac_accel_G_per_count = 0.016;
|
||||
// private final float scale_fac_accel_G_per_count = 0.002 / ((float)pow(2,4)); //assume set to +/4G, so 2 mG per digit (datasheet). Account for 4 bits unused
|
||||
// private final float leadOffDrive_amps = 6.0e-9; //6 nA, set by its Arduino code
|
||||
|
||||
private int bleErrorCounter = 0;
|
||||
private int prevSampleIndex = 0;
|
||||
|
||||
private DataPacket_ADS1299 dataPacket;
|
||||
|
||||
public Client tcpClient;
|
||||
private boolean portIsOpen = false;
|
||||
private boolean connected = false;
|
||||
|
||||
public int numberOfDevices = 0;
|
||||
public int maxNumberOfDevices = 10;
|
||||
public String[] deviceList = new String[0];
|
||||
public boolean deviceListUpdated = false;
|
||||
private boolean hubRunning = false;
|
||||
public char[] tcpBuffer = new char[1024];
|
||||
public int tcpBufferPositon = 0;
|
||||
|
||||
private boolean waitingForResponse = false;
|
||||
private boolean nodeProcessHandshakeComplete = false;
|
||||
public boolean shouldStartNodeApp = false;
|
||||
private boolean checkingImpedance = false;
|
||||
private boolean accelModeActive = false;
|
||||
private boolean newAccelData = false;
|
||||
private int[] accelArray = new int[NUM_ACCEL_DIMS];
|
||||
|
||||
public boolean impedanceUpdated = false;
|
||||
public int[] impedanceArray = new int[NCHAN_GANGLION + 1];
|
||||
|
||||
// Getters
|
||||
public float get_fs_Hz() { return fs_Hz; }
|
||||
public boolean isPortOpen() { return portIsOpen; }
|
||||
public float get_scale_fac_uVolts_per_count() { return scale_fac_uVolts_per_count; }
|
||||
public float get_scale_fac_accel_G_per_count() { return scale_fac_accel_G_per_count; }
|
||||
public boolean isHubRunning() { return hubRunning; }
|
||||
public boolean isCheckingImpedance() { return checkingImpedance; }
|
||||
public boolean isAccelModeActive() { return accelModeActive; }
|
||||
|
||||
private PApplet mainApplet;
|
||||
|
||||
//constructors
|
||||
OpenBCI_Ganglion() {}; //only use this if you simply want access to some of the constants
|
||||
OpenBCI_Ganglion(PApplet applet) {
|
||||
mainApplet = applet;
|
||||
|
||||
// Able to start tcpClient connection?
|
||||
startTCPClient(mainApplet);
|
||||
// if (getStatus()) {
|
||||
// println("Able to start tcpClient connection -- YES");
|
||||
// hubRunning = true;
|
||||
// } else {
|
||||
// println("Able to start tcpClient connection -- NO");
|
||||
// }
|
||||
|
||||
// if (getStatus()) {
|
||||
// println("Able to send status message, now waiting for response.");
|
||||
// } else {
|
||||
// // We should try to start the node process because we were not able to
|
||||
// // establish a connection with the node process.
|
||||
// println("Failure: Not able to send status message. Trying to start tcpConnection.");
|
||||
// startTCPClient(applet);
|
||||
// if (getStatus()) {
|
||||
// println("Connection established with node server.");
|
||||
// } else {
|
||||
// println("Connection failed to establish with node server. Recommend trying to launch application from data dir.");
|
||||
// shouldStartNodeApp = true;
|
||||
// }
|
||||
// }
|
||||
|
||||
// For storing data into
|
||||
dataPacket = new DataPacket_ADS1299(nEEGValuesPerPacket, nAuxValuesPerPacket); //this should always be 8 channels
|
||||
for(int i = 0; i < nEEGValuesPerPacket; i++) {
|
||||
dataPacket.values[i] = 0;
|
||||
}
|
||||
for(int i = 0; i < nAuxValuesPerPacket; i++){
|
||||
dataPacket.auxValues[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @descirpiton Used to `try` and start the tcpClient
|
||||
* @param applet {PApplet} - The main applet.
|
||||
* @return {boolean} - True if able to start.
|
||||
*/
|
||||
public boolean startTCPClient(PApplet applet) {
|
||||
try {
|
||||
tcpClient = new Client(applet, tcpGanglionIP, tcpGanglionPort);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
println("startTCPClient: ConnectException: " + e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends a status message to the node process.
|
||||
*/
|
||||
public boolean getStatus() {
|
||||
try {
|
||||
safeTCPWrite(TCP_CMD_STATUS + TCP_STOP);
|
||||
waitingForResponse = true;
|
||||
return true;
|
||||
} catch (NullPointerException E) {
|
||||
// The tcp client is not initalized, try now
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void setHubIsRunning(boolean isRunning) {
|
||||
hubRunning = isRunning;
|
||||
}
|
||||
|
||||
// Return true if the display needs to be updated for the BLE list
|
||||
public void parseMessage(String msg) {
|
||||
// println(msg);
|
||||
String[] list = split(msg, ',');
|
||||
switch (list[0].charAt(0)) {
|
||||
case 'c': // Connect
|
||||
processConnect(msg);
|
||||
break;
|
||||
case 'a': // Accel
|
||||
processAccel(msg);
|
||||
break;
|
||||
case 'd': // Disconnect
|
||||
processDisconnect(msg);
|
||||
break;
|
||||
case 'i': // Impedance
|
||||
processImpedance(msg);
|
||||
break;
|
||||
case 't': // Data
|
||||
processData(msg);
|
||||
break;
|
||||
case 'e': // Error
|
||||
println("OpenBCI_Ganglion: parseMessage: error: " + list[2]);
|
||||
break;
|
||||
case 's': // Scan
|
||||
processScan(msg);
|
||||
break;
|
||||
case 'l':
|
||||
println("OpenBCI_Ganglion: Log: " + list[1]);
|
||||
break;
|
||||
case 'q':
|
||||
processStatus(msg);
|
||||
break;
|
||||
default:
|
||||
println("OpenBCI_Ganglion: parseMessage: default: " + msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void processAccel(String msg) {
|
||||
String[] list = split(msg, ',');
|
||||
if (Integer.parseInt(list[1]) == RESP_SUCCESS_DATA_ACCEL) {
|
||||
for (int i = 0; i < NUM_ACCEL_DIMS; i++) {
|
||||
accelArray[i] = Integer.parseInt(list[i + 2]);
|
||||
}
|
||||
newAccelData = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void processConnect(String msg) {
|
||||
String[] list = split(msg, ',');
|
||||
if (isSuccessCode(Integer.parseInt(list[1]))) {
|
||||
println("OpenBCI_Ganglion: parseMessage: connect: success!");
|
||||
output("OpenBCI_Ganglion: The GUI is done intializing. Click outside of the control panel to interact with the GUI.");
|
||||
systemMode = 10;
|
||||
connected = true;
|
||||
controlPanel.close();
|
||||
} else {
|
||||
println("OpenBCI_Ganglion: parseMessage: connect: failure!");
|
||||
haltSystem();
|
||||
initSystemButton.setString("START SYSTEM");
|
||||
controlPanel.open();
|
||||
abandonInit = true;
|
||||
output("Unable to connect to Ganglion! Please ensure board is powered on and in range!");
|
||||
connected = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void processData(String msg) {
|
||||
String[] list = split(msg, ',');
|
||||
int code = Integer.parseInt(list[1]);
|
||||
if (eegDataSource == DATASOURCE_GANGLION && systemMode == 10 && isRunning) { //<>//
|
||||
if (Integer.parseInt(list[1]) == RESP_SUCCESS_DATA_SAMPLE) { //<>//
|
||||
// Sample number stuff
|
||||
dataPacket.sampleIndex = int(Integer.parseInt(list[2]));
|
||||
if ((dataPacket.sampleIndex - prevSampleIndex) != 1) {
|
||||
if(dataPacket.sampleIndex != 0){ // if we rolled over, don't count as error
|
||||
bleErrorCounter++;
|
||||
|
||||
werePacketsDroppedGang = true; //set this true to activate packet duplication in serialEvent
|
||||
if(dataPacket.sampleIndex < prevSampleIndex){ //handle the situation in which the index jumps from 250s past 255, and back to 0
|
||||
numPacketsDroppedGang = (dataPacket.sampleIndex+200) - prevSampleIndex; //calculate how many times the last received packet should be duplicated...
|
||||
} else {
|
||||
numPacketsDroppedGang = dataPacket.sampleIndex - prevSampleIndex; //calculate how many times the last received packet should be duplicated...
|
||||
}
|
||||
println("OpenBCI_Ganglion: apparent sampleIndex jump from Serial data: " + prevSampleIndex + " to " + dataPacket.sampleIndex + ". Keeping packet. (" + bleErrorCounter + ")");
|
||||
println("numPacketsDropped = " + numPacketsDropped);
|
||||
}
|
||||
}
|
||||
prevSampleIndex = dataPacket.sampleIndex;
|
||||
|
||||
// Channel data storage
|
||||
for (int i = 0; i < NCHAN_GANGLION; i++) {
|
||||
dataPacket.values[i] = Integer.parseInt(list[3 + i]);
|
||||
}
|
||||
if (newAccelData) {
|
||||
newAccelData = false;
|
||||
for (int i = 0; i < NUM_ACCEL_DIMS; i++) {
|
||||
dataPacket.auxValues[i] = accelArray[i];
|
||||
dataPacket.rawAuxValues[i][0] = byte(accelArray[i]);
|
||||
}
|
||||
}
|
||||
getRawValues(dataPacket);
|
||||
// println(binary(dataPacket.values[0], 24) + '\n' + binary(dataPacket.rawValues[0][0], 8) + binary(dataPacket.rawValues[0][1], 8) + binary(dataPacket.rawValues[0][2], 8) + '\n'); //<>//
|
||||
curDataPacketInd = (curDataPacketInd+1) % dataPacketBuff.length; // This is also used to let the rest of the code that it may be time to do something
|
||||
|
||||
ganglion.copyDataPacketTo(dataPacketBuff[curDataPacketInd]); // Resets isNewDataPacketAvailable to false
|
||||
|
||||
// KILL SPIKES!!!
|
||||
if(werePacketsDroppedGang){
|
||||
// println("Packets Dropped ... doing some stuff...");
|
||||
for(int i = numPacketsDroppedGang; i > 0; i--){
|
||||
int tempDataPacketInd = curDataPacketInd - i; //
|
||||
if(tempDataPacketInd >= 0 && tempDataPacketInd < dataPacketBuff.length){
|
||||
// println("i = " + i);
|
||||
ganglion.copyDataPacketTo(dataPacketBuff[tempDataPacketInd]);
|
||||
} else {
|
||||
ganglion.copyDataPacketTo(dataPacketBuff[tempDataPacketInd+200]);
|
||||
}
|
||||
//put the last stored packet in # of packets dropped after that packet
|
||||
}
|
||||
|
||||
//reset werePacketsDropped & numPacketsDropped
|
||||
werePacketsDroppedGang = false;
|
||||
numPacketsDroppedGang = 0;
|
||||
}
|
||||
|
||||
switch (outputDataSource) {
|
||||
case OUTPUT_SOURCE_ODF:
|
||||
fileoutput_odf.writeRawData_dataPacket(dataPacketBuff[curDataPacketInd], ganglion.get_scale_fac_uVolts_per_count(), get_scale_fac_accel_G_per_count());
|
||||
break;
|
||||
case OUTPUT_SOURCE_BDF:
|
||||
// curBDFDataPacketInd = curDataPacketInd;
|
||||
// thread("writeRawData_dataPacket_bdf");
|
||||
fileoutput_bdf.writeRawData_dataPacket(dataPacketBuff[curDataPacketInd]);
|
||||
break;
|
||||
case OUTPUT_SOURCE_NONE:
|
||||
default:
|
||||
// Do nothing...
|
||||
break;
|
||||
}
|
||||
newPacketCounter++;
|
||||
} else {
|
||||
bleErrorCounter++;
|
||||
println("OpenBCI_Ganglion: parseMessage: data: bad");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleError(int code, String msg) {
|
||||
output("Code " + code + "Error: " + msg);
|
||||
println("Code " + code + "Error: " + msg);
|
||||
}
|
||||
|
||||
private void processDisconnect(String msg) {
|
||||
if (!waitingForResponse) {
|
||||
haltSystem();
|
||||
initSystemButton.setString("START SYSTEM");
|
||||
controlPanel.open();
|
||||
output("Dang! Lost connection to Ganglion. Please move closer or get a new battery!");
|
||||
} else {
|
||||
waitingForResponse = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void processImpedance(String msg) {
|
||||
String[] list = split(msg, ',');
|
||||
if (Integer.parseInt(list[1]) == RESP_SUCCESS_DATA_IMPEDANCE) {
|
||||
int channel = Integer.parseInt(list[2]);
|
||||
if (channel < 5) { //<>//
|
||||
int value = Integer.parseInt(list[3]);
|
||||
impedanceArray[channel] = value;
|
||||
if (channel == 0) {
|
||||
impedanceUpdated = true;
|
||||
println("Impedance for channel reference is " + value + " ohms.");
|
||||
} else {
|
||||
println("? for channel " + channel + " is " + value + " ohms.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processStatus(String msg) {
|
||||
String[] list = split(msg, ',');
|
||||
int code = Integer.parseInt(list[1]);
|
||||
if (waitingForResponse) {
|
||||
waitingForResponse = false;
|
||||
println("Node process up!");
|
||||
}
|
||||
if (code == RESP_ERROR_BAD_NOBLE_START) {
|
||||
println("OpenBCI_Ganglion: processStatus: Problem in the Hub");
|
||||
output("Problem starting Ganglion Hub. Please make sure compatible USB is configured, then restart this GUI.");
|
||||
} else {
|
||||
println("OpenBCI_Ganglion: processStatus: Started Successfully");
|
||||
}
|
||||
}
|
||||
|
||||
private void processScan(String msg) {
|
||||
String[] list = split(msg, ',');
|
||||
int code = Integer.parseInt(list[1]);
|
||||
switch(code) {
|
||||
case RESP_GANGLION_FOUND:
|
||||
// Sent every time a new ganglion device is found
|
||||
if (searchDeviceAdd(list[2])) {
|
||||
deviceListUpdated = true;
|
||||
}
|
||||
break;
|
||||
case RESP_ERROR_SCAN_ALREADY_SCANNING:
|
||||
// Sent when a start send command is sent and the module is already
|
||||
// scanning.
|
||||
handleError(code, list[2]);
|
||||
break;
|
||||
case RESP_SUCCESS:
|
||||
// Sent when either a scan was stopped or started Successfully
|
||||
String action = list[2];
|
||||
switch (action) {
|
||||
case TCP_ACTION_START:
|
||||
break;
|
||||
case TCP_ACTION_STOP:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case RESP_ERROR_SCAN_COULD_NOT_START:
|
||||
// Sent when err on search start
|
||||
handleError(code, list[2]);
|
||||
break;
|
||||
case RESP_ERROR_SCAN_COULD_NOT_STOP:
|
||||
// Send when err on search stop
|
||||
handleError(code, list[2]);
|
||||
break;
|
||||
case RESP_STATUS_SCANNING:
|
||||
// Sent when after status action sent to node and module is searching
|
||||
break;
|
||||
case RESP_STATUS_NOT_SCANNING:
|
||||
// Sent when after status action sent to node and module is NOT searching
|
||||
break;
|
||||
case RESP_ERROR_SCAN_NO_SCAN_TO_STOP:
|
||||
// Sent when a 'stop' action is sent to node and there is no scan to stop.
|
||||
handleError(code, list[2]);
|
||||
break;
|
||||
case RESP_ERROR_UNKNOWN:
|
||||
default:
|
||||
handleError(code, list[2]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void writeRawData_dataPacket_bdf() {
|
||||
fileoutput_bdf.writeRawData_dataPacket(dataPacketBuff[curBDFDataPacketInd]);
|
||||
}
|
||||
|
||||
public int copyDataPacketTo(DataPacket_ADS1299 target) {
|
||||
return dataPacket.copyTo(target);
|
||||
}
|
||||
|
||||
private void getRawValues(DataPacket_ADS1299 packet) {
|
||||
for (int i=0; i < nchan; i++) {
|
||||
int val = packet.values[i];
|
||||
//println(binary(val, 24));
|
||||
byte rawValue[] = new byte[3];
|
||||
// Breakdown values into
|
||||
rawValue[2] = byte(val & 0xFF);
|
||||
//println("rawValue[2] " + binary(rawValue[2], 8));
|
||||
rawValue[1] = byte((val & (0xFF << 8)) >> 8);
|
||||
//println("rawValue[1] " + binary(rawValue[1], 8));
|
||||
rawValue[0] = byte((val & (0xFF << 16)) >> 16);
|
||||
//println("rawValue[0] " + binary(rawValue[0], 8));
|
||||
// Store to the target raw values
|
||||
packet.rawValues[i] = rawValue;
|
||||
//println();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Figure out how to ping the server at localhost listening on port 10996
|
||||
// /**
|
||||
// * Used to ping the local hub tcp server and check it's status.
|
||||
// */
|
||||
// public boolean pingHub() {
|
||||
// boolean pingStat;
|
||||
//
|
||||
// try {
|
||||
// println("GanglionSync: pingHub: trying... ");
|
||||
// pingStat = InetAddress.getByName("127.0.0.1:10996").isReachable(tcpTimeout);
|
||||
// print("GanglionSync: pingHub: ");
|
||||
// println(pingStat);
|
||||
// return pingStat;
|
||||
// }
|
||||
// catch(Exception E){
|
||||
// E.printStackTrace();
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
|
||||
public boolean isSuccessCode(int c) {
|
||||
return c == RESP_SUCCESS;
|
||||
}
|
||||
|
||||
// SCANNING/SEARHING FOR DEVICES
|
||||
|
||||
public void searchDeviceStart() {
|
||||
deviceList = null;
|
||||
numberOfDevices = 0;
|
||||
safeTCPWrite(TCP_CMD_SCAN + ',' + TCP_ACTION_START + TCP_STOP);
|
||||
}
|
||||
|
||||
public void searchDeviceStop() {
|
||||
safeTCPWrite(TCP_CMD_SCAN + ',' + TCP_ACTION_STOP + TCP_STOP);
|
||||
}
|
||||
|
||||
public boolean searchDeviceAdd(String ganglionLocalName) {
|
||||
if (numberOfDevices == 0) {
|
||||
numberOfDevices++;
|
||||
deviceList = new String[numberOfDevices];
|
||||
deviceList[0] = ganglionLocalName;
|
||||
return true;
|
||||
} else {
|
||||
boolean willAddToDeviceList = true;
|
||||
for (int i = 0; i < numberOfDevices; i++) {
|
||||
if (ganglionLocalName.equals(deviceList[i])) {
|
||||
willAddToDeviceList = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (willAddToDeviceList) {
|
||||
numberOfDevices++;
|
||||
String[] tempList = new String[numberOfDevices];
|
||||
arrayCopy(deviceList, tempList);
|
||||
tempList[numberOfDevices - 1] = ganglionLocalName;
|
||||
deviceList = tempList;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// CONNECTION
|
||||
public void connectBLE(String id) {
|
||||
safeTCPWrite(TCP_CMD_CONNECT + "," + id + TCP_STOP);
|
||||
}
|
||||
|
||||
public void disconnectBLE() {
|
||||
waitingForResponse = true;
|
||||
safeTCPWrite(TCP_CMD_DISCONNECT + TCP_STOP);
|
||||
}
|
||||
|
||||
public void updateSyncState() {
|
||||
//has it been 3000 milliseconds since we initiated the serial port? We want to make sure we wait for the OpenBCI board to finish its setup()
|
||||
if ((millis() - prevState_millis > COM_INIT_MSEC) && (prevState_millis != 0) && (state == openBCI.STATE_COMINIT) ) {
|
||||
// We are synced and ready to go!
|
||||
state = STATE_SYNCWITHHARDWARE;
|
||||
println("OpenBCI_Ganglion: Sending reset command");
|
||||
// serial_openBCI.write('v');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Sends a start streaming command to the Ganglion Node module.
|
||||
*/
|
||||
void startDataTransfer(){
|
||||
changeState(STATE_NORMAL); // make sure it's now interpretting as binary
|
||||
println("OpenBCI_Ganglion: startDataTransfer(): sending \'" + command_startBinary);
|
||||
safeTCPWrite(TCP_CMD_COMMAND + "," + command_startBinary + TCP_STOP);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Sends a stop streaming command to the Ganglion Node module.
|
||||
*/
|
||||
public void stopDataTransfer() {
|
||||
changeState(STATE_STOPPED); // make sure it's now interpretting as binary
|
||||
println("OpenBCI_Ganglion: stopDataTransfer(): sending \'" + command_stop);
|
||||
safeTCPWrite(TCP_CMD_COMMAND + "," + command_stop + TCP_STOP);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Write to TCP server
|
||||
* @params out {String} - The string message to write to the server.
|
||||
* @returns {boolean} - True if able to write, false otherwise.
|
||||
*/
|
||||
public boolean safeTCPWrite(String out) {
|
||||
try {
|
||||
tcpClient.write(out);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
println("Error: Attempted to TCP write with no server connection initialized");
|
||||
return false;
|
||||
}
|
||||
// return false;
|
||||
// if (nodeProcessHandshakeComplete) { //<>//
|
||||
// try {
|
||||
// tcpClient.write(out);
|
||||
// return true;
|
||||
// } catch (NullPointerException e) {
|
||||
// println("Error: Attempted to TCP write with no server connection initialized");
|
||||
// return false;
|
||||
// }
|
||||
// } else {
|
||||
// println("Waiting on node handshake!");
|
||||
// return false;
|
||||
// }
|
||||
}
|
||||
|
||||
private void printGanglion(String msg) {
|
||||
print("OpenBCI_Ganglion: "); println(msg);
|
||||
}
|
||||
|
||||
public int changeState(int newState) {
|
||||
state = newState;
|
||||
prevState_millis = millis();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Channel setting
|
||||
//activate or deactivate an EEG channel...channel counting is zero through nchan-1
|
||||
public void changeChannelState(int Ichan, boolean activate) {
|
||||
if (connected) {
|
||||
if ((Ichan >= 0)) {
|
||||
if (activate) {
|
||||
println("OpenBCI_Ganglion: changeChannelState(): activate: sending " + command_activate_channel[Ichan]);
|
||||
safeTCPWrite(TCP_CMD_COMMAND + "," + command_activate_channel[Ichan] + TCP_STOP);
|
||||
w_timeSeries.hsc.powerUpChannel(Ichan);
|
||||
} else {
|
||||
println("OpenBCI_Ganglion: changeChannelState(): deactivate: sending " + command_deactivate_channel[Ichan]);
|
||||
safeTCPWrite(TCP_CMD_COMMAND + "," + command_deactivate_channel[Ichan] + TCP_STOP);
|
||||
w_timeSeries.hsc.powerDownChannel(Ichan);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to start accel data mode. Accel arrays will arrive asynchronously!
|
||||
*/
|
||||
public void accelStart() {
|
||||
println("OpenBCI_Ganglion: accell: START");
|
||||
safeTCPWrite(TCP_CMD_ACCEL + "," + TCP_ACTION_START + TCP_STOP);
|
||||
accelModeActive = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to stop accel data mode. Some accel arrays may arrive after stop command
|
||||
* was sent by this function.
|
||||
*/
|
||||
public void accelStop() {
|
||||
println("OpenBCI_Ganglion: accel: STOP");
|
||||
safeTCPWrite(TCP_CMD_ACCEL + "," + TCP_ACTION_STOP + TCP_STOP);
|
||||
accelModeActive = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to start impedance testing. Impedances will arrive asynchronously!
|
||||
*/
|
||||
public void impedanceStart() {
|
||||
println("OpenBCI_Ganglion: impedance: START");
|
||||
safeTCPWrite(TCP_CMD_IMPEDANCE + "," + TCP_ACTION_START + TCP_STOP);
|
||||
checkingImpedance = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to stop impedance testing. Some impedances may arrive after stop command
|
||||
* was sent by this function.
|
||||
*/
|
||||
public void impedanceStop() {
|
||||
println("OpenBCI_Ganglion: impedance: STOP");
|
||||
safeTCPWrite(TCP_CMD_IMPEDANCE + "," + TCP_ACTION_STOP + TCP_STOP);
|
||||
checkingImpedance = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the ganglion in bootloader mode.
|
||||
*/
|
||||
public void enterBootloaderMode() {
|
||||
println("OpenBCI_Ganglion: Entering Bootloader Mode");
|
||||
safeTCPWrite(TCP_CMD_COMMAND + "," + GANGLION_BOOTLOADER_MODE + TCP_STOP);
|
||||
delay(500);
|
||||
disconnectBLE();
|
||||
haltSystem();
|
||||
initSystemButton.setString("START SYSTEM");
|
||||
controlPanel.open();
|
||||
output("Ganglion now in bootloader mode! Enjoy!");
|
||||
}
|
||||
};
|
||||
@@ -22,10 +22,10 @@ public void updateChannelArrays(int _nchan) {
|
||||
//activateChannel: Ichan is [0 nchan-1] (aka zero referenced)
|
||||
void activateChannel(int Ichan) {
|
||||
println("OpenBCI_GUI: activating channel " + (Ichan+1));
|
||||
if (eegDataSource == DATASOURCE_NORMAL_W_AUX) {
|
||||
if (openBCI.isSerialPortOpen()) {
|
||||
if (eegDataSource == DATASOURCE_CYTON) {
|
||||
if (cyton.isPortOpen()) {
|
||||
verbosePrint("**");
|
||||
openBCI.changeChannelState(Ichan, true); //activate
|
||||
cyton.changeChannelState(Ichan, true); //activate
|
||||
}
|
||||
} else if (eegDataSource == DATASOURCE_GANGLION) {
|
||||
// println("activating channel on ganglion");
|
||||
@@ -38,18 +38,16 @@ void activateChannel(int Ichan) {
|
||||
}
|
||||
void deactivateChannel(int Ichan) {
|
||||
println("OpenBCI_GUI: deactivating channel " + (Ichan+1));
|
||||
if (eegDataSource == DATASOURCE_NORMAL_W_AUX) {
|
||||
if (openBCI.isSerialPortOpen()) {
|
||||
if (eegDataSource == DATASOURCE_CYTON) {
|
||||
if (cyton.isPortOpen()) {
|
||||
verbosePrint("**");
|
||||
openBCI.changeChannelState(Ichan, false); //de-activate
|
||||
cyton.changeChannelState(Ichan, false); //de-activate
|
||||
}
|
||||
} else if (eegDataSource == DATASOURCE_GANGLION) {
|
||||
// println("deactivating channel on ganglion");
|
||||
ganglion.changeChannelState(Ichan, false);
|
||||
}
|
||||
if (Ichan < nchan) {
|
||||
channelSettingValues[Ichan][0] = '1';
|
||||
// gui.cc.update();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,9 +68,9 @@ class HardwareSettingsController{
|
||||
|
||||
int x, y, w, h;
|
||||
|
||||
int numSettingsPerChannel = 6; //each channel has 6 different settings
|
||||
char[][] channelSettingValues = new char [nchan][numSettingsPerChannel]; // [channel#][Button#-value] ... this will incfluence text of button
|
||||
char[][] impedanceCheckValues = new char [nchan][2];
|
||||
// int numSettingsPerChannel = 6; //each channel has 6 different settings
|
||||
// char[][] channelSettingValues = new char [nchan][numSettingsPerChannel]; // [channel#][Button#-value] ... this will incfluence text of button
|
||||
// char[][] impedanceCheckValues = new char [nchan][2];
|
||||
|
||||
int spaceBetweenButtons = 5; //space between buttons
|
||||
|
||||
@@ -207,21 +205,21 @@ class HardwareSettingsController{
|
||||
}
|
||||
//then reset to 1
|
||||
|
||||
//
|
||||
if (openBCI.get_isWritingChannel()) {
|
||||
openBCI.writeChannelSettings(channelToWrite,channelSettingValues);
|
||||
}
|
||||
// AJ KELLER
|
||||
// if (cyton.get_isWritingChannel()) {
|
||||
// cyton.writeChannelSettings(channelToWrite,channelSettingValues);
|
||||
// }
|
||||
|
||||
if (rewriteChannelWhenDoneWriting == true && openBCI.get_isWritingChannel() == false) {
|
||||
initChannelWrite(channelToWriteWhenDoneWriting);
|
||||
rewriteChannelWhenDoneWriting = false;
|
||||
}
|
||||
// if (rewriteChannelWhenDoneWriting == true) {
|
||||
// initChannelWrite(channelToWriteWhenDoneWriting);
|
||||
// rewriteChannelWhenDoneWriting = false;
|
||||
// }
|
||||
|
||||
if (openBCI.get_isWritingImp()) {
|
||||
openBCI.writeImpedanceSettings(impChannelToWrite,impedanceCheckValues);
|
||||
}
|
||||
// if (cyton.get_isWritingImp()) {
|
||||
// cyton.writeImpedanceSettings(impChannelToWrite,impedanceCheckValues);
|
||||
// }
|
||||
|
||||
if (rewriteImpedanceWhenDoneWriting == true && openBCI.get_isWritingImp() == false) {
|
||||
if (rewriteImpedanceWhenDoneWriting == true && cyton.get_isWritingImp() == false) {
|
||||
initImpWrite(impChannelToWriteWhenDoneWriting, final_pORn, final_onORoff);
|
||||
rewriteImpedanceWhenDoneWriting = false;
|
||||
}
|
||||
@@ -246,15 +244,17 @@ class HardwareSettingsController{
|
||||
}
|
||||
|
||||
//draw column headers for channel settings behind EEG graph
|
||||
// fill(bgColor);
|
||||
// text("PGA Gain", x2 + (w2/10)*1, y1 - 12);
|
||||
// text("Input Type", x2 + (w2/10)*3, y1 - 12);
|
||||
// text(" Bias ", x2 + (w2/10)*5, y1 - 12);
|
||||
// text("SRB2", x2 + (w2/10)*7, y1 - 12);
|
||||
// text("SRB1", x2 + (w2/10)*9, y1 - 12);
|
||||
fill(bgColor);
|
||||
textFont(p6, 10);
|
||||
textAlign(CENTER, TOP);
|
||||
text("PGA Gain", x + (w/10)*1, y-1);
|
||||
text("Input Type", x + (w/10)*3, y-1);
|
||||
text(" Bias ", x + (w/10)*5, y-1);
|
||||
text("SRB2", x + (w/10)*7, y-1);
|
||||
text("SRB1", x + (w/10)*9, y-1);
|
||||
|
||||
//if mode is not from OpenBCI, draw a dark overlay to indicate that you cannot edit these settings
|
||||
if (eegDataSource != DATASOURCE_NORMAL_W_AUX) {
|
||||
if (eegDataSource != DATASOURCE_CYTON) {
|
||||
fill(0, 0, 0, 200);
|
||||
noStroke();
|
||||
rect(x-2, y, w+1, h);
|
||||
@@ -275,37 +275,44 @@ class HardwareSettingsController{
|
||||
}
|
||||
|
||||
public void loadDefaultChannelSettings() {
|
||||
verbosePrint("ChannelController: loading default channel settings to GUI's channel controller...");
|
||||
// println("loadDefaultChannelSettings");
|
||||
// verbosePrint("ChannelController: loading default channel settings to GUI's channel controller...");
|
||||
for (int i = 0; i < nchan; i++) {
|
||||
verbosePrint("chan: " + i + " ");
|
||||
for (int j = 0; j < numSettingsPerChannel; j++) { //channel setting values
|
||||
channelSettingValues[i][j] = char(openBCI.get_defaultChannelSettings().toCharArray()[j]); //parse defaultChannelSettings string created in the OpenBCI_ADS1299 class
|
||||
if (j == numSettingsPerChannel - 1) {
|
||||
println(char(openBCI.get_defaultChannelSettings().toCharArray()[j]));
|
||||
} else {
|
||||
print(char(openBCI.get_defaultChannelSettings().toCharArray()[j]) + ",");
|
||||
}
|
||||
}
|
||||
// verbosePrint("chan: " + i + " ");
|
||||
channelSettingValues[i][0] = '0';
|
||||
channelSettingValues[i][1] = '6';
|
||||
channelSettingValues[i][2] = '0';
|
||||
channelSettingValues[i][3] = '1';
|
||||
channelSettingValues[i][4] = '1';
|
||||
channelSettingValues[i][5] = '0';
|
||||
// for (int j = 0; j < numSettingsPerChannel; j++) { //channel setting values
|
||||
// channelSettingValues[i][j] = char(cyton.get_defaultChannelSettings().toCharArray()[j]); //parse defaultChannelSettings string created in the Cyton class
|
||||
// if (j == numSettingsPerChannel - 1) {
|
||||
// println(char(cyton.get_defaultChannelSettings().toCharArray()[j]));
|
||||
// } else {
|
||||
// print(char(cyton.get_defaultChannelSettings().toCharArray()[j]) + ",");
|
||||
// }
|
||||
// }
|
||||
for (int k = 0; k < 2; k++) { //impedance setting values
|
||||
impedanceCheckValues[i][k] = '0';
|
||||
}
|
||||
}
|
||||
verbosePrint("made it!");
|
||||
// verbosePrint("made it!");
|
||||
update(); //update 1 time to refresh button values based on new loaded settings
|
||||
}
|
||||
|
||||
void updateChannelArrays(int _nchan) {
|
||||
channelSettingValues = new char [_nchan][numSettingsPerChannel]; // [channel#][Button#-value] ... this will incfluence text of button
|
||||
impedanceCheckValues = new char [_nchan][2];
|
||||
}
|
||||
// void updateChannelArrays(int _nchan) {
|
||||
// channelSettingValues = new char [_nchan][numSettingsPerChannel]; // [channel#][Button#-value] ... this will incfluence text of button
|
||||
// impedanceCheckValues = new char [_nchan][2];
|
||||
// }
|
||||
|
||||
//activateChannel: Ichan is [0 nchan-1] (aka zero referenced)
|
||||
void activateChannel(int Ichan) {
|
||||
println("OpenBCI_GUI: activating channel " + (Ichan+1));
|
||||
if (eegDataSource == DATASOURCE_NORMAL_W_AUX) {
|
||||
if (openBCI.isSerialPortOpen()) {
|
||||
if (eegDataSource == DATASOURCE_CYTON) {
|
||||
if (cyton.isPortOpen()) {
|
||||
verbosePrint("**");
|
||||
openBCI.changeChannelState(Ichan, true); //activate
|
||||
cyton.changeChannelState(Ichan, true); //activate
|
||||
}
|
||||
} else if (eegDataSource == DATASOURCE_GANGLION) {
|
||||
// println("activating channel on ganglion");
|
||||
@@ -319,10 +326,10 @@ class HardwareSettingsController{
|
||||
|
||||
void deactivateChannel(int Ichan) {
|
||||
println("OpenBCI_GUI: deactivating channel " + (Ichan+1));
|
||||
if (eegDataSource == DATASOURCE_NORMAL_W_AUX) {
|
||||
if (openBCI.isSerialPortOpen()) {
|
||||
if (eegDataSource == DATASOURCE_CYTON) {
|
||||
if (cyton.isPortOpen()) {
|
||||
verbosePrint("**");
|
||||
openBCI.changeChannelState(Ichan, false); //de-activate
|
||||
cyton.changeChannelState(Ichan, false); //de-activate
|
||||
}
|
||||
} else if (eegDataSource == DATASOURCE_GANGLION) {
|
||||
// println("deactivating channel on ganglion");
|
||||
@@ -355,7 +362,7 @@ class HardwareSettingsController{
|
||||
|
||||
channelSettingValues[_numChannel][0] = '1'; //update powerUp/powerDown value of 2D array
|
||||
verbosePrint("Command: " + command_deactivate_channel[_numChannel]);
|
||||
openBCI.deactivateChannel(_numChannel); //assumes numChannel counts from zero (not one)...handles regular and daisy channels
|
||||
cyton.deactivateChannel(_numChannel); //assumes numChannel counts from zero (not one)...handles regular and daisy channels
|
||||
}
|
||||
|
||||
public void powerUpChannel(int _numChannel) {
|
||||
@@ -366,46 +373,55 @@ class HardwareSettingsController{
|
||||
|
||||
channelSettingValues[_numChannel][0] = '0'; //update powerUp/powerDown value of 2D array
|
||||
verbosePrint("Command: " + command_activate_channel[_numChannel]);
|
||||
openBCI.activateChannel(_numChannel); //assumes numChannel counts from zero (not one)...handles regular and daisy channels//assumes numChannel counts from zero (not one)...handles regular and daisy channels
|
||||
cyton.activateChannel(_numChannel); //assumes numChannel counts from zero (not one)...handles regular and daisy channels//assumes numChannel counts from zero (not one)...handles regular and daisy channels
|
||||
}
|
||||
|
||||
public void initChannelWrite(int _numChannel) {
|
||||
//after clicking any button, write the new settings for that channel to OpenBCI
|
||||
if (!openBCI.get_isWritingImp()) { //make sure you aren't currently writing imp settings for a channel
|
||||
if (!cyton.get_isWritingImp()) { //make sure you aren't currently writing imp settings for a channel
|
||||
verbosePrint("Writing channel settings for channel " + str(_numChannel+1) + " to OpenBCI!");
|
||||
openBCI.initChannelWrite(_numChannel);
|
||||
cyton.initChannelWrite(_numChannel);
|
||||
channelToWrite = _numChannel;
|
||||
}
|
||||
}
|
||||
|
||||
public void initImpWrite(int _numChannel, char pORn, char onORoff) {
|
||||
//after clicking any button, write the new settings for that channel to OpenBCI
|
||||
if (!openBCI.get_isWritingChannel()) { //make sure you aren't currently writing imp settings for a channel
|
||||
// if you're not currently writing a channel and not waiting to rewrite after you've finished mashing the button
|
||||
if (!openBCI.get_isWritingImp() && rewriteImpedanceWhenDoneWriting == false) {
|
||||
verbosePrint("Writing impedance check settings (" + pORn + "," + onORoff + ") for channel " + str(_numChannel+1) + " to OpenBCI!");
|
||||
if (pORn == 'p') {
|
||||
impedanceCheckValues[_numChannel][0] = onORoff;
|
||||
}
|
||||
if (pORn == 'n') {
|
||||
impedanceCheckValues[_numChannel][1] = onORoff;
|
||||
}
|
||||
openBCI.initImpWrite(_numChannel);
|
||||
impChannelToWrite = _numChannel;
|
||||
} else { //else wait until a the current write has finished and then write again ... this is to not overwrite the wrong values while writing a channel
|
||||
verbosePrint("CONGRATULATIONS, YOU'RE MASHING BUTTONS!");
|
||||
rewriteImpedanceWhenDoneWriting = true;
|
||||
impChannelToWriteWhenDoneWriting = _numChannel;
|
||||
|
||||
if (pORn == 'p') {
|
||||
final_pORn = 'p';
|
||||
}
|
||||
if (pORn == 'n') {
|
||||
final_pORn = 'n';
|
||||
}
|
||||
final_onORoff = onORoff;
|
||||
}
|
||||
verbosePrint("Writing impedance check settings (" + pORn + "," + onORoff + ") for channel " + str(_numChannel+1) + " to OpenBCI!");
|
||||
if (pORn == 'p') {
|
||||
impedanceCheckValues[_numChannel][0] = onORoff;
|
||||
}
|
||||
if (pORn == 'n') {
|
||||
impedanceCheckValues[_numChannel][1] = onORoff;
|
||||
}
|
||||
cyton.writeImpedanceSettings(_numChannel, impedanceCheckValues);
|
||||
// impChannelToWrite = _numChannel;
|
||||
//after clicking any button, write the new settings for that channel to OpenBCI
|
||||
// if (!cyton.get_isWritingChannel()) { //make sure you aren't currently writing imp settings for a channel
|
||||
// if you're not currently writing a channel and not waiting to rewrite after you've finished mashing the button
|
||||
// if (!cyton.get_isWritingImp() && rewriteImpedanceWhenDoneWriting == false) {
|
||||
// verbosePrint("Writing impedance check settings (" + pORn + "," + onORoff + ") for channel " + str(_numChannel+1) + " to OpenBCI!");
|
||||
// if (pORn == 'p') {
|
||||
// impedanceCheckValues[_numChannel][0] = onORoff;
|
||||
// }
|
||||
// if (pORn == 'n') {
|
||||
// impedanceCheckValues[_numChannel][1] = onORoff;
|
||||
// }
|
||||
// cyton.initImpWrite(_numChannel);
|
||||
// impChannelToWrite = _numChannel;
|
||||
// } else { //else wait until a the current write has finished and then write again ... this is to not overwrite the wrong values while writing a channel
|
||||
// verbosePrint("CONGRATULATIONS, YOU'RE MASHING BUTTONS!");
|
||||
// rewriteImpedanceWhenDoneWriting = true;
|
||||
// impChannelToWriteWhenDoneWriting = _numChannel;
|
||||
//
|
||||
// if (pORn == 'p') {
|
||||
// final_pORn = 'p';
|
||||
// }
|
||||
// if (pORn == 'n') {
|
||||
// final_pORn = 'n';
|
||||
// }
|
||||
// final_onORoff = onORoff;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
public void createChannelSettingButtons(int _channelBarHeight) {
|
||||
@@ -470,14 +486,16 @@ class HardwareSettingsController{
|
||||
} else {
|
||||
channelSettingValues[i][j] = '0';
|
||||
}
|
||||
cyton.writeChannelSettings(i, channelSettingValues);
|
||||
// if you're not currently writing a channel and not waiting to rewrite after you've finished mashing the button
|
||||
if (!openBCI.get_isWritingChannel() && rewriteChannelWhenDoneWriting == false) {
|
||||
initChannelWrite(i);//write new ADS1299 channel row values to OpenBCI
|
||||
} else { //else wait until a the current write has finished and then write again ... this is to not overwrite the wrong values while writing a channel
|
||||
verbosePrint("CONGRATULATIONS, YOU'RE MASHING BUTTONS!");
|
||||
rewriteChannelWhenDoneWriting = true;
|
||||
channelToWriteWhenDoneWriting = i;
|
||||
}
|
||||
// if (!cyton.get_isWritingChannel() && rewriteChannelWhenDoneWriting == false) { AJ KEller
|
||||
// if (rewriteChannelWhenDoneWriting == false) {
|
||||
// initChannelWrite(i);//write new ADS1299 channel row values to OpenBCI
|
||||
// } else { //else wait until a the current write has finished and then write again ... this is to not overwrite the wrong values while writing a channel
|
||||
// verbosePrint("CONGRATULATIONS, YOU'RE MASHING BUTTONS!");
|
||||
// rewriteChannelWhenDoneWriting = true;
|
||||
// channelToWriteWhenDoneWriting = i;
|
||||
// }
|
||||
}
|
||||
|
||||
// if(!channelSettingButtons[i][j].isMouseHere()){
|
||||
@@ -548,7 +566,7 @@ class HardwareSettingsController{
|
||||
// //if fullChannelController and one of the buttons (other than ON/OFF) is clicked
|
||||
//
|
||||
// //if dataSource is coming from OpenBCI, allow user to interact with channel controller
|
||||
// if (eegDataSource == DATASOURCE_NORMAL_W_AUX) {
|
||||
// if (eegDataSource == DATASOURCE_CYTON) {
|
||||
// if (showFullController) {
|
||||
// for (int i = 0; i < nchan; i++) { //When [i][j] button is clicked
|
||||
// for (int j = 1; j < numSettingsPerChannel; j++) {
|
||||
@@ -569,7 +587,7 @@ class HardwareSettingsController{
|
||||
// }
|
||||
//
|
||||
// //only allow editing of impedance if dataSource == from OpenBCI
|
||||
// if (eegDataSource == DATASOURCE_NORMAL_W_AUX) {
|
||||
// if (eegDataSource == DATASOURCE_CYTON) {
|
||||
// if (impedanceCheckButtons[i][0].isMouseHere()) {
|
||||
// impedanceCheckButtons[i][0].wasPressed = true;
|
||||
// impedanceCheckButtons[i][0].isActive = true;
|
||||
@@ -594,7 +612,7 @@ class HardwareSettingsController{
|
||||
// channelSettingValues[i][j] = '0';
|
||||
// }
|
||||
// // if you're not currently writing a channel and not waiting to rewrite after you've finished mashing the button
|
||||
// if (!openBCI.get_isWritingChannel() && rewriteChannelWhenDoneWriting == false) {
|
||||
// if (!cyton.get_isWritingChannel() && rewriteChannelWhenDoneWriting == false) {
|
||||
// initChannelWrite(i);//write new ADS1299 channel row values to OpenBCI
|
||||
// } else { //else wait until a the current write has finished and then write again ... this is to not overwrite the wrong values while writing a channel
|
||||
// verbosePrint("CONGRATULATIONS, YOU'RE MASHING BUTTONS!");
|
||||
|
||||
@@ -21,7 +21,7 @@ void keyPressed() {
|
||||
//note that the Processing variable "keyCode" is the keypress as a JAVA keycode. This differs from ASCII
|
||||
//println("OpenBCI_GUI: keyPressed: key = " + key + ", int(key) = " + int(key) + ", keyCode = " + keyCode);
|
||||
|
||||
if(!controlPanel.isOpen){ //don't parse the key if the control panel is open
|
||||
if(!controlPanel.isOpen && !isNetworkingTextActive()){ //don't parse the key if the control panel is open
|
||||
if ((int(key) >=32) && (int(key) <= 126)) { //32 through 126 represent all the usual printable ASCII characters
|
||||
parseKey(key);
|
||||
} else {
|
||||
@@ -163,6 +163,10 @@ void parseKey(char val) {
|
||||
deactivateChannel(16-1);
|
||||
}
|
||||
break;
|
||||
case ':':
|
||||
println("test..."); //@@@@@
|
||||
boolean test = isNetworkingTextActive();
|
||||
break;
|
||||
|
||||
//activate channels 1-8
|
||||
case '!':
|
||||
@@ -245,19 +249,19 @@ void parseKey(char val) {
|
||||
// stopButtonWasPressed();
|
||||
break;
|
||||
case 'n':
|
||||
println("openBCI: " + openBCI);
|
||||
println("cyton: " + cyton);
|
||||
break;
|
||||
|
||||
case '?':
|
||||
printRegisters();
|
||||
cyton.printRegisters();
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
verbosePrint("Updating GUI's channel settings to default...");
|
||||
// gui.cc.loadDefaultChannelSettings();
|
||||
w_timeSeries.hsc.loadDefaultChannelSettings();
|
||||
//openBCI.serial_openBCI.write('d');
|
||||
openBCI.configureAllChannelsToDefault();
|
||||
//cyton.serial_openBCI.write('d');
|
||||
cyton.configureAllChannelsToDefault();
|
||||
break;
|
||||
|
||||
// //change the state of the impedance measurements...activate the N-channels
|
||||
@@ -320,12 +324,14 @@ void parseKey(char val) {
|
||||
break;
|
||||
|
||||
default:
|
||||
println("OpenBCI_GUI: '" + key + "' Pressed...sending to OpenBCI...");
|
||||
// if (openBCI.serial_openBCI != null) openBCI.serial_openBCI.write(key);//send the value as ascii with a newline character
|
||||
//if (openBCI.serial_openBCI != null) openBCI.serial_openBCI.write(key);//send the value as ascii with a newline character
|
||||
openBCI.sendChar(key);
|
||||
|
||||
break;
|
||||
if (eegDataSource == DATASOURCE_CYTON) {
|
||||
println("Interactivity: '" + key + "' Pressed...sending to Cyton...");
|
||||
cyton.write(key);
|
||||
} else if (eegDataSource == DATASOURCE_GANGLION) {
|
||||
println("Interactivity: '" + key + "' Pressed...sending to Ganglion...");
|
||||
hub.sendCommand(key);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -444,11 +450,22 @@ void parseKeycode(int val) {
|
||||
}
|
||||
}
|
||||
|
||||
void mouseDragged() {
|
||||
|
||||
if (systemMode >= SYSTEMMODE_POSTINIT) {
|
||||
|
||||
//calling mouse dragged inly outside of Control Panel
|
||||
if (controlPanel.isOpen == false) {
|
||||
wm.mouseDragged();
|
||||
}
|
||||
}
|
||||
}
|
||||
//swtich yard if a click is detected
|
||||
void mousePressed() {
|
||||
|
||||
verbosePrint("OpenBCI_GUI: mousePressed: mouse pressed");
|
||||
// verbosePrint("OpenBCI_GUI: mousePressed: mouse pressed");
|
||||
// println("systemMode" + systemMode);
|
||||
// controlPanel.CPmousePressed();
|
||||
|
||||
//if not before "Start System" ... i.e. after initial setup
|
||||
if (systemMode >= SYSTEMMODE_POSTINIT) {
|
||||
@@ -976,3 +993,50 @@ void openURLInBrowser(String _url){
|
||||
System.out.println(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
void toggleFrameRate(){
|
||||
if(frameRateCounter<3){
|
||||
frameRateCounter++;
|
||||
} else {
|
||||
frameRateCounter = 1; // until we resolve the latency issue with 24hz, only allow 30hz minimum (aka frameRateCounter = 1)
|
||||
}
|
||||
if(frameRateCounter==0){
|
||||
frameRate(24); //refresh rate ... this will slow automatically, if your processor can't handle the specified rate
|
||||
topNav.fpsButton.setString("24 fps");
|
||||
}
|
||||
if(frameRateCounter==1){
|
||||
frameRate(30); //refresh rate ... this will slow automatically, if your processor can't handle the specified rate
|
||||
topNav.fpsButton.setString("30 fps");
|
||||
}
|
||||
if(frameRateCounter==2){
|
||||
frameRate(45); //refresh rate ... this will slow automatically, if your processor can't handle the specified rate
|
||||
topNav.fpsButton.setString("45 fps");
|
||||
}
|
||||
if(frameRateCounter==3){
|
||||
frameRate(60); //refresh rate ... this will slow automatically, if your processor can't handle the specified rate
|
||||
topNav.fpsButton.setString("60 fps");
|
||||
}
|
||||
}
|
||||
|
||||
boolean isNetworkingTextActive(){
|
||||
boolean isAFieldActive = false;
|
||||
if (w_networking != null) {
|
||||
int numTextFields = w_networking.cp5_networking.getAll(Textfield.class).size();
|
||||
for(int i = 0; i < numTextFields; i++){
|
||||
if(w_networking.cp5_networking.getAll(Textfield.class).get(i).isFocus()){
|
||||
isAFieldActive = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// println("Test - " + w_networking.cp5_networking.getAll(Textfield.class)); //loop through networking textfields and find out if any of the are active
|
||||
|
||||
//isFocus(); returns true if active for textField...
|
||||
println(isAFieldActive);
|
||||
return isAFieldActive; //if not, return false
|
||||
}
|
||||
|
||||
boolean highDPI = false;
|
||||
void toggleHighDPI(){
|
||||
highDPI = !highDPI;
|
||||
println("High DPI? " + highDPI);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,734 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// This class configures and manages the connection to the Serial port for
|
||||
// the Arduino.
|
||||
//
|
||||
// Created: Chip Audette, Oct 2013
|
||||
// Modified: through April 2014
|
||||
// Modified again: Conor Russomanno Sept-Oct 2014
|
||||
// Modified for Daisy (16-chan) OpenBCI V3: Conor Russomanno Nov 2014
|
||||
// Modified Daisy Behaviors: Chip Audette Dec 2014
|
||||
// Modified For Wifi Addition: AJ Keller July 2017
|
||||
//
|
||||
// Note: this class now expects the data format produced by OpenBCI V3.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
import java.io.OutputStream; //for logging raw bytes to an output file
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Global Variables & Instances
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
|
||||
//these variables are used for "Kill Spikes" ... duplicating the last received data packet if packets were droppeds
|
||||
boolean werePacketsDroppedSerial = false;
|
||||
int numPacketsDroppedSerial = 0;
|
||||
|
||||
|
||||
//everything below is now deprecated...
|
||||
// final String[] command_activate_leadoffP_channel = {"!", "@", "#", "$", "%", "^", "&", "*"}; //shift + 1-8
|
||||
// final String[] command_deactivate_leadoffP_channel = {"Q", "W", "E", "R", "T", "Y", "U", "I"}; //letters (plus shift) right below 1-8
|
||||
// final String[] command_activate_leadoffN_channel = {"A", "S", "D", "F", "G", "H", "J", "K"}; //letters (plus shift) below the letters below 1-8
|
||||
// final String[] command_deactivate_leadoffN_channel = {"Z", "X", "C", "V", "B", "N", "M", "<"}; //letters (plus shift) below the letters below the letters below 1-8
|
||||
// final String command_biasAuto = "`";
|
||||
// final String command_biasFixed = "~";
|
||||
|
||||
// ArrayList defaultChannelSettings;
|
||||
|
||||
//here is the routine that listens to the serial port.
|
||||
//if any data is waiting, get it, parse it, and stuff it into our vector of
|
||||
//pre-allocated dataPacketBuff
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Global Functions
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
void serialEvent(Serial port){
|
||||
//check to see which serial port it is
|
||||
if (iSerial.isOpenBCISerial(port)) {
|
||||
|
||||
// boolean echoBytes = !cyton.isStateNormal();
|
||||
boolean echoBytes;
|
||||
|
||||
if (iSerial.isStateNormal() != true) { // || printingRegisters == true){
|
||||
echoBytes = true;
|
||||
} else {
|
||||
echoBytes = false;
|
||||
}
|
||||
iSerial.read(echoBytes);
|
||||
openBCI_byteCount++;
|
||||
if (iSerial.get_isNewDataPacketAvailable()) {
|
||||
println("woo got a new packet");
|
||||
//copy packet into buffer of data packets
|
||||
curDataPacketInd = (curDataPacketInd+1) % dataPacketBuff.length; //this is also used to let the rest of the code that it may be time to do something
|
||||
|
||||
cyton.copyDataPacketTo(dataPacketBuff[curDataPacketInd]);
|
||||
iSerial.set_isNewDataPacketAvailable(false); //resets isNewDataPacketAvailable to false
|
||||
|
||||
// KILL SPIKES!!!
|
||||
if(werePacketsDroppedSerial){
|
||||
for(int i = numPacketsDroppedSerial; i > 0; i--){
|
||||
int tempDataPacketInd = curDataPacketInd - i; //
|
||||
if(tempDataPacketInd >= 0 && tempDataPacketInd < dataPacketBuff.length){
|
||||
cyton.copyDataPacketTo(dataPacketBuff[tempDataPacketInd]);
|
||||
} else {
|
||||
cyton.copyDataPacketTo(dataPacketBuff[tempDataPacketInd+255]);
|
||||
}
|
||||
//put the last stored packet in # of packets dropped after that packet
|
||||
}
|
||||
|
||||
//reset werePacketsDroppedSerial & numPacketsDroppedSerial
|
||||
werePacketsDroppedSerial = false;
|
||||
numPacketsDroppedSerial = 0;
|
||||
}
|
||||
|
||||
switch (outputDataSource) {
|
||||
case OUTPUT_SOURCE_ODF:
|
||||
fileoutput_odf.writeRawData_dataPacket(dataPacketBuff[curDataPacketInd], cyton.get_scale_fac_uVolts_per_count(), cyton.get_scale_fac_accel_G_per_count(), byte(0xC0));
|
||||
break;
|
||||
case OUTPUT_SOURCE_BDF:
|
||||
curBDFDataPacketInd = curDataPacketInd;
|
||||
thread("writeRawData_dataPacket_bdf");
|
||||
// fileoutput_bdf.writeRawData_dataPacket(dataPacketBuff[curDataPacketInd]);
|
||||
break;
|
||||
case OUTPUT_SOURCE_NONE:
|
||||
default:
|
||||
// Do nothing...
|
||||
break;
|
||||
}
|
||||
|
||||
newPacketCounter++;
|
||||
}
|
||||
} else {
|
||||
|
||||
//Used for serial communications, primarily everything in no_start_connection
|
||||
if (no_start_connection) {
|
||||
|
||||
|
||||
if (board_message == null || dollaBillz>2) {
|
||||
board_message = new StringBuilder();
|
||||
dollaBillz = 0;
|
||||
}
|
||||
|
||||
inByte = byte(port.read());
|
||||
print(inByte);
|
||||
if (char(inByte) == 'S' || char(inByte) == 'F') isOpenBCI = true;
|
||||
|
||||
// print(char(inByte));
|
||||
if (inByte != -1) {
|
||||
if (isGettingPoll) {
|
||||
if (inByte != '$') {
|
||||
if (!spaceFound) board_message.append(char(inByte));
|
||||
else hexToInt = Integer.parseInt(String.format("%02X", inByte), 16);
|
||||
|
||||
if (char(inByte) == ' ') spaceFound = true;
|
||||
} else dollaBillz++;
|
||||
} else {
|
||||
if (inByte != '$') board_message.append(char(inByte));
|
||||
else dollaBillz++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//println("Recieved serial data not from OpenBCI"); //this is a bit of a lie
|
||||
inByte = byte(port.read());
|
||||
if (isOpenBCI) {
|
||||
|
||||
if (board_message == null || dollaBillz >2) {
|
||||
board_message = new StringBuilder();
|
||||
dollaBillz=0;
|
||||
}
|
||||
if(inByte != '$'){
|
||||
board_message.append(char(inByte));
|
||||
} else { dollaBillz++; }
|
||||
} else if(char(inByte) == 'S' || char(inByte) == 'F'){
|
||||
isOpenBCI = true;
|
||||
if(board_message == null){
|
||||
board_message = new StringBuilder();
|
||||
board_message.append(char(inByte));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Classes
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
class InterfaceSerial {
|
||||
|
||||
//here is the serial port for this OpenBCI board
|
||||
private Serial serial_openBCI = null;
|
||||
private boolean portIsOpen = false;
|
||||
|
||||
//final static int DATAMODE_TXT = 0;
|
||||
final static int DATAMODE_BIN = 2;
|
||||
final static int DATAMODE_BIN_WAUX = 1; //switched to this value so that receiving Accel data is now the default
|
||||
//final static int DATAMODE_BIN_4CHAN = 4;
|
||||
|
||||
final static int STATE_NOCOM = 0;
|
||||
final static int STATE_COMINIT = 1;
|
||||
final static int STATE_SYNCWITHHARDWARE = 2;
|
||||
final static int STATE_NORMAL = 3;
|
||||
final static int STATE_STOPPED = 4;
|
||||
final static int COM_INIT_MSEC = 3000; //you may need to vary this for your computer or your Arduino
|
||||
|
||||
//int[] measured_packet_length = {0,0,0,0,0};
|
||||
//int measured_packet_length_ind = 0;
|
||||
//int known_packet_length_bytes = 0;
|
||||
|
||||
final static byte BYTE_START = (byte)0xA0;
|
||||
final static byte BYTE_END = (byte)0xC0;
|
||||
|
||||
int prefered_datamode = DATAMODE_BIN_WAUX;
|
||||
|
||||
private int state = STATE_NOCOM;
|
||||
int dataMode = -1;
|
||||
int prevState_millis = 0;
|
||||
|
||||
private int nEEGValuesPerPacket = 8; //defined by the data format sent by cyton boards
|
||||
private int nAuxValuesPerPacket = 3; //defined by the data format sent by cyton boards
|
||||
private DataPacket_ADS1299 rawReceivedDataPacket;
|
||||
private DataPacket_ADS1299 missedDataPacket;
|
||||
private DataPacket_ADS1299 dataPacket;
|
||||
public int [] validAuxValues = {0, 0, 0};
|
||||
public boolean[] freshAuxValuesAvailable = {false, false, false};
|
||||
public boolean freshAuxValues = false;
|
||||
//DataPacket_ADS1299 prevDataPacket;
|
||||
|
||||
private int nAuxValues;
|
||||
private boolean isNewDataPacketAvailable = false;
|
||||
private OutputStream output; //for debugging WEA 2014-01-26
|
||||
private int prevSampleIndex = 0;
|
||||
private int serialErrorCounter = 0;
|
||||
|
||||
private final float fs_Hz = 250.0f; //sample rate used by OpenBCI board...set by its Arduino code
|
||||
private final float ADS1299_Vref = 4.5f; //reference voltage for ADC in ADS1299. set by its hardware
|
||||
private float ADS1299_gain = 24.0; //assumed gain setting for ADS1299. set by its Arduino code
|
||||
private float openBCI_series_resistor_ohms = 2200; // Ohms. There is a series resistor on the 32 bit board.
|
||||
private float scale_fac_uVolts_per_count = ADS1299_Vref / ((float)(pow(2, 23)-1)) / ADS1299_gain * 1000000.f; //ADS1299 datasheet Table 7, confirmed through experiment
|
||||
//float LIS3DH_full_scale_G = 4; // +/- 4G, assumed full scale setting for the accelerometer
|
||||
private final float scale_fac_accel_G_per_count = 0.002 / ((float)pow(2, 4)); //assume set to +/4G, so 2 mG per digit (datasheet). Account for 4 bits unused
|
||||
//final float scale_fac_accel_G_per_count = 1.0;
|
||||
private final float leadOffDrive_amps = 6.0e-9; //6 nA, set by its Arduino code
|
||||
private final String failureMessage = "Failure: Communications timeout - Device failed to poll Host";
|
||||
|
||||
boolean isBiasAuto = true; //not being used?
|
||||
|
||||
//data related to Conor's setup for V3 boards
|
||||
final char[] EOT = {'$', '$', '$'};
|
||||
char[] prev3chars = {'#', '#', '#'};
|
||||
private boolean readyToSend = false; //system waits for $$$ after requesting information from OpenBCI board
|
||||
private long timeOfLastCommand = 0; //used when sync'ing to hardware
|
||||
|
||||
//wait for $$$ to iterate... applies to commands expecting a response
|
||||
public boolean isReadyToSend() {
|
||||
return readyToSend;
|
||||
}
|
||||
public void setReadyToSend(boolean _readyToSend) {
|
||||
readyToSend = _readyToSend;
|
||||
}
|
||||
public int get_state() {
|
||||
return state;
|
||||
};
|
||||
public boolean get_isNewDataPacketAvailable() {
|
||||
return isNewDataPacketAvailable;
|
||||
}
|
||||
public void set_isNewDataPacketAvailable(boolean _isNewDataPacketAvailable) {
|
||||
isNewDataPacketAvailable = _isNewDataPacketAvailable;
|
||||
}
|
||||
|
||||
//constructors
|
||||
InterfaceSerial() {
|
||||
}; //only use this if you simply want access to some of the constants
|
||||
InterfaceSerial(PApplet applet, String comPort, int baud, int nEEGValuesPerOpenBCI, boolean useAux, int nAuxValuesPerOpenBCI) {
|
||||
//choose data mode
|
||||
println("InterfaceSerial: prefered_datamode = " + prefered_datamode + ", nValuesPerPacket = " + nEEGValuesPerPacket);
|
||||
if (prefered_datamode == DATAMODE_BIN_WAUX) {
|
||||
if (!useAux) {
|
||||
//must be requesting the aux data, so change the referred data mode
|
||||
prefered_datamode = DATAMODE_BIN;
|
||||
nAuxValues = 0;
|
||||
//println("InterfaceSerial: nAuxValuesPerPacket = " + nAuxValuesPerPacket + " so setting prefered_datamode to " + prefered_datamode);
|
||||
}
|
||||
}
|
||||
|
||||
dataMode = prefered_datamode;
|
||||
|
||||
initDataPackets(nEEGValuesPerOpenBCI, nAuxValuesPerOpenBCI);
|
||||
|
||||
}
|
||||
|
||||
public void initDataPackets(int numEEG, int numAux) {
|
||||
nEEGValuesPerPacket = numEEG;
|
||||
nAuxValuesPerPacket = numAux;
|
||||
//allocate space for data packet
|
||||
rawReceivedDataPacket = new DataPacket_ADS1299(nEEGValuesPerPacket, nAuxValuesPerPacket); //this should always be 8 channels
|
||||
missedDataPacket = new DataPacket_ADS1299(nEEGValuesPerPacket, nAuxValuesPerPacket); //this should always be 8 channels
|
||||
dataPacket = new DataPacket_ADS1299(nEEGValuesPerPacket, nAuxValuesPerPacket); //this could be 8 or 16 channels
|
||||
|
||||
for (int i = 0; i < nEEGValuesPerPacket; i++) {
|
||||
rawReceivedDataPacket.values[i] = 0;
|
||||
//prevDataPacket.values[i] = 0;
|
||||
}
|
||||
for (int i=0; i < nEEGValuesPerPacket; i++) {
|
||||
// println("i = " + i);
|
||||
dataPacket.values[i] = 0;
|
||||
missedDataPacket.values[i] = 0;
|
||||
}
|
||||
for (int i = 0; i < nAuxValuesPerPacket; i++) {
|
||||
rawReceivedDataPacket.auxValues[i] = 0;
|
||||
dataPacket.auxValues[i] = 0;
|
||||
missedDataPacket.auxValues[i] = 0;
|
||||
//prevDataPacket.auxValues[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// //manage the serial port
|
||||
public int openSerialPort(PApplet applet, String comPort, int baud) {
|
||||
|
||||
output("Attempting to open Serial/COM port: " + openBCI_portName);
|
||||
try {
|
||||
println("InterfaceSerial: openSerialPort: attempting to open serial port: " + openBCI_portName);
|
||||
serial_openBCI = new Serial(applet, comPort, baud); //open the com port
|
||||
serial_openBCI.clear(); // clear anything in the com port's buffer
|
||||
portIsOpen = true;
|
||||
println("InterfaceSerial: openSerialPort: port is open (t)? ... " + portIsOpen);
|
||||
changeState(STATE_COMINIT);
|
||||
return 0;
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
if (e.getMessage().contains("<init>")) {
|
||||
serial_openBCI = null;
|
||||
System.out.println("InterfaceSerial: openSerialPort: port in use, trying again later...");
|
||||
portIsOpen = false;
|
||||
} else {
|
||||
println("RunttimeException: " + e);
|
||||
output("Error connecting to selected Serial/COM port. Make sure your board is powered up and your dongle is plugged in.");
|
||||
abandonInit = true; //global variable in OpenBCI_GUI.pde
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public int changeState(int newState) {
|
||||
state = newState;
|
||||
prevState_millis = millis();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int finalizeCOMINIT() {
|
||||
// //wait specified time for COM/serial port to initialize
|
||||
// if (state == STATE_COMINIT) {
|
||||
// // println("InterfaceSerial: finalizeCOMINIT: Initializing Serial: millis() = " + millis());
|
||||
// if ((millis() - prevState_millis) > COM_INIT_MSEC) {
|
||||
// //serial_openBCI.write(command_activates + "\n"); println("Processing: Serial: activating filters");
|
||||
// println("InterfaceSerial: finalizeCOMINIT: State = NORMAL");
|
||||
changeState(STATE_NORMAL);
|
||||
// // startRunning();
|
||||
// }
|
||||
// }
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int closeSDandSerialPort() {
|
||||
int returnVal=0;
|
||||
|
||||
cyton.closeSDFile();
|
||||
|
||||
readyToSend = false;
|
||||
returnVal = closeSerialPort();
|
||||
prevState_millis = 0; //reset Serial state clock to use as a conditional for timing at the beginnign of systemUpdate()
|
||||
cyton.hardwareSyncStep = 0; //reset Hardware Sync step to be ready to go again...
|
||||
|
||||
return returnVal;
|
||||
}
|
||||
|
||||
public int closeSerialPort() {
|
||||
// if (serial_openBCI != null) {
|
||||
portIsOpen = false;
|
||||
if (serial_openBCI != null) {
|
||||
serial_openBCI.stop();
|
||||
}
|
||||
serial_openBCI = null;
|
||||
state = STATE_NOCOM;
|
||||
println("InterfaceSerial: closeSerialPort: closed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void updateSyncState(int sdSetting) {
|
||||
//has it been 3000 milliseconds since we initiated the serial port? We want to make sure we wait for the OpenBCI board to finish its setup()
|
||||
// println("0");
|
||||
|
||||
if ( (millis() - prevState_millis > COM_INIT_MSEC) && (prevState_millis != 0) && (state == STATE_COMINIT) ) {
|
||||
state = STATE_SYNCWITHHARDWARE;
|
||||
timeOfLastCommand = millis();
|
||||
serial_openBCI.clear();
|
||||
cyton.potentialFailureMessage = "";
|
||||
cyton.defaultChannelSettings = ""; //clear channel setting string to be reset upon a new Init System
|
||||
cyton.daisyOrNot = ""; //clear daisyOrNot string to be reset upon a new Init System
|
||||
println("InterfaceSerial: systemUpdate: [0] Sending 'v' to OpenBCI to reset hardware in case of 32bit board...");
|
||||
serial_openBCI.write('v');
|
||||
}
|
||||
|
||||
//if we are in SYNC WITH HARDWARE state ... trigger a command
|
||||
if ( (state == STATE_SYNCWITHHARDWARE) && (currentlySyncing == false) ) {
|
||||
if (millis() - timeOfLastCommand > 200 && readyToSend == true) {
|
||||
println("sdSetting: " + sdSetting);
|
||||
timeOfLastCommand = millis();
|
||||
cyton.hardwareSyncStep++;
|
||||
cyton.syncWithHardware(sdSetting);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void sendChar(char val) {
|
||||
if (isSerialPortOpen()) {
|
||||
println("sending out: " + val);
|
||||
serial_openBCI.write(val);//send the value as ascii (with a newline character?)
|
||||
} else {
|
||||
println("nope no out: " + val);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void write(String msg) {
|
||||
if (isSerialPortOpen()) {
|
||||
serial_openBCI.write(msg);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSerialPortOpen() {
|
||||
if (portIsOpen & (serial_openBCI != null)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public boolean isOpenBCISerial(Serial port) {
|
||||
if (serial_openBCI == port) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
if (serial_openBCI != null) {
|
||||
serial_openBCI.clear();
|
||||
}
|
||||
}
|
||||
|
||||
//read from the serial port
|
||||
public int read() {
|
||||
return read(false);
|
||||
}
|
||||
public int read(boolean echoChar) {
|
||||
//println("InterfaceSerial: read(): State: " + state);
|
||||
//get the byte
|
||||
byte inByte;
|
||||
if (isSerialPortOpen()) {
|
||||
inByte = byte(serial_openBCI.read());
|
||||
} else {
|
||||
println("InterfaceSerial port not open aborting.");
|
||||
return 0;
|
||||
}
|
||||
print(inByte);
|
||||
//write the most recent char to the console
|
||||
// If the GUI is in streaming mode then echoChar will be false
|
||||
if (echoChar) { //if not in interpret binary (NORMAL) mode
|
||||
// print("hardwareSyncStep: "); println(hardwareSyncStep);
|
||||
// print(".");
|
||||
char inASCII = char(inByte);
|
||||
if (isRunning == false && (millis() - timeSinceStopRunning) > 500) {
|
||||
print(char(inByte));
|
||||
}
|
||||
|
||||
//keep track of previous three chars coming from OpenBCI
|
||||
prev3chars[0] = prev3chars[1];
|
||||
prev3chars[1] = prev3chars[2];
|
||||
prev3chars[2] = inASCII;
|
||||
|
||||
if (cyton.hardwareSyncStep == 0 && inASCII != '$') {
|
||||
cyton.potentialFailureMessage+=inASCII;
|
||||
}
|
||||
|
||||
if (cyton.hardwareSyncStep == 1 && inASCII != '$') {
|
||||
cyton.daisyOrNot+=inASCII;
|
||||
//if hardware returns 8 because daisy is not attached, switch the GUI mode back to 8 channels
|
||||
// if(nchan == 16 && char(daisyOrNot.substring(daisyOrNot.length() - 1)) == '8'){
|
||||
if (nchan == 16 && cyton.daisyOrNot.charAt(cyton.daisyOrNot.length() - 1) == '8') {
|
||||
// verbosePrint(" received from OpenBCI... Switching to nchan = 8 bc daisy is not present...");
|
||||
verbosePrint(" received from OpenBCI... Abandoning hardware initiation.");
|
||||
abandonInit = true;
|
||||
// haltSystem();
|
||||
|
||||
// updateToNChan(8);
|
||||
//
|
||||
// //initialize the FFT objects
|
||||
// for (int Ichan=0; Ichan < nchan; Ichan++) {
|
||||
// verbosePrint("Init FFT Buff – "+Ichan);
|
||||
// fftBuff[Ichan] = new FFT(Nfft, getSampleRateSafe());
|
||||
// } //make the FFT objects
|
||||
//
|
||||
// initializeFFTObjects(fftBuff, dataBuffY_uV, Nfft, getSampleRateSafe());
|
||||
// setupWidgetManager();
|
||||
}
|
||||
}
|
||||
|
||||
if (cyton.hardwareSyncStep == 3 && inASCII != '$') { //if we're retrieving channel settings from OpenBCI
|
||||
cyton.defaultChannelSettings+=inASCII;
|
||||
}
|
||||
|
||||
//if the last three chars are $$$, it means we are moving on to the next stage of initialization
|
||||
if (prev3chars[0] == EOT[0] && prev3chars[1] == EOT[1] && prev3chars[2] == EOT[2]) {
|
||||
verbosePrint(" > EOT detected...");
|
||||
// Added for V2 system down rejection line
|
||||
if (cyton.hardwareSyncStep == 0) {
|
||||
// Failure: Communications timeout - Device failed to poll Host$$$
|
||||
if (cyton.potentialFailureMessage.equals(failureMessage)) {
|
||||
closeLogFile();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// hardwareSyncStep++;
|
||||
prev3chars[2] = '#';
|
||||
if (cyton.hardwareSyncStep == 3) {
|
||||
println("InterfaceSerial: read(): x");
|
||||
println(cyton.defaultChannelSettings);
|
||||
println("InterfaceSerial: read(): y");
|
||||
// gui.cc.loadDefaultChannelSettings();
|
||||
w_timeSeries.hsc.loadDefaultChannelSettings();
|
||||
println("InterfaceSerial: read(): z");
|
||||
}
|
||||
readyToSend = true;
|
||||
// println(hardwareSyncStep);
|
||||
// syncWithHardware(); //haha, I'm getting very verbose with my naming... it's late...
|
||||
}
|
||||
}
|
||||
|
||||
//write raw unprocessed bytes to a binary data dump file
|
||||
if (output != null) {
|
||||
try {
|
||||
output.write(inByte); //for debugging WEA 2014-01-26
|
||||
}
|
||||
catch (IOException e) {
|
||||
System.err.println("InterfaceSerial: read(): Caught IOException: " + e.getMessage());
|
||||
//do nothing
|
||||
}
|
||||
}
|
||||
|
||||
interpretBinaryStream(inByte); //new 2014-02-02 WEA
|
||||
return int(inByte);
|
||||
}
|
||||
|
||||
/* **** Borrowed from Chris Viegl from his OpenBCI parser for BrainBay
|
||||
Modified by Joel Murphy and Conor Russomanno to read OpenBCI data
|
||||
Packet Parser for OpenBCI (1-N channel binary format):
|
||||
3-byte data values are stored in 'little endian' formant in AVRs
|
||||
so this protocol parser expects the lower bytes first.
|
||||
Start Indicator: 0xA0
|
||||
EXPECTING STANDARD PACKET LENGTH DON'T NEED: Packet_length : 1 byte (length = 4 bytes framenumber + 4 bytes per active channel + (optional) 4 bytes for 1 Aux value)
|
||||
Framenumber : 1 byte (Sequential counter of packets)
|
||||
Channel 1 data : 3 bytes
|
||||
...
|
||||
Channel 8 data : 3 bytes
|
||||
Aux Values : UP TO 6 bytes
|
||||
End Indcator : 0xC0
|
||||
TOTAL OF 33 bytes ALL DAY
|
||||
********************************************************************* */
|
||||
private int nDataValuesInPacket = 0;
|
||||
private int localByteCounter=0;
|
||||
private int localChannelCounter=0;
|
||||
private int PACKET_readstate = 0;
|
||||
// byte[] localByteBuffer = {0,0,0,0};
|
||||
private byte[] localAdsByteBuffer = {0, 0, 0};
|
||||
private byte[] localAccelByteBuffer = {0, 0};
|
||||
|
||||
void interpretBinaryStream(byte actbyte) {
|
||||
boolean flag_copyRawDataToFullData = false;
|
||||
|
||||
//println("InterfaceSerial: interpretBinaryStream: PACKET_readstate " + PACKET_readstate);
|
||||
switch (PACKET_readstate) {
|
||||
case 0:
|
||||
//look for header byte
|
||||
if (actbyte == byte(0xA0)) { // look for start indicator
|
||||
// println("InterfaceSerial: interpretBinaryStream: found 0xA0");
|
||||
PACKET_readstate++;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
//check the packet counter
|
||||
// println("case 1");
|
||||
byte inByte = actbyte;
|
||||
rawReceivedDataPacket.sampleIndex = int(inByte); //changed by JAM
|
||||
if ((rawReceivedDataPacket.sampleIndex-prevSampleIndex) != 1) {
|
||||
if (rawReceivedDataPacket.sampleIndex != 0) { // if we rolled over, don't count as error
|
||||
serialErrorCounter++;
|
||||
werePacketsDroppedSerial = true; //set this true to activate packet duplication in serialEvent
|
||||
|
||||
if(rawReceivedDataPacket.sampleIndex < prevSampleIndex){ //handle the situation in which the index jumps from 250s past 255, and back to 0
|
||||
numPacketsDroppedSerial = (rawReceivedDataPacket.sampleIndex+255) - prevSampleIndex; //calculate how many times the last received packet should be duplicated...
|
||||
} else {
|
||||
numPacketsDroppedSerial = rawReceivedDataPacket.sampleIndex - prevSampleIndex; //calculate how many times the last received packet should be duplicated...
|
||||
}
|
||||
|
||||
println("InterfaceSerial: apparent sampleIndex jump from Serial data: " + prevSampleIndex + " to " + rawReceivedDataPacket.sampleIndex + ". Keeping packet. (" + serialErrorCounter + ")");
|
||||
if (outputDataSource == OUTPUT_SOURCE_BDF) {
|
||||
int fakePacketsToWrite = (rawReceivedDataPacket.sampleIndex - prevSampleIndex) - 1;
|
||||
for (int i = 0; i < fakePacketsToWrite; i++) {
|
||||
fileoutput_bdf.writeRawData_dataPacket(missedDataPacket);
|
||||
}
|
||||
println("InterfaceSerial: because BDF, wrote " + fakePacketsToWrite + " empty data packet(s)");
|
||||
}
|
||||
}
|
||||
}
|
||||
prevSampleIndex = rawReceivedDataPacket.sampleIndex;
|
||||
localByteCounter=0;//prepare for next usage of localByteCounter
|
||||
localChannelCounter=0; //prepare for next usage of localChannelCounter
|
||||
PACKET_readstate++;
|
||||
break;
|
||||
case 2:
|
||||
// get ADS channel values
|
||||
// println("case 2");
|
||||
localAdsByteBuffer[localByteCounter] = actbyte;
|
||||
localByteCounter++;
|
||||
if (localByteCounter==3) {
|
||||
rawReceivedDataPacket.values[localChannelCounter] = interpret24bitAsInt32(localAdsByteBuffer);
|
||||
arrayCopy(localAdsByteBuffer, rawReceivedDataPacket.rawValues[localChannelCounter]);
|
||||
localChannelCounter++;
|
||||
if (localChannelCounter==8) { //nDataValuesInPacket) {
|
||||
// all ADS channels arrived !
|
||||
// println("InterfaceSerial: interpretBinaryStream: localChannelCounter = " + localChannelCounter);
|
||||
PACKET_readstate++;
|
||||
if (prefered_datamode != DATAMODE_BIN_WAUX) PACKET_readstate++; //if not using AUX, skip over the next readstate
|
||||
localByteCounter = 0;
|
||||
localChannelCounter = 0;
|
||||
//isNewDataPacketAvailable = true; //tell the rest of the code that the data packet is complete
|
||||
} else {
|
||||
//prepare for next data channel
|
||||
localByteCounter=0; //prepare for next usage of localByteCounter
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
// get LIS3DH channel values 2 bytes times 3 axes
|
||||
// println("case 3");
|
||||
localAccelByteBuffer[localByteCounter] = actbyte;
|
||||
localByteCounter++;
|
||||
if (localByteCounter==2) {
|
||||
rawReceivedDataPacket.auxValues[localChannelCounter] = interpret16bitAsInt32(localAccelByteBuffer);
|
||||
arrayCopy(localAccelByteBuffer, rawReceivedDataPacket.rawAuxValues[localChannelCounter]);
|
||||
if (rawReceivedDataPacket.auxValues[localChannelCounter] != 0) {
|
||||
validAuxValues[localChannelCounter] = rawReceivedDataPacket.auxValues[localChannelCounter];
|
||||
freshAuxValuesAvailable[localChannelCounter] = true;
|
||||
freshAuxValues = true;
|
||||
} else freshAuxValues = false;
|
||||
localChannelCounter++;
|
||||
if (localChannelCounter==nAuxValues) { //number of accelerometer axis) {
|
||||
// all Accelerometer channels arrived !
|
||||
// println("InterfaceSerial: interpretBinaryStream: Accel Data: " + rawReceivedDataPacket.auxValues[0] + ", " + rawReceivedDataPacket.auxValues[1] + ", " + rawReceivedDataPacket.auxValues[2]);
|
||||
PACKET_readstate++;
|
||||
localByteCounter = 0;
|
||||
//isNewDataPacketAvailable = true; //tell the rest of the code that the data packet is complete
|
||||
} else {
|
||||
//prepare for next data channel
|
||||
localByteCounter=0; //prepare for next usage of localByteCounter
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
//look for end byte
|
||||
// println("case 4");
|
||||
if (actbyte == byte(0xC0) || actbyte == byte(0xC1)) { // if correct end delimiter found:
|
||||
// println("... 0xCx found");
|
||||
// println("InterfaceSerial: interpretBinaryStream: found end byte. Setting isNewDataPacketAvailable to TRUE");
|
||||
isNewDataPacketAvailable = true; //original place for this. but why not put it in the previous case block
|
||||
flag_copyRawDataToFullData = true; //time to copy the raw data packet into the full data packet (mainly relevant for 16-chan OpenBCI)
|
||||
} else {
|
||||
serialErrorCounter++;
|
||||
println("InterfaceSerial: interpretBinaryStream: Actbyte = " + actbyte);
|
||||
println("InterfaceSerial: interpretBinaryStream: expecteding end-of-packet byte is missing. Discarding packet. (" + serialErrorCounter + ")");
|
||||
}
|
||||
PACKET_readstate=0; // either way, look for next packet
|
||||
break;
|
||||
default:
|
||||
println("InterfaceSerial: interpretBinaryStream: Unknown byte: " + actbyte + " . Continuing...");
|
||||
PACKET_readstate=0; // look for next packet
|
||||
}
|
||||
|
||||
if (flag_copyRawDataToFullData) {
|
||||
copyRawDataToFullData();
|
||||
}
|
||||
} // end of interpretBinaryStream
|
||||
|
||||
|
||||
|
||||
//return the state
|
||||
public boolean isStateNormal() {
|
||||
if (state == STATE_NORMAL) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private int interpret24bitAsInt32(byte[] byteArray) {
|
||||
//little endian
|
||||
int newInt = (
|
||||
((0xFF & byteArray[0]) << 16) |
|
||||
((0xFF & byteArray[1]) << 8) |
|
||||
(0xFF & byteArray[2])
|
||||
);
|
||||
if ((newInt & 0x00800000) > 0) {
|
||||
newInt |= 0xFF000000;
|
||||
} else {
|
||||
newInt &= 0x00FFFFFF;
|
||||
}
|
||||
return newInt;
|
||||
}
|
||||
|
||||
private int interpret16bitAsInt32(byte[] byteArray) {
|
||||
int newInt = (
|
||||
((0xFF & byteArray[0]) << 8) |
|
||||
(0xFF & byteArray[1])
|
||||
);
|
||||
if ((newInt & 0x00008000) > 0) {
|
||||
newInt |= 0xFFFF0000;
|
||||
} else {
|
||||
newInt &= 0x0000FFFF;
|
||||
}
|
||||
return newInt;
|
||||
}
|
||||
|
||||
|
||||
private int copyRawDataToFullData() {
|
||||
//Prior to the 16-chan OpenBCI, we did NOT have rawReceivedDataPacket along with dataPacket...we just had dataPacket.
|
||||
//With the 16-chan OpenBCI, where the first 8 channels are sent and then the second 8 channels are sent, we introduced
|
||||
//this extra structure so that we could alternate between them.
|
||||
//
|
||||
//This function here decides how to join the latest data (rawReceivedDataPacket) into the full dataPacket
|
||||
|
||||
if (dataPacket.values.length < 2*rawReceivedDataPacket.values.length) {
|
||||
//this is an 8 channel board, so simply copy the data
|
||||
return rawReceivedDataPacket.copyTo(dataPacket);
|
||||
} else {
|
||||
//this is 16-channels, so copy the raw data into the correct channels of the new data
|
||||
int offsetInd_values = 0; //this is correct assuming we just recevied a "board" packet (ie, channels 1-8)
|
||||
int offsetInd_aux = 0; //this is correct assuming we just recevied a "board" packet (ie, channels 1-8)
|
||||
if (rawReceivedDataPacket.sampleIndex % 2 == 0) { // even data packets are from the daisy board
|
||||
offsetInd_values = rawReceivedDataPacket.values.length; //start copying to the 8th slot
|
||||
//offsetInd_aux = rawReceivedDataPacket.auxValues.length; //start copying to the 3rd slot
|
||||
offsetInd_aux = 0;
|
||||
}
|
||||
return rawReceivedDataPacket.copyTo(dataPacket, offsetInd_values, offsetInd_aux);
|
||||
}
|
||||
}
|
||||
|
||||
public int copyDataPacketTo(DataPacket_ADS1299 target) {
|
||||
isNewDataPacketAvailable = false;
|
||||
return dataPacket.copyTo(target);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -6,6 +6,7 @@
|
||||
// Created: Chip Audette, Oct 2013 - May 2014
|
||||
// Modified: Conor Russomanno & Joel Murphy, August 2014 - Dec 2014
|
||||
// Modified (v2.0): Conor Russomanno & Joel Murphy (AJ Keller helped too), June 2016
|
||||
// Modified (v3.0) AJ Keller (Conor Russomanno & Joel Murphy & Wangshu), September 2017
|
||||
//
|
||||
// Requires gwoptics graphing library for processing. Built on V0.5.0
|
||||
// http://www.gwoptics.org/processing/gwoptics_p5lib/
|
||||
@@ -38,6 +39,7 @@ import netP5.*; // for OSC
|
||||
import oscP5.*; // for OSC
|
||||
import hypermedia.net.*; //for UDP
|
||||
import java.nio.ByteBuffer; //for UDP
|
||||
import edu.ucsd.sccn.LSL; //for LSL
|
||||
|
||||
|
||||
import gifAnimation.*;
|
||||
@@ -62,18 +64,29 @@ final int NCHAN_CYTON = 8;
|
||||
final int NCHAN_CYTON_DAISY = 16;
|
||||
final int NCHAN_GANGLION = 4;
|
||||
|
||||
boolean hasIntroAnimation = false;
|
||||
boolean hasIntroAnimation = true;
|
||||
PImage cog;
|
||||
Gif loadingGIF;
|
||||
Gif loadingGIF_blue;
|
||||
|
||||
boolean initSystemThreadLock = false;
|
||||
|
||||
// ---- Define variables related to OpenBCI_GUI UDPMarker functionality
|
||||
UDP udpRX;
|
||||
|
||||
//choose where to get the EEG data
|
||||
final int DATASOURCE_NORMAL_W_AUX = 0; // new default, data from serial with Accel data CHIP 2014-11-03
|
||||
final int DATASOURCE_CYTON = 0; // new default, data from serial with Accel data CHIP 2014-11-03
|
||||
final int DATASOURCE_GANGLION = 1; //looking for signal from OpenBCI board via Serial/COM port, no Aux data
|
||||
final int DATASOURCE_PLAYBACKFILE = 2; //playback from a pre-recorded text file
|
||||
final int DATASOURCE_SYNTHETIC = 3; //Synthetically generated data
|
||||
public int eegDataSource = -1; //default to none of the options
|
||||
|
||||
final int INTERFACE_NONE = -1; // Used to indicate no choice made yet on interface
|
||||
final int INTERFACE_SERIAL = 0; // Used only by cyton
|
||||
final int INTERFACE_HUB_BLE = 1; // used only by ganglion
|
||||
final int INTERFACE_HUB_WIFI = 2; // used by both cyton and ganglion
|
||||
final int INTERFACE_HUB_BLED112 = 3; // used only by ganglion with bled dongle
|
||||
|
||||
//here are variables that are used if loading input data from a CSV text file...double slash ("\\") is necessary to make a single slash
|
||||
String playbackData_fname = "N/A"; //only used if loading input data from a file
|
||||
// String playbackData_fname; //leave blank to cause an "Open File" dialog box to appear at startup. USEFUL!
|
||||
@@ -82,16 +95,28 @@ int currentTableRowIndex = 0;
|
||||
Table_CSV playbackData_table;
|
||||
int nextPlayback_millis = -100; //any negative number
|
||||
|
||||
//Global Serial/COM communications constants
|
||||
OpenBCI_ADS1299 openBCI = new OpenBCI_ADS1299(); //dummy creation to get access to constants, create real one later
|
||||
// Initialize boards for constants
|
||||
Cyton cyton = new Cyton(); //dummy creation to get access to constants, create real one later
|
||||
Ganglion ganglion = new Ganglion(); //dummy creation to get access to constants, create real one later
|
||||
// Intialize interface protocols
|
||||
InterfaceSerial iSerial = new InterfaceSerial();
|
||||
Hub hub = new Hub(); //dummy creation to get access to constants, create real one later
|
||||
|
||||
String openBCI_portName = "N/A"; //starts as N/A but is selected from control panel to match your OpenBCI USB Dongle's serial/COM
|
||||
int openBCI_baud = 115200; //baud rate from the Arduino
|
||||
|
||||
OpenBCI_Ganglion ganglion; //dummy creation to get access to constants, create real one later
|
||||
String ganglion_portName = "N/A";
|
||||
|
||||
String wifi_portName = "N/A";
|
||||
String wifi_ipAddress = "192.168.4.1";
|
||||
|
||||
final static String PROTOCOL_BLE = "ble";
|
||||
final static String PROTOCOL_BLED112 = "bled112";
|
||||
final static String PROTOCOL_SERIAL = "serial";
|
||||
final static String PROTOCOL_WIFI = "wifi";
|
||||
|
||||
////// ---- Define variables related to OpenBCI board operations
|
||||
//Define number of channels from openBCI...first EEG channels, then aux channels
|
||||
//Define number of channels from cyton...first EEG channels, then aux channels
|
||||
int nchan = NCHAN_CYTON; //Normally, 8 or 16. Choose a smaller number to show fewer on the GUI
|
||||
int n_aux_ifEnabled = 3; // this is the accelerometer data CHIP 2014-11-03
|
||||
//define variables related to warnings to the user about whether the EEG data is nearly railed (and, therefore, of dubious quality)
|
||||
@@ -101,9 +126,9 @@ final int threshold_railed_warn = int(pow(2, 23)*0.9); //set a somewhat smaller
|
||||
//OpenBCI SD Card setting (if eegDataSource == 0)
|
||||
int sdSetting = 0; //0 = do not write; 1 = 5 min; 2 = 15 min; 3 = 30 min; etc...
|
||||
String sdSettingString = "Do not write to SD";
|
||||
//openBCI data packet
|
||||
final int nDataBackBuff = 3*(int)get_fs_Hz_safe();
|
||||
DataPacket_ADS1299 dataPacketBuff[] = new DataPacket_ADS1299[nDataBackBuff]; //allocate the array, but doesn't call constructor. Still need to call the constructor!
|
||||
//cyton data packet
|
||||
int nDataBackBuff;
|
||||
DataPacket_ADS1299 dataPacketBuff[]; //allocate later in InitSystem
|
||||
int curDataPacketInd = -1;
|
||||
int curBDFDataPacketInd = -1;
|
||||
int lastReadDataPacketInd = -1;
|
||||
@@ -119,19 +144,24 @@ long timeOfInit;
|
||||
long timeSinceStopRunning = 1000;
|
||||
int prev_time_millis = 0;
|
||||
|
||||
// Calculate nPointsPerUpdate based on sampling rate and buffer update rate
|
||||
// @update_millis: update the buffer every 40 milliseconds
|
||||
// @nPointsPerUpdate: update the GUI after this many data points have been received.
|
||||
// The sampling rate should be ideally a multiple of 25, so as to make actual buffer update rate exactly 40ms
|
||||
final int update_millis = 40;
|
||||
int nPointsPerUpdate; // no longer final, calculate every time in initSystem
|
||||
// final int nPointsPerUpdate = 50; //update the GUI after this many data points have been received
|
||||
// final int nPointsPerUpdate = 24; //update the GUI after this many data points have been received
|
||||
final int nPointsPerUpdate = 10; //update the GUI after this many data points have been received
|
||||
|
||||
// final int nPointsPerUpdate = 10; //update the GUI after this many data points have been received
|
||||
|
||||
//define some data fields for handling data here in processing
|
||||
float dataBuffX[]; //define the size later
|
||||
float dataBuffY_uV[][]; //2D array to handle multiple data channels, each row is a new channel so that dataBuffY[3][] is channel 4
|
||||
float dataBuffY_filtY_uV[][];
|
||||
float yLittleBuff[] = new float[nPointsPerUpdate];
|
||||
float yLittleBuff_uV[][] = new float[nchan][nPointsPerUpdate]; //small buffer used to send data to the filters
|
||||
float yLittleBuff[];
|
||||
float yLittleBuff_uV[][]; //small buffer used to send data to the filters
|
||||
float accelerometerBuff[][]; // accelerometer buff 500 points
|
||||
float auxBuff[][] = new float[3][nPointsPerUpdate];
|
||||
float auxBuff[][];
|
||||
float data_elec_imp_ohm[];
|
||||
|
||||
float displayTime_sec = 5f; //define how much time is shown on the time-domain montage plot (and how much is used in the FFT plot?)
|
||||
@@ -149,9 +179,9 @@ public int outputDataSource = OUTPUT_SOURCE_ODF;
|
||||
// public int outputDataSource = OUTPUT_SOURCE_BDF;
|
||||
|
||||
// Serial output
|
||||
String serial_output_portName = "/dev/tty.usbmodem1411"; //must edit this based on the name of the serial/COM port
|
||||
String serial_output_portName = "/dev/tty.usbmodem1421"; //must edit this based on the name of the serial/COM port
|
||||
Serial serial_output;
|
||||
int serial_output_baud = 115200; //baud rate from the Arduino
|
||||
int serial_output_baud = 9600; //baud rate from the Arduino
|
||||
|
||||
//Control Panel for (re)configuring system settings
|
||||
PlotFontInfo fontInfo;
|
||||
@@ -211,7 +241,7 @@ PFont p6; //small Open Sans
|
||||
ButtonHelpText buttonHelpText;
|
||||
|
||||
//EMG_Widget emg_widget;
|
||||
PulseSensor_Widget pulseWidget;
|
||||
// PulseSensor_Widget pulseWidget;
|
||||
|
||||
boolean no_start_connection = false;
|
||||
boolean has_processed = false;
|
||||
@@ -223,7 +253,7 @@ boolean synthesizeData = false;
|
||||
|
||||
int timeOfSetup = 0;
|
||||
boolean isHubInitialized = false;
|
||||
boolean isGanglionObjectInitialized = false;
|
||||
boolean isHubObjectInitialized = false;
|
||||
color bgColor = color(1, 18, 41);
|
||||
color openbciBlue = color(31, 69, 110);
|
||||
int COLOR_SCHEME_DEFAULT = 1;
|
||||
@@ -233,38 +263,43 @@ int colorScheme = COLOR_SCHEME_ALTERNATIVE_A;
|
||||
|
||||
Process nodeHubby;
|
||||
int hubPid = 0;
|
||||
String nodeHubName = "GanglionHub";
|
||||
String nodeHubName = "OpenBCIHub";
|
||||
Robot rob3115;
|
||||
|
||||
PApplet ourApplet;
|
||||
|
||||
//-----------------------------------------1-------------------------------
|
||||
//------------------------------------------------------------------------
|
||||
// Global Functions
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
//========================SETUP============================//
|
||||
|
||||
int frameRateCounter = 1; //0 = 24, 1 = 30, 2 = 45, 3 = 60
|
||||
|
||||
void setup() {
|
||||
// Step 1: Prepare the exit handler that will attempt to close a running node
|
||||
// server on shut down of this app, the main process.
|
||||
// prepareExitHandler();
|
||||
if (dev == false) {
|
||||
// On windows wait to start the hub until Ganglion is clicked on in the control panel.
|
||||
// See issue #111
|
||||
hubStop(); //kill any existing hubs before starting a new one..
|
||||
if (!isWindows()) {
|
||||
hubInit();
|
||||
}
|
||||
}
|
||||
if (!isWindows()) hubStop(); //kill any existing hubs before starting a new one..
|
||||
hubInit(); // putting down here gives windows time to close any open apps
|
||||
|
||||
println("Welcome to the Processing-based OpenBCI GUI!"); //Welcome line.
|
||||
println("Last update: 12/20/2016"); //Welcome line.
|
||||
println("Last update: 9/5/2016"); //Welcome line.
|
||||
println("For more information about how to work with this code base, please visit: http://docs.openbci.com/OpenBCI%20Software/");
|
||||
//open window
|
||||
size(1024, 768, P2D);
|
||||
ourApplet = this;
|
||||
frameRate(60); //refresh rate ... this will slow automatically, if your processor can't handle the specified rate
|
||||
|
||||
if(frameRateCounter==0){
|
||||
frameRate(24); //refresh rate ... this will slow automatically, if your processor can't handle the specified rate
|
||||
}
|
||||
if(frameRateCounter==1){
|
||||
frameRate(30); //refresh rate ... this will slow automatically, if your processor can't handle the specified rate
|
||||
}
|
||||
if(frameRateCounter==2){
|
||||
frameRate(45); //refresh rate ... this will slow automatically, if your processor can't handle the specified rate
|
||||
}
|
||||
if(frameRateCounter==3){
|
||||
frameRate(60); //refresh rate ... this will slow automatically, if your processor can't handle the specified rate
|
||||
}
|
||||
|
||||
smooth(); //turn this off if it's too slow
|
||||
|
||||
surface.setResizable(true); //updated from frame.setResizable in Processing 2
|
||||
@@ -330,34 +365,65 @@ void setup() {
|
||||
|
||||
playground = new Playground(navBarHeight);
|
||||
|
||||
//attempt to open a serial port for "output"
|
||||
// try {
|
||||
// verbosePrint("OpenBCI_GUI.pde: attempting to open serial/COM port for data output = " + serial_output_portName);
|
||||
// serial_output = new Serial(this, serial_output_portName, serial_output_baud); //open the com port
|
||||
// serial_output.clear(); // clear anything in the com port's buffer
|
||||
// }
|
||||
// catch (RuntimeException e) {
|
||||
// verbosePrint("OpenBCI_GUI.pde: could not open " + serial_output_portName);
|
||||
// }
|
||||
|
||||
// println("OpenBCI_GUI: setup: hub is running " + ganglion.isHubRunning());
|
||||
buttonHelpText = new ButtonHelpText();
|
||||
|
||||
myPresentation = new Presentation();
|
||||
|
||||
// try{
|
||||
// rob3115 = new Robot();
|
||||
// } catch (AWTException e){
|
||||
// println("couldn't create robot...");
|
||||
// }
|
||||
// UDPMarker functionality
|
||||
// Setup the UDP receiver
|
||||
int portRX = 51000; // this is the UDP port the application will be listening on
|
||||
String ip = "127.0.0.1"; // Currently only localhost is supported as UDP Marker source
|
||||
|
||||
// ganglion = new OpenBCI_Ganglion(this);
|
||||
// wm = new WidgetManager(this);
|
||||
//create new object for receiving
|
||||
udpRX=new UDP(this,portRX,ip);
|
||||
udpRX.setReceiveHandler("udpReceiveHandler");
|
||||
udpRX.log(true);
|
||||
udpRX.listen(true);
|
||||
// Print some useful diagnostics
|
||||
println("OpenBCI_GUI::Setup: Is RX mulitcast: "+udpRX.isMulticast());
|
||||
println("OpenBCI_GUI::Setup: Has RX joined multicast: "+udpRX.isJoined());
|
||||
|
||||
timeOfSetup = millis(); //keep track of time when setup is finished... used to make sure enough time has passed before creating some other objects (such as the Ganglion instance)
|
||||
}
|
||||
//====================== END-OF-SETUP ==========================//
|
||||
|
||||
//====================UDP Packet Handler==========================//
|
||||
// This function handles the received UDP packet
|
||||
// See the documentation for the Java UDP class here:
|
||||
// https://ubaa.net/shared/processing/udp/udp_class_udp.htm
|
||||
|
||||
String udpReceiveString = null;
|
||||
|
||||
void udpReceiveHandler(byte[] data, String ip, int portRX){
|
||||
|
||||
String udpString = new String(data);
|
||||
println(udpString+" from: "+ip+" and port: "+portRX);
|
||||
if (udpString.length() >=5 && udpString.indexOf("MARK") >= 0){
|
||||
|
||||
/* Old version with 10 markers
|
||||
char c = value.charAt(4);
|
||||
if ( c>= '0' && c <= '9'){
|
||||
println("Found a valid UDP STIM of value: "+int(c)+" chr: "+c);
|
||||
hub.sendCommand("`"+char(c-(int)'0'));
|
||||
*/
|
||||
int intValue = Integer.parseInt(udpString.substring(4));
|
||||
|
||||
if (intValue > 0 && intValue < 96){ // Since we only send single char ascii value markers (from space to char(126)
|
||||
|
||||
String sendString = "`"+char(intValue+31);
|
||||
|
||||
println("Marker value: "+udpString+" with numeric value of char("+intValue+") as : "+sendString);
|
||||
hub.sendCommand(sendString);
|
||||
|
||||
} else {
|
||||
println("udpReceiveHandler::Warning:invalid UDP STIM of value: "+intValue+" Received String: "+udpString);
|
||||
}
|
||||
} else {
|
||||
println("udpReceiveHandler::Warning:invalid UDP marker packet: "+udpString);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//======================== DRAW LOOP =============================//
|
||||
|
||||
void draw() {
|
||||
@@ -395,8 +461,10 @@ private void prepareExitHandler () {
|
||||
*/
|
||||
void hubInit() {
|
||||
isHubInitialized = true;
|
||||
hubStart();
|
||||
prepareExitHandler();
|
||||
if (!isWindows()) {
|
||||
hubStart();
|
||||
prepareExitHandler();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -408,13 +476,13 @@ void hubStart() {
|
||||
// https://forum.processing.org/two/discussion/13053/use-launch-for-applications-kept-in-data-folder
|
||||
if (isWindows()) {
|
||||
println("OpenBCI_GUI: hubStart: OS Detected: Windows");
|
||||
nodeHubby = launch(dataPath("GanglionHub.exe"));
|
||||
nodeHubby = launch(dataPath("OpenBCIHub.exe"));
|
||||
} else if (isLinux()) {
|
||||
println("OpenBCI_GUI: hubStart: OS Detected: Linux");
|
||||
nodeHubby = exec(dataPath("GanglionHub"));
|
||||
nodeHubby = exec(dataPath("OpenBCIHub"));
|
||||
} else {
|
||||
println("OpenBCI_GUI: hubStart: OS Detected: Mac");
|
||||
nodeHubby = launch(dataPath("GanglionHub.app"));
|
||||
nodeHubby = launch(dataPath("OpenBCIHub.app"));
|
||||
}
|
||||
// hubRunning = true;
|
||||
}
|
||||
@@ -451,6 +519,14 @@ private boolean isWindows() {
|
||||
return System.getProperty("os.name").toLowerCase().indexOf("windows") > -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Helper function to determine if the system is macOS or not.
|
||||
* @return {boolean} true if os is windows, false otherwise.
|
||||
*/
|
||||
private boolean isMac() {
|
||||
return !isWindows() && !isLinux();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Parses the running process list for processes whose name have ganglion hub, if found, kills them one by one.
|
||||
* function dubbed "death dealer"
|
||||
@@ -486,7 +562,7 @@ void killRunningProcessMac() {
|
||||
*/
|
||||
boolean killRunningprocessWin() {
|
||||
try {
|
||||
Runtime.getRuntime().exec("taskkill /F /IM GanglionHub.exe");
|
||||
Runtime.getRuntime().exec("taskkill /F /IM OpenBCIHub.exe");
|
||||
return true;
|
||||
}
|
||||
catch (Exception err) {
|
||||
@@ -502,7 +578,7 @@ boolean killRunningprocessWin() {
|
||||
int getProcessIdFromLineMac(String line) {
|
||||
line = trim(line);
|
||||
String[] components = line.split(" ");
|
||||
return Integer.parseInt(components[0]); //<>//
|
||||
return Integer.parseInt(components[0]);
|
||||
}
|
||||
|
||||
void endProcess(int pid) {
|
||||
@@ -528,7 +604,6 @@ void setupWidgetManager() {
|
||||
}
|
||||
|
||||
void initSystem() {
|
||||
|
||||
println();
|
||||
println();
|
||||
println("=================================================");
|
||||
@@ -542,9 +617,34 @@ void initSystem() {
|
||||
|
||||
//prepare data variables
|
||||
verbosePrint("OpenBCI_GUI: initSystem: Preparing data variables...");
|
||||
dataBuffX = new float[(int)(dataBuff_len_sec * get_fs_Hz_safe())];
|
||||
|
||||
if (eegDataSource == DATASOURCE_PLAYBACKFILE) {
|
||||
//open and load the data file
|
||||
println("OpenBCI_GUI: initSystem: loading playback data from " + playbackData_fname);
|
||||
try {
|
||||
playbackData_table = new Table_CSV(playbackData_fname);
|
||||
playbackData_table.removeColumn(0);
|
||||
} catch (Exception e) {
|
||||
println("OpenBCI_GUI: initSystem: could not open file for playback: " + playbackData_fname);
|
||||
println(" : quitting...");
|
||||
hub.killAndShowMsg("Could not open file for playback: " + playbackData_fname);
|
||||
}
|
||||
println("OpenBCI_GUI: initSystem: loading complete. " + playbackData_table.getRowCount() + " rows of data, which is " + round(float(playbackData_table.getRowCount())/getSampleRateSafe()) + " seconds of EEG data");
|
||||
//removing first column of data from data file...the first column is a time index and not eeg data
|
||||
|
||||
}
|
||||
verbosePrint("OpenBCI_GUI: initSystem: Initializing core data objects");
|
||||
|
||||
// Nfft = getNfftSafe();
|
||||
nDataBackBuff = 3*(int)getSampleRateSafe();
|
||||
dataPacketBuff = new DataPacket_ADS1299[nDataBackBuff]; // call the constructor here
|
||||
nPointsPerUpdate = int(round(float(update_millis) * getSampleRateSafe()/ 1000.f));
|
||||
dataBuffX = new float[(int)(dataBuff_len_sec * getSampleRateSafe())];
|
||||
dataBuffY_uV = new float[nchan][dataBuffX.length];
|
||||
dataBuffY_filtY_uV = new float[nchan][dataBuffX.length];
|
||||
yLittleBuff = new float[nPointsPerUpdate];
|
||||
yLittleBuff_uV = new float[nchan][nPointsPerUpdate]; //small buffer used to send data to the filters
|
||||
auxBuff = new float[3][nPointsPerUpdate];
|
||||
accelerometerBuff = new float[3][500]; // 500 points
|
||||
for (int i=0; i<n_aux_ifEnabled; i++) {
|
||||
for (int j=0; j<accelerometerBuff[0].length; j++) {
|
||||
@@ -558,68 +658,76 @@ void initSystem() {
|
||||
for (int i=0; i<nDataBackBuff; i++) {
|
||||
dataPacketBuff[i] = new DataPacket_ADS1299(nchan, n_aux_ifEnabled);
|
||||
}
|
||||
dataProcessing = new DataProcessing(nchan, get_fs_Hz_safe());
|
||||
dataProcessing_user = new DataProcessing_User(nchan, get_fs_Hz_safe());
|
||||
|
||||
|
||||
dataProcessing = new DataProcessing(nchan, getSampleRateSafe());
|
||||
dataProcessing_user = new DataProcessing_User(nchan, getSampleRateSafe());
|
||||
|
||||
//initialize the data
|
||||
prepareData(dataBuffX, dataBuffY_uV, get_fs_Hz_safe());
|
||||
prepareData(dataBuffX, dataBuffY_uV, getSampleRateSafe());
|
||||
|
||||
verbosePrint("OpenBCI_GUI: initSystem: -- Init 1 -- " + millis());
|
||||
verbosePrint("OpenBCI_GUI: initSystem: Initializing FFT data objects");
|
||||
|
||||
//initialize the FFT objects
|
||||
for (int Ichan=0; Ichan < nchan; Ichan++) {
|
||||
verbosePrint("Init FFT Buff – "+Ichan);
|
||||
fftBuff[Ichan] = new FFT(Nfft, get_fs_Hz_safe());
|
||||
// verbosePrint("Init FFT Buff – " + Ichan);
|
||||
fftBuff[Ichan] = new FFT(getNfftSafe(), getSampleRateSafe());
|
||||
} //make the FFT objects
|
||||
|
||||
initializeFFTObjects(fftBuff, dataBuffY_uV, Nfft, get_fs_Hz_safe());
|
||||
initializeFFTObjects(fftBuff, dataBuffY_uV, getNfftSafe(), getSampleRateSafe());
|
||||
|
||||
//prepare some signal processing stuff
|
||||
//for (int Ichan=0; Ichan < nchan; Ichan++) { detData_freqDomain[Ichan] = new DetectionData_FreqDomain(); }
|
||||
|
||||
verbosePrint("OpenBCI_GUI: initSystem: -- Init 2 -- " + millis());
|
||||
verbosePrint("OpenBCI_GUI: initSystem: Closing ControlPanel...");
|
||||
|
||||
controlPanel.close();
|
||||
topNav.controlPanelCollapser.setIsActive(false);
|
||||
verbosePrint("OpenBCI_GUI: initSystem: Initializing comms with hub....");
|
||||
hub.changeState(STATE_COMINIT);
|
||||
// hub.searchDeviceStop();
|
||||
|
||||
//prepare the source of the input data
|
||||
switch (eegDataSource) {
|
||||
case DATASOURCE_NORMAL_W_AUX:
|
||||
int nEEDataValuesPerPacket = nchan;
|
||||
boolean useAux = false;
|
||||
if (eegDataSource == DATASOURCE_NORMAL_W_AUX) useAux = true; //switch this back to true CHIP 2014-11-04
|
||||
openBCI = new OpenBCI_ADS1299(this, openBCI_portName, openBCI_baud, nEEDataValuesPerPacket, useAux, n_aux_ifEnabled); //this also starts the data transfer after XX seconds
|
||||
break;
|
||||
case DATASOURCE_SYNTHETIC:
|
||||
//do nothing
|
||||
break;
|
||||
case DATASOURCE_PLAYBACKFILE:
|
||||
//open and load the data file
|
||||
println("OpenBCI_GUI: initSystem: loading playback data from " + playbackData_fname);
|
||||
try {
|
||||
playbackData_table = new Table_CSV(playbackData_fname);
|
||||
case DATASOURCE_CYTON:
|
||||
int nEEDataValuesPerPacket = nchan;
|
||||
boolean useAux = true;
|
||||
if (cyton.getInterface() == INTERFACE_SERIAL) {
|
||||
cyton = new Cyton(this, openBCI_portName, openBCI_baud, nEEDataValuesPerPacket, useAux, n_aux_ifEnabled, cyton.getInterface()); //this also starts the data transfer after XX seconds
|
||||
} else {
|
||||
if (hub.getWiFiStyle() == WIFI_DYNAMIC) {
|
||||
cyton = new Cyton(this, wifi_portName, openBCI_baud, nEEDataValuesPerPacket, useAux, n_aux_ifEnabled, cyton.getInterface()); //this also starts the data transfer after XX seconds
|
||||
} else {
|
||||
cyton = new Cyton(this, wifi_ipAddress, openBCI_baud, nEEDataValuesPerPacket, useAux, n_aux_ifEnabled, cyton.getInterface()); //this also starts the data transfer after XX seconds
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DATASOURCE_SYNTHETIC:
|
||||
//do nothing
|
||||
break;
|
||||
case DATASOURCE_PLAYBACKFILE:
|
||||
break;
|
||||
case DATASOURCE_GANGLION:
|
||||
if (ganglion.getInterface() == INTERFACE_HUB_BLE || ganglion.getInterface() == INTERFACE_HUB_BLED112) {
|
||||
hub.connectBLE(ganglion_portName);
|
||||
} else {
|
||||
if (hub.getWiFiStyle() == WIFI_DYNAMIC) {
|
||||
hub.connectWifi(wifi_portName);
|
||||
} else {
|
||||
hub.connectWifi(wifi_ipAddress);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
catch (Exception e) {
|
||||
println("OpenBCI_GUI: initSystem: could not open file for playback: " + playbackData_fname);
|
||||
println(" : quitting...");
|
||||
exit();
|
||||
}
|
||||
println("OpenBCI_GUI: initSystem: loading complete. " + playbackData_table.getRowCount() + " rows of data, which is " + round(float(playbackData_table.getRowCount())/get_fs_Hz_safe()) + " seconds of EEG data");
|
||||
//removing first column of data from data file...the first column is a time index and not eeg data
|
||||
playbackData_table.removeColumn(0);
|
||||
break;
|
||||
case DATASOURCE_GANGLION:
|
||||
ganglion.connectBLE(ganglion_portName);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
verbosePrint("OpenBCI_GUI: initSystem: -- Init 3 -- " + millis());
|
||||
|
||||
if (abandonInit) {
|
||||
haltSystem();
|
||||
println("Failed to connect to data source...");
|
||||
output("Failed to connect to data source...");
|
||||
println("Failed to connect to data source... 1");
|
||||
outputError("Failed to connect to data source fail point 1");
|
||||
} else {
|
||||
println(" 3a -- " + millis());
|
||||
//initilize the GUI
|
||||
@@ -627,6 +735,10 @@ void initSystem() {
|
||||
topNav.initSecondaryNav();
|
||||
println(" 3b -- " + millis());
|
||||
|
||||
//open data file
|
||||
if (eegDataSource == DATASOURCE_CYTON) openNewLogFile(fileName); //open a new log file
|
||||
if (eegDataSource == DATASOURCE_GANGLION) openNewLogFile(fileName); // println("open ganglion output file");
|
||||
|
||||
// wm = new WidgetManager(this);
|
||||
setupWidgetManager();
|
||||
|
||||
@@ -634,27 +746,23 @@ void initSystem() {
|
||||
println(" 3c -- " + millis());
|
||||
// setupGUIWidgets(); //####
|
||||
|
||||
//open data file
|
||||
if (eegDataSource == DATASOURCE_NORMAL_W_AUX) openNewLogFile(fileName); //open a new log file
|
||||
if (eegDataSource == DATASOURCE_GANGLION) openNewLogFile(fileName); // println("open ganglion output file");
|
||||
|
||||
nextPlayback_millis = millis(); //used for synthesizeData and readFromFile. This restarts the clock that keeps the playback at the right pace.
|
||||
w_timeSeries.hsc.loadDefaultChannelSettings();
|
||||
|
||||
if (eegDataSource != DATASOURCE_GANGLION && eegDataSource != DATASOURCE_NORMAL_W_AUX) {
|
||||
if (eegDataSource != DATASOURCE_GANGLION && eegDataSource != DATASOURCE_CYTON) {
|
||||
systemMode = SYSTEMMODE_POSTINIT; //tell system it's ok to leave control panel and start interfacing GUI
|
||||
}
|
||||
if (!abandonInit) {
|
||||
println("WOOHOO!!!");
|
||||
controlPanel.close();
|
||||
} else {
|
||||
haltSystem();
|
||||
println("Failed to connect to data source...");
|
||||
output("Failed to connect to data source...");
|
||||
println("Failed to connect to data source... 2");
|
||||
// output("Failed to connect to data source...");
|
||||
}
|
||||
} else {
|
||||
haltSystem();
|
||||
println("Failed to connect to data source...");
|
||||
output("Failed to connect to data source...");
|
||||
println("Failed to connect to data source... 3");
|
||||
// output("Failed to connect to data source...");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -669,22 +777,122 @@ void initSystem() {
|
||||
* @description Useful function to get the correct sample rate based on data source
|
||||
* @returns `float` - The frequency / sample rate of the data source
|
||||
*/
|
||||
float get_fs_Hz_safe() {
|
||||
float getSampleRateSafe() {
|
||||
if (eegDataSource == DATASOURCE_GANGLION) {
|
||||
return ganglion.get_fs_Hz();
|
||||
return ganglion.getSampleRate();
|
||||
} else if (eegDataSource == DATASOURCE_CYTON){
|
||||
return cyton.getSampleRate();
|
||||
} else if (eegDataSource == DATASOURCE_PLAYBACKFILE) {
|
||||
return playbackData_table.getSampleRate();
|
||||
} else {
|
||||
return openBCI.get_fs_Hz();
|
||||
return 250;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Get the correct points of FFT based on sampling rate
|
||||
* @returns `int` - Points of FFT. 125Hz, 200Hz, 250Hz -> 256points. 1000Hz -> 1024points. 1600Hz -> 2048 points.
|
||||
*/
|
||||
int getNfftSafe() {
|
||||
int sampleRate = (int)getSampleRateSafe();
|
||||
switch (sampleRate) {
|
||||
case 1000:
|
||||
return 1024;
|
||||
case 1600:
|
||||
return 2048;
|
||||
case 125:
|
||||
case 200:
|
||||
case 250:
|
||||
default:
|
||||
return 256;
|
||||
}
|
||||
}
|
||||
|
||||
void startRunning() {
|
||||
verbosePrint("startRunning...");
|
||||
output("Data stream started.");
|
||||
if (eegDataSource == DATASOURCE_GANGLION) {
|
||||
if (ganglion != null) {
|
||||
ganglion.startDataTransfer();
|
||||
}
|
||||
} else {
|
||||
if (cyton != null) {
|
||||
println("DEBUG: start data transfer");
|
||||
cyton.startDataTransfer();
|
||||
}
|
||||
}
|
||||
isRunning = true;
|
||||
}
|
||||
|
||||
void stopRunning() {
|
||||
// openBCI.changeState(0); //make sure it's no longer interpretting as binary
|
||||
verbosePrint("OpenBCI_GUI: stopRunning: stop running...");
|
||||
if (isRunning) {
|
||||
output("Data stream stopped.");
|
||||
}
|
||||
if (eegDataSource == DATASOURCE_GANGLION) {
|
||||
if (ganglion != null) {
|
||||
ganglion.stopDataTransfer();
|
||||
}
|
||||
} else {
|
||||
if (cyton != null) {
|
||||
cyton.stopDataTransfer();
|
||||
}
|
||||
}
|
||||
|
||||
timeSinceStopRunning = millis(); //used as a timer to prevent misc. bytes from flooding serial...
|
||||
isRunning = false;
|
||||
// openBCI.changeState(0); //make sure it's no longer interpretting as binary
|
||||
// systemMode = 0;
|
||||
// closeLogFile();
|
||||
}
|
||||
|
||||
//execute this function whenver the stop button is pressed
|
||||
void stopButtonWasPressed() {
|
||||
//toggle the data transfer state of the ADS1299...stop it or start it...
|
||||
if (isRunning) {
|
||||
verbosePrint("openBCI_GUI: stopButton was pressed...stopping data transfer...");
|
||||
wm.setUpdating(false);
|
||||
stopRunning();
|
||||
topNav.stopButton.setString(topNav.stopButton_pressToStart_txt);
|
||||
topNav.stopButton.setColorNotPressed(color(184, 220, 105));
|
||||
if (eegDataSource == DATASOURCE_GANGLION && ganglion.isCheckingImpedance()) {
|
||||
ganglion.impedanceStop();
|
||||
w_ganglionImpedance.startStopCheck.but_txt = "Start Impedance Check";
|
||||
}
|
||||
} else { //not running
|
||||
verbosePrint("openBCI_GUI: startButton was pressed...starting data transfer...");
|
||||
wm.setUpdating(true);
|
||||
startRunning();
|
||||
topNav.stopButton.setString(topNav.stopButton_pressToStop_txt);
|
||||
topNav.stopButton.setColorNotPressed(color(224, 56, 45));
|
||||
nextPlayback_millis = millis(); //used for synthesizeData and readFromFile. This restarts the clock that keeps the playback at the right pace.
|
||||
if (eegDataSource == DATASOURCE_GANGLION && ganglion.isCheckingImpedance()) {
|
||||
ganglion.impedanceStop();
|
||||
w_ganglionImpedance.startStopCheck.but_txt = "Start Impedance Check";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//halt the data collection
|
||||
void haltSystem() {
|
||||
println("openBCI_GUI: haltSystem: Halting system for reconfiguration of settings...");
|
||||
if (initSystemButton.but_txt == "STOP SYSTEM") {
|
||||
initSystemButton.but_txt = "START SYSTEM";
|
||||
}
|
||||
|
||||
stopRunning(); //stop data transfer
|
||||
|
||||
if(cyton.isPortOpen()) {
|
||||
if (w_pulsesensor.analogReadOn) {
|
||||
hub.sendCommand("/0");
|
||||
println("Stopping Analog Read to read accelerometer");
|
||||
w_pulsesensor.analogModeButton.setString("Turn Analog Read On");
|
||||
w_pulsesensor.analogReadOn = false;
|
||||
}
|
||||
}
|
||||
|
||||
//reset variables for data processing
|
||||
curDataPacketInd = -1;
|
||||
lastReadDataPacketInd = -1;
|
||||
@@ -699,42 +907,64 @@ void haltSystem() {
|
||||
|
||||
//reset connect loadStrings
|
||||
openBCI_portName = "N/A"; // Fixes inability to reconnect after halding JAM 1/2017
|
||||
ganglion_portName = "";
|
||||
ganglion_portName = "N/A";
|
||||
wifi_portName = "N/A";
|
||||
|
||||
controlPanel.resetListItems();
|
||||
|
||||
// w_networking.clearCP5(); //closes all networking controllers
|
||||
|
||||
// stopDataTransfer(); // make sure to stop data transfer, if data is streaming and being drawn
|
||||
|
||||
if (eegDataSource == DATASOURCE_NORMAL_W_AUX) {
|
||||
if (eegDataSource == DATASOURCE_CYTON) {
|
||||
closeLogFile(); //close log file
|
||||
openBCI.closeSDandSerialPort();
|
||||
cyton.closeSDandPort();
|
||||
}
|
||||
if (eegDataSource == DATASOURCE_GANGLION) {
|
||||
if(ganglion.isCheckingImpedance()){
|
||||
ganglion.impedanceStop();
|
||||
w_ganglionImpedance.startStopCheck.but_txt = "Start Impedance Check";
|
||||
}
|
||||
closeLogFile(); //close log file
|
||||
ganglion.disconnectBLE();
|
||||
ganglion.closePort();
|
||||
}
|
||||
systemMode = SYSTEMMODE_PREINIT;
|
||||
hub.changeState(STATE_NOCOM);
|
||||
abandonInit = false;
|
||||
|
||||
bleList.items.clear();
|
||||
wifiList.items.clear();
|
||||
|
||||
// TODO: Comment this back in
|
||||
// if (ganglion.isBLE() || ganglion.isWifi() || cyton.isWifi()) {
|
||||
// hub.searchDeviceStart();
|
||||
// }
|
||||
}
|
||||
|
||||
void delayedInit() {
|
||||
// Initialize a plot
|
||||
GPlot plot = new GPlot(this);
|
||||
}
|
||||
|
||||
void systemUpdate() { // for updating data values and variables
|
||||
|
||||
if (isHubInitialized && isGanglionObjectInitialized == false && millis() - timeOfSetup >= 1500) {
|
||||
ganglion = new OpenBCI_Ganglion(this);
|
||||
println("Instantiating Ganglion object...");
|
||||
isGanglionObjectInitialized = true;
|
||||
if (isHubInitialized && isHubObjectInitialized == false && millis() - timeOfSetup >= 1500) {
|
||||
hub = new Hub(this);
|
||||
println("Instantiating hub object...");
|
||||
isHubObjectInitialized = true;
|
||||
thread("delayedInit");
|
||||
}
|
||||
|
||||
//update the sync state with the OpenBCI hardware
|
||||
if (openBCI.state == openBCI.STATE_NOCOM || openBCI.state == openBCI.STATE_COMINIT || openBCI.state == openBCI.STATE_SYNCWITHHARDWARE) {
|
||||
openBCI.updateSyncState(sdSetting);
|
||||
}
|
||||
// //update the sync state with the OpenBCI hardware
|
||||
// if (iSerial.get_state() == iSerial.STATE_NOCOM || iSerial.get_state() == iSerial.STATE_COMINIT || iSerial.get_state() == iSerial.STATE_SYNCWITHHARDWARE) {
|
||||
// iSerial.updateSyncState(sdSetting);
|
||||
// }
|
||||
|
||||
// if (hub.get_state() == STATE_NOCOM || hub.get_state() == STATE_COMINIT || hub.get_state() == STATE_SYNCWITHHARDWARE) {
|
||||
// hub.updateSyncState(sdSetting);
|
||||
// }
|
||||
|
||||
//prepare for updating the GUI
|
||||
win_x = width;
|
||||
win_y = height;
|
||||
|
||||
|
||||
helpWidget.update();
|
||||
if (systemMode == SYSTEMMODE_PREINIT) {
|
||||
//updates while in system control panel before START SYSTEM
|
||||
controlPanel.update();
|
||||
@@ -819,19 +1049,36 @@ void systemUpdate() { // for updating data values and variables
|
||||
playground.x = width; //reset the x for the playground...
|
||||
}
|
||||
|
||||
wm.update();
|
||||
playground.update();
|
||||
if (!initSystemThreadLock) {
|
||||
if (wm.isWMInitialized) {
|
||||
wm.update();
|
||||
playground.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void systemDraw() { //for drawing to the screen
|
||||
|
||||
|
||||
// Conor's attempt at adjusting the GUI to be 2x in size for High DPI screens ... attempt failed
|
||||
// int currentWidth;
|
||||
// int currentHeight;
|
||||
// if(!highDPI){
|
||||
// currentWidth = width;
|
||||
// currentHeight = height;
|
||||
// }
|
||||
// if(highDPI){
|
||||
// pushMatrix();
|
||||
// scale(2);
|
||||
// }
|
||||
|
||||
//redraw the screen...not every time, get paced by when data is being plotted
|
||||
background(bgColor); //clear the screen
|
||||
noStroke();
|
||||
//background(255); //clear the screen
|
||||
|
||||
if (systemMode >= SYSTEMMODE_POSTINIT) {
|
||||
if (systemMode >= SYSTEMMODE_POSTINIT && !initSystemThreadLock) {
|
||||
int drawLoopCounter_thresh = 100;
|
||||
if ((redrawScreenNow) || (drawLoop_counter >= drawLoopCounter_thresh)) {
|
||||
//if (drawLoop_counter >= drawLoopCounter_thresh) println("OpenBCI_GUI: redrawing based on loop counter...");
|
||||
@@ -840,17 +1087,17 @@ void systemDraw() { //for drawing to the screen
|
||||
|
||||
//update the title of the figure;
|
||||
switch (eegDataSource) {
|
||||
case DATASOURCE_NORMAL_W_AUX:
|
||||
case DATASOURCE_CYTON:
|
||||
switch (outputDataSource) {
|
||||
case OUTPUT_SOURCE_ODF:
|
||||
surface.setTitle(int(frameRate) + " fps, Byte Count = " + openBCI_byteCount + ", bit rate = " + byteRate_perSec*8 + " bps" + ", " + int(float(fileoutput_odf.getRowsWritten())/get_fs_Hz_safe()) + " secs Saved, Writing to " + output_fname);
|
||||
surface.setTitle(int(frameRate) + " fps, " + int(float(fileoutput_odf.getRowsWritten())/getSampleRateSafe()) + " secs Saved, Writing to " + output_fname);
|
||||
break;
|
||||
case OUTPUT_SOURCE_BDF:
|
||||
surface.setTitle(int(frameRate) + " fps, Byte Count = " + openBCI_byteCount + ", bit rate = " + byteRate_perSec*8 + " bps" + ", " + int(fileoutput_bdf.getRecordsWritten()) + " secs Saved, Writing to " + output_fname);
|
||||
surface.setTitle(int(frameRate) + " fps, " + int(fileoutput_bdf.getRecordsWritten()) + " secs Saved, Writing to " + output_fname);
|
||||
break;
|
||||
case OUTPUT_SOURCE_NONE:
|
||||
default:
|
||||
surface.setTitle(int(frameRate) + " fps, Byte Count = " + openBCI_byteCount + ", bit rate = " + byteRate_perSec*8 + " bps");
|
||||
surface.setTitle(int(frameRate) + " fps");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
@@ -858,7 +1105,7 @@ void systemDraw() { //for drawing to the screen
|
||||
surface.setTitle(int(frameRate) + " fps, Using Synthetic EEG Data");
|
||||
break;
|
||||
case DATASOURCE_PLAYBACKFILE:
|
||||
surface.setTitle(int(frameRate) + " fps, Playing " + int(float(currentTableRowIndex)/get_fs_Hz_safe()) + " of " + int(float(playbackData_table.getRowCount())/get_fs_Hz_safe()) + " secs, Reading from: " + playbackData_fname);
|
||||
surface.setTitle(int(frameRate) + " fps, Playing " + int(float(currentTableRowIndex)/getSampleRateSafe()) + " of " + int(float(playbackData_table.getRowCount())/getSampleRateSafe()) + " secs, Reading from: " + playbackData_fname);
|
||||
break;
|
||||
case DATASOURCE_GANGLION:
|
||||
surface.setTitle(int(frameRate) + " fps, Ganglion!");
|
||||
@@ -923,8 +1170,7 @@ void systemDraw() { //for drawing to the screen
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ((openBCI.get_state() == openBCI.STATE_COMINIT || openBCI.get_state() == openBCI.STATE_SYNCWITHHARDWARE) && systemMode == SYSTEMMODE_PREINIT) {
|
||||
if ((hub.get_state() == STATE_COMINIT || hub.get_state() == STATE_SYNCWITHHARDWARE) && systemMode == SYSTEMMODE_PREINIT) {
|
||||
//make out blink the text "Initalizing GUI..."
|
||||
pushStyle();
|
||||
imageMode(CENTER);
|
||||
@@ -957,6 +1203,14 @@ void systemDraw() { //for drawing to the screen
|
||||
|
||||
buttonHelpText.draw();
|
||||
mouseOutOfBounds(); // to fix
|
||||
|
||||
|
||||
// Conor's attempt at adjusting the GUI to be 2x in size for High DPI screens ... attempt failed
|
||||
// if(highDPI){
|
||||
// popMatrix();
|
||||
// size(currentWidth*2, currentHeight*2);
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
void introAnimation() {
|
||||
@@ -977,7 +1231,7 @@ void introAnimation() {
|
||||
textLeading(24);
|
||||
fill(31, 69, 110, transparency);
|
||||
textAlign(CENTER, CENTER);
|
||||
text("OpenBCI GUI v2.1.2\nJanuary 2017", width/2, height/2 + width/9);
|
||||
text("OpenBCI GUI v3.3.1\nMay 2018", width/2, height/2 + width/9);
|
||||
}
|
||||
|
||||
//exit intro animation at t2
|
||||
|
||||
@@ -1,187 +0,0 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// PulseSensor_Widget is used to visiualze heartbeat data using a pulse sensor
|
||||
//
|
||||
// Created: Colin Fausnaught, September 2016
|
||||
// Source Code by Joel Murphy
|
||||
//
|
||||
// Use '/' to toggle between accelerometer and pulse sensor.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class PulseSensor_Widget{
|
||||
|
||||
//button for opening and closing
|
||||
int x, y, w, h;
|
||||
int parentContainer = 3;
|
||||
color boxBG;
|
||||
color strokeColor;
|
||||
|
||||
// Pulse Sensor Stuff
|
||||
int count = 0;
|
||||
int heart = 0;
|
||||
int PulseBuffSize = 500;
|
||||
|
||||
int PulseWindowWidth;
|
||||
int PulseWindowHeight;
|
||||
int PulseWindowX;
|
||||
int PulseWindowY;
|
||||
color eggshell;
|
||||
int[] PulseWaveY; // HOLDS HEARTBEAT WAVEFORM DATA
|
||||
boolean rising;
|
||||
//boolean OBCI_inited= false;
|
||||
|
||||
//OpenBCI_ADS1299 OBCI;
|
||||
|
||||
PulseSensor_Widget(PApplet parent) {
|
||||
x = (int)container[parentContainer].x;
|
||||
y = (int)container[parentContainer].y;
|
||||
w = (int)container[parentContainer].w;
|
||||
h = (int)container[parentContainer].h;
|
||||
|
||||
boxBG = bgColor;
|
||||
strokeColor = color(138, 146, 153);
|
||||
|
||||
// Pulse Sensor Stuff
|
||||
eggshell = color(255, 253, 248);
|
||||
|
||||
PulseWindowWidth = 500;
|
||||
PulseWindowHeight = 183;
|
||||
PulseWindowX = int(x)+5;
|
||||
PulseWindowY = int(y)-10+int(h)/2;
|
||||
PulseWaveY = new int[PulseBuffSize];
|
||||
rising = true;
|
||||
for (int i=0; i<PulseWaveY.length; i++){
|
||||
PulseWaveY[i] = PulseWindowY + PulseWindowHeight/2; // initialize the pulse window data line to V/2
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void update() {
|
||||
if (isRunning) {
|
||||
if(synthesizeData){
|
||||
count++;
|
||||
}
|
||||
|
||||
if(frameCount%60 == 0){ heart = 15; } // fake the beat for now
|
||||
if(openBCI.freshAuxValues){ heart = 4; }
|
||||
heart--; // heart is used to time how long the heart graphic swells when your heart beats
|
||||
heart = max(heart,0); // don't let the heart variable go into negative numbers
|
||||
|
||||
float upperClip = 800.0; // used to keep the pulse waveform within the pulse wave window
|
||||
float lowerClip = 200.0;
|
||||
if(synthesizeData){
|
||||
if(rising){ // MAKE A SAW WAVE FOR TESTING
|
||||
PulseWaveY[PulseWaveY.length-1]--; // place the new raw datapoint at the end of the array
|
||||
if(PulseWaveY[PulseWaveY.length-1] == PulseWindowY){ rising = false; }
|
||||
}else{
|
||||
PulseWaveY[PulseWaveY.length-1]++; // place the new raw datapoint at the end of the array
|
||||
if(PulseWaveY[PulseWaveY.length-1] == PulseWindowY+PulseWindowHeight){ rising = true; }
|
||||
}
|
||||
}else{
|
||||
float sensorValue = float(openBCI.rawReceivedDataPacket.auxValues[0]);
|
||||
PulseWaveY[PulseWaveY.length-1] =
|
||||
int(map(sensorValue,lowerClip,upperClip,float(PulseWindowY+PulseWindowHeight),float(PulseWindowY)));
|
||||
PulseWaveY[PulseWaveY.length-1] = constrain(PulseWaveY[PulseWaveY.length-1],PulseWindowY,PulseWindowY+PulseWindowHeight);
|
||||
}
|
||||
|
||||
for (int i = 0; i < PulseWaveY.length-1; i++) { // move the pulse waveform by
|
||||
PulseWaveY[i] = PulseWaveY[i+1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void draw() {
|
||||
if(drawPulse){
|
||||
// verbosePrint("yeaaa");
|
||||
fill(boxBG);
|
||||
stroke(strokeColor);
|
||||
rect(x, y, w, h);
|
||||
|
||||
textFont(f4,24);
|
||||
textAlign(LEFT, TOP);
|
||||
fill(eggshell);
|
||||
text("Pulse Sensor Amped", x + 10, y + 10);
|
||||
textFont(f4,32);
|
||||
if(synthesizeData){
|
||||
text("BPM " + count, x+10, y+50);
|
||||
text("IBI 760", x+10, y+100);
|
||||
//text("Width "+ w, x+10, y+50);
|
||||
//text("Height "+ h, x+10, y+70);
|
||||
}else{
|
||||
text("BPM " + openBCI.validAuxValues[1], x+10, y+40);
|
||||
text("IBI " + openBCI.validAuxValues[2], x+10, y+100);
|
||||
}
|
||||
|
||||
// heart shape
|
||||
fill(250,0,0);
|
||||
stroke(250,0,0);
|
||||
|
||||
strokeWeight(1);
|
||||
if (heart > 0){ // if a beat happened recently,
|
||||
strokeWeight(8); // make the heart pulse
|
||||
}
|
||||
|
||||
translate(-35,0);
|
||||
smooth(); // draw the heart with two bezier curves
|
||||
bezier(x+w-60,y+40, x+w+20,y-30, x+w+40,y+130, x+w-60,y+140);
|
||||
bezier(x+w-60,y+40, x+w-150,y-30, x+w-160,y+130, x+w-60,y+140);
|
||||
translate(35,0);
|
||||
|
||||
strokeWeight(1); // reset the strokeWeight for next time
|
||||
fill(eggshell); // pulse window background
|
||||
stroke(eggshell);
|
||||
rect(PulseWindowX,PulseWindowY,PulseWindowWidth,PulseWindowHeight);
|
||||
|
||||
stroke(255,0,0); // red is a good color for the pulse waveform
|
||||
noFill();
|
||||
beginShape(); // using beginShape() renders fast
|
||||
for (int x = 0; x < PulseWaveY.length; x++) {
|
||||
int xi = int(map(x, 0, PulseWaveY.length-1, 0, PulseWindowWidth-1));
|
||||
vertex(PulseWindowX+xi, PulseWaveY[x]); //draw a line connecting the data points
|
||||
}
|
||||
endShape();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void screenResized(PApplet _parent, int _winX, int _winY) {
|
||||
//when screen is resized...
|
||||
//update position/size of Pulse Widget
|
||||
x = (int)container[parentContainer].x;
|
||||
y = (int)container[parentContainer].y;
|
||||
w = (int)container[parentContainer].w;
|
||||
h = (int)container[parentContainer].h;
|
||||
|
||||
|
||||
PulseWindowX = int(x)+5;
|
||||
PulseWindowY = int(y)-10+int(h)/2;
|
||||
PulseWindowWidth = int(w)-10;
|
||||
PulseWindowHeight = 183;
|
||||
}
|
||||
|
||||
//boolean isMouseHere() {
|
||||
// if (mouseX >= x && mouseX <= width && mouseY >= y && mouseY <= height - bottomMargin) {
|
||||
// return true;
|
||||
// } else {
|
||||
// return false;
|
||||
// }
|
||||
//}
|
||||
|
||||
//boolean isMouseInButton() {
|
||||
// //verbosePrint("Playground: isMouseInButton: attempting");
|
||||
// if (mouseX >= collapser.but_x && mouseX <= collapser.but_x+collapser.but_dx && mouseY >= collapser.but_y && mouseY <= collapser.but_y + collapser.but_dy) {
|
||||
// return true;
|
||||
// } else {
|
||||
// return false;
|
||||
// }
|
||||
//}
|
||||
|
||||
public void mousePressed() {
|
||||
verbosePrint("PulseSensor >> mousePressed()");
|
||||
}
|
||||
|
||||
public void mouseReleased() {
|
||||
verbosePrint("PulseSensor >> mouseReleased()");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
//
|
||||
// Handles interactions between the radio system and OpenBCI systems.
|
||||
// It is important to note that this is using Serial communication directly
|
||||
// rather than the OpenBCI_ADS1299 class. I just found this easier to work
|
||||
// rather than the Cyton class. I just found this easier to work
|
||||
// with.
|
||||
//
|
||||
// Modified by Joel Murphy, January 2017
|
||||
@@ -33,7 +33,7 @@ void autoconnect(){
|
||||
try{
|
||||
serialPort = serialPorts[i];
|
||||
board = new Serial(this,serialPort,115200);
|
||||
print("try "); print(i); print(" "); print(serialPort); println(" at 115200 baud");
|
||||
print("blasss try "); print(i); print(" "); print(serialPort); println(" at 115200 baud");
|
||||
output("Attempting to connect at 115200 baud to " + serialPort); // not working
|
||||
delay(5000);
|
||||
|
||||
@@ -212,7 +212,7 @@ boolean connect_to_portName(RadioConfigBox rcConfig){
|
||||
output("Attempting to open Serial/COM port: " + openBCI_portName);
|
||||
try {
|
||||
println("Radios_Config: connect_to_portName: attempting to open serial port: " + openBCI_portName);
|
||||
serial_output = new Serial(this,openBCI_portName,openBCI_baud); //open the com port
|
||||
serial_output = new Serial(this, openBCI_portName, openBCI_baud); //open the com port
|
||||
serial_output.clear(); // clear anything in the com port's buffer
|
||||
// portIsOpen = true;
|
||||
println("Radios_Config: connect_to_portName: port is open!");
|
||||
@@ -255,6 +255,8 @@ boolean connect_to_portName(RadioConfigBox rcConfig){
|
||||
//==========================================
|
||||
|
||||
void system_status(RadioConfigBox rcConfig){
|
||||
println("Radios_Config: system_status");
|
||||
|
||||
if(board == null){
|
||||
if(!connect_to_portName(rcConfig)){
|
||||
return;
|
||||
@@ -275,6 +277,7 @@ void system_status(RadioConfigBox rcConfig){
|
||||
|
||||
//Scans through channels until a success message has been found
|
||||
void scan_channels(RadioConfigBox rcConfig){
|
||||
println("Radios_Config: scan_channels");
|
||||
if(board == null){
|
||||
if(!connect_to_portName(rcConfig)){
|
||||
return;
|
||||
@@ -302,6 +305,7 @@ void scan_channels(RadioConfigBox rcConfig){
|
||||
//==========================================
|
||||
|
||||
void get_channel(RadioConfigBox rcConfig){
|
||||
println("Radios_Config: get_channel");
|
||||
if(board == null){
|
||||
if(!connect_to_portName(rcConfig)){
|
||||
return;
|
||||
@@ -336,6 +340,7 @@ void get_channel(RadioConfigBox rcConfig){
|
||||
//==========================================
|
||||
|
||||
void set_channel(RadioConfigBox rcConfig, int channel_number){
|
||||
println("Radios_Config: set_channel");
|
||||
if(board == null){
|
||||
if(!connect_to_portName(rcConfig)){
|
||||
return;
|
||||
@@ -373,6 +378,7 @@ void set_channel(RadioConfigBox rcConfig, int channel_number){
|
||||
//==========================================
|
||||
|
||||
void set_channel_over(RadioConfigBox rcConfig, int channel_number){
|
||||
println("Radios_Config: set_ovr_channel");
|
||||
if(board == null){
|
||||
if(!connect_to_portName(rcConfig)){
|
||||
return;
|
||||
|
||||
|
Antes Largura: | Altura: | Tamanho: 241 KiB |
|
Antes Largura: | Altura: | Tamanho: 269 KiB |
|
Antes Largura: | Altura: | Tamanho: 276 KiB |
|
Antes Largura: | Altura: | Tamanho: 275 KiB |
|
Antes Largura: | Altura: | Tamanho: 269 KiB |
|
Antes Largura: | Altura: | Tamanho: 167 KiB |
|
Antes Largura: | Altura: | Tamanho: 168 KiB |
|
Antes Largura: | Altura: | Tamanho: 214 KiB |
|
Antes Largura: | Altura: | Tamanho: 187 KiB |
|
Antes Largura: | Altura: | Tamanho: 167 KiB |
|
Antes Largura: | Altura: | Tamanho: 163 KiB |
|
Antes Largura: | Altura: | Tamanho: 152 KiB |
|
Antes Largura: | Altura: | Tamanho: 150 KiB |
|
Antes Largura: | Altura: | Tamanho: 151 KiB |
|
Antes Largura: | Altura: | Tamanho: 153 KiB |
|
Antes Largura: | Altura: | Tamanho: 152 KiB |
|
Antes Largura: | Altura: | Tamanho: 175 KiB |
|
Antes Largura: | Altura: | Tamanho: 175 KiB |
|
Antes Largura: | Altura: | Tamanho: 173 KiB |
|
Antes Largura: | Altura: | Tamanho: 172 KiB |
|
Antes Largura: | Altura: | Tamanho: 173 KiB |
|
Antes Largura: | Altura: | Tamanho: 221 KiB |
|
Antes Largura: | Altura: | Tamanho: 216 KiB |
|
Antes Largura: | Altura: | Tamanho: 245 KiB |
@@ -17,6 +17,9 @@ class TopNav {
|
||||
|
||||
Button controlPanelCollapser;
|
||||
|
||||
Button fpsButton;
|
||||
Button highRezButton;
|
||||
|
||||
Button stopButton;
|
||||
public final static String stopButton_pressToStop_txt = "Stop Data Stream";
|
||||
public final static String stopButton_pressToStart_txt = "Start Data Stream";
|
||||
@@ -44,16 +47,36 @@ class TopNav {
|
||||
controlPanelCollapser.setIsActive(true);
|
||||
controlPanelCollapser.isDropdownButton = true;
|
||||
|
||||
fpsButton = new Button(3+3+256, 3, 73, 26, "XX" + " fps", fontInfo.buttonLabel_size);
|
||||
if(frameRateCounter==0){
|
||||
fpsButton.setString("24 fps");
|
||||
}
|
||||
if(frameRateCounter==1){
|
||||
fpsButton.setString("30 fps");
|
||||
}
|
||||
if(frameRateCounter==2){
|
||||
fpsButton.setString("45 fps");
|
||||
}
|
||||
if(frameRateCounter==3){
|
||||
fpsButton.setString("60 fps");
|
||||
}
|
||||
|
||||
fpsButton.setFont(h3, 16);
|
||||
fpsButton.setHelpText("If you're having latency issues, try adjusting the frame rate and see if it helps!");
|
||||
|
||||
highRezButton = new Button(3+3+256+73+3, 3, 26, 26, "XX", fontInfo.buttonLabel_size);
|
||||
controlPanelCollapser.setFont(h3, 16);
|
||||
|
||||
//top right buttons from right to left
|
||||
int butNum = 1;
|
||||
tutorialsButton = new Button(width - 3*(butNum) - 80, 3, 80, 26, "Help", fontInfo.buttonLabel_size);
|
||||
tutorialsButton.setFont(h3, 16);
|
||||
tutorialsButton.setHelpText("Here you will find links to helpful online tutorials and getting started guides. Also, check out how to create custom widgets for the GUI!");
|
||||
tutorialsButton.setHelpText("Click to find links to helpful online tutorials and getting started guides. Also, check out how to create custom widgets for the GUI!");
|
||||
|
||||
butNum = 2;
|
||||
issuesButton = new Button(width - 3*(butNum) - 80 - tutorialsButton.but_dx, 3, 80, 26, "Issues", fontInfo.buttonLabel_size);
|
||||
issuesButton.setHelpText("If you have suggestions or want to share a bug you've found, please create an issue on the GUI's Github repo!");
|
||||
issuesButton.setURL("https://github.com/OpenBCI/OpenBCI_GUI_v2.0/issues");
|
||||
issuesButton.setURL("https://github.com/OpenBCI/OpenBCI_GUI/issues");
|
||||
issuesButton.setFont(h3, 16);
|
||||
|
||||
butNum = 3;
|
||||
@@ -79,8 +102,11 @@ class TopNav {
|
||||
|
||||
filtNotchButton = new Button(7 + stopButton.but_dx, 35, 70, 26, "Notch\n" + dataProcessing.getShortNotchDescription(), fontInfo.buttonLabel_size);
|
||||
filtNotchButton.setFont(p5, 12);
|
||||
filtNotchButton.setHelpText("Here you can adjust the Notch Filter that is applied to all \"Filtered\" data.");
|
||||
|
||||
filtBPButton = new Button(11 + stopButton.but_dx + 70, 35, 70, 26, "BP Filt\n" + dataProcessing.getShortFilterDescription(), fontInfo.buttonLabel_size);
|
||||
filtBPButton.setFont(p5, 12);
|
||||
filtBPButton.setHelpText("Here you can adjust the Band Pass Filter that is applied to all \"Filtered\" data.");
|
||||
|
||||
//right to left in top right (secondary nav)
|
||||
layoutButton = new Button(width - 3 - 60, 35, 60, 26, "Layout", fontInfo.buttonLabel_size);
|
||||
@@ -93,11 +119,15 @@ class TopNav {
|
||||
void updateNavButtonsBasedOnColorScheme(){
|
||||
if(colorScheme == COLOR_SCHEME_DEFAULT){
|
||||
controlPanelCollapser.setColorNotPressed(color(255));
|
||||
fpsButton.setColorNotPressed(color(255));
|
||||
highRezButton.setColorNotPressed(color(255));
|
||||
issuesButton.setColorNotPressed(color(255));
|
||||
shopButton.setColorNotPressed(color(255));
|
||||
tutorialsButton.setColorNotPressed(color(255));
|
||||
|
||||
controlPanelCollapser.textColorNotActive = color(bgColor);
|
||||
fpsButton.textColorNotActive = color(bgColor);
|
||||
highRezButton.textColorNotActive = color(bgColor);
|
||||
issuesButton.textColorNotActive = color(bgColor);
|
||||
shopButton.textColorNotActive = color(bgColor);
|
||||
tutorialsButton.textColorNotActive = color(bgColor);
|
||||
@@ -115,11 +145,15 @@ class TopNav {
|
||||
// tutorialsButton.setColorNotPressed(bgColor);
|
||||
|
||||
controlPanelCollapser.setColorNotPressed(openbciBlue);
|
||||
fpsButton.setColorNotPressed(openbciBlue);
|
||||
highRezButton.setColorNotPressed(openbciBlue);
|
||||
issuesButton.setColorNotPressed(openbciBlue);
|
||||
shopButton.setColorNotPressed(openbciBlue);
|
||||
tutorialsButton.setColorNotPressed(openbciBlue);
|
||||
|
||||
controlPanelCollapser.textColorNotActive = color(255);
|
||||
fpsButton.textColorNotActive = color(255);
|
||||
highRezButton.textColorNotActive = color(255);
|
||||
issuesButton.textColorNotActive = color(255);
|
||||
shopButton.textColorNotActive = color(255);
|
||||
tutorialsButton.textColorNotActive = color(255);
|
||||
@@ -207,6 +241,8 @@ class TopNav {
|
||||
}
|
||||
|
||||
controlPanelCollapser.draw();
|
||||
fpsButton.draw();
|
||||
// highRezButton.draw();
|
||||
tutorialsButton.draw();
|
||||
issuesButton.draw();
|
||||
shopButton.draw();
|
||||
@@ -273,6 +309,15 @@ class TopNav {
|
||||
controlPanelCollapser.setIsActive(true);
|
||||
}
|
||||
|
||||
if(fpsButton.isMouseHere()){
|
||||
fpsButton.setIsActive(true);
|
||||
}
|
||||
|
||||
// Conor's attempt at adjusting the GUI to be 2x in size for High DPI screens ... attempt failed
|
||||
// if(highRezButton.isMouseHere()){
|
||||
// highRezButton.setIsActive(true);
|
||||
// }
|
||||
|
||||
if (tutorialsButton.isMouseHere()) {
|
||||
tutorialsButton.setIsActive(true);
|
||||
//toggle help/tutorial dropdown menu
|
||||
@@ -292,6 +337,15 @@ class TopNav {
|
||||
|
||||
void mouseReleased(){
|
||||
|
||||
if (fpsButton.isMouseHere() && fpsButton.isActive()) {
|
||||
toggleFrameRate();
|
||||
}
|
||||
|
||||
// Conor's attempt at adjusting the GUI to be 2x in size for High DPI screens ... attempt failed
|
||||
// if (highRezButton.isMouseHere() && highRezButton.isActive()) {
|
||||
// toggleHighDPI();
|
||||
// }
|
||||
|
||||
if (tutorialsButton.isMouseHere() && tutorialsButton.isActive()) {
|
||||
tutorialSelector.toggleVisibility();
|
||||
tutorialsButton.setIsActive(true);
|
||||
@@ -325,6 +379,8 @@ class TopNav {
|
||||
layoutButton.setIsActive(false);
|
||||
}
|
||||
|
||||
fpsButton.setIsActive(false);
|
||||
highRezButton.setIsActive(false);
|
||||
tutorialsButton.setIsActive(false);
|
||||
issuesButton.setIsActive(false);
|
||||
shopButton.setIsActive(false);
|
||||
@@ -693,14 +749,14 @@ class TutorialSelector{
|
||||
int buttonNumber = 0;
|
||||
Button tempTutorialButton = new Button(x + margin, y + margin*(buttonNumber+1) + b_h*(buttonNumber), b_w, b_h, "Getting Started");
|
||||
tempTutorialButton.setFont(p5, 12);
|
||||
tempTutorialButton.setURL("http://docs.openbci.com/");
|
||||
tempTutorialButton.setURL("http://docs.openbci.com/Tutorials/01-Cyton_Getting%20Started_Guide");
|
||||
tutorialOptions.add(tempTutorialButton);
|
||||
|
||||
buttonNumber = 1;
|
||||
h = margin*(buttonNumber+2) + b_h*(buttonNumber+1);
|
||||
tempTutorialButton = new Button(x + margin, y + margin*(buttonNumber+1) + b_h*(buttonNumber), b_w, b_h, "Testing Impedance");
|
||||
tempTutorialButton.setFont(p5, 12);
|
||||
tempTutorialButton.setURL("http://docs.openbci.com/hardware/01-OpenBCI_Hardware");
|
||||
tempTutorialButton.setURL("http://docs.openbci.com/Tutorials/01-Cyton_Getting%20Started_Guide#cyton-getting-started-guide-v-connect-yourself-to-openbci-4-launch-the-gui-and-adjust-your-channel-settings");
|
||||
tutorialOptions.add(tempTutorialButton);
|
||||
|
||||
buttonNumber = 2;
|
||||
@@ -712,9 +768,9 @@ class TutorialSelector{
|
||||
|
||||
buttonNumber = 3;
|
||||
h = margin*(buttonNumber+2) + b_h*(buttonNumber+1);
|
||||
tempTutorialButton = new Button(x + margin, y + margin*(buttonNumber+1) + b_h*(buttonNumber), b_w, b_h, "Building Widgets");
|
||||
tempTutorialButton = new Button(x + margin, y + margin*(buttonNumber+1) + b_h*(buttonNumber), b_w, b_h, "Building Custom Widgets");
|
||||
tempTutorialButton.setFont(p5, 12);
|
||||
tempTutorialButton.setURL("http://docs.openbci.com/software/01-OpenBCI_SDK");
|
||||
tempTutorialButton.setURL("http://docs.openbci.com/Tutorials/15-Custom_Widgets");
|
||||
tutorialOptions.add(tempTutorialButton);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,544 @@
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
//
|
||||
// W_AnalogRead is used to visiualze analog voltage values
|
||||
//
|
||||
// Created: AJ Keller
|
||||
//
|
||||
//
|
||||
///////////////////////////////////////////////////,
|
||||
|
||||
class W_AnalogRead extends Widget {
|
||||
|
||||
//to see all core variables/methods of the Widget class, refer to Widget.pde
|
||||
//put your custom variables here...
|
||||
|
||||
int numAnalogReadBars;
|
||||
float xF, yF, wF, hF;
|
||||
float ts_padding;
|
||||
float ts_x, ts_y, ts_h, ts_w; //values for actual time series chart (rectangle encompassing all analogReadBars)
|
||||
float plotBottomWell;
|
||||
float playbackWidgetHeight;
|
||||
int analogReadBarHeight;
|
||||
|
||||
AnalogReadBar[] analogReadBars;
|
||||
|
||||
int[] xLimOptions = {1, 3, 5, 7}; // number of seconds (x axis of graph)
|
||||
int[] yLimOptions = {0, 50, 100, 200, 400, 1000, 10000}; // 0 = Autoscale ... everything else is uV
|
||||
|
||||
boolean allowSpillover = false;
|
||||
|
||||
TextBox[] chanValuesMontage;
|
||||
boolean showMontageValues;
|
||||
|
||||
private boolean visible = true;
|
||||
private boolean updating = true;
|
||||
|
||||
int startingVertScaleIndex = 5;
|
||||
int startingHoriztonalScaleIndex = 2;
|
||||
|
||||
private boolean hasScrollbar = false;
|
||||
|
||||
Button analogModeButton;
|
||||
|
||||
W_AnalogRead(PApplet _parent){
|
||||
super(_parent); //calls the parent CONSTRUCTOR method of Widget (DON'T REMOVE)
|
||||
|
||||
//This is the protocol for setting up dropdowns.
|
||||
//Note that these 3 dropdowns correspond to the 3 global functions below
|
||||
//You just need to make sure the "id" (the 1st String) has the same name as the corresponding function
|
||||
|
||||
addDropdown("VertScale_AR", "Vert Scale", Arrays.asList("Auto", "50", "100", "200", "400", "1000", "10000"), startingVertScaleIndex);
|
||||
addDropdown("Duration_AR", "Window", Arrays.asList("1 sec", "3 sec", "5 sec", "7 sec"), startingHoriztonalScaleIndex);
|
||||
// addDropdown("Spillover", "Spillover", Arrays.asList("False", "True"), 0);
|
||||
|
||||
//set number of anaolg reads
|
||||
if (cyton.isWifi()) {
|
||||
numAnalogReadBars = 2;
|
||||
} else {
|
||||
numAnalogReadBars = 3;
|
||||
}
|
||||
|
||||
xF = float(x); //float(int( ... is a shortcut for rounding the float down... so that it doesn't creep into the 1px margin
|
||||
yF = float(y);
|
||||
wF = float(w);
|
||||
hF = float(h);
|
||||
|
||||
plotBottomWell = 45.0; //this appears to be an arbitrary vertical space adds GPlot leaves at bottom, I derived it through trial and error
|
||||
ts_padding = 10.0;
|
||||
ts_x = xF + ts_padding;
|
||||
ts_y = yF + (ts_padding);
|
||||
ts_w = wF - ts_padding*2;
|
||||
ts_h = hF - playbackWidgetHeight - plotBottomWell - (ts_padding*2);
|
||||
analogReadBarHeight = int(ts_h/numAnalogReadBars);
|
||||
|
||||
analogReadBars = new AnalogReadBar[numAnalogReadBars];
|
||||
|
||||
//create our channel bars and populate our analogReadBars array!
|
||||
for(int i = 0; i < numAnalogReadBars; i++){
|
||||
println("init analog read bar " + i);
|
||||
int analogReadBarY = int(ts_y) + i*(analogReadBarHeight); //iterate through bar locations
|
||||
AnalogReadBar tempBar = new AnalogReadBar(_parent, i+5, int(ts_x), analogReadBarY, int(ts_w), analogReadBarHeight); //int _channelNumber, int _x, int _y, int _w, int _h
|
||||
analogReadBars[i] = tempBar;
|
||||
analogReadBars[i].adjustVertScale(yLimOptions[startingVertScaleIndex]);
|
||||
analogReadBars[i].adjustTimeAxis(xLimOptions[startingHoriztonalScaleIndex]);
|
||||
}
|
||||
|
||||
analogModeButton = new Button((int)(x + 3), (int)(y + 3 - navHeight), 120, navHeight - 6, "Turn Analog Read On", 12);
|
||||
analogModeButton.setCornerRoundess((int)(navHeight-6));
|
||||
analogModeButton.setFont(p6,10);
|
||||
// analogModeButton.setStrokeColor((int)(color(150)));
|
||||
// analogModeButton.setColorNotPressed(openbciBlue);
|
||||
analogModeButton.setColorNotPressed(color(57,128,204));
|
||||
analogModeButton.textColorNotActive = color(255);
|
||||
// analogModeButton.setStrokeColor((int)(color(138, 182, 229, 100)));
|
||||
analogModeButton.hasStroke(false);
|
||||
// analogModeButton.setColorNotPressed((int)(color(138, 182, 229)));
|
||||
if (cyton.isWifi()) {
|
||||
analogModeButton.setHelpText("Click this button to activate/deactivate the analog read of your Cyton board from A5(D11) and A6(D12)");
|
||||
} else {
|
||||
analogModeButton.setHelpText("Click this button to activate/deactivate the analog read of your Cyton board from A5(D11), A6(D12) and A7(D13)");
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isVisible() {
|
||||
return visible;
|
||||
}
|
||||
public boolean isUpdating() {
|
||||
return updating;
|
||||
}
|
||||
|
||||
public void setVisible(boolean _visible) {
|
||||
visible = _visible;
|
||||
}
|
||||
public void setUpdating(boolean _updating) {
|
||||
updating = _updating;
|
||||
}
|
||||
|
||||
void update(){
|
||||
if(visible && updating){
|
||||
super.update(); //calls the parent update() method of Widget (DON'T REMOVE)
|
||||
|
||||
//put your code here...
|
||||
//update channel bars ... this means feeding new EEG data into plots
|
||||
for(int i = 0; i < numAnalogReadBars; i++){
|
||||
analogReadBars[i].update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void draw(){
|
||||
if(visible){
|
||||
super.draw(); //calls the parent draw() method of Widget (DON'T REMOVE)
|
||||
|
||||
//put your code here... //remember to refer to x,y,w,h which are the positioning variables of the Widget class
|
||||
pushStyle();
|
||||
//draw channel bars
|
||||
analogModeButton.draw();
|
||||
if (cyton.getBoardMode() != BOARD_MODE_ANALOG) {
|
||||
analogModeButton.setString("Turn Analog Read On");
|
||||
} else {
|
||||
analogModeButton.setString("Turn Analog Read Off");
|
||||
for(int i = 0; i < numAnalogReadBars; i++){
|
||||
analogReadBars[i].draw();
|
||||
}
|
||||
}
|
||||
popStyle();
|
||||
}
|
||||
}
|
||||
|
||||
void screenResized(){
|
||||
super.screenResized(); //calls the parent screenResized() method of Widget (DON'T REMOVE)
|
||||
|
||||
//put your code here...
|
||||
xF = float(x); //float(int( ... is a shortcut for rounding the float down... so that it doesn't creep into the 1px margin
|
||||
yF = float(y);
|
||||
wF = float(w);
|
||||
hF = float(h);
|
||||
|
||||
ts_x = xF + ts_padding;
|
||||
ts_y = yF + (ts_padding);
|
||||
ts_w = wF - ts_padding*2;
|
||||
ts_h = hF - playbackWidgetHeight - plotBottomWell - (ts_padding*2);
|
||||
analogReadBarHeight = int(ts_h/numAnalogReadBars);
|
||||
|
||||
for(int i = 0; i < numAnalogReadBars; i++){
|
||||
int analogReadBarY = int(ts_y) + i*(analogReadBarHeight); //iterate through bar locations
|
||||
analogReadBars[i].screenResized(int(ts_x), analogReadBarY, int(ts_w), analogReadBarHeight); //bar x, bar y, bar w, bar h
|
||||
}
|
||||
|
||||
analogModeButton.setPos((int)(x + 3), (int)(y + 3 - navHeight));
|
||||
}
|
||||
|
||||
void mousePressed(){
|
||||
super.mousePressed(); //calls the parent mousePressed() method of Widget (DON'T REMOVE)
|
||||
|
||||
if (analogModeButton.isMouseHere()) {
|
||||
analogModeButton.setIsActive(true);
|
||||
}
|
||||
}
|
||||
|
||||
void mouseReleased(){
|
||||
super.mouseReleased(); //calls the parent mouseReleased() method of Widget (DON'T REMOVE)
|
||||
|
||||
//put your code here...
|
||||
if(analogModeButton.isActive && analogModeButton.isMouseHere()){
|
||||
// println("analogModeButton...");
|
||||
if(cyton.isPortOpen()) {
|
||||
if (cyton.getBoardMode() != BOARD_MODE_ANALOG) {
|
||||
cyton.setBoardMode(BOARD_MODE_ANALOG);
|
||||
if (cyton.isWifi()) {
|
||||
output("Starting to read analog inputs on pin marked A5 (D11) and A6 (D12)");
|
||||
} else {
|
||||
output("Starting to read analog inputs on pin marked A5 (D11), A6 (D12) and A7 (D13)");
|
||||
}
|
||||
} else {
|
||||
cyton.setBoardMode(BOARD_MODE_DEFAULT);
|
||||
output("Starting to read accelerometer");
|
||||
}
|
||||
}
|
||||
}
|
||||
analogModeButton.setIsActive(false);
|
||||
}
|
||||
};
|
||||
|
||||
//These functions need to be global! These functions are activated when an item from the corresponding dropdown is selected
|
||||
void VertScale_AR(int n) {
|
||||
if (n==0) { //autoscale
|
||||
for(int i = 0; i < w_analogRead.numAnalogReadBars; i++){
|
||||
w_analogRead.analogReadBars[i].adjustVertScale(0);
|
||||
}
|
||||
} else if(n==1) { //50uV
|
||||
for(int i = 0; i < w_analogRead.numAnalogReadBars; i++){
|
||||
w_analogRead.analogReadBars[i].adjustVertScale(50);
|
||||
}
|
||||
} else if(n==2) { //100uV
|
||||
for(int i = 0; i < w_analogRead.numAnalogReadBars; i++){
|
||||
w_analogRead.analogReadBars[i].adjustVertScale(100);
|
||||
}
|
||||
} else if(n==3) { //200uV
|
||||
for(int i = 0; i < w_analogRead.numAnalogReadBars; i++){
|
||||
w_analogRead.analogReadBars[i].adjustVertScale(200);
|
||||
}
|
||||
} else if(n==4) { //400uV
|
||||
for(int i = 0; i < w_analogRead.numAnalogReadBars; i++){
|
||||
w_analogRead.analogReadBars[i].adjustVertScale(400);
|
||||
}
|
||||
} else if(n==5) { //1000uV
|
||||
for(int i = 0; i < w_analogRead.numAnalogReadBars; i++){
|
||||
w_analogRead.analogReadBars[i].adjustVertScale(1000);
|
||||
}
|
||||
} else if(n==6) { //10000uV
|
||||
for(int i = 0; i < w_analogRead.numAnalogReadBars; i++){
|
||||
w_analogRead.analogReadBars[i].adjustVertScale(10000);
|
||||
}
|
||||
}
|
||||
closeAllDropdowns();
|
||||
}
|
||||
|
||||
//triggered when there is an event in the LogLin Dropdown
|
||||
void Duration_AR(int n) {
|
||||
// println("adjust duration to: ");
|
||||
if(n==0){ //set time series x axis to 1 secconds
|
||||
for(int i = 0; i < w_analogRead.numAnalogReadBars; i++){
|
||||
w_analogRead.analogReadBars[i].adjustTimeAxis(1);
|
||||
}
|
||||
} else if(n==1){ //set time series x axis to 3 secconds
|
||||
for(int i = 0; i < w_analogRead.numAnalogReadBars; i++){
|
||||
w_analogRead.analogReadBars[i].adjustTimeAxis(3);
|
||||
}
|
||||
} else if(n==2){ //set to 5 seconds
|
||||
for(int i = 0; i < w_analogRead.numAnalogReadBars; i++){
|
||||
w_analogRead.analogReadBars[i].adjustTimeAxis(5);
|
||||
}
|
||||
} else if(n==3){ //set to 7 seconds (max due to arry size ... 2000 total packets saved)
|
||||
for(int i = 0; i < w_analogRead.numAnalogReadBars; i++){
|
||||
w_analogRead.analogReadBars[i].adjustTimeAxis(7);
|
||||
}
|
||||
}
|
||||
closeAllDropdowns();
|
||||
}
|
||||
|
||||
//========================================================================================================================
|
||||
// Analog Voltage BAR CLASS -- Implemented by Analog Read Widget Class
|
||||
//========================================================================================================================
|
||||
//this class contains the plot and buttons for a single channel of the Time Series widget
|
||||
//one of these will be created for each channel (4, 8, or 16)
|
||||
class AnalogReadBar{
|
||||
|
||||
int analogInputPin;
|
||||
int auxValuesPosition;
|
||||
String analogInputString;
|
||||
int x, y, w, h;
|
||||
boolean isOn; //true means data is streaming and channel is active on hardware ... this will send message to OpenBCI Hardware
|
||||
|
||||
GPlot plot; //the actual grafica-based GPlot that will be rendering the Time Series trace
|
||||
GPointsArray analogReadPoints;
|
||||
int nPoints;
|
||||
int numSeconds;
|
||||
float timeBetweenPoints;
|
||||
|
||||
color channelColor; //color of plot trace
|
||||
|
||||
boolean isAutoscale; //when isAutoscale equals true, the y-axis of each channelBar will automatically update to scale to the largest visible amplitude
|
||||
int autoScaleYLim = 0;
|
||||
|
||||
TextBox analogValue;
|
||||
TextBox analogPin;
|
||||
TextBox digitalPin;
|
||||
|
||||
boolean drawAnalogValue;
|
||||
int lastProcessedDataPacketInd = 0;
|
||||
|
||||
int[] analogReadData;
|
||||
|
||||
AnalogReadBar(PApplet _parent, int _analogInputPin, int _x, int _y, int _w, int _h){ // channel number, x/y location, height, width
|
||||
|
||||
analogInputPin = _analogInputPin;
|
||||
int digitalPinNum = 0;
|
||||
if (analogInputPin == 7) {
|
||||
auxValuesPosition = 2;
|
||||
digitalPinNum = 13;
|
||||
} else if (analogInputPin == 6) {
|
||||
auxValuesPosition = 1;
|
||||
digitalPinNum = 12;
|
||||
} else {
|
||||
analogInputPin = 5;
|
||||
auxValuesPosition = 0;
|
||||
digitalPinNum = 11;
|
||||
}
|
||||
|
||||
analogInputString = str(analogInputPin);
|
||||
isOn = true;
|
||||
|
||||
x = _x;
|
||||
y = _y;
|
||||
w = _w;
|
||||
h = _h;
|
||||
|
||||
numSeconds = 5;
|
||||
plot = new GPlot(_parent);
|
||||
plot.setPos(x + 36 + 4, y);
|
||||
plot.setDim(w - 36 - 4, h);
|
||||
plot.setMar(0f, 0f, 0f, 0f);
|
||||
plot.setLineColor((int)channelColors[(auxValuesPosition)%8]);
|
||||
plot.setXLim(-3.2,-2.9);
|
||||
plot.setYLim(-200,200);
|
||||
plot.setPointSize(2);
|
||||
plot.setPointColor(0);
|
||||
if (cyton.isWifi()) {
|
||||
if(auxValuesPosition == 1){
|
||||
plot.getXAxis().setAxisLabelText("Time (s)");
|
||||
}
|
||||
} else {
|
||||
if(auxValuesPosition == 2){
|
||||
plot.getXAxis().setAxisLabelText("Time (s)");
|
||||
}
|
||||
}
|
||||
|
||||
nPoints = nPointsBasedOnDataSource();
|
||||
|
||||
analogReadData = new int[nPoints];
|
||||
|
||||
analogReadPoints = new GPointsArray(nPoints);
|
||||
timeBetweenPoints = (float)numSeconds / (float)nPoints;
|
||||
|
||||
for (int i = 0; i < nPoints; i++) {
|
||||
float time = -(float)numSeconds + (float)i*timeBetweenPoints;
|
||||
float analog_value = 0.0; //0.0 for all points to start
|
||||
GPoint tempPoint = new GPoint(time, analog_value);
|
||||
analogReadPoints.set(i, tempPoint);
|
||||
}
|
||||
|
||||
plot.setPoints(analogReadPoints); //set the plot with 0.0 for all analogReadPoints to start
|
||||
|
||||
analogValue = new TextBox("t", x + 36 + 4 + (w - 36 - 4) - 2, y + h);
|
||||
analogValue.textColor = color(bgColor);
|
||||
analogValue.alignH = RIGHT;
|
||||
// analogValue.alignV = TOP;
|
||||
analogValue.drawBackground = true;
|
||||
analogValue.backgroundColor = color(255,255,255,125);
|
||||
|
||||
analogPin = new TextBox("A" + analogInputString, x+3, y + h);
|
||||
analogPin.textColor = color(bgColor);
|
||||
analogPin.alignH = CENTER;
|
||||
digitalPin = new TextBox("(D" + digitalPinNum + ")", x+3, y + h + 12);
|
||||
digitalPin.textColor = color(bgColor);
|
||||
digitalPin.alignH = CENTER;
|
||||
|
||||
drawAnalogValue = true;
|
||||
|
||||
}
|
||||
|
||||
void update(){
|
||||
|
||||
//update the voltage value text string
|
||||
String fmt; float val;
|
||||
|
||||
//update the voltage values
|
||||
val = hub.validAccelValues[auxValuesPosition];
|
||||
analogValue.string = String.format(getFmt(val),val);
|
||||
|
||||
// update data in plot
|
||||
updatePlotPoints();
|
||||
if(isAutoscale){
|
||||
autoScale();
|
||||
}
|
||||
}
|
||||
|
||||
private String getFmt(float val) {
|
||||
String fmt;
|
||||
if (val > 100.0f) {
|
||||
fmt = "%.0f";
|
||||
} else if (val > 10.0f) {
|
||||
fmt = "%.1f";
|
||||
} else {
|
||||
fmt = "%.2f";
|
||||
}
|
||||
return fmt;
|
||||
}
|
||||
|
||||
void updatePlotPoints(){
|
||||
// update data in plot
|
||||
int numSamplesToProcess = curDataPacketInd - lastProcessedDataPacketInd;
|
||||
if (numSamplesToProcess < 0) {
|
||||
numSamplesToProcess += dataPacketBuff.length;
|
||||
}
|
||||
|
||||
// Shift internal ring buffer numSamplesToProcess
|
||||
if (numSamplesToProcess > 0) {
|
||||
for(int i = 0; i < analogReadData.length - numSamplesToProcess; i++){
|
||||
analogReadData[i] = analogReadData[i + numSamplesToProcess];
|
||||
}
|
||||
}
|
||||
|
||||
// for each new sample
|
||||
int samplesProcessed = 0;
|
||||
while (samplesProcessed < numSamplesToProcess) {
|
||||
lastProcessedDataPacketInd++;
|
||||
|
||||
// Watch for wrap around
|
||||
if (lastProcessedDataPacketInd > dataPacketBuff.length - 1) {
|
||||
lastProcessedDataPacketInd = 0;
|
||||
}
|
||||
|
||||
int voltage = dataPacketBuff[lastProcessedDataPacketInd].auxValues[auxValuesPosition];
|
||||
|
||||
analogReadData[analogReadData.length - numSamplesToProcess + samplesProcessed] = voltage; //<>//
|
||||
|
||||
samplesProcessed++;
|
||||
}
|
||||
|
||||
if (numSamplesToProcess > 0) {
|
||||
for (int i = 0; i < nPoints; i++) {
|
||||
float timey = -(float)numSeconds + (float)i*timeBetweenPoints;
|
||||
float voltage = analogReadData[i];
|
||||
|
||||
GPoint tempPoint = new GPoint(timey, voltage);
|
||||
analogReadPoints.set(i, tempPoint);
|
||||
|
||||
}
|
||||
plot.setPoints(analogReadPoints); //reset the plot with updated analogReadPoints
|
||||
}
|
||||
}
|
||||
|
||||
void draw(){
|
||||
pushStyle();
|
||||
|
||||
//draw plot
|
||||
stroke(31,69,110, 50);
|
||||
fill(color(125,30,12,30));
|
||||
|
||||
rect(x + 36 + 4, y, w - 36 - 4, h);
|
||||
|
||||
plot.beginDraw();
|
||||
plot.drawBox(); // we won't draw this eventually ...
|
||||
plot.drawGridLines(0);
|
||||
plot.drawLines();
|
||||
// plot.drawPoints();
|
||||
// plot.drawYAxis();
|
||||
if (cyton.isWifi()) {
|
||||
if(auxValuesPosition == 1){ //only draw the x axis label on the bottom channel bar
|
||||
plot.drawXAxis();
|
||||
plot.getXAxis().draw();
|
||||
}
|
||||
} else {
|
||||
if(auxValuesPosition == 2){ //only draw the x axis label on the bottom channel bar
|
||||
plot.drawXAxis();
|
||||
plot.getXAxis().draw();
|
||||
}
|
||||
}
|
||||
|
||||
plot.endDraw();
|
||||
|
||||
if(drawAnalogValue){
|
||||
analogValue.draw();
|
||||
analogPin.draw();
|
||||
digitalPin.draw();
|
||||
}
|
||||
|
||||
popStyle();
|
||||
}
|
||||
|
||||
int nPointsBasedOnDataSource(){
|
||||
return numSeconds * (int)getSampleRateSafe();
|
||||
}
|
||||
|
||||
void adjustTimeAxis(int _newTimeSize){
|
||||
numSeconds = _newTimeSize;
|
||||
plot.setXLim(-_newTimeSize,0);
|
||||
|
||||
nPoints = nPointsBasedOnDataSource();
|
||||
|
||||
analogReadPoints = new GPointsArray(nPoints);
|
||||
if(_newTimeSize > 1){
|
||||
plot.getXAxis().setNTicks(_newTimeSize); //sets the number of axis divisions...
|
||||
}else{
|
||||
plot.getXAxis().setNTicks(10);
|
||||
}
|
||||
if (w_analogRead != null) {
|
||||
if(w_analogRead.isUpdating()){
|
||||
updatePlotPoints();
|
||||
}
|
||||
}
|
||||
// println("New X axis = " + _newTimeSize);
|
||||
}
|
||||
|
||||
void adjustVertScale(int _vertScaleValue){
|
||||
if(_vertScaleValue == 0){
|
||||
isAutoscale = true;
|
||||
} else {
|
||||
isAutoscale = false;
|
||||
plot.setYLim(-_vertScaleValue, _vertScaleValue);
|
||||
}
|
||||
}
|
||||
|
||||
void autoScale(){
|
||||
autoScaleYLim = 0;
|
||||
for(int i = 0; i < nPoints; i++){
|
||||
if(int(abs(analogReadPoints.getY(i))) > autoScaleYLim){
|
||||
autoScaleYLim = int(abs(analogReadPoints.getY(i)));
|
||||
}
|
||||
}
|
||||
plot.setYLim(-autoScaleYLim, autoScaleYLim);
|
||||
}
|
||||
|
||||
void screenResized(int _x, int _y, int _w, int _h){
|
||||
x = _x;
|
||||
y = _y;
|
||||
w = _w;
|
||||
h = _h;
|
||||
|
||||
//reposition & resize the plot
|
||||
plot.setPos(x + 36 + 4, y);
|
||||
plot.setDim(w - 36 - 4, h);
|
||||
|
||||
analogValue.x = x + 36 + 4 + (w - 36 - 4) - 2;
|
||||
analogValue.y = y + h;
|
||||
|
||||
analogPin.x = x + 14;
|
||||
analogPin.y = y + int(h/2.0);
|
||||
digitalPin.x = analogPin.x;
|
||||
digitalPin.y = analogPin.y + 12;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,137 @@
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
//
|
||||
// W_BandPowers.pde
|
||||
//
|
||||
// This is a band power visualization widget!
|
||||
// (Couldn't think up more)
|
||||
// This is for visualizing the power of each brainwave band: delta, theta, alpha, beta, gamma
|
||||
// Averaged over all channels
|
||||
//
|
||||
// Created by: Wangshu Sun, May 2017
|
||||
//
|
||||
///////////////////////////////////////////////////,
|
||||
|
||||
class W_BandPower extends Widget {
|
||||
|
||||
GPlot plot3;
|
||||
String bands[] = {"DELTA", "THETA", "ALPHA", "BETA", "GAMMA"};
|
||||
|
||||
W_BandPower(PApplet _parent){
|
||||
super(_parent); //calls the parent CONSTRUCTOR method of Widget (DON'T REMOVE)
|
||||
|
||||
//This is the protocol for setting up dropdowns.
|
||||
//Note that these 3 dropdowns correspond to the 3 global functions below
|
||||
//You just need to make sure the "id" (the 1st String) has the same name as the corresponding function
|
||||
// addDropdown("Dropdown1", "Drop 1", Arrays.asList("A", "B"), 0);
|
||||
// addDropdown("Dropdown2", "Drop 2", Arrays.asList("C", "D", "E"), 1);
|
||||
// addDropdown("Dropdown3", "Drop 3", Arrays.asList("F", "G", "H", "I"), 3);
|
||||
|
||||
// Setup for the third plot
|
||||
plot3 = new GPlot(_parent, x, y-navHeight, w, h+navHeight);
|
||||
plot3.setPos(x, y);
|
||||
plot3.setDim(w, h);
|
||||
plot3.setLogScale("y");
|
||||
plot3.setYLim(0.1, 100);
|
||||
plot3.setXLim(0, 5);
|
||||
plot3.getYAxis().setNTicks(9);
|
||||
plot3.getTitle().setTextAlignment(LEFT);
|
||||
plot3.getTitle().setRelativePos(0);
|
||||
plot3.getYAxis().getAxisLabel().setText("(uV)^2 / Hz per channel");
|
||||
plot3.getYAxis().getAxisLabel().setTextAlignment(RIGHT);
|
||||
plot3.getYAxis().getAxisLabel().setRelativePos(1);
|
||||
// plot3.setPoints(points3);
|
||||
plot3.startHistograms(GPlot.VERTICAL);
|
||||
plot3.getHistogram().setDrawLabels(true);
|
||||
//plot3.getHistogram().setRotateLabels(true);
|
||||
plot3.getHistogram().setBgColors(new color[] {
|
||||
color(0, 0, 255, 50), color(0, 0, 255, 100),
|
||||
color(0, 0, 255, 150), color(0, 0, 255, 200)
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
void update(){
|
||||
super.update(); //calls the parent update() method of Widget (DON'T REMOVE)
|
||||
|
||||
GPointsArray points3 = new GPointsArray(dataProcessing.headWidePower.length);
|
||||
points3.add(DELTA + 0.5, dataProcessing.headWidePower[DELTA], "DELTA");
|
||||
points3.add(THETA + 0.5, dataProcessing.headWidePower[THETA], "THETA");
|
||||
points3.add(ALPHA + 0.5, dataProcessing.headWidePower[ALPHA], "ALPHA");
|
||||
points3.add(BETA + 0.5, dataProcessing.headWidePower[BETA], "BETA");
|
||||
points3.add(GAMMA + 0.5, dataProcessing.headWidePower[GAMMA], "GAMMA");
|
||||
|
||||
plot3.setPoints(points3);
|
||||
plot3.getTitle().setText("Band Power");
|
||||
|
||||
}
|
||||
|
||||
void draw(){
|
||||
super.draw(); //calls the parent draw() method of Widget (DON'T REMOVE)
|
||||
|
||||
//put your code here... //remember to refer to x,y,w,h which are the positioning variables of the Widget class
|
||||
// Draw the third plot
|
||||
plot3.beginDraw();
|
||||
plot3.drawBackground();
|
||||
plot3.drawBox();
|
||||
plot3.drawYAxis();
|
||||
plot3.drawTitle();
|
||||
plot3.drawHistograms();
|
||||
plot3.endDraw();
|
||||
|
||||
}
|
||||
|
||||
void screenResized(){
|
||||
super.screenResized(); //calls the parent screenResized() method of Widget (DON'T REMOVE)
|
||||
|
||||
//put your code here...
|
||||
plot3.setPos(x, y-navHeight);//update position
|
||||
plot3.setOuterDim(w, h+navHeight);//update dimensions
|
||||
|
||||
|
||||
}
|
||||
|
||||
void mousePressed(){
|
||||
super.mousePressed(); //calls the parent mousePressed() method of Widget (DON'T REMOVE)
|
||||
|
||||
//put your code here...
|
||||
|
||||
}
|
||||
|
||||
void mouseReleased(){
|
||||
super.mouseReleased(); //calls the parent mouseReleased() method of Widget (DON'T REMOVE)
|
||||
|
||||
//put your code here...
|
||||
|
||||
}
|
||||
|
||||
//add custom functions here
|
||||
void customFunction(){
|
||||
//this is a fake function... replace it with something relevant to this widget
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// //These functions need to be global! These functions are activated when an item from the corresponding dropdown is selected
|
||||
// void Dropdown1(int n){
|
||||
// println("Item " + (n+1) + " selected from Dropdown 1");
|
||||
// if(n==0){
|
||||
// //do this
|
||||
// } else if(n==1){
|
||||
// //do this instead
|
||||
// }
|
||||
//
|
||||
// closeAllDropdowns(); // do this at the end of all widget-activated functions to ensure proper widget interactivity ... we want to make sure a click makes the menu close
|
||||
// }
|
||||
//
|
||||
// void Dropdown2(int n){
|
||||
// println("Item " + (n+1) + " selected from Dropdown 2");
|
||||
// closeAllDropdowns();
|
||||
// }
|
||||
//
|
||||
// void Dropdown3(int n){
|
||||
// println("Item " + (n+1) + " selected from Dropdown 3");
|
||||
// closeAllDropdowns();
|
||||
// }
|
||||
@@ -0,0 +1,334 @@
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
//
|
||||
// W_DigitalRead is used to visiualze digital input values
|
||||
//
|
||||
// Created: AJ Keller
|
||||
//
|
||||
//
|
||||
///////////////////////////////////////////////////,
|
||||
|
||||
class W_DigitalRead extends Widget {
|
||||
|
||||
//to see all core variables/methods of the Widget class, refer to Widget.pde
|
||||
//put your custom variables here...
|
||||
|
||||
int numDigitalReadDots;
|
||||
float xF, yF, wF, hF;
|
||||
int dot_padding;
|
||||
float dot_x, dot_y, dot_h, dot_w; //values for actual time series chart (rectangle encompassing all digitalReadDots)
|
||||
float plotBottomWell;
|
||||
float playbackWidgetHeight;
|
||||
int digitalReadDotHeight;
|
||||
|
||||
DigitalReadDot[] digitalReadDots;
|
||||
|
||||
TextBox[] chanValuesMontage;
|
||||
boolean showMontageValues;
|
||||
|
||||
private boolean visible = true;
|
||||
private boolean updating = true;
|
||||
|
||||
Button digitalModeButton;
|
||||
|
||||
W_DigitalRead(PApplet _parent){
|
||||
super(_parent); //calls the parent CONSTRUCTOR method of Widget (DON'T REMOVE)
|
||||
|
||||
//This is the protocol for setting up dropdowns.
|
||||
//Note that these 3 dropdowns correspond to the 3 global functions below
|
||||
//You just need to make sure the "id" (the 1st String) has the same name as the corresponding function
|
||||
|
||||
//set number of digital reads
|
||||
if (cyton.isWifi()) {
|
||||
numDigitalReadDots = 3;
|
||||
} else {
|
||||
numDigitalReadDots = 5;
|
||||
}
|
||||
|
||||
xF = float(x); //float(int( ... is a shortcut for rounding the float down... so that it doesn't creep into the 1px margin
|
||||
yF = float(y);
|
||||
wF = float(w);
|
||||
hF = float(h);
|
||||
|
||||
dot_padding = 10;
|
||||
dot_x = xF + dot_padding;
|
||||
dot_y = yF + (dot_padding);
|
||||
dot_w = wF - dot_padding*2;
|
||||
dot_h = hF - playbackWidgetHeight - plotBottomWell - (dot_padding*2);
|
||||
digitalReadDotHeight = int(dot_h/numDigitalReadDots);
|
||||
|
||||
digitalReadDots = new DigitalReadDot[numDigitalReadDots];
|
||||
|
||||
//create our channel bars and populate our digitalReadDots array!
|
||||
for(int i = 0; i < numDigitalReadDots; i++){
|
||||
int digitalReadDotY = int(dot_y) + i*(digitalReadDotHeight); //iterate through bar locations
|
||||
int digitalReadDotX = int(dot_x) + i*(digitalReadDotHeight); //iterate through bar locations
|
||||
int digitalPin = 0;
|
||||
if (i == 0) {
|
||||
digitalPin = 11;
|
||||
} else if (i == 1) {
|
||||
digitalPin = 12;
|
||||
} else if (i == 2) {
|
||||
if (cyton.isWifi()) {
|
||||
digitalPin = 17;
|
||||
} else {
|
||||
digitalPin = 13;
|
||||
}
|
||||
} else if (i == 3) {
|
||||
digitalPin = 17;
|
||||
} else {
|
||||
digitalPin = 18;
|
||||
}
|
||||
DigitalReadDot tempDot = new DigitalReadDot(_parent, digitalPin, digitalReadDotX, digitalReadDotY, int(dot_w), digitalReadDotHeight, dot_padding);
|
||||
digitalReadDots[i] = tempDot;
|
||||
}
|
||||
|
||||
digitalModeButton = new Button((int)(x + 3), (int)(y + 3 - navHeight), 120, navHeight - 6, "Turn Analog Read On", 12);
|
||||
digitalModeButton.setCornerRoundess((int)(navHeight-6));
|
||||
digitalModeButton.setFont(p6,10);
|
||||
digitalModeButton.setColorNotPressed(color(57,128,204));
|
||||
digitalModeButton.textColorNotActive = color(255);
|
||||
digitalModeButton.hasStroke(false);
|
||||
|
||||
if (cyton.isWifi()) {
|
||||
digitalModeButton.setHelpText("Click this button to activate/deactivate digital reading on the Cyton D11, D12, and D17");
|
||||
} else {
|
||||
digitalModeButton.setHelpText("Click this button to activate/deactivate digital reading on the Cyton D11, D12, D13, D17 and D18");
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isVisible() {
|
||||
return visible;
|
||||
}
|
||||
public boolean isUpdating() {
|
||||
return updating;
|
||||
}
|
||||
|
||||
public void setVisible(boolean _visible) {
|
||||
visible = _visible;
|
||||
}
|
||||
public void setUpdating(boolean _updating) {
|
||||
updating = _updating;
|
||||
}
|
||||
|
||||
void update(){
|
||||
if(visible && updating){
|
||||
super.update(); //calls the parent update() method of Widget (DON'T REMOVE)
|
||||
|
||||
//put your code here...
|
||||
//update channel bars ... this means feeding new EEG data into plots
|
||||
for(int i = 0; i < numDigitalReadDots; i++){
|
||||
digitalReadDots[i].update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void draw(){
|
||||
if(visible){
|
||||
super.draw(); //calls the parent draw() method of Widget (DON'T REMOVE)
|
||||
|
||||
//put your code here... //remember to refer to x,y,w,h which are the positioning variables of the Widget class
|
||||
pushStyle();
|
||||
//draw channel bars
|
||||
digitalModeButton.draw();
|
||||
if (cyton.getBoardMode() != BOARD_MODE_DIGITAL) {
|
||||
digitalModeButton.setString("Turn Digital Read On");
|
||||
} else {
|
||||
digitalModeButton.setString("Turn Digital Read Off");
|
||||
for(int i = 0; i < numDigitalReadDots; i++){
|
||||
digitalReadDots[i].draw();
|
||||
}
|
||||
}
|
||||
popStyle();
|
||||
}
|
||||
}
|
||||
|
||||
void screenResized(){
|
||||
super.screenResized(); //calls the parent screenResized() method of Widget (DON'T REMOVE)
|
||||
|
||||
//put your code here...
|
||||
xF = float(x); //float(int( ... is a shortcut for rounding the float down... so that it doesn't creep into the 1px margin
|
||||
yF = float(y);
|
||||
wF = float(w);
|
||||
hF = float(h);
|
||||
// println("w_digitalRead: screenResized: x: " + x + " y: " + y + " w: "+ w + " h: " + h + " navBarHeight: " + navBarHeight);
|
||||
|
||||
if (wF > hF) {
|
||||
digitalReadDotHeight = int(hF/(numDigitalReadDots+1));
|
||||
} else {
|
||||
digitalReadDotHeight = int(wF/(numDigitalReadDots+1));
|
||||
}
|
||||
|
||||
if (numDigitalReadDots == 3) {
|
||||
digitalReadDots[0].screenResized(x+int(wF*(1.0/3.0)), y+int(hF*(1.0/3.0)), digitalReadDotHeight, digitalReadDotHeight); //bar x, bar y, bar w, bar h
|
||||
digitalReadDots[1].screenResized(x+int(wF/2), y+int(hF/2), digitalReadDotHeight, digitalReadDotHeight); //bar x, bar y, bar w, bar h
|
||||
digitalReadDots[2].screenResized(x+int(wF*(2.0/3.0)), y+int(hF*(2.0/3.0)), digitalReadDotHeight, digitalReadDotHeight); //bar x, bar y, bar w, bar h
|
||||
} else {
|
||||
int y_pad = y + dot_padding;
|
||||
digitalReadDots[0].screenResized(x+int(wF*(1.0/8.0)), y_pad+int(hF*(1.0/8.0)), digitalReadDotHeight, digitalReadDotHeight);
|
||||
digitalReadDots[2].screenResized(x+int(wF/2), y_pad+int(hF/2), digitalReadDotHeight, digitalReadDotHeight);
|
||||
digitalReadDots[4].screenResized(x+int(wF*(7.0/8.0)), y_pad+int(hF*(7.0/8.0)), digitalReadDotHeight, digitalReadDotHeight);
|
||||
digitalReadDots[1].screenResized(digitalReadDots[0].DotX+int(wF*(3.0/16.0)), digitalReadDots[0].DotY+int(hF*(3.0/16.0)), digitalReadDotHeight, digitalReadDotHeight);
|
||||
digitalReadDots[3].screenResized(digitalReadDots[2].DotX+int(wF*(3.0/16.0)), digitalReadDots[2].DotY+int(hF*(3.0/16.0)), digitalReadDotHeight, digitalReadDotHeight);
|
||||
|
||||
}
|
||||
|
||||
digitalModeButton.setPos((int)(x + 3), (int)(y + 3 - navHeight));
|
||||
}
|
||||
|
||||
void mousePressed(){
|
||||
super.mousePressed(); //calls the parent mousePressed() method of Widget (DON'T REMOVE)
|
||||
|
||||
if (digitalModeButton.isMouseHere()) {
|
||||
digitalModeButton.setIsActive(true);
|
||||
}
|
||||
}
|
||||
|
||||
void mouseReleased(){
|
||||
super.mouseReleased(); //calls the parent mouseReleased() method of Widget (DON'T REMOVE)
|
||||
|
||||
//put your code here...
|
||||
if(digitalModeButton.isActive && digitalModeButton.isMouseHere()){
|
||||
// println("digitalModeButton...");
|
||||
if(cyton.isPortOpen()) {
|
||||
if (cyton.getBoardMode() != BOARD_MODE_DIGITAL) {
|
||||
cyton.setBoardMode(BOARD_MODE_DIGITAL);
|
||||
if (cyton.isWifi()) {
|
||||
output("Starting to read digital inputs on pin marked D11, D12 and D17");
|
||||
} else {
|
||||
output("Starting to read digital inputs on pin marked D11, D12, D13, D17 and D18");
|
||||
}
|
||||
} else {
|
||||
cyton.setBoardMode(BOARD_MODE_DEFAULT);
|
||||
output("Starting to read accelerometer");
|
||||
}
|
||||
}
|
||||
}
|
||||
digitalModeButton.setIsActive(false);
|
||||
}
|
||||
};
|
||||
|
||||
//========================================================================================================================
|
||||
// Analog Voltage BAR CLASS -- Implemented by Analog Read Widget Class
|
||||
//========================================================================================================================
|
||||
//this class contains the plot and buttons for a single channel of the Time Series widget
|
||||
//one of these will be created for each channel (4, 8, or 16)
|
||||
class DigitalReadDot{
|
||||
|
||||
int digitalInputPin;
|
||||
int digitalInputVal;
|
||||
String digitalInputString;
|
||||
int padding;
|
||||
boolean isOn; //true means data is streaming and channel is active on hardware ... this will send message to OpenBCI Hardware
|
||||
|
||||
TextBox digitalValue;
|
||||
TextBox digitalPin;
|
||||
|
||||
boolean drawDigitalValue;
|
||||
|
||||
color dotStroke = #d2d2d2;
|
||||
color dot0Fill = #f5f5f5;
|
||||
color dot1Fill = #f5f5f5;
|
||||
color val0Fill = #000000;
|
||||
color val1Fill = #ffffff;
|
||||
|
||||
int DotX;
|
||||
int DotY;
|
||||
int DotWidth;
|
||||
int DotHeight;
|
||||
float DotCorner;
|
||||
|
||||
DigitalReadDot(PApplet _parent, int _digitalInputPin, int _x, int _y, int _w, int _h, int _padding){ // channel number, x/y location, height, width
|
||||
|
||||
digitalInputPin = _digitalInputPin;
|
||||
digitalInputString = str(digitalInputPin);
|
||||
digitalInputVal = 0;
|
||||
isOn = true;
|
||||
|
||||
if (digitalInputPin == 11) {
|
||||
dot1Fill = channelColors[0];
|
||||
} else if (digitalInputPin == 12) {
|
||||
dot1Fill = channelColors[1];
|
||||
} else if (digitalInputPin == 13) {
|
||||
dot1Fill = channelColors[2];
|
||||
} else if (digitalInputPin == 17) {
|
||||
dot1Fill = channelColors[3];
|
||||
} else { // 18
|
||||
dot1Fill = channelColors[4];
|
||||
}
|
||||
|
||||
DotX = _x;
|
||||
DotY = _y;
|
||||
DotWidth = _w;
|
||||
DotHeight = _h;
|
||||
padding = _padding;
|
||||
|
||||
digitalValue = new TextBox("", DotX, DotY);
|
||||
digitalValue.textColor = color(val0Fill);
|
||||
digitalValue.alignH = CENTER;
|
||||
digitalValue.alignV = CENTER;
|
||||
|
||||
digitalPin = new TextBox("D" + digitalInputString, DotX, DotY - DotWidth);
|
||||
digitalPin.textColor = color(bgColor);
|
||||
digitalPin.alignH = CENTER;
|
||||
// digitalPin.alignV = CENTER;
|
||||
|
||||
drawDigitalValue = true;
|
||||
}
|
||||
|
||||
void update(){
|
||||
//update the voltage values
|
||||
if (digitalInputPin == 11) {
|
||||
digitalInputVal = (hub.validAccelValues[0] & 0xFF00) >> 8;
|
||||
} else if (digitalInputPin == 12) {
|
||||
digitalInputVal = hub.validAccelValues[0] & 0xFF;
|
||||
} else if (digitalInputPin == 13) {
|
||||
digitalInputVal = (hub.validAccelValues[1] & 0xFF00) >> 8;
|
||||
} else if (digitalInputPin == 17) {
|
||||
digitalInputVal = hub.validAccelValues[1] & 0xFF;
|
||||
} else { // 18
|
||||
digitalInputVal = hub.validAccelValues[2];
|
||||
}
|
||||
|
||||
digitalValue.string = String.format("%d", digitalInputVal);
|
||||
}
|
||||
|
||||
void draw(){
|
||||
pushStyle();
|
||||
|
||||
//draw plot
|
||||
|
||||
if (digitalInputVal == 1) {
|
||||
fill(dot1Fill);
|
||||
digitalValue.textColor = val1Fill;
|
||||
} else {
|
||||
fill(dot0Fill);
|
||||
digitalValue.textColor = val0Fill;
|
||||
}
|
||||
stroke(dotStroke);
|
||||
ellipse(DotX, DotY, DotWidth, DotHeight);
|
||||
|
||||
if(drawDigitalValue){
|
||||
digitalValue.draw();
|
||||
digitalPin.draw();
|
||||
}
|
||||
|
||||
popStyle();
|
||||
}
|
||||
|
||||
void screenResized(int _x, int _y, int _w, int _h){
|
||||
DotX = _x;
|
||||
DotY = _y;
|
||||
DotWidth = _w;
|
||||
DotHeight = _h;
|
||||
DotCorner = (sqrt(2)*DotWidth/2)/2;
|
||||
|
||||
// println("DigitalReadDot: " + digitalInputPin + " screenResized: DotX: " + DotX + " DotY: " + DotY + " DotWidth: "+ DotWidth + " DotHeight: " + DotHeight);
|
||||
|
||||
digitalPin.x = DotX;
|
||||
digitalPin.y = DotY - int(DotWidth/2.0);
|
||||
|
||||
digitalValue.x = DotX;
|
||||
digitalValue.y = DotY;
|
||||
}
|
||||
};
|
||||
@@ -10,8 +10,9 @@
|
||||
//
|
||||
///////////////////////////////////////////////////
|
||||
|
||||
//fft constants
|
||||
int Nfft = 256; //set resolution of the FFT. Use N=256 for normal, N=512 for MU waves
|
||||
//fft global variables
|
||||
// int Nfft; //125Hz, 200Hz, 250Hz -> 256points. 1000Hz -> 1024points. 1600Hz -> 2048 points. //prev: Use N=256 for normal, N=512 for MU waves
|
||||
// float fs_Hz; // AJ Keller removed because shall get sample rate at runtime
|
||||
FFT[] fftBuff = new FFT[nchan]; //from the minim library
|
||||
boolean isFFTFiltered = true; //yes by default ... this is used in dataProcessing.pde to determine which uV array feeds the FFT calculation
|
||||
|
||||
@@ -41,13 +42,13 @@ class W_fft extends Widget {
|
||||
(int)color(162, 82, 49)
|
||||
};
|
||||
|
||||
int[] xLimOptions = {20, 40, 60, 120};
|
||||
int[] xLimOptions = {20, 40, 60, 100, 120, 250, 500, 800};
|
||||
int[] yLimOptions = {10, 50, 100, 1000};
|
||||
|
||||
int xLim = xLimOptions[2]; //maximum value of x axis ... in this case 20 Hz, 40 Hz, 60 Hz, 120 Hz
|
||||
int xMax = xLimOptions[3];
|
||||
int FFT_indexLim = int(1.0*xMax*(Nfft/get_fs_Hz_safe())); // maxim value of FFT index
|
||||
int yLim = 100; //maximum value of y axis ... 100 uV
|
||||
int xMax = xLimOptions[xLimOptions.length-1]; //maximum possible frequency in FFT
|
||||
int FFT_indexLim = int(1.0*xMax*(getNfftSafe()/getSampleRateSafe())); // maxim value of FFT index
|
||||
int yLim = yLimOptions[2]; //maximum value of y axis ... 100 uV
|
||||
|
||||
W_fft(PApplet _parent){
|
||||
super(_parent); //calls the parent CONSTRUCTOR method of Widget (DON'T REMOVE)
|
||||
@@ -55,14 +56,14 @@ class W_fft extends Widget {
|
||||
//This is the protocol for setting up dropdowns.
|
||||
//Note that these 3 dropdowns correspond to the 3 global functions below
|
||||
//You just need to make sure the "id" (the 1st String) has the same name as the corresponding function
|
||||
addDropdown("MaxFreq", "Max Freq", Arrays.asList("20 Hz", "40 Hz", "60 Hz", "120 Hz"), 2);
|
||||
addDropdown("MaxFreq", "Max Freq", Arrays.asList("20 Hz", "40 Hz", "60 Hz", "100 Hz", "120 Hz", "250 Hz", "500 Hz", "800 Hz"), 2);
|
||||
addDropdown("VertScale", "Max uV", Arrays.asList("10 uV", "50 uV", "100 uV", "1000 uV"), 2);
|
||||
addDropdown("LogLin", "Log/Lin", Arrays.asList("Log", "Linear"), 0);
|
||||
addDropdown("Smoothing", "Smooth", Arrays.asList("0.0", "0.5", "0.75", "0.9", "0.95", "0.98"), smoothFac_ind); //smoothFac_ind is a global variable at the top of W_headPlot.pde
|
||||
addDropdown("UnfiltFilt", "Filters?", Arrays.asList("Filtered", "Unfilt."), 0);
|
||||
|
||||
fft_points = new GPointsArray[nchan];
|
||||
println(fft_points.length);
|
||||
// println("fft_points.length: " + fft_points.length);
|
||||
initializeFFTPlot(_parent);
|
||||
|
||||
}
|
||||
@@ -106,6 +107,8 @@ class W_fft extends Widget {
|
||||
void update(){
|
||||
|
||||
super.update(); //calls the parent update() method of Widget (DON'T REMOVE)
|
||||
float sr = getSampleRateSafe();
|
||||
int nfft = getNfftSafe();
|
||||
|
||||
//put your code here...
|
||||
//update the points of the FFT channel arrays
|
||||
@@ -119,14 +122,14 @@ class W_fft extends Widget {
|
||||
GPoint powerAtBin;
|
||||
|
||||
// println("i = " + i);
|
||||
// float a = get_fs_Hz_safe();
|
||||
// float a = getSampleRateSafe();
|
||||
// float aa = fftBuff[i].getBand(j);
|
||||
// float b = fftBuff[i].getBand(j);
|
||||
// float c = Nfft;
|
||||
|
||||
powerAtBin = new GPoint((1.0*get_fs_Hz_safe()/Nfft)*j, fftBuff[i].getBand(j));
|
||||
powerAtBin = new GPoint((1.0*sr/nfft)*j, fftBuff[i].getBand(j));
|
||||
fft_points[i].set(j, powerAtBin);
|
||||
// GPoint powerAtBin = new GPoint((1.0*get_fs_Hz_safe()/Nfft)*j, fftBuff[i].getBand(j));
|
||||
// GPoint powerAtBin = new GPoint((1.0*getSampleRateSafe()/Nfft)*j, fftBuff[i].getBand(j));
|
||||
|
||||
//println("=========================================");
|
||||
//println(j);
|
||||
@@ -225,13 +228,13 @@ void LogLin(int n) {
|
||||
closeAllDropdowns();
|
||||
}
|
||||
|
||||
//triggered when there is an event in the LogLin Dropdown
|
||||
//triggered when there is an event in the Smoothing Dropdown
|
||||
void Smoothing(int n) {
|
||||
smoothFac_ind = n;
|
||||
closeAllDropdowns();
|
||||
}
|
||||
|
||||
//triggered when there is an event in the LogLin Dropdown
|
||||
//triggered when there is an event in the UnfiltFilt Dropdown
|
||||
void UnfiltFilt(int n) {
|
||||
if (n==0) {
|
||||
//have FFT use filtered data -- default
|
||||
|
||||
@@ -0,0 +1,593 @@
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
//
|
||||
// W_focus.pde (ie "Focus Widget")
|
||||
//
|
||||
// This widget helps you visualize the alpha and beta value and the calculated focused state
|
||||
// You can ask a robot to press Up Arrow key stroke whenever you are focused.
|
||||
// You can also send the focused state to Arduino
|
||||
//
|
||||
// Created by: Wangshu Sun, August 2016
|
||||
//
|
||||
///////////////////////////////////////////////////,
|
||||
|
||||
import java.awt.AWTException;
|
||||
import java.awt.Robot;
|
||||
import java.awt.event.KeyEvent;
|
||||
|
||||
// color enums
|
||||
public enum FocusColors {
|
||||
GREEN, CYAN, ORANGE
|
||||
}
|
||||
|
||||
class W_Focus extends Widget {
|
||||
//to see all core variables/methods of the Widget class, refer to Widget.pde
|
||||
Robot robot; // a key-stroking robot waiting for focused state
|
||||
boolean enableKey = false; // enable key stroke by the robot
|
||||
int keyNum = 0; // 0 - up arrow, 1 - Spacebar
|
||||
boolean enableSerial = false; // send the Focused state to Arduino
|
||||
|
||||
// output values
|
||||
float alpha_avg = 0, beta_avg = 0;
|
||||
boolean isFocused;
|
||||
|
||||
// alpha, beta threshold default values
|
||||
float alpha_thresh = 0.7, beta_thresh = 0.7, alpha_upper = 2, beta_upper = 2;
|
||||
|
||||
// drawing parameters
|
||||
boolean showAbout = false;
|
||||
PFont myfont = createFont("fonts/Raleway-SemiBold.otf", 12);
|
||||
PFont f = createFont("Arial Bold", 24); //for widget title
|
||||
|
||||
FocusColors focusColors = FocusColors.GREEN;
|
||||
|
||||
color cBack, cDark, cMark, cFocus, cWave, cPanel;
|
||||
|
||||
// float x, y, w, h; //widget topleft xy, width and height
|
||||
float xc, yc, wc, hc; // crystal ball center xy, width and height
|
||||
float wg, hg; //graph width, graph height
|
||||
float wl; // line width
|
||||
float xg1, yg1; //graph1 center xy
|
||||
float xg2, yg2; //graph1 center xy
|
||||
float rp; // padding radius
|
||||
float rb; // button radius
|
||||
float xb, yb; // button center xy
|
||||
|
||||
// two sliders for alpha and one slider for beta
|
||||
FocusSlider sliderAlphaMid, sliderBetaMid;
|
||||
FocusSlider_Static sliderAlphaTop;
|
||||
|
||||
W_Focus(PApplet _parent){
|
||||
super(_parent); //calls the parent CONSTRUCTOR method of Widget (DON'T REMOVE)
|
||||
|
||||
// initialize graphics parameters
|
||||
onColorChange();
|
||||
update_graphic_parameters();
|
||||
|
||||
// sliders
|
||||
sliderAlphaMid = new FocusSlider(x + xg1 + wg * 0.8, y + yg1 + hg/2, y + yg1 - hg/2, alpha_thresh / alpha_upper);
|
||||
sliderAlphaTop = new FocusSlider_Static(x + xg1 + wg * 0.8, y + yg1 + hg/2, y + yg1 - hg/2);
|
||||
sliderBetaMid = new FocusSlider(x + xg2 + wg * 0.8, y + yg2 + hg/2, y + yg2 - hg/2, beta_thresh / beta_upper);
|
||||
|
||||
//Dropdowns.
|
||||
addDropdown("ChooseFocusColor", "Theme", Arrays.asList("Green", "Orange", "Cyan"), 0);
|
||||
addDropdown("StrokeKeyWhenFocused", "KeyPress", Arrays.asList("OFF", "UP", "SPACE"), 0);
|
||||
addDropdown("SerialSendFocused", "Serial", Arrays.asList("OFF", "ON"), 0);
|
||||
|
||||
// prepare simulate keystroking
|
||||
try {
|
||||
robot = new Robot();
|
||||
} catch (AWTException e) {
|
||||
e.printStackTrace();
|
||||
exit();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void onColorChange() {
|
||||
switch(focusColors) {
|
||||
case GREEN:
|
||||
cBack = #ffffff; //white
|
||||
cDark = #3068a6; //medium/dark blue
|
||||
cMark = #4d91d9; //lighter blue
|
||||
cFocus = #b8dc69; //theme green
|
||||
cWave = #ffdd3a; //yellow
|
||||
cPanel = #f5f5f5; //little grey
|
||||
break;
|
||||
case ORANGE:
|
||||
cBack = #ffffff; //white
|
||||
cDark = #377bc4; //medium/dark blue
|
||||
cMark = #5e9ee2; //lighter blue
|
||||
cFocus = #fcce51; //orange
|
||||
cWave = #ffdd3a; //yellow
|
||||
cPanel = #f5f5f5; //little grey
|
||||
break;
|
||||
case CYAN:
|
||||
cBack = #ffffff; //white
|
||||
cDark = #377bc4; //medium/dark blue
|
||||
cMark = #5e9ee2; //lighter blue
|
||||
cFocus = #91f4fc; //cyan
|
||||
cWave = #ffdd3a; //yellow
|
||||
cPanel = #f5f5f5; //little grey
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void update(){
|
||||
super.update(); //calls the parent update() method of Widget (DON'T REMOVE)
|
||||
|
||||
updateFocusState(); // focus calculation
|
||||
invokeKeyStroke(); // robot keystroke
|
||||
sendFocusSerial(); // send focus data to serial port
|
||||
|
||||
// update sliders
|
||||
sliderAlphaMid.update();
|
||||
sliderAlphaTop.update();
|
||||
sliderBetaMid.update();
|
||||
|
||||
// update threshold values
|
||||
alpha_thresh = alpha_upper * sliderAlphaMid.getVal();
|
||||
beta_thresh = beta_upper * sliderBetaMid.getVal();
|
||||
|
||||
alpha_upper = sliderAlphaTop.getVal() * 2;
|
||||
beta_upper = alpha_upper;
|
||||
|
||||
sliderAlphaMid.setVal(alpha_thresh / alpha_upper);
|
||||
sliderBetaMid.setVal(beta_thresh / beta_upper);
|
||||
}
|
||||
|
||||
void updateFocusState() {
|
||||
// focus detection algorithm based on Jordan's clean mind: focus == high alpha average && low beta average
|
||||
float FFT_freq_Hz, FFT_value_uV;
|
||||
int alpha_count = 0, beta_count = 0;
|
||||
|
||||
for (int Ichan=0; Ichan < 2; Ichan++) { // only consider first two channels
|
||||
for (int Ibin=0; Ibin < fftBuff[Ichan].specSize(); Ibin++) {
|
||||
FFT_freq_Hz = fftBuff[Ichan].indexToFreq(Ibin);
|
||||
FFT_value_uV = fftBuff[Ichan].getBand(Ibin);
|
||||
|
||||
if (FFT_freq_Hz >= 7.5 && FFT_freq_Hz <= 12.5) { //FFT bins in alpha range
|
||||
alpha_avg += FFT_value_uV;
|
||||
alpha_count ++;
|
||||
}
|
||||
else if (FFT_freq_Hz > 12.5 && FFT_freq_Hz <= 30) { //FFT bins in beta range
|
||||
beta_avg += FFT_value_uV;
|
||||
beta_count ++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
alpha_avg = alpha_avg / alpha_count; // average uV per bin
|
||||
//alpha_avg = alpha_avg / (cyton.getSampleRate()/Nfft); // average uV per delta freq
|
||||
beta_avg = beta_avg / beta_count; // average uV per bin
|
||||
//beta_avg = beta_avg / (cyton.getSampleRate()/Nfft); // average uV per delta freq
|
||||
//current time = int(float(currentTableRowIndex)/cyton.getSampleRate());
|
||||
|
||||
// version 1
|
||||
if (alpha_avg > alpha_thresh && alpha_avg < alpha_upper && beta_avg < beta_thresh) {
|
||||
isFocused = true;
|
||||
} else {
|
||||
isFocused = false;
|
||||
}
|
||||
|
||||
//alpha_avg = beta_avg = 0;
|
||||
|
||||
}
|
||||
|
||||
void invokeKeyStroke() {
|
||||
// robot keystroke
|
||||
if (enableKey) {
|
||||
if (keyNum == 0) {
|
||||
if (isFocused) {
|
||||
robot.keyPress(KeyEvent.VK_UP); //if you want to change to other key, google "java keyEvent" to see the full list
|
||||
}
|
||||
else {
|
||||
robot.keyRelease(KeyEvent.VK_UP);
|
||||
}
|
||||
}
|
||||
else if (keyNum == 1) {
|
||||
if (isFocused) {
|
||||
robot.keyPress(KeyEvent.VK_SPACE); //if you want to change to other key, google "java keyEvent" to see the full list
|
||||
}
|
||||
else {
|
||||
robot.keyRelease(KeyEvent.VK_SPACE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sendFocusSerial() {
|
||||
// ----------- if turned on, send the focused state to Arduino via serial port -----------
|
||||
if (enableSerial) {
|
||||
try {
|
||||
serial_output.write(int(isFocused) + 48);
|
||||
serial_output.write('\n');
|
||||
}
|
||||
catch(RuntimeException e) {
|
||||
if (isVerbose) println("serial not present, search 'serial_output' in OpenBCI.pde and check serial settings.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void draw(){
|
||||
super.draw(); //calls the parent draw() method of Widget (DON'T REMOVE)
|
||||
|
||||
//put your code here... //remember to refer to x,y,w,h which are the positioning variables of the Widget class
|
||||
pushStyle();
|
||||
|
||||
//----------------- presettings before drawing Focus Viz --------------
|
||||
translate(x, y);
|
||||
textAlign(CENTER, CENTER);
|
||||
textFont(myfont);
|
||||
|
||||
//----------------- draw background rectangle and panel -----------------
|
||||
fill(cBack);
|
||||
noStroke();
|
||||
rect(0, 0, w, h);
|
||||
|
||||
fill(cPanel);
|
||||
noStroke();
|
||||
rect(rp, rp, w-rp*2, h-rp*2);
|
||||
|
||||
//----------------- draw focus crystalball -----------------
|
||||
noStroke();
|
||||
if (isFocused) {
|
||||
fill(cFocus);
|
||||
stroke(cFocus);
|
||||
} else {
|
||||
fill(cDark);
|
||||
}
|
||||
ellipse(xc, yc, wc, hc);
|
||||
noStroke();
|
||||
// draw focus label
|
||||
if (isFocused) {
|
||||
fill(cFocus);
|
||||
text("focused!", xc, yc + hc/2 + 16);
|
||||
} else {
|
||||
fill(cMark);
|
||||
text("not focused", xc, yc + hc/2 + 16);
|
||||
}
|
||||
|
||||
//----------------- draw alpha meter -----------------
|
||||
noStroke();
|
||||
fill(cDark);
|
||||
rect(xg1 - wg/2, yg1 - hg/2, wg, hg);
|
||||
|
||||
float hat = map(alpha_thresh, 0, alpha_upper, 0, hg); // alpha threshold height
|
||||
stroke(cMark);
|
||||
line(xg1 - wl/2, yg1 + hg/2, xg1 + wl/2, yg1 + hg/2);
|
||||
line(xg1 - wl/2, yg1 - hg/2, xg1 + wl/2, yg1 - hg/2);
|
||||
line(xg1 - wl/2, yg1 + hg/2 - hat, xg1 + wl/2, yg1 + hg/2 - hat);
|
||||
|
||||
// draw alpha zone and text
|
||||
noStroke();
|
||||
if (alpha_avg > alpha_thresh && alpha_avg < alpha_upper) {
|
||||
fill(cFocus);
|
||||
} else {
|
||||
fill(cMark);
|
||||
}
|
||||
rect(xg1 - wg/2, yg1 - hg/2, wg, hg - hat);
|
||||
text("alpha", xg1, yg1 + hg/2 + 16);
|
||||
|
||||
// draw connection between two sliders
|
||||
stroke(cMark);
|
||||
line(xg1 + wg * 0.8, yg1 - hg/2 + 10, xg1 + wg * 0.8, yg1 + hg/2 - hat - 10);
|
||||
|
||||
noStroke();
|
||||
fill(cMark);
|
||||
text(String.format("%.01f", alpha_upper), xg1 - wl/2 - 14, yg1 - hg/2);
|
||||
text(String.format("%.01f", alpha_thresh), xg1 - wl/2 - 14, yg1 + hg/2 - hat);
|
||||
text("0.0", xg1 - wl/2 - 14, yg1 + hg/2);
|
||||
|
||||
stroke(cWave);
|
||||
strokeWeight(4);
|
||||
float ha = map(alpha_avg, 0, alpha_upper, 0, hg); //alpha height
|
||||
ha = constrain(ha, 0, hg);
|
||||
line(xg1 - wl/2, yg1 + hg/2 - ha, xg1 + wl/2, yg1 + hg/2 - ha);
|
||||
strokeWeight(1);
|
||||
|
||||
//----------------- draw beta meter -----------------
|
||||
noStroke();
|
||||
fill(cDark);
|
||||
rect(xg2 - wg/2, yg2 - hg/2, wg, hg);
|
||||
|
||||
float hbt = map(beta_thresh, 0, beta_upper, 0, hg); // beta threshold height
|
||||
stroke(cMark);
|
||||
line(xg2 - wl/2, yg2 + hg/2, xg2 + wl/2, yg2 + hg/2);
|
||||
line(xg2 - wl/2, yg2 - hg/2, xg2 + wl/2, yg2 - hg/2);
|
||||
line(xg2 - wl/2, yg2 + hg/2 - hbt, xg2 + wl/2, yg2 + hg/2 - hbt);
|
||||
|
||||
// draw beta zone and text
|
||||
noStroke();
|
||||
if (beta_avg < beta_thresh) {
|
||||
fill(cFocus);
|
||||
} else {
|
||||
fill(cMark);
|
||||
}
|
||||
rect(xg2 - wg/2, yg2 + hg/2 - hbt, wg, hbt);
|
||||
text("beta", xg2, yg2 + hg/2 + 16);
|
||||
|
||||
// draw connection between slider and bottom
|
||||
stroke(cMark);
|
||||
float yt = yg2 + hg/2 - hbt + 10; // y threshold
|
||||
yt = constrain(yt, yg2 - hg/2 + 10, yg2 + hg/2);
|
||||
line(xg2 + wg * 0.8, yg2 + hg/2, xg2 + wg * 0.8, yt);
|
||||
|
||||
noStroke();
|
||||
fill(cMark);
|
||||
text(String.format("%.01f", beta_upper), xg2 - wl/2 - 14, yg2 - hg/2);
|
||||
text(String.format("%.01f", beta_thresh), xg2 - wl/2 - 14, yg2 + hg/2 - hbt);
|
||||
text("0.0", xg2 - wl/2 - 14, yg2 + hg/2);
|
||||
|
||||
stroke(cWave);
|
||||
strokeWeight(4);
|
||||
float hb = map(beta_avg, 0, beta_upper, 0, hg); //beta height
|
||||
hb = constrain(hb, 0, hg);
|
||||
line(xg2 - wl/2, yg2 + hg/2 - hb, xg2 + wl/2, yg2 + hg/2 - hb);
|
||||
strokeWeight(1);
|
||||
|
||||
translate(-x, -y);
|
||||
|
||||
//------------------ draw sliders --------------------
|
||||
sliderAlphaMid.draw();
|
||||
sliderAlphaTop.draw();
|
||||
sliderBetaMid.draw();
|
||||
|
||||
//----------------- draw about button -----------------
|
||||
translate(x, y);
|
||||
if (showAbout) {
|
||||
stroke(cDark);
|
||||
fill(cBack);
|
||||
|
||||
rect(rp, rp, w-rp*2, h-rp*2);
|
||||
textAlign(LEFT, TOP);
|
||||
fill(cDark);
|
||||
text("This widget recognizes a focused mental state by looking at alpha and beta wave levels on channel 1 & 2. For better result, try setting the smooth at 0.98 in FFT plot.\n\nThe algorithm thinks you are focused when the alpha level is between 0.7~2uV and the beta level is between 0~0.7 uV, otherwise it thinks you are not focused. It is designed based on Jordan Frand’s brainwave and tested on other subjects, and you can playback Jordan's file in W_Focus folder.\n\nYou can turn on KeyPress and use your focus play a game, so whenever you are focused, the specified UP arrow or SPACE key will be pressed down, otherwise it will be released. You can also try out the Arduino output feature, example and instructions are included in W_Focus folder. For more information, contact wangshu.sun@hotmail.com.", rp*1.5, rp*1.5, w-rp*3, h-rp*3);
|
||||
}
|
||||
// draw the button that toggles information
|
||||
noStroke();
|
||||
fill(cDark);
|
||||
ellipse(xb, yb, rb, rb);
|
||||
fill(cBack);
|
||||
textAlign(CENTER, CENTER);
|
||||
if (showAbout) {
|
||||
text("x", xb, yb);
|
||||
} else {
|
||||
text("?", xb, yb);
|
||||
}
|
||||
|
||||
//----------------- revert origin point of draw to default -----------------
|
||||
translate(-x, -y);
|
||||
textAlign(LEFT, BASELINE);
|
||||
|
||||
popStyle();
|
||||
|
||||
}
|
||||
|
||||
void screenResized(){
|
||||
super.screenResized(); //calls the parent screenResized() method of Widget (DON'T REMOVE)
|
||||
|
||||
//put your code here...
|
||||
update_graphic_parameters();
|
||||
|
||||
//update sliders...
|
||||
sliderAlphaMid.screenResized(x + xg1 + wg * 0.8, y + yg1 + hg/2, y + yg1 - hg/2);
|
||||
sliderAlphaTop.screenResized(x + xg1 + wg * 0.8, y + yg1 + hg/2, y + yg1 - hg/2);
|
||||
sliderBetaMid.screenResized(x + xg2 + wg * 0.8, y + yg2 + hg/2, y + yg2 - hg/2);
|
||||
}
|
||||
|
||||
void update_graphic_parameters () {
|
||||
xc = w/4;
|
||||
yc = h/2;
|
||||
wc = w/4;
|
||||
hc = w/4;
|
||||
wg = 0.07*w;
|
||||
hg = 0.64*h;
|
||||
wl = 0.11*w;
|
||||
xg1 = 0.6*w;
|
||||
yg1 = 0.5*h;
|
||||
xg2 = 0.83*w;
|
||||
yg2 = 0.5*h;
|
||||
rp = max(w*0.05, h*0.05);
|
||||
rb = 20;
|
||||
xb = w-rp;
|
||||
yb = rp;
|
||||
}
|
||||
|
||||
void mousePressed(){
|
||||
super.mousePressed(); //calls the parent mousePressed() method of Widget (DON'T REMOVE)
|
||||
|
||||
// about button
|
||||
if (dist(mouseX,mouseY,xb+x,yb+y) <= rb) {
|
||||
showAbout = !showAbout;
|
||||
}
|
||||
|
||||
// sliders
|
||||
sliderAlphaMid.mousePressed();
|
||||
sliderAlphaTop.mousePressed();
|
||||
sliderBetaMid.mousePressed();
|
||||
}
|
||||
|
||||
void mouseReleased(){
|
||||
super.mouseReleased(); //calls the parent mouseReleased() method of Widget (DON'T REMOVE)
|
||||
|
||||
// sliders
|
||||
sliderAlphaMid.mouseReleased();
|
||||
sliderAlphaTop.mouseReleased();
|
||||
sliderBetaMid.mouseReleased();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/* ---------------------- Supporting Slider Classes ---------------------------*/
|
||||
|
||||
// abstract basic slider
|
||||
public abstract class BasicSlider {
|
||||
float x, y, w, h; // center x, y. w, h means width and height of triangle
|
||||
float yBot, yTop; // y range. Notice val of top y is less than bottom y
|
||||
boolean isPressed = false;
|
||||
color cNormal = #CCCCCC;
|
||||
color cPressed = #FF0000;
|
||||
|
||||
BasicSlider(float _x, float _yBot, float _yTop) {
|
||||
x = _x;
|
||||
yBot = _yBot;
|
||||
yTop = _yTop;
|
||||
w = 10;
|
||||
h = 10;
|
||||
}
|
||||
|
||||
// abstract functions
|
||||
|
||||
abstract void update();
|
||||
abstract void screenResized(float _x, float _yBot, float _yTop);
|
||||
abstract float getVal();
|
||||
abstract void setVal(float _val);
|
||||
|
||||
// shared functions
|
||||
|
||||
void draw() {
|
||||
if (isPressed) fill(cPressed);
|
||||
else fill(cNormal);
|
||||
noStroke();
|
||||
triangle(x-w/2, y, x+w/2, y-h/2, x+w/2, y+h/2);
|
||||
}
|
||||
|
||||
void mousePressed() {
|
||||
if (abs(mouseX - (x)) <= w/2 && abs(mouseY - y) <= h/2) {
|
||||
isPressed = true;
|
||||
}
|
||||
}
|
||||
|
||||
void mouseReleased() {
|
||||
if (isPressed) {
|
||||
isPressed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// middle slider that changes value and move
|
||||
public class FocusSlider extends BasicSlider {
|
||||
private float val = 0; // val = 0 ~ 1 -> yBot to yTop
|
||||
final float valMin = 0;
|
||||
final float valMax = 0.90;
|
||||
FocusSlider(float _x, float _yBot, float _yTop, float _val) {
|
||||
super(_x, _yBot, _yTop);
|
||||
val = constrain(_val, valMin, valMax);
|
||||
y = map(val, 0, 1, yBot, yTop);
|
||||
}
|
||||
|
||||
public void update() {
|
||||
if (isPressed) {
|
||||
float newVal = map(mouseY, yBot, yTop, 0, 1);
|
||||
val = constrain(newVal, valMin, valMax);
|
||||
y = map(val, 0, 1, yBot, yTop);
|
||||
println(val);
|
||||
}
|
||||
}
|
||||
|
||||
public void screenResized(float _x, float _yBot, float _yTop) {
|
||||
x = _x;
|
||||
yBot = _yBot;
|
||||
yTop = _yTop;
|
||||
y = map(val, 0, 1, yBot, yTop);
|
||||
}
|
||||
|
||||
public float getVal() {
|
||||
return val;
|
||||
}
|
||||
|
||||
public void setVal(float _val) {
|
||||
val = constrain(_val, valMin, valMax);
|
||||
y = map(val, 0, 1, yBot, yTop);
|
||||
}
|
||||
}
|
||||
|
||||
// top slider that changes value but doesn't move
|
||||
public class FocusSlider_Static extends BasicSlider {
|
||||
private float val = 0; // val = 0 ~ 1 -> yBot to yTop
|
||||
final float valMin = 0.5;
|
||||
final float valMax = 5.0;
|
||||
FocusSlider_Static(float _x, float _yBot, float _yTop) {
|
||||
super(_x, _yBot, _yTop);
|
||||
val = 1;
|
||||
y = yTop;
|
||||
}
|
||||
|
||||
public void update() {
|
||||
if (isPressed) {
|
||||
float diff = map(mouseY, yBot, yTop, -0.07, 0);
|
||||
val = constrain(val + diff, valMin, valMax);
|
||||
println(val);
|
||||
}
|
||||
}
|
||||
|
||||
public void screenResized(float _x, float _yBot, float _yTop) {
|
||||
x = _x;
|
||||
yBot = _yBot;
|
||||
yTop = _yTop;
|
||||
y = yTop;
|
||||
}
|
||||
|
||||
public float getVal() {
|
||||
return val;
|
||||
}
|
||||
|
||||
public void setVal(float _val) {
|
||||
val = constrain(_val, valMin, valMax);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* ---------------- Global Functions For Menu Entries --------------------*/
|
||||
|
||||
// //These functions need to be global! These functions are activated when an item from the corresponding dropdown is selected
|
||||
void StrokeKeyWhenFocused(int n){
|
||||
// println("Item " + (n+1) + " selected from Dropdown 1");
|
||||
if(n==0){
|
||||
//do this
|
||||
w_focus.enableKey = false;
|
||||
println("The robot ignores focused state and will not press any key.");
|
||||
} else if(n==1){
|
||||
//do this instead
|
||||
w_focus.enableKey = true;
|
||||
w_focus.keyNum = 0;
|
||||
println("The robot will keep pressing Arrow Up key when you are focused, and release the key when you lose focus.");
|
||||
} else if(n==2){
|
||||
//do this instead
|
||||
w_focus.enableKey = true;
|
||||
w_focus.keyNum = 1;
|
||||
println("The robot will keep pressing Spacebar when you are focused, and release the key when you lose focus.");
|
||||
}
|
||||
|
||||
closeAllDropdowns(); // do this at the end of all widget-activated functions to ensure proper widget interactivity ... we want to make sure a click makes the menu close
|
||||
}
|
||||
|
||||
void SerialSendFocused(int n){
|
||||
if(n==0){
|
||||
//do this
|
||||
w_focus.enableSerial = false;
|
||||
println("Serial write off.");
|
||||
} else if(n==1){
|
||||
//do this instead
|
||||
w_focus.enableSerial = true;
|
||||
println("Serial write on, writing character 1 (int 49) when focused, and character 0 (int 48) when losing focus.");
|
||||
println("Current output port name: " + serial_output_portName + ". Current baud rate: " + serial_output_baud + ".");
|
||||
println("You can change serial settings in OpenBCI_GUI.pde by searching serial_output.");
|
||||
}
|
||||
closeAllDropdowns();
|
||||
}
|
||||
|
||||
void ChooseFocusColor(int n){
|
||||
if(n==0){
|
||||
w_focus.focusColors = FocusColors.GREEN;
|
||||
w_focus.onColorChange();
|
||||
} else if(n==1){
|
||||
w_focus.focusColors = FocusColors.ORANGE;
|
||||
w_focus.onColorChange();
|
||||
} else if(n==2){
|
||||
w_focus.focusColors = FocusColors.CYAN;
|
||||
w_focus.onColorChange();
|
||||
}
|
||||
closeAllDropdowns();
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
Serial Event example
|
||||
|
||||
When new serial data arrives, this sketch adds it to a String.
|
||||
When a newline is received, the loop prints the string and
|
||||
clears it.
|
||||
|
||||
A good test for this is to try it with a GPS receiver
|
||||
that sends out NMEA 0183 sentences.
|
||||
|
||||
Created 9 May 2011
|
||||
by Tom Igoe
|
||||
|
||||
This example code is in the public domain.
|
||||
|
||||
http://www.arduino.cc/en/Tutorial/SerialEvent
|
||||
|
||||
*/
|
||||
|
||||
String inputString = ""; // a string to hold incoming data
|
||||
boolean stringComplete = false; // whether the string is complete
|
||||
|
||||
|
||||
void setup() {
|
||||
// initialize serial:
|
||||
Serial.begin(9600);
|
||||
pinMode(2, OUTPUT); //use pin 2 to turn on a light
|
||||
|
||||
// reserve 200 bytes for the inputString:
|
||||
//inputString.reserve(20);
|
||||
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// print the string when a newline arrives:
|
||||
if (stringComplete) {
|
||||
if (inputString[0] == '1') {
|
||||
digitalWrite(2, HIGH);
|
||||
}
|
||||
else {
|
||||
digitalWrite(2, LOW);
|
||||
}
|
||||
|
||||
//clear the string:
|
||||
inputString = "";
|
||||
stringComplete = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
SerialEvent occurs whenever a new data comes in the
|
||||
hardware serial RX. This routine is run between each
|
||||
time loop() runs, so using delay inside loop can delay
|
||||
response. Multiple bytes of data may be available.
|
||||
*/
|
||||
void serialEvent() {
|
||||
if (Serial.available() > 0) {
|
||||
// get the new byte:
|
||||
char inChar = (char)Serial.read();
|
||||
// add it to the inputString:
|
||||
inputString += inChar;
|
||||
// if the incoming character is a newline, set a flag
|
||||
// so the main loop can do something about it:
|
||||
if (inChar == '\n') {
|
||||
stringComplete = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
notes_Arduino
|
||||
|
||||
Modified from Tom Igoe's code.
|
||||
It should be able to turn on an LED by focus on pin 2.
|
||||
To replicate this example, you will need an Arduino Uno (101 is ok as well) and set up an LED output there, then upload this code to your Arduino.
|
||||
Also you need to set the serial_output_portName in OpenBCI.pde as your Arduino's serial port name, and also set serial_output_baud to be 9600 (you can use other baud rate but need to ensure serial_output_baud is the same as the number in Serial.begin() in the Arduino file).
|
||||
Once you're prepared, you can run the openBCI GUI and turn on Serial in Focus Widget.
|
||||
|
||||
Enjoy then :)
|
||||
|
||||
Wangshu
|
||||
@@ -0,0 +1,3 @@
|
||||
0-30s prep
|
||||
30-85s focus on a banana
|
||||
85s- losing focus
|
||||
@@ -0,0 +1,287 @@
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
//
|
||||
// W_MarkerMode is used to put the board into marker mode
|
||||
// by Gerrie van Zyl
|
||||
// Basd on W_Analogread by AJ Keller
|
||||
//
|
||||
//
|
||||
///////////////////////////////////////////////////,
|
||||
|
||||
class W_MarkerMode extends Widget {
|
||||
|
||||
//to see all core variables/methods of the Widget class, refer to Widget.pde
|
||||
//put your custom variables here...
|
||||
|
||||
// color boxBG;
|
||||
color graphStroke = #d2d2d2;
|
||||
color graphBG = #f5f5f5;
|
||||
color textColor = #000000;
|
||||
|
||||
color strokeColor;
|
||||
|
||||
// Accelerometer Stuff
|
||||
int MarkerBuffSize = 500; //points registered in accelerometer buff
|
||||
|
||||
int padding = 30;
|
||||
|
||||
// bottom xyz graph
|
||||
int MarkerWindowWidth;
|
||||
int MarkerWindowHeight;
|
||||
int MarkerWindowX;
|
||||
int MarkerWindowY;
|
||||
|
||||
|
||||
color eggshell;
|
||||
color Xcolor;
|
||||
|
||||
float yMaxMin;
|
||||
|
||||
float currentXvalue;
|
||||
|
||||
int[] X;
|
||||
|
||||
int lastMarker=0;
|
||||
int localValidLastMarker;
|
||||
|
||||
float dummyX;
|
||||
|
||||
// for the synthetic markers
|
||||
float synthTime;
|
||||
int synthCount;
|
||||
|
||||
boolean OBCI_inited= true;
|
||||
|
||||
Button markerModeButton;
|
||||
|
||||
W_MarkerMode(PApplet _parent){
|
||||
super(_parent); //calls the parent CONSTRUCTOR method of Widget (DON'T REMOVE)
|
||||
|
||||
// boxBG = bgColor;
|
||||
strokeColor = color(138, 146, 153);
|
||||
|
||||
// Marker Sensor Stuff
|
||||
eggshell = color(255, 253, 248);
|
||||
Xcolor = color(224, 56, 45);
|
||||
|
||||
|
||||
setGraphDimensions();
|
||||
|
||||
// The range of markers
|
||||
yMaxMin = 256;
|
||||
|
||||
// XYZ buffer for bottom graph
|
||||
X = new int[MarkerBuffSize];
|
||||
|
||||
// for synthesizing values
|
||||
synthTime = 0.0;
|
||||
|
||||
markerModeButton = new Button((int)(x + 3), (int)(y + 3 - navHeight), 120, navHeight - 6, "Turn MarkerMode On", 12);
|
||||
markerModeButton.setCornerRoundess((int)(navHeight-6));
|
||||
markerModeButton.setFont(p6,10);
|
||||
markerModeButton.setColorNotPressed(color(57,128,204));
|
||||
markerModeButton.textColorNotActive = color(255);
|
||||
markerModeButton.hasStroke(false);
|
||||
markerModeButton.setHelpText("Click this button to activate/deactivate the MarkerMode of your Cyton board!");
|
||||
}
|
||||
|
||||
public void initPlayground(Cyton _OBCI) {
|
||||
OBCI_inited = true;
|
||||
}
|
||||
|
||||
void update(){
|
||||
super.update(); //calls the parent update() method of Widget (DON'T REMOVE)
|
||||
|
||||
localValidLastMarker = hub.validLastMarker; // make a local copy so it can be manipulated in SYNTHETIC mode
|
||||
hub.validLastMarker = 0;
|
||||
|
||||
if (eegDataSource == DATASOURCE_SYNTHETIC) {
|
||||
localValidLastMarker = synthesizeMarkerData();
|
||||
}
|
||||
if (eegDataSource == DATASOURCE_CYTON || eegDataSource == DATASOURCE_SYNTHETIC) {
|
||||
if (isRunning && cyton.getBoardMode() == BOARD_MODE_MARKER) {
|
||||
if (localValidLastMarker > 0){
|
||||
lastMarker = localValidLastMarker; // this holds the last marker for the display
|
||||
}
|
||||
X[X.length-1] =
|
||||
int(map(logScaleMarker(localValidLastMarker), 0, yMaxMin, float(MarkerWindowY+MarkerWindowHeight), float(MarkerWindowY)));
|
||||
X[X.length-1] = constrain(X[X.length-1], MarkerWindowY, MarkerWindowY+MarkerWindowHeight);
|
||||
|
||||
shiftWave();
|
||||
}
|
||||
} else { // playback data
|
||||
currentXvalue = accelerometerBuff[0][accelerometerBuff[0].length-1];
|
||||
}
|
||||
}
|
||||
|
||||
void draw(){
|
||||
super.draw(); //calls the parent draw() method of Widget (DON'T REMOVE)
|
||||
|
||||
pushStyle();
|
||||
//put your code here...
|
||||
//remember to refer to x,y,w,h which are the positioning variables of the Widget class
|
||||
if (true) {
|
||||
|
||||
fill(50);
|
||||
textFont(p4, 14);
|
||||
textAlign(CENTER,CENTER);
|
||||
|
||||
fill(graphBG);
|
||||
stroke(graphStroke);
|
||||
rect(MarkerWindowX, MarkerWindowY, MarkerWindowWidth, MarkerWindowHeight);
|
||||
line(MarkerWindowX, MarkerWindowY + MarkerWindowHeight/2, MarkerWindowX+MarkerWindowWidth, MarkerWindowY + MarkerWindowHeight/2); //midline
|
||||
|
||||
fill(50);
|
||||
textFont(p5, 12);
|
||||
textAlign(CENTER,CENTER);
|
||||
text((int)yMaxMin, MarkerWindowX+MarkerWindowWidth + 12, MarkerWindowY);
|
||||
text((int)16, MarkerWindowX+MarkerWindowWidth + 12, MarkerWindowY + MarkerWindowHeight/2);
|
||||
text("0", MarkerWindowX+MarkerWindowWidth + 12, MarkerWindowY + MarkerWindowHeight);
|
||||
|
||||
|
||||
fill(graphBG); // pulse window background
|
||||
stroke(graphStroke);
|
||||
|
||||
stroke(180);
|
||||
|
||||
fill(50);
|
||||
textFont(p3, 16);
|
||||
|
||||
if (eegDataSource == DATASOURCE_CYTON || eegDataSource == DATASOURCE_SYNTHETIC) { // LIVE
|
||||
markerModeButton.draw();
|
||||
drawMarkerValues();
|
||||
drawMarkerWave();
|
||||
}
|
||||
else { // PLAYBACK
|
||||
drawMarkerValues();
|
||||
drawMarkerWave2();
|
||||
}
|
||||
}
|
||||
popStyle();
|
||||
}
|
||||
|
||||
void setGraphDimensions(){
|
||||
MarkerWindowWidth = w - padding*2;
|
||||
MarkerWindowHeight = int((float(h) - float(padding*3)));
|
||||
MarkerWindowX = x + padding;
|
||||
MarkerWindowY = y + h - MarkerWindowHeight - padding;
|
||||
|
||||
}
|
||||
|
||||
void screenResized(){
|
||||
int prevX = x;
|
||||
int prevY = y;
|
||||
int prevW = w;
|
||||
int prevH = h;
|
||||
|
||||
super.screenResized(); //calls the parent screenResized() method of Widget (DON'T REMOVE)
|
||||
|
||||
int dy = y - prevY;
|
||||
println("dy = " + dy);
|
||||
|
||||
//put your code here...
|
||||
println("Acc Widget -- Screen Resized.");
|
||||
|
||||
setGraphDimensions();
|
||||
|
||||
//empty arrays to start redrawing from scratch
|
||||
for (int i=0; i<X.length; i++) { // initialize the accelerometer data
|
||||
X[i] = MarkerWindowY + MarkerWindowHeight; // X at 1/4
|
||||
}
|
||||
|
||||
markerModeButton.setPos((int)(x + 3), (int)(y + 3 - navHeight));
|
||||
}
|
||||
|
||||
void mousePressed(){
|
||||
super.mousePressed(); //calls the parent mousePressed() method of Widget (DON'T REMOVE)
|
||||
|
||||
if (markerModeButton.isMouseHere()) {
|
||||
markerModeButton.setIsActive(true);
|
||||
}
|
||||
}
|
||||
|
||||
void mouseReleased(){
|
||||
super.mouseReleased(); //calls the parent mouseReleased() method of Widget (DON'T REMOVE)
|
||||
|
||||
//put your code here...
|
||||
if(markerModeButton.isActive && markerModeButton.isMouseHere()){
|
||||
// println("markerModeButton...");
|
||||
if((cyton.isPortOpen() && eegDataSource == DATASOURCE_CYTON) || eegDataSource == DATASOURCE_SYNTHETIC) {
|
||||
if (cyton.getBoardMode() != BOARD_MODE_MARKER) {
|
||||
cyton.setBoardMode(BOARD_MODE_MARKER);
|
||||
output("Starting to read markers");
|
||||
markerModeButton.setString("Turn Marker Off");
|
||||
} else {
|
||||
cyton.setBoardMode(BOARD_MODE_DEFAULT);
|
||||
output("Starting to read accelerometer");
|
||||
markerModeButton.setString("Turn Marker On");
|
||||
}
|
||||
}
|
||||
}
|
||||
markerModeButton.setIsActive(false);
|
||||
}
|
||||
|
||||
//add custom classes functions here
|
||||
void drawMarkerValues() {
|
||||
textAlign(LEFT,CENTER);
|
||||
textFont(h1,20);
|
||||
fill(Xcolor);
|
||||
text("Last Marker = " + lastMarker, x+padding , y + (h/12)*1.5);
|
||||
}
|
||||
|
||||
void shiftWave() {
|
||||
for (int i = 0; i < X.length-1; i++) { // move the pulse waveform by
|
||||
X[i] = X[i+1];
|
||||
}
|
||||
}
|
||||
|
||||
void drawMarkerWave() {
|
||||
noFill();
|
||||
strokeWeight(2);
|
||||
beginShape(); // using beginShape() renders fast
|
||||
stroke(Xcolor);
|
||||
for (int i = 0; i < X.length; i++) {
|
||||
// int xi = int(map(i, 0, X.length-1, 0, MarkerWindowWidth-1));
|
||||
// vertex(MarkerWindowX+xi, X[i]); //draw a line connecting the data points
|
||||
int xi = int(map(i, 0, X.length-1, 0, MarkerWindowWidth-1));
|
||||
// int yi = int(map(X[i], yMaxMin, -yMaxMin, 0.0, MarkerWindowHeight-1));
|
||||
// int yi = 2;
|
||||
vertex(MarkerWindowX+xi, X[i]); //draw a line connecting the data points
|
||||
}
|
||||
endShape();
|
||||
}
|
||||
|
||||
void drawMarkerWave2() {
|
||||
noFill();
|
||||
strokeWeight(1);
|
||||
beginShape(); // using beginShape() renders fast
|
||||
stroke(Xcolor);
|
||||
for (int i = 0; i < accelerometerBuff[0].length; i++) {
|
||||
int x = int(map(accelerometerBuff[0][i], -yMaxMin, yMaxMin, float(MarkerWindowY+MarkerWindowHeight), float(MarkerWindowY))); // ss
|
||||
x = constrain(x, MarkerWindowY, MarkerWindowY+MarkerWindowHeight);
|
||||
vertex(MarkerWindowX+i, x); //draw a line connecting the data points
|
||||
}
|
||||
endShape();
|
||||
}
|
||||
|
||||
int synthesizeMarkerData() {
|
||||
synthTime += 0.02;
|
||||
int valueMarker;
|
||||
|
||||
if (synthCount++ > 10){
|
||||
valueMarker = int((sin(synthTime) +1.0)*127.);
|
||||
synthCount = 0;
|
||||
} else {
|
||||
valueMarker = 0;
|
||||
}
|
||||
|
||||
return valueMarker;
|
||||
}
|
||||
|
||||
|
||||
int logScaleMarker( float value ) {
|
||||
// this returns log value between 0 and yMaxMin for a value between 0. and 255.
|
||||
return int(log(int(value)+1.0)*yMaxMin/log(yMaxMin+1));
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,404 @@
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
//
|
||||
// W_PulseSensor.pde
|
||||
//
|
||||
// Created: Joel Murphy, Spring 2017
|
||||
//
|
||||
///////////////////////////////////////////////////,
|
||||
|
||||
class W_PulseSensor extends Widget {
|
||||
|
||||
//to see all core variables/methods of the Widget class, refer to Widget.pde
|
||||
//put your custom variables here...
|
||||
|
||||
|
||||
color graphStroke = #d2d2d2;
|
||||
color graphBG = #f5f5f5;
|
||||
color textColor = #000000;
|
||||
|
||||
// Pulse Sensor Visualizer Stuff
|
||||
int count = 0;
|
||||
int heart = 0;
|
||||
int PulseBuffSize = dataPacketBuff.length; // Originally 400
|
||||
int BPMbuffSize = 100;
|
||||
|
||||
int PulseWindowWidth;
|
||||
int PulseWindowHeight;
|
||||
int PulseWindowX;
|
||||
int PulseWindowY;
|
||||
int BPMwindowWidth;
|
||||
int BPMwindowHeight;
|
||||
int BPMwindowX;
|
||||
int BPMwindowY;
|
||||
int BPMposX;
|
||||
int BPMposY;
|
||||
int IBIposX;
|
||||
int IBIposY;
|
||||
int padding = 15;
|
||||
color eggshell;
|
||||
color pulseWave;
|
||||
int[] PulseWaveY; // HOLDS HEARTBEAT WAVEFORM DATA
|
||||
int[] BPMwaveY; // HOLDS BPM WAVEFORM DATA
|
||||
boolean rising;
|
||||
|
||||
// Synthetic Wave Generator Stuff
|
||||
float theta; // Start angle at 0
|
||||
float amplitude; // Height of wave
|
||||
int syntheticMultiplier;
|
||||
long thisTime;
|
||||
long thatTime;
|
||||
int refreshRate;
|
||||
|
||||
// Pulse Sensor Beat Finder Stuff
|
||||
// ASSUMES 250Hz SAMPLE RATE
|
||||
int[] rate; // array to hold last ten IBI values
|
||||
int sampleCounter; // used to determine pulse timing
|
||||
int lastBeatTime; // used to find IBI
|
||||
int P =512; // used to find peak in pulse wave, seeded
|
||||
int T = 512; // used to find trough in pulse wave, seeded
|
||||
int thresh = 530; // used to find instant moment of heart beat, seeded
|
||||
int amp = 0; // used to hold amplitude of pulse waveform, seeded
|
||||
boolean firstBeat = true; // used to seed rate array so we startup with reasonable BPM
|
||||
boolean secondBeat = false; // used to seed rate array so we startup with reasonable BPM
|
||||
int BPM; // int that holds raw Analog in 0. updated every 2mS
|
||||
int Signal; // holds the incoming raw data
|
||||
int IBI = 600; // int that holds the time interval between beats! Must be seeded!
|
||||
boolean Pulse = false; // "True" when User's live heartbeat is detected. "False" when not a "live beat".
|
||||
boolean QS = false; // becomes true when Arduoino finds a beat.
|
||||
int lastProcessedDataPacketInd = 0;
|
||||
boolean analogReadOn = false;
|
||||
|
||||
// testing stuff
|
||||
|
||||
Button analogModeButton;
|
||||
|
||||
|
||||
|
||||
W_PulseSensor(PApplet _parent){
|
||||
super(_parent); //calls the parent CONSTRUCTOR method of Widget (DON'T REMOVE)
|
||||
|
||||
|
||||
|
||||
// Pulse Sensor Stuff
|
||||
eggshell = color(255, 253, 248);
|
||||
pulseWave = color(224, 56, 45);
|
||||
|
||||
PulseWaveY = new int[PulseBuffSize];
|
||||
BPMwaveY = new int[BPMbuffSize];
|
||||
rate = new int[10];
|
||||
setPulseWidgetVariables();
|
||||
initializePulseFinderVariables();
|
||||
|
||||
analogModeButton = new Button((int)(x + 3), (int)(y + 3 - navHeight), 120, navHeight - 6, "Turn Analog Read On", 12);
|
||||
analogModeButton.setCornerRoundess((int)(navHeight-6));
|
||||
analogModeButton.setFont(p6,10);
|
||||
analogModeButton.setColorNotPressed(color(57,128,204));
|
||||
analogModeButton.textColorNotActive = color(255);
|
||||
analogModeButton.hasStroke(false);
|
||||
analogModeButton.setHelpText("Click this button to activate analog reading on the Cyton");
|
||||
|
||||
}
|
||||
|
||||
void update(){
|
||||
super.update(); //calls the parent update() method of Widget (DON'T REMOVE)
|
||||
|
||||
if (curDataPacketInd < 0) return;
|
||||
|
||||
if (eegDataSource == DATASOURCE_CYTON) { // LIVE FROM CYTON
|
||||
|
||||
} else if (eegDataSource == DATASOURCE_GANGLION) { // LIVE FROM GANGLION
|
||||
|
||||
} else if (eegDataSource == DATASOURCE_SYNTHETIC) { // SYNTHETIC
|
||||
|
||||
}
|
||||
else { // PLAYBACK
|
||||
|
||||
}
|
||||
|
||||
int numSamplesToProcess = curDataPacketInd - lastProcessedDataPacketInd;
|
||||
if (numSamplesToProcess < 0) {
|
||||
numSamplesToProcess += dataPacketBuff.length; //<>//
|
||||
}
|
||||
// Shift internal ring buffer numSamplesToProcess
|
||||
if (numSamplesToProcess > 0) {
|
||||
for(int i=0; i < PulseWaveY.length - numSamplesToProcess; i++){
|
||||
PulseWaveY[i] = PulseWaveY[i+numSamplesToProcess]; //<>//
|
||||
}
|
||||
}
|
||||
|
||||
// for each new sample
|
||||
int samplesProcessed = 0;
|
||||
while (samplesProcessed < numSamplesToProcess) {
|
||||
lastProcessedDataPacketInd++;
|
||||
|
||||
// Watch for wrap around
|
||||
if (lastProcessedDataPacketInd > dataPacketBuff.length - 1) {
|
||||
lastProcessedDataPacketInd = 0;
|
||||
}
|
||||
|
||||
int signal = dataPacketBuff[lastProcessedDataPacketInd].auxValues[0];
|
||||
|
||||
processSignal(signal);
|
||||
PulseWaveY[PulseWaveY.length - numSamplesToProcess + samplesProcessed] = signal; //<>//
|
||||
|
||||
samplesProcessed++;
|
||||
}
|
||||
|
||||
if(QS){
|
||||
QS = false;
|
||||
for(int i=0; i<BPMwaveY.length-1; i++){
|
||||
BPMwaveY[i] = BPMwaveY[i+1];
|
||||
}
|
||||
BPMwaveY[BPMwaveY.length-1] = BPM;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void draw(){
|
||||
super.draw(); //calls the parent draw() method of Widget (DON'T REMOVE)
|
||||
|
||||
|
||||
//remember to refer to x,y,w,h which are the positioning variables of the Widget class
|
||||
pushStyle();
|
||||
|
||||
|
||||
fill(graphBG);
|
||||
stroke(graphStroke);
|
||||
rect(PulseWindowX,PulseWindowY,PulseWindowWidth,PulseWindowHeight);
|
||||
rect(BPMwindowX,BPMwindowY,BPMwindowWidth,BPMwindowHeight);
|
||||
|
||||
fill(50);
|
||||
textFont(p4, 16);
|
||||
textAlign(LEFT,CENTER);
|
||||
text("BPM "+BPM, BPMposX, BPMposY);
|
||||
text("IBI "+IBI+"mS", IBIposX, IBIposY);
|
||||
|
||||
if (analogReadOn) {
|
||||
drawWaves();
|
||||
}
|
||||
|
||||
analogModeButton.draw();
|
||||
|
||||
popStyle();
|
||||
}
|
||||
|
||||
void screenResized(){
|
||||
super.screenResized(); //calls the parent screenResized() method of Widget (DON'T REMOVE)
|
||||
|
||||
println("Pulse Sensor Widget -- Screen Resized.");
|
||||
|
||||
setPulseWidgetVariables();
|
||||
analogModeButton.setPos((int)(x + 3), (int)(y + 3 - navHeight));
|
||||
}
|
||||
|
||||
void mousePressed(){
|
||||
super.mousePressed(); //calls the parent mousePressed() method of Widget (DON'T REMOVE)
|
||||
|
||||
if (analogModeButton.isMouseHere()) {
|
||||
analogModeButton.setIsActive(true);
|
||||
}
|
||||
}
|
||||
|
||||
void mouseReleased(){
|
||||
super.mouseReleased(); //calls the parent mouseReleased() method of Widget (DON'T REMOVE)
|
||||
|
||||
//put your code here...
|
||||
if(analogModeButton.isActive && analogModeButton.isMouseHere()){
|
||||
// println("analogModeButton...");
|
||||
if(cyton.isPortOpen()) {
|
||||
if (analogReadOn) {
|
||||
cyton.setBoardMode(BOARD_MODE_DEFAULT);
|
||||
output("Starting to read accelerometer");
|
||||
analogModeButton.setString("Turn Analog Read On");
|
||||
} else {
|
||||
cyton.setBoardMode(BOARD_MODE_ANALOG);
|
||||
output("Starting to read analog inputs on pin marked D11");
|
||||
analogModeButton.setString("Turn Analog Read Off");
|
||||
}
|
||||
analogReadOn = !analogReadOn;
|
||||
}
|
||||
}
|
||||
analogModeButton.setIsActive(false);
|
||||
}
|
||||
|
||||
//add custom functions here
|
||||
void setPulseWidgetVariables(){
|
||||
PulseWindowWidth = ((w/4)*3) - padding;
|
||||
PulseWindowHeight = h - padding *2;
|
||||
PulseWindowX = x + padding;
|
||||
PulseWindowY = y + h - PulseWindowHeight - padding;
|
||||
|
||||
BPMwindowWidth = w/4 - (padding + padding/2);
|
||||
BPMwindowHeight = PulseWindowHeight; // - padding;
|
||||
BPMwindowX = PulseWindowX + PulseWindowWidth + padding/2;
|
||||
BPMwindowY = PulseWindowY; // + padding;
|
||||
|
||||
BPMposX = BPMwindowX + padding/2;
|
||||
BPMposY = y - padding; // BPMwindowHeight + int(float(padding)*2.5);
|
||||
IBIposX = PulseWindowX + PulseWindowWidth/2; // + padding/2
|
||||
IBIposY = y - padding;
|
||||
|
||||
// float py;
|
||||
// float by;
|
||||
// for(int i=0; i<PulseWaveY.length; i++){
|
||||
// py = map(float(PulseWaveY[i]),
|
||||
// 0.0,1023.0,
|
||||
// float(PulseWindowY + PulseWindowHeight),float(PulseWindowY)
|
||||
// );
|
||||
// PulseWaveY[i] = int(py);
|
||||
// }
|
||||
// for(int i=0; i<BPMwaveY.length; i++){
|
||||
// BPMwaveY[i] = BPMwindowY + BPMwindowHeight-1;
|
||||
// }
|
||||
}
|
||||
|
||||
void initializePulseFinderVariables(){
|
||||
sampleCounter = 0;
|
||||
lastBeatTime = 0;
|
||||
P = 512;
|
||||
T = 512;
|
||||
thresh = 530;
|
||||
amp = 0;
|
||||
firstBeat = true;
|
||||
secondBeat = false;
|
||||
BPM = 0;
|
||||
Signal = 512;
|
||||
IBI = 600;
|
||||
Pulse = false;
|
||||
QS = false;
|
||||
|
||||
theta = 0.0;
|
||||
amplitude = 300;
|
||||
syntheticMultiplier = 1;
|
||||
|
||||
thatTime = millis();
|
||||
|
||||
// float py = map(float(Signal),
|
||||
// 0.0,1023.0,
|
||||
// float(PulseWindowY + PulseWindowHeight),float(PulseWindowY)
|
||||
// );
|
||||
for(int i=0; i<PulseWaveY.length; i++){
|
||||
PulseWaveY[i] = Signal;
|
||||
|
||||
// PulseWaveY[i] = PulseWindowY + PulseWindowHeight/2;
|
||||
}
|
||||
for(int i=0; i<BPMwaveY.length; i++){
|
||||
BPMwaveY[i] = BPM;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void drawWaves(){
|
||||
int xi, yi;
|
||||
noFill();
|
||||
strokeWeight(1);
|
||||
stroke(pulseWave);
|
||||
beginShape(); // using beginShape() renders fast
|
||||
for(int i=0; i<PulseWaveY.length; i++){
|
||||
xi = int(map(i,0, PulseWaveY.length-1,0, PulseWindowWidth-1));
|
||||
xi += PulseWindowX;
|
||||
yi = int(map(PulseWaveY[i],0.0,1023.0,
|
||||
float(PulseWindowY + PulseWindowHeight),float(PulseWindowY)));
|
||||
vertex(xi, yi);
|
||||
}
|
||||
endShape();
|
||||
|
||||
strokeWeight(2);
|
||||
stroke(pulseWave);
|
||||
beginShape(); // using beginShape() renders fast
|
||||
for(int i=0; i<BPMwaveY.length; i++){
|
||||
xi = int(map(i,0, BPMwaveY.length-1,0, BPMwindowWidth-1));
|
||||
xi += BPMwindowX;
|
||||
yi = int(map(BPMwaveY[i], 0.0,200.0,
|
||||
float(BPMwindowY + BPMwindowHeight), float(BPMwindowY)));
|
||||
vertex(xi, yi);
|
||||
}
|
||||
endShape();
|
||||
|
||||
}
|
||||
|
||||
// THIS IS THE BEAT FINDING FUNCTION
|
||||
// BASED ON CODE FROM World Famous Electronics, MAKERS OF PULSE SENSOR
|
||||
// https://github.com/WorldFamousElectronics/PulseSensor_Amped_Arduino
|
||||
void processSignal(int sample){ // triggered when Timer2 counts to 124
|
||||
// cli(); // disable interrupts while we do this
|
||||
// Signal = analogRead(pulsePin); // read the Pulse Sensor
|
||||
sampleCounter += (4 * syntheticMultiplier); // keep track of the time in mS with this variable
|
||||
int N = sampleCounter - lastBeatTime; // monitor the time since the last beat to avoid noise
|
||||
|
||||
// find the peak and trough of the pulse wave
|
||||
if(sample < thresh && N > (IBI/5)*3){ // avoid dichrotic noise by waiting 3/5 of last IBI
|
||||
if (sample < T){ // T is the trough
|
||||
T = sample; // keep track of lowest point in pulse wave
|
||||
}
|
||||
}
|
||||
|
||||
if(sample > thresh && sample > P){ // thresh condition helps avoid noise
|
||||
P = sample; // P is the peak
|
||||
} // keep track of highest point in pulse wave
|
||||
|
||||
// NOW IT'S TIME TO LOOK FOR THE HEART BEAT
|
||||
// signal surges up in value every time there is a pulse
|
||||
if (N > 250){ // avoid high frequency noise
|
||||
if ( (sample > thresh) && (Pulse == false) && (N > (IBI/5)*3) ){
|
||||
Pulse = true; // set the Pulse flag when we think there is a pulse
|
||||
IBI = sampleCounter - lastBeatTime; // measure time between beats in mS
|
||||
lastBeatTime = sampleCounter; // keep track of time for next pulse
|
||||
|
||||
if(secondBeat){ // if this is the second beat, if secondBeat == TRUE
|
||||
secondBeat = false; // clear secondBeat flag
|
||||
for(int i=0; i<=9; i++){ // seed the running total to get a realisitic BPM at startup
|
||||
rate[i] = IBI;
|
||||
}
|
||||
}
|
||||
|
||||
if(firstBeat){ // if it's the first time we found a beat, if firstBeat == TRUE
|
||||
firstBeat = false; // clear firstBeat flag
|
||||
secondBeat = true; // set the second beat flag
|
||||
// sei(); // enable interrupts again
|
||||
return; // IBI value is unreliable so discard it
|
||||
}
|
||||
|
||||
|
||||
// keep a running total of the last 10 IBI values
|
||||
int runningTotal = 0; // clear the runningTotal variable
|
||||
|
||||
for(int i=0; i<=8; i++){ // shift data in the rate array
|
||||
rate[i] = rate[i+1]; // and drop the oldest IBI value
|
||||
runningTotal += rate[i]; // add up the 9 oldest IBI values
|
||||
}
|
||||
|
||||
rate[9] = IBI; // add the latest IBI to the rate array
|
||||
runningTotal += rate[9]; // add the latest IBI to runningTotal
|
||||
runningTotal /= 10; // average the last 10 IBI values
|
||||
BPM = 60000/runningTotal; // how many beats can fit into a minute? that's BPM!
|
||||
BPM = constrain(BPM,0,200);
|
||||
QS = true; // set Quantified Self flag
|
||||
// QS FLAG IS NOT CLEARED INSIDE THIS FUNCTION
|
||||
}
|
||||
}
|
||||
|
||||
if (sample < thresh && Pulse == true){ // when the values are going down, the beat is over
|
||||
// digitalWrite(blinkPin,LOW); // turn off pin 13 LED
|
||||
Pulse = false; // reset the Pulse flag so we can do it again
|
||||
amp = P - T; // get amplitude of the pulse wave
|
||||
thresh = amp/2 + T; // set thresh at 50% of the amplitude
|
||||
P = thresh; // reset these for next time
|
||||
T = thresh;
|
||||
}
|
||||
|
||||
if (N > 2500){ // if 2.5 seconds go by without a beat
|
||||
thresh = 530; // set thresh default
|
||||
P = 512; // set P default
|
||||
T = 512; // set T default
|
||||
lastBeatTime = sampleCounter; // bring the lastBeatTime up to date
|
||||
firstBeat = true; // set these to avoid noise
|
||||
secondBeat = false; // when we get the heartbeat back
|
||||
}
|
||||
|
||||
// sei(); // enable interrupts when youre done!
|
||||
}// end processSignal
|
||||
|
||||
|
||||
};
|
||||
@@ -28,8 +28,7 @@ class W_template extends Widget {
|
||||
|
||||
widgetTemplateButton = new Button (x + w/2, y + h/2, 200, navHeight, "Design Your Own Widget!", 12);
|
||||
widgetTemplateButton.setFont(p4, 14);
|
||||
widgetTemplateButton.setURL("http://docs.openbci.com/OpenBCI%20Software/");
|
||||
|
||||
widgetTemplateButton.setURL("http://docs.openbci.com/Tutorials/15-Custom_Widgets");
|
||||
}
|
||||
|
||||
void update(){
|
||||
|
||||
@@ -28,7 +28,7 @@ class W_timeSeries extends Widget {
|
||||
|
||||
ChannelBar[] channelBars;
|
||||
|
||||
int[] xLimOptions = {3, 5, 8}; // number of seconds (x axis of graph)
|
||||
int[] xLimOptions = {1, 3, 5, 7}; // number of seconds (x axis of graph)
|
||||
int[] yLimOptions = {0, 50, 100, 200, 400, 1000, 10000}; // 0 = Autoscale ... everything else is uV
|
||||
|
||||
int xLim = xLimOptions[1]; //start at 5s
|
||||
@@ -57,7 +57,6 @@ class W_timeSeries extends Widget {
|
||||
//Note that these 3 dropdowns correspond to the 3 global functions below
|
||||
//You just need to make sure the "id" (the 1st String) has the same name as the corresponding function
|
||||
|
||||
|
||||
addDropdown("VertScale_TS", "Vert Scale", Arrays.asList("Auto", "50 uV", "100 uV", "200 uV", "400 uV", "1000 uV", "10000 uV"), startingVertScaleIndex);
|
||||
addDropdown("Duration", "Window", Arrays.asList("1 sec", "3 sec", "5 sec", "7 sec"), 2);
|
||||
// addDropdown("Spillover", "Spillover", Arrays.asList("False", "True"), 0);
|
||||
@@ -92,7 +91,7 @@ class W_timeSeries extends Widget {
|
||||
channelBars[i] = tempBar;
|
||||
}
|
||||
|
||||
if(eegDataSource == DATASOURCE_NORMAL_W_AUX){
|
||||
if(eegDataSource == DATASOURCE_CYTON){
|
||||
hardwareSettingsButton = new Button((int)(x + 3), (int)(y + navHeight + 3), 120, navHeight - 6, "Hardware Settings", 12);
|
||||
hardwareSettingsButton.setCornerRoundess((int)(navHeight-6));
|
||||
hardwareSettingsButton.setFont(p6,10);
|
||||
@@ -110,7 +109,6 @@ class W_timeSeries extends Widget {
|
||||
int y_hsc = int(ts_y);
|
||||
int w_hsc = int(ts_w); //width of montage controls (on left of montage)
|
||||
int h_hsc = int(ts_h); //height of montage controls (on left of montage)
|
||||
|
||||
hsc = new HardwareSettingsController((int)channelBars[0].plot.getPos()[0] + 2, (int)channelBars[0].plot.getPos()[1], (int)channelBars[0].plot.getOuterDim()[0], h_hsc - 4, channelBarHeight);
|
||||
}
|
||||
|
||||
@@ -154,7 +152,7 @@ class W_timeSeries extends Widget {
|
||||
channelBars[i].draw();
|
||||
}
|
||||
|
||||
if(eegDataSource == DATASOURCE_NORMAL_W_AUX){
|
||||
if(eegDataSource == DATASOURCE_CYTON){
|
||||
hardwareSettingsButton.draw();
|
||||
}
|
||||
|
||||
@@ -198,7 +196,7 @@ class W_timeSeries extends Widget {
|
||||
|
||||
hsc.screenResized((int)channelBars[0].plot.getPos()[0] + 2, (int)channelBars[0].plot.getPos()[1], (int)channelBars[0].plot.getOuterDim()[0], (int)ts_h - 4, channelBarHeight);
|
||||
|
||||
if(eegDataSource == DATASOURCE_NORMAL_W_AUX){
|
||||
if(eegDataSource == DATASOURCE_CYTON){
|
||||
hardwareSettingsButton.setPos((int)(x0 + 3), (int)(y0 + navHeight + 3));
|
||||
}
|
||||
}
|
||||
@@ -207,7 +205,7 @@ class W_timeSeries extends Widget {
|
||||
super.mousePressed(); //calls the parent mousePressed() method of Widget (DON'T REMOVE)
|
||||
|
||||
|
||||
if(eegDataSource == DATASOURCE_NORMAL_W_AUX){
|
||||
if(eegDataSource == DATASOURCE_CYTON){
|
||||
//put your code here...
|
||||
if (hardwareSettingsButton.isMouseHere()) {
|
||||
hardwareSettingsButton.setIsActive(true);
|
||||
@@ -228,7 +226,7 @@ class W_timeSeries extends Widget {
|
||||
void mouseReleased(){
|
||||
super.mouseReleased(); //calls the parent mouseReleased() method of Widget (DON'T REMOVE)
|
||||
|
||||
if(eegDataSource == DATASOURCE_NORMAL_W_AUX){
|
||||
if(eegDataSource == DATASOURCE_CYTON){
|
||||
//put your code here...
|
||||
if(hardwareSettingsButton.isActive && hardwareSettingsButton.isMouseHere()){
|
||||
println("toggle...");
|
||||
@@ -253,7 +251,6 @@ class W_timeSeries extends Widget {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
//These functions need to be global! These functions are activated when an item from the corresponding dropdown is selected
|
||||
@@ -390,7 +387,7 @@ class ChannelBar{
|
||||
onOffButton.setColorNotPressed(channelColors[(channelNumber-1)%8]);
|
||||
onOffButton.hasStroke(false);
|
||||
|
||||
if(eegDataSource == DATASOURCE_NORMAL_W_AUX){
|
||||
if(eegDataSource == DATASOURCE_CYTON){
|
||||
impButton_diameter = 22;
|
||||
impCheckButton = new Button (x + 36, y + int(h/2) - int(impButton_diameter/2), impButton_diameter, impButton_diameter, "\u2126", fontInfo.buttonLabel_size);
|
||||
impCheckButton.setFont(h2, 16);
|
||||
@@ -400,7 +397,6 @@ class ChannelBar{
|
||||
} else {
|
||||
impButton_diameter = 0;
|
||||
}
|
||||
|
||||
numSeconds = 5;
|
||||
plot = new GPlot(_parent);
|
||||
plot.setPos(x + 36 + 4 + impButton_diameter, y);
|
||||
@@ -411,7 +407,6 @@ class ChannelBar{
|
||||
plot.setYLim(-200,200);
|
||||
plot.setPointSize(2);
|
||||
plot.setPointColor(0);
|
||||
|
||||
if(channelNumber == nchan){
|
||||
plot.getXAxis().setAxisLabelText("Time (s)");
|
||||
}
|
||||
@@ -419,14 +414,6 @@ class ChannelBar{
|
||||
|
||||
nPoints = nPointsBasedOnDataSource();
|
||||
|
||||
if(eegDataSource == DATASOURCE_NORMAL_W_AUX){
|
||||
nPoints = numSeconds * (int)openBCI.fs_Hz;
|
||||
}else if(eegDataSource == DATASOURCE_GANGLION || nchan == 4){
|
||||
nPoints = numSeconds * (int)ganglion.fs_Hz;
|
||||
}else{
|
||||
nPoints = numSeconds * (int)openBCI.fs_Hz;
|
||||
}
|
||||
|
||||
channelPoints = new GPointsArray(nPoints);
|
||||
timeBetweenPoints = (float)numSeconds / (float)nPoints;
|
||||
|
||||
@@ -529,7 +516,7 @@ class ChannelBar{
|
||||
//draw onOff Button
|
||||
onOffButton.draw();
|
||||
//draw impedance check Button
|
||||
if(eegDataSource == DATASOURCE_NORMAL_W_AUX){
|
||||
if(eegDataSource == DATASOURCE_CYTON){
|
||||
impCheckButton.draw();
|
||||
}
|
||||
|
||||
@@ -566,16 +553,7 @@ class ChannelBar{
|
||||
}
|
||||
|
||||
int nPointsBasedOnDataSource(){
|
||||
int _nPoints;
|
||||
if(eegDataSource == DATASOURCE_NORMAL_W_AUX){
|
||||
_nPoints = numSeconds * (int)openBCI.fs_Hz;
|
||||
}else if(eegDataSource == DATASOURCE_GANGLION || nchan == 4){
|
||||
_nPoints = numSeconds * (int)ganglion.fs_Hz;
|
||||
}else{
|
||||
_nPoints = numSeconds * (int)openBCI.fs_Hz;
|
||||
}
|
||||
|
||||
return _nPoints;
|
||||
return numSeconds * (int)getSampleRateSafe();
|
||||
}
|
||||
|
||||
void adjustTimeAxis(int _newTimeSize){
|
||||
@@ -635,7 +613,7 @@ class ChannelBar{
|
||||
onOffButton.but_x = x + 6;
|
||||
onOffButton.but_y = y + int(h/2) - int(onOff_diameter/2);
|
||||
|
||||
if(eegDataSource == DATASOURCE_NORMAL_W_AUX){
|
||||
if(eegDataSource == DATASOURCE_CYTON){
|
||||
impCheckButton.but_x = x + 36;
|
||||
impCheckButton.but_y = y + int(h/2) - int(impButton_diameter/2);
|
||||
}
|
||||
@@ -657,7 +635,7 @@ class ChannelBar{
|
||||
onOffButton.setIsActive(true);
|
||||
}
|
||||
|
||||
if(eegDataSource == DATASOURCE_NORMAL_W_AUX){
|
||||
if(eegDataSource == DATASOURCE_CYTON){
|
||||
if(impCheckButton.isMouseHere()){
|
||||
println("[" + channelNumber + "] imp pressed");
|
||||
impCheckButton.setIsActive(true);
|
||||
@@ -683,7 +661,7 @@ class ChannelBar{
|
||||
|
||||
onOffButton.setIsActive(false);
|
||||
|
||||
if(eegDataSource == DATASOURCE_NORMAL_W_AUX){
|
||||
if(eegDataSource == DATASOURCE_CYTON){
|
||||
if(impCheckButton.isMouseHere() && impCheckButton.isActive()){
|
||||
println("[" + channelNumber + "] imp released");
|
||||
w_timeSeries.hsc.toggleImpedanceCheck(channelNumber-1); // 'n' indicates the N inputs and '1' indicates test impedance
|
||||
@@ -864,7 +842,7 @@ if(has_processed){
|
||||
val_uV = processed_file.get(elm)[Ichan][startIndex];
|
||||
|
||||
|
||||
data[Ichan][i] = (int) (0.5f+ val_uV / openBCI.get_scale_fac_uVolts_per_count()); //convert to counts, the 0.5 is to ensure roundi
|
||||
data[Ichan][i] = (int) (0.5f+ val_uV / cyton.get_scale_fac_uVolts_per_count()); //convert to counts, the 0.5 is to ensure roundi
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
@@ -98,37 +98,22 @@ class W_accelerometer extends Widget {
|
||||
Z[i] = AccelWindowY + (AccelWindowHeight/4)*3; // Z at 3/4
|
||||
}
|
||||
|
||||
if(eegDataSource == DATASOURCE_GANGLION){
|
||||
// accelModeButton = new Button((int)(x + w/2), (int)(y +80), 120, navHeight - 6, "Turn Accel. On", 12);
|
||||
accelModeButton = new Button((int)(x + 3), (int)(y + 3 - navHeight), 120, navHeight - 6, "Turn Accel. On", 12);
|
||||
accelModeButton.setCornerRoundess((int)(navHeight-6));
|
||||
accelModeButton.setFont(p6,10);
|
||||
// accelModeButton.setStrokeColor((int)(color(150)));
|
||||
// accelModeButton.setColorNotPressed(openbciBlue);
|
||||
accelModeButton.setColorNotPressed(color(57,128,204));
|
||||
accelModeButton.textColorNotActive = color(255);
|
||||
// accelModeButton.setStrokeColor((int)(color(138, 182, 229, 100)));
|
||||
accelModeButton.hasStroke(false);
|
||||
// accelModeButton.setColorNotPressed((int)(color(138, 182, 229)));
|
||||
accelModeButton.setHelpText("Click this button to activate/deactivate the accelerometer of your Ganglion board!");
|
||||
}
|
||||
|
||||
//This is the protocol for setting up dropdowns.
|
||||
//Note that these 3 dropdowns correspond to the 3 global functions below
|
||||
//You just need to make sure the "id" (the 1st String) has the same name as the corresponding function
|
||||
// addDropdown("Thisdrop", "Drop 1", Arrays.asList("A", "B"), 0);
|
||||
// addDropdown("Dropdown2", "Drop 2", Arrays.asList("C", "D", "E"), 1);
|
||||
// addDropdown("Dropdown3", "Drop 3", Arrays.asList("F", "G", "H", "I"), 3);
|
||||
|
||||
accelModeButton = new Button((int)(x + 3), (int)(y + 3 - navHeight), 120, navHeight - 6, "Turn Accel. On", 12);
|
||||
accelModeButton.setCornerRoundess((int)(navHeight-6));
|
||||
accelModeButton.setFont(p6,10);
|
||||
accelModeButton.setColorNotPressed(color(57,128,204));
|
||||
accelModeButton.textColorNotActive = color(255);
|
||||
accelModeButton.hasStroke(false);
|
||||
accelModeButton.setHelpText("Click this button to activate/deactivate the accelerometer!");
|
||||
}
|
||||
|
||||
public void initPlayground(OpenBCI_ADS1299 _OBCI) {
|
||||
public void initPlayground(Cyton _OBCI) {
|
||||
OBCI_inited = true;
|
||||
}
|
||||
|
||||
float adjustYMaxMinBasedOnSource(){
|
||||
float _yMaxMin;
|
||||
if(eegDataSource == DATASOURCE_NORMAL_W_AUX){
|
||||
if(eegDataSource == DATASOURCE_CYTON){
|
||||
_yMaxMin = 4.0;
|
||||
}else if(eegDataSource == DATASOURCE_GANGLION || nchan == 4){
|
||||
_yMaxMin = 2.0;
|
||||
@@ -150,10 +135,10 @@ class W_accelerometer extends Widget {
|
||||
currentYvalue = map(Y[Y.length-1], AccelWindowY, AccelWindowY+AccelWindowHeight, yMaxMin, -yMaxMin);
|
||||
currentZvalue = map(Z[Z.length-1], AccelWindowY, AccelWindowY+AccelWindowHeight, yMaxMin, -yMaxMin);
|
||||
shiftWave();
|
||||
} else if (eegDataSource == DATASOURCE_NORMAL_W_AUX) {
|
||||
currentXvalue = openBCI.validAuxValues[0] * openBCI.get_scale_fac_accel_G_per_count();
|
||||
currentYvalue = openBCI.validAuxValues[1] * openBCI.get_scale_fac_accel_G_per_count();
|
||||
currentZvalue = openBCI.validAuxValues[2] * openBCI.get_scale_fac_accel_G_per_count();
|
||||
} else if (eegDataSource == DATASOURCE_CYTON) {
|
||||
currentXvalue = hub.validAccelValues[0] * cyton.get_scale_fac_accel_G_per_count();
|
||||
currentYvalue = hub.validAccelValues[1] * cyton.get_scale_fac_accel_G_per_count();
|
||||
currentZvalue = hub.validAccelValues[2] * cyton.get_scale_fac_accel_G_per_count();
|
||||
X[X.length-1] =
|
||||
int(map(currentXvalue, -yMaxMin, yMaxMin, float(AccelWindowY+AccelWindowHeight), float(AccelWindowY)));
|
||||
X[X.length-1] = constrain(X[X.length-1], AccelWindowY, AccelWindowY+AccelWindowHeight);
|
||||
@@ -166,9 +151,9 @@ class W_accelerometer extends Widget {
|
||||
|
||||
shiftWave();
|
||||
} else if (eegDataSource == DATASOURCE_GANGLION) {
|
||||
currentXvalue = ganglion.accelArray[0] * ganglion.get_scale_fac_accel_G_per_count();
|
||||
currentYvalue = ganglion.accelArray[1] * ganglion.get_scale_fac_accel_G_per_count();
|
||||
currentZvalue = ganglion.accelArray[2] * ganglion.get_scale_fac_accel_G_per_count();
|
||||
currentXvalue = hub.validAccelValues[0] * ganglion.get_scale_fac_accel_G_per_count();
|
||||
currentYvalue = hub.validAccelValues[1] * ganglion.get_scale_fac_accel_G_per_count();
|
||||
currentZvalue = hub.validAccelValues[2] * ganglion.get_scale_fac_accel_G_per_count();
|
||||
X[X.length-1] =
|
||||
int(map(currentXvalue, -yMaxMin, yMaxMin, float(AccelWindowY+AccelWindowHeight), float(AccelWindowY)));
|
||||
X[X.length-1] = constrain(X[X.length-1], AccelWindowY, AccelWindowY+AccelWindowHeight);
|
||||
@@ -194,90 +179,77 @@ class W_accelerometer extends Widget {
|
||||
pushStyle();
|
||||
//put your code here...
|
||||
//remember to refer to x,y,w,h which are the positioning variables of the Widget class
|
||||
if (true) {
|
||||
// fill(graphBG);
|
||||
// stroke(strokeColor);
|
||||
// rect(x, y, w, h);
|
||||
// textFont(f4, 24);
|
||||
// textAlign(LEFT, TOP);
|
||||
// fill(textColor);
|
||||
// text("Acellerometer Gs", x + 10, y + 10);
|
||||
|
||||
fill(50);
|
||||
textFont(p4, 14);
|
||||
textAlign(CENTER,CENTER);
|
||||
text("z", PolarWindowX, (PolarWindowY-PolarWindowHeight/2)-12);
|
||||
text("x", (PolarWindowX+PolarWindowWidth/2)+8, PolarWindowY-5);
|
||||
text("y", (PolarWindowX+PolarCorner)+10, (PolarWindowY-PolarCorner)-10);
|
||||
fill(50);
|
||||
textFont(p4, 14);
|
||||
textAlign(CENTER,CENTER);
|
||||
text("z", PolarWindowX, (PolarWindowY-PolarWindowHeight/2)-12);
|
||||
text("x", (PolarWindowX+PolarWindowWidth/2)+8, PolarWindowY-5);
|
||||
text("y", (PolarWindowX+PolarCorner)+10, (PolarWindowY-PolarCorner)-10);
|
||||
|
||||
fill(graphBG); // pulse window background
|
||||
stroke(graphStroke);
|
||||
rect(AccelWindowX, AccelWindowY, AccelWindowWidth, AccelWindowHeight);
|
||||
line(AccelWindowX, AccelWindowY + AccelWindowHeight/2, AccelWindowX+AccelWindowWidth, AccelWindowY + AccelWindowHeight/2); //midline
|
||||
fill(graphBG);
|
||||
stroke(graphStroke);
|
||||
rect(AccelWindowX, AccelWindowY, AccelWindowWidth, AccelWindowHeight);
|
||||
line(AccelWindowX, AccelWindowY + AccelWindowHeight/2, AccelWindowX+AccelWindowWidth, AccelWindowY + AccelWindowHeight/2); //midline
|
||||
|
||||
fill(50);
|
||||
textFont(p5, 12);
|
||||
textAlign(CENTER,CENTER);
|
||||
text("+"+(int)yMaxMin+"g", AccelWindowX+AccelWindowWidth + 12, AccelWindowY);
|
||||
text("0g", AccelWindowX+AccelWindowWidth + 12, AccelWindowY + AccelWindowHeight/2);
|
||||
text("-"+(int)yMaxMin+"g", AccelWindowX+AccelWindowWidth + 12, AccelWindowY + AccelWindowHeight);
|
||||
fill(50);
|
||||
textFont(p5, 12);
|
||||
textAlign(CENTER,CENTER);
|
||||
text("+"+(int)yMaxMin+"g", AccelWindowX+AccelWindowWidth + 12, AccelWindowY);
|
||||
text("0g", AccelWindowX+AccelWindowWidth + 12, AccelWindowY + AccelWindowHeight/2);
|
||||
text("-"+(int)yMaxMin+"g", AccelWindowX+AccelWindowWidth + 12, AccelWindowY + AccelWindowHeight);
|
||||
|
||||
|
||||
fill(graphBG); // pulse window background
|
||||
stroke(graphStroke);
|
||||
ellipse(PolarWindowX,PolarWindowY,PolarWindowWidth,PolarWindowHeight);
|
||||
fill(graphBG); // pulse window background
|
||||
stroke(graphStroke);
|
||||
ellipse(PolarWindowX,PolarWindowY,PolarWindowWidth,PolarWindowHeight);
|
||||
|
||||
stroke(180);
|
||||
line(PolarWindowX-PolarWindowWidth/2, PolarWindowY, PolarWindowX+PolarWindowWidth/2, PolarWindowY);
|
||||
line(PolarWindowX, PolarWindowY-PolarWindowHeight/2, PolarWindowX, PolarWindowY+PolarWindowHeight/2);
|
||||
line(PolarWindowX-PolarCorner, PolarWindowY+PolarCorner, PolarWindowX+PolarCorner, PolarWindowY-PolarCorner);
|
||||
stroke(180);
|
||||
line(PolarWindowX-PolarWindowWidth/2, PolarWindowY, PolarWindowX+PolarWindowWidth/2, PolarWindowY);
|
||||
line(PolarWindowX, PolarWindowY-PolarWindowHeight/2, PolarWindowX, PolarWindowY+PolarWindowHeight/2);
|
||||
line(PolarWindowX-PolarCorner, PolarWindowY+PolarCorner, PolarWindowX+PolarCorner, PolarWindowY-PolarCorner);
|
||||
|
||||
fill(50);
|
||||
textFont(p3, 16);
|
||||
fill(50);
|
||||
textFont(p3, 16);
|
||||
|
||||
if (eegDataSource == DATASOURCE_NORMAL_W_AUX) { // LIVE
|
||||
// fill(Xcolor);
|
||||
// text("X " + nf(currentXvalue, 1, 3), x+10, y+40);
|
||||
// fill(Ycolor);
|
||||
// text("Y " + nf(currentYvalue, 1, 3), x+10, y+80);
|
||||
// fill(Zcolor);
|
||||
// text("Z " + nf(currentZvalue, 1, 3), x+10, y+120);
|
||||
drawAccValues();
|
||||
draw3DGraph();
|
||||
drawAccWave();
|
||||
} else if (eegDataSource == DATASOURCE_GANGLION) {
|
||||
if (eegDataSource == DATASOURCE_CYTON) { // LIVE
|
||||
// fill(Xcolor);
|
||||
// text("X " + nf(currentXvalue, 1, 3), x+10, y+40);
|
||||
// fill(Ycolor);
|
||||
// text("Y " + nf(currentYvalue, 1, 3), x+10, y+80);
|
||||
// fill(Zcolor);
|
||||
// text("Z " + nf(currentZvalue, 1, 3), x+10, y+120);
|
||||
drawAccValues();
|
||||
draw3DGraph();
|
||||
drawAccWave();
|
||||
if (cyton.getBoardMode() != BOARD_MODE_DEFAULT) {
|
||||
accelModeButton.setString("Turn Accel On");
|
||||
accelModeButton.draw();
|
||||
drawAccValues();
|
||||
draw3DGraph();
|
||||
drawAccWave();
|
||||
} else if (eegDataSource == DATASOURCE_SYNTHETIC) { // SYNTHETIC
|
||||
// fill(Xcolor);
|
||||
// text("X "+nf(currentXvalue, 1, 3), x+10, y+40);
|
||||
// fill(Ycolor);
|
||||
// text("Y "+nf(currentYvalue, 1, 3), x+10, y+80);
|
||||
// fill(Zcolor);
|
||||
// text("Z "+nf(currentZvalue, 1, 3), x+10, y+120);
|
||||
drawAccValues();
|
||||
draw3DGraph();
|
||||
drawAccWave();
|
||||
}
|
||||
else { // PLAYBACK
|
||||
drawAccValues();
|
||||
draw3DGraph();
|
||||
drawAccWave2();
|
||||
}
|
||||
} else if (eegDataSource == DATASOURCE_GANGLION) {
|
||||
if (ganglion.isBLE()) accelModeButton.draw();
|
||||
drawAccValues();
|
||||
draw3DGraph();
|
||||
drawAccWave();
|
||||
} else if (eegDataSource == DATASOURCE_SYNTHETIC) { // SYNTHETIC
|
||||
drawAccValues();
|
||||
draw3DGraph();
|
||||
drawAccWave();
|
||||
}
|
||||
else { // PLAYBACK
|
||||
drawAccValues();
|
||||
draw3DGraph();
|
||||
drawAccWave2();
|
||||
}
|
||||
|
||||
// pushStyle();
|
||||
// textFont(h1,24);
|
||||
// fill(bgColor);
|
||||
// textAlign(CENTER,CENTER);
|
||||
// text(widgetTitle, x + w/2, y + h/2);
|
||||
// popStyle();
|
||||
popStyle();
|
||||
}
|
||||
|
||||
void setGraphDimensions(){
|
||||
println("accel w "+w);
|
||||
println("accel h "+h);
|
||||
println("accel x "+x);
|
||||
println("accel y "+y);
|
||||
AccelWindowWidth = w - padding*2;
|
||||
AccelWindowHeight = int((float(h) - float(padding*3))/2.0);
|
||||
AccelWindowX = x + padding;
|
||||
@@ -301,18 +273,6 @@ class W_accelerometer extends Widget {
|
||||
super.screenResized(); //calls the parent screenResized() method of Widget (DON'T REMOVE)
|
||||
|
||||
int dy = y - prevY;
|
||||
println("dy = " + dy);
|
||||
|
||||
//put your code here...
|
||||
// AccelWindowWidth = int(w) - 10;
|
||||
// AccelWindowX = int(x)+5;
|
||||
// AccelWindowY = int(y)-10+int(h)/2;
|
||||
//
|
||||
// PolarWindowX = x+AccelWindowWidth-90;
|
||||
// PolarWindowY = y+83;
|
||||
// PolarCorner = (sqrt(2)*PolarWindowWidth/2)/2;
|
||||
println("Acc Widget -- Screen Resized.");
|
||||
|
||||
setGraphDimensions();
|
||||
|
||||
//empty arrays to start redrawing from scratch
|
||||
@@ -320,15 +280,9 @@ class W_accelerometer extends Widget {
|
||||
X[i] = AccelWindowY + AccelWindowHeight/4; // X at 1/4
|
||||
Y[i] = AccelWindowY + AccelWindowHeight/2; // Y at 1/2
|
||||
Z[i] = AccelWindowY + (AccelWindowHeight/4)*3; // Z at 3/4
|
||||
// X[i] = X[i] + dy;
|
||||
// Y[i] = Y[i] + dy;
|
||||
// Z[i] = Z[i] + dy;
|
||||
}
|
||||
|
||||
if(eegDataSource == DATASOURCE_GANGLION){
|
||||
// accelModeButton.setPos((int)(x + w/2 - accelModeButton.but_dx/2), (int)(y + 80));
|
||||
accelModeButton.setPos((int)(x + 3), (int)(y + 3 - navHeight));
|
||||
}
|
||||
accelModeButton.setPos((int)(x + 3), (int)(y + 3 - navHeight));
|
||||
}
|
||||
|
||||
void mousePressed(){
|
||||
@@ -337,6 +291,12 @@ class W_accelerometer extends Widget {
|
||||
//put your code here...
|
||||
if(eegDataSource == DATASOURCE_GANGLION){
|
||||
//put your code here...
|
||||
if (ganglion.isBLE()) {
|
||||
if (accelModeButton.isMouseHere()) {
|
||||
accelModeButton.setIsActive(true);
|
||||
}
|
||||
}
|
||||
} else if (eegDataSource == DATASOURCE_CYTON) {
|
||||
if (accelModeButton.isMouseHere()) {
|
||||
accelModeButton.setIsActive(true);
|
||||
}
|
||||
@@ -350,7 +310,6 @@ class W_accelerometer extends Widget {
|
||||
if(eegDataSource == DATASOURCE_GANGLION){
|
||||
//put your code here...
|
||||
if(accelModeButton.isActive && accelModeButton.isMouseHere()){
|
||||
println("toggle...");
|
||||
if(ganglion.isAccelModeActive()){
|
||||
ganglion.accelStop();
|
||||
|
||||
@@ -361,6 +320,12 @@ class W_accelerometer extends Widget {
|
||||
}
|
||||
}
|
||||
accelModeButton.setIsActive(false);
|
||||
} else if (eegDataSource == DATASOURCE_CYTON) {
|
||||
if(accelModeButton.isActive && accelModeButton.isMouseHere()){
|
||||
cyton.setBoardMode(BOARD_MODE_DEFAULT);
|
||||
output("Starting to read accelerometer");
|
||||
}
|
||||
accelModeButton.setIsActive(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ class W_ganglionImpedance extends Widget {
|
||||
popStyle();
|
||||
}
|
||||
|
||||
if(isHubInitialized && isGanglionObjectInitialized && eegDataSource == DATASOURCE_GANGLION){
|
||||
if(isHubInitialized && isHubObjectInitialized && eegDataSource == DATASOURCE_GANGLION){
|
||||
if(ganglion.isCheckingImpedance()){
|
||||
image(loadingGIF_blue, x + padding + startStopCheck.but_dx + 15, y + padding - 8, 40, 40);
|
||||
}
|
||||
@@ -138,7 +138,7 @@ class W_ganglionImpedance extends Widget {
|
||||
|
||||
//put your code here...
|
||||
if(startStopCheck.isActive && startStopCheck.isMouseHere()){
|
||||
if(isHubInitialized && isGanglionObjectInitialized && eegDataSource == DATASOURCE_GANGLION){
|
||||
if(isHubInitialized && isHubObjectInitialized && eegDataSource == DATASOURCE_GANGLION){
|
||||
if(ganglion.isCheckingImpedance()){
|
||||
ganglion.impedanceStop();
|
||||
startStopCheck.but_txt = "Start Impedance Check";
|
||||
|
||||
@@ -63,7 +63,15 @@ class W_headPlot extends Widget {
|
||||
super.screenResized(); //calls the parent screenResized() method of Widget (DON'T REMOVE)
|
||||
|
||||
//put your code here...
|
||||
headPlot.setPositionSize(x, y, w, h, width, height); //update position of headplot
|
||||
headPlot.hp_x = x;
|
||||
headPlot.hp_y = y;
|
||||
headPlot.hp_w = w;
|
||||
headPlot.hp_h = h;
|
||||
headPlot.hp_win_x = x;
|
||||
headPlot.hp_win_y = y;
|
||||
|
||||
thread("doHardCalcs");
|
||||
// headPlot.setPositionSize(x, y, w, h, width, height); //update position of headplot
|
||||
|
||||
}
|
||||
|
||||
@@ -71,14 +79,21 @@ class W_headPlot extends Widget {
|
||||
super.mousePressed(); //calls the parent mousePressed() method of Widget (DON'T REMOVE)
|
||||
|
||||
//put your code here...
|
||||
|
||||
headPlot.mousePressed();
|
||||
}
|
||||
|
||||
void mouseReleased(){
|
||||
super.mouseReleased(); //calls the parent mouseReleased() method of Widget (DON'T REMOVE)
|
||||
|
||||
//put your code here...
|
||||
headPlot.mouseReleased();
|
||||
}
|
||||
|
||||
void mouseDragged(){
|
||||
super.mouseDragged(); //calls the parent mouseReleased() method of Widget (DON'T REMOVE)
|
||||
|
||||
//put your code here...
|
||||
headPlot.mouseDragged();
|
||||
}
|
||||
|
||||
//add custom class functions here
|
||||
@@ -99,7 +114,7 @@ void Ten20(int n) { //triggered when there is an event in the Ten20 Dropdown
|
||||
*/
|
||||
|
||||
//fft_widget.fft_plot.setXLim(0.1, fft_widget.xLimOptions[n]); //update the xLim of the FFT_Plot
|
||||
println("BOOOOM!" + n);
|
||||
// println("BOOOOM!" + n);
|
||||
closeAllDropdowns(); // do this at the end of all widget-activated functions to ensure proper widget interactivity ... we want to make sure a click makes the menu close
|
||||
|
||||
}
|
||||
@@ -166,6 +181,16 @@ void updateVertScale() {
|
||||
vertScale_uV = default_vertScale_uV * vertScaleFactor[vertScaleFactor_ind];
|
||||
w_headPlot.headPlot.setMaxIntensity_uV(vertScale_uV);
|
||||
}
|
||||
|
||||
void doHardCalcs() {
|
||||
if (!w_headPlot.headPlot.threadLock) {
|
||||
w_headPlot.headPlot.threadLock = true;
|
||||
w_headPlot.headPlot.setPositionSize(w_headPlot.headPlot.hp_x, w_headPlot.headPlot.hp_y, w_headPlot.headPlot.hp_w, w_headPlot.headPlot.hp_h, w_headPlot.headPlot.hp_win_x, w_headPlot.headPlot.hp_win_y);
|
||||
w_headPlot.headPlot.hardCalcsDone = true;
|
||||
w_headPlot.headPlot.threadLock = false;
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
@@ -208,8 +233,20 @@ class HeadPlot {
|
||||
private boolean plot_color_as_log = true;
|
||||
public float smooth_fac = 0.0f;
|
||||
private boolean use_polarity = true;
|
||||
private int mouse_over_elec_index = -1;
|
||||
private boolean isDragging = false;
|
||||
private float drag_x, drag_y;
|
||||
public int hp_win_x = 0;
|
||||
public int hp_win_y = 0;
|
||||
public int hp_x = 0;
|
||||
public int hp_y = 0;
|
||||
public int hp_w = 0;
|
||||
public int hp_h = 0;
|
||||
public boolean hardCalcsDone = false;
|
||||
public boolean threadLock = false;
|
||||
|
||||
HeadPlot(float x, float y, float w, float h, int win_x, int win_y, int n) {
|
||||
|
||||
final int n_elec = n; //8 electrodes assumed....or 16 for 16-channel? Change this!!!
|
||||
nose_x = new int[3];
|
||||
nose_y = new int[3];
|
||||
@@ -225,7 +262,6 @@ class HeadPlot {
|
||||
rel_width = w;
|
||||
rel_height = h;
|
||||
setWindowDimensions(win_x, win_y);
|
||||
|
||||
setMaxIntensity_uV(200.0f); //default intensity scaling for electrodes
|
||||
}
|
||||
|
||||
@@ -252,7 +288,14 @@ class HeadPlot {
|
||||
//rel_height = float(_h)/_win_y;
|
||||
//setWindowDimensions(_win_x, _win_y);
|
||||
|
||||
setPositionSize(_x, _y, _w, _h, _win_x, _win_y);
|
||||
hp_x = _x;
|
||||
hp_y = _y;
|
||||
hp_w = _w;
|
||||
hp_h = _h;
|
||||
hp_win_x = _win_x;
|
||||
hp_win_y = _win_y;
|
||||
thread("doHardCalcs");
|
||||
// setPositionSize(_x, _y, _w, _h, _win_x, _win_y);
|
||||
setMaxIntensity_uV(200.0f); //default intensity scaling for electrodes
|
||||
}
|
||||
|
||||
@@ -639,17 +682,22 @@ class HeadPlot {
|
||||
int toPixels[][][][] = new int[n_wide][n_tall][4][2];
|
||||
int toElectrodes[][][] = new int[n_wide][n_tall][4];
|
||||
//int numConnections[][] = new int[n_wide][n_tall];
|
||||
// println(" HeadPlot B 2 0 -- " + millis());
|
||||
|
||||
//find which pixesl are within the head and which pixels are within an electrode
|
||||
whereAreThePixels(pixelAddress, withinHead, withinElectrode);
|
||||
// println(" HeadPlot B 2 1 -- " + millis());
|
||||
|
||||
//loop over the pixels and make all the connections
|
||||
makeAllTheConnections(withinHead, withinElectrode, toPixels, toElectrodes);
|
||||
// println(" HeadPlot B 2 3 -- " + millis());
|
||||
|
||||
//compute the pixel values when lighting up each electrode invididually
|
||||
for (int Ielec=0; Ielec<n_elec; Ielec++) {
|
||||
computeWeightFactorsGivenOneElectrode_iterative(toPixels, toElectrodes, Ielec, weightFac);
|
||||
}
|
||||
// println(" HeadPlot B 2 4 -- " + millis());
|
||||
|
||||
}
|
||||
|
||||
private void cleanUpTheBoundaries(int pixelAddress[][][], float weightFac[][][]) {
|
||||
@@ -1223,6 +1271,38 @@ class HeadPlot {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isMouseOverElectrode(int n){
|
||||
float elec_mouse_x_dist = electrode_xy[n][0] - mouseX;
|
||||
float elec_mouse_y_dist = electrode_xy[n][1] - mouseY;
|
||||
return elec_mouse_x_dist * elec_mouse_x_dist + elec_mouse_y_dist * elec_mouse_y_dist < elec_diam * elec_diam / 4;
|
||||
}
|
||||
|
||||
private boolean isDraggedElecInsideHead() {
|
||||
int dx = mouseX - circ_x;
|
||||
int dy = mouseY - circ_y;
|
||||
return dx * dx + dy * dy < (circ_diam - elec_diam) * (circ_diam - elec_diam) / 4;
|
||||
}
|
||||
|
||||
void mousePressed() {
|
||||
if (mouse_over_elec_index > -1) {
|
||||
isDragging = true;
|
||||
drag_x = mouseX - electrode_xy[mouse_over_elec_index][0];
|
||||
drag_y = mouseY - electrode_xy[mouse_over_elec_index][1];
|
||||
} else {
|
||||
isDragging = false;
|
||||
}
|
||||
}
|
||||
|
||||
void mouseDragged() {
|
||||
if (isDragging && mouse_over_elec_index > -1 && isDraggedElecInsideHead()) {
|
||||
electrode_xy[mouse_over_elec_index][0] = mouseX - drag_x;
|
||||
electrode_xy[mouse_over_elec_index][1] = mouseY - drag_y;
|
||||
}
|
||||
}
|
||||
|
||||
void mouseReleased() {
|
||||
isDragging = false;
|
||||
}
|
||||
|
||||
public boolean isPixelInsideHead(int pixel_x, int pixel_y) {
|
||||
int dx = pixel_x - circ_x;
|
||||
@@ -1246,8 +1326,10 @@ class HeadPlot {
|
||||
if (drawHeadAsContours) updateHeadImage();
|
||||
} else {
|
||||
//update head voltages
|
||||
updateHeadVoltages();
|
||||
convertVoltagesToHeadImage();
|
||||
if (!threadLock && hardCalcsDone) {
|
||||
updateHeadVoltages();
|
||||
convertVoltagesToHeadImage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1275,14 +1357,25 @@ class HeadPlot {
|
||||
}
|
||||
|
||||
//draw electrodes on the head
|
||||
strokeWeight(1);
|
||||
if (!isDragging) {
|
||||
mouse_over_elec_index = -1;
|
||||
}
|
||||
for (int Ielec=0; Ielec < electrode_xy.length; Ielec++) {
|
||||
if (drawHeadAsContours) {
|
||||
noFill(); //make transparent to allow color to come through from below
|
||||
} else {
|
||||
fill(electrode_rgb[0][Ielec], electrode_rgb[1][Ielec], electrode_rgb[2][Ielec]);
|
||||
}
|
||||
ellipse(electrode_xy[Ielec][0], electrode_xy[Ielec][1], elec_diam, elec_diam); //big circle for the head
|
||||
if (!isDragging && isMouseOverElectrode(Ielec)) {
|
||||
//electrode with a bigger index gets priority in dragging
|
||||
mouse_over_elec_index = Ielec;
|
||||
strokeWeight(2);
|
||||
} else if (mouse_over_elec_index == Ielec) {
|
||||
strokeWeight(2);
|
||||
} else{
|
||||
strokeWeight(1);
|
||||
}
|
||||
ellipse(electrode_xy[Ielec][0], electrode_xy[Ielec][1], elec_diam, elec_diam); //electrode circle
|
||||
}
|
||||
|
||||
//add labels to electrodes
|
||||
|
||||
@@ -75,9 +75,11 @@ class W_networking extends Widget {
|
||||
stream1 = null;
|
||||
stream2 = null;
|
||||
stream3 = null;
|
||||
dataTypes = Arrays.asList("None", "TimeSeries", "FFT", "EMG", "PowerBands", "Widget");
|
||||
defaultBaud = "9600";
|
||||
baudRates = Arrays.asList("1200", "9600", "57600", "115200");
|
||||
|
||||
dataTypes = Arrays.asList("None", "TimeSeries", "FFT", "EMG", "BandPower", "Focus", "Widget");
|
||||
defaultBaud = "115200";
|
||||
// baudRates = Arrays.asList("1200", "9600", "57600", "115200");
|
||||
baudRates = Arrays.asList("57600", "115200", "250000", "500000");
|
||||
protocolMode = "OSC"; //default to OSC
|
||||
addDropdown("Protocol", "Protocol", Arrays.asList("OSC", "UDP", "LSL", "Serial"), protocolIndex);
|
||||
comPorts = new ArrayList<String>(Arrays.asList(Serial.list()));
|
||||
@@ -735,9 +737,11 @@ class W_networking extends Widget {
|
||||
break;
|
||||
case 3 : dt1 = "EMG";
|
||||
break;
|
||||
case 4 : dt1 = "PowerBands";
|
||||
case 4 : dt1 = "BandPower";
|
||||
break;
|
||||
case 5 : dt1 = "Widget";
|
||||
case 5 : dt1 = "Focus";
|
||||
break;
|
||||
case 6 : dt1 = "Widget";
|
||||
break;
|
||||
}
|
||||
switch ((int)cp5_networking_dropdowns.get(ScrollableList.class, "dataType2").getValue()){
|
||||
@@ -749,9 +753,11 @@ class W_networking extends Widget {
|
||||
break;
|
||||
case 3 : dt2 = "EMG";
|
||||
break;
|
||||
case 4 : dt2 = "PowerBands";
|
||||
case 4 : dt2 = "BandPower";
|
||||
break;
|
||||
case 5 : dt2 = "Widget";
|
||||
case 5 : dt2 = "Focus";
|
||||
break;
|
||||
case 6 : dt2 = "Widget";
|
||||
break;
|
||||
}
|
||||
switch ((int)cp5_networking_dropdowns.get(ScrollableList.class, "dataType3").getValue()){
|
||||
@@ -763,9 +769,11 @@ class W_networking extends Widget {
|
||||
break;
|
||||
case 3 : dt3 = "EMG";
|
||||
break;
|
||||
case 4 : dt3 = "PowerBands";
|
||||
case 4 : dt3 = "BandPower";
|
||||
break;
|
||||
case 5 : dt3 = "Widget";
|
||||
case 5 : dt3 = "Focus";
|
||||
break;
|
||||
case 6 : dt3 = "Widget";
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -776,7 +784,7 @@ class W_networking extends Widget {
|
||||
port = Integer.parseInt(cp5_networking.get(Textfield.class, "osc_port1").getText());
|
||||
address = cp5_networking.get(Textfield.class, "osc_address1").getText();
|
||||
filt_pos = (int)cp5_networking.get(RadioButton.class, "filter1").getValue();
|
||||
stream1 = new Stream(dt1,ip,port,address,filt_pos);
|
||||
stream1 = new Stream(dt1, ip, port, address, filt_pos, nchan);
|
||||
}else{
|
||||
stream1 = null;
|
||||
}
|
||||
@@ -785,7 +793,7 @@ class W_networking extends Widget {
|
||||
port = Integer.parseInt(cp5_networking.get(Textfield.class, "osc_port2").getText());
|
||||
address = cp5_networking.get(Textfield.class, "osc_address2").getText();
|
||||
filt_pos = (int)cp5_networking.get(RadioButton.class, "filter2").getValue();
|
||||
stream2 = new Stream(dt2, ip,port,address,filt_pos);
|
||||
stream2 = new Stream(dt2, ip, port, address, filt_pos, nchan);
|
||||
}else{
|
||||
stream2 = null;
|
||||
}
|
||||
@@ -794,7 +802,7 @@ class W_networking extends Widget {
|
||||
port = Integer.parseInt(cp5_networking.get(Textfield.class, "osc_port3").getText());
|
||||
address = cp5_networking.get(Textfield.class, "osc_address3").getText();
|
||||
filt_pos = (int)cp5_networking.get(RadioButton.class, "filter3").getValue();
|
||||
stream3 = new Stream(dt3, ip,port,address,filt_pos);
|
||||
stream3 = new Stream(dt3, ip, port, address, filt_pos, nchan);
|
||||
}else{
|
||||
stream3 = null;
|
||||
}
|
||||
@@ -805,7 +813,7 @@ class W_networking extends Widget {
|
||||
ip = cp5_networking.get(Textfield.class, "udp_ip1").getText();
|
||||
port = Integer.parseInt(cp5_networking.get(Textfield.class, "udp_port1").getText());
|
||||
filt_pos = (int)cp5_networking.get(RadioButton.class, "filter1").getValue();
|
||||
stream1 = new Stream(dt1,ip,port,filt_pos);
|
||||
stream1 = new Stream(dt1, ip, port, filt_pos, nchan);
|
||||
}else{
|
||||
stream1 = null;
|
||||
}
|
||||
@@ -813,7 +821,7 @@ class W_networking extends Widget {
|
||||
ip = cp5_networking.get(Textfield.class, "udp_ip2").getText();
|
||||
port = Integer.parseInt(cp5_networking.get(Textfield.class, "udp_port2").getText());
|
||||
filt_pos = (int)cp5_networking.get(RadioButton.class, "filter2").getValue();
|
||||
stream2 = new Stream(dt2,ip,port,filt_pos);
|
||||
stream2 = new Stream(dt2, ip, port, filt_pos, nchan);
|
||||
}else{
|
||||
stream2 = null;
|
||||
}
|
||||
@@ -821,7 +829,7 @@ class W_networking extends Widget {
|
||||
ip = cp5_networking.get(Textfield.class, "udp_ip3").getText();
|
||||
port = Integer.parseInt(cp5_networking.get(Textfield.class, "udp_port3").getText());
|
||||
filt_pos = (int)cp5_networking.get(RadioButton.class, "filter3").getValue();
|
||||
stream3 = new Stream(dt3,ip,port,filt_pos);
|
||||
stream3 = new Stream(dt3, ip, port, filt_pos, nchan);
|
||||
}else{
|
||||
stream3 = null;
|
||||
}
|
||||
@@ -833,7 +841,7 @@ class W_networking extends Widget {
|
||||
type = cp5_networking.get(Textfield.class, "lsl_type1").getText();
|
||||
nChanLSL = Integer.parseInt(cp5_networking.get(Textfield.class, "lsl_numchan1").getText());
|
||||
filt_pos = (int)cp5_networking.get(RadioButton.class, "filter1").getValue();
|
||||
stream1 = new Stream(dt1,name,type,nChanLSL,filt_pos);
|
||||
stream1 = new Stream(dt1, name, type, nChanLSL, filt_pos, nchan);
|
||||
}else{
|
||||
stream1 = null;
|
||||
}
|
||||
@@ -842,7 +850,7 @@ class W_networking extends Widget {
|
||||
type = cp5_networking.get(Textfield.class, "lsl_type2").getText();
|
||||
nChanLSL = Integer.parseInt(cp5_networking.get(Textfield.class, "lsl_numchan2").getText());
|
||||
filt_pos = (int)cp5_networking.get(RadioButton.class, "filter2").getValue();
|
||||
stream2 = new Stream(dt2,name,type,nChanLSL,filt_pos);
|
||||
stream2 = new Stream(dt2, name, type, nChanLSL, filt_pos, nchan);
|
||||
}else{
|
||||
stream2 = null;
|
||||
}
|
||||
@@ -851,7 +859,7 @@ class W_networking extends Widget {
|
||||
type = cp5_networking.get(Textfield.class, "lsl_type3").getText();
|
||||
nChanLSL = Integer.parseInt(cp5_networking.get(Textfield.class, "lsl_numchan3").getText());
|
||||
filt_pos = (int)cp5_networking.get(RadioButton.class, "filter3").getValue();
|
||||
stream3 = new Stream(dt3,name,type,nChanLSL,filt_pos);
|
||||
stream3 = new Stream(dt3, name, type, nChanLSL, filt_pos, nchan);
|
||||
}else{
|
||||
stream3 = null;
|
||||
}
|
||||
@@ -865,7 +873,7 @@ class W_networking extends Widget {
|
||||
baudRate = Integer.parseInt(baudRates.get((int)(cp5_networking_baudRate.get(ScrollableList.class, "baud_rate").getValue())));
|
||||
|
||||
filt_pos = (int)cp5_networking.get(RadioButton.class, "filter1").getValue();
|
||||
stream1 = new Stream(dt1,name,baudRate,filt_pos,pApplet); //String dataType, String portName, int baudRate, int filter, PApplet _this
|
||||
stream1 = new Stream(dt1, name, baudRate, filt_pos, pApplet, nchan); //String dataType, String portName, int baudRate, int filter, PApplet _this
|
||||
}else{
|
||||
stream1 = null;
|
||||
}
|
||||
@@ -930,15 +938,15 @@ class Stream extends Thread{
|
||||
String streamType;
|
||||
String streamName;
|
||||
int nChanLSL;
|
||||
int numChan = 0;
|
||||
|
||||
Boolean isStreaming;
|
||||
Boolean newData = false;
|
||||
int numChan = nchan;
|
||||
// Data buffers
|
||||
int start = dataBuffY_filtY_uV[0].length-11;
|
||||
int end = dataBuffY_filtY_uV[0].length-1;
|
||||
int bufferLen = end-start;
|
||||
float[] dataToSend = new float[numChan];
|
||||
float[] dataToSend = new float[numChan*bufferLen];
|
||||
|
||||
//OSC Objects
|
||||
OscP5 osc;
|
||||
@@ -961,10 +969,17 @@ class Stream extends Thread{
|
||||
|
||||
PApplet pApplet;
|
||||
|
||||
private void updateNumChan(int _numChan) {
|
||||
numChan = _numChan;
|
||||
println("Stream update numChan to " + numChan);
|
||||
dataToSend = new float[numChan * nPointsPerUpdate];
|
||||
println("nPointsPerUpdate " + nPointsPerUpdate);
|
||||
|
||||
println("dataToSend len: " + numChan * nPointsPerUpdate);
|
||||
}
|
||||
|
||||
/* OSC Stream */
|
||||
Stream(String dataType, String ip, int port, String address, int filter){
|
||||
Stream(String dataType, String ip, int port, String address, int filter, int _nchan){
|
||||
this.protocol = "OSC";
|
||||
this.dataType = dataType;
|
||||
this.ip = ip;
|
||||
@@ -972,19 +987,21 @@ class Stream extends Thread{
|
||||
this.address = address;
|
||||
this.filter = filter;
|
||||
this.isStreaming = false;
|
||||
updateNumChan(_nchan);
|
||||
try{
|
||||
closeNetwork(); //make sure everything is closed!
|
||||
}catch (Exception e){
|
||||
}
|
||||
}
|
||||
/*UDP Stream */
|
||||
Stream(String dataType, String ip, int port, int filter){
|
||||
Stream(String dataType, String ip, int port, int filter, int _nchan){
|
||||
this.protocol = "UDP";
|
||||
this.dataType = dataType;
|
||||
this.ip = ip;
|
||||
this.port = port;
|
||||
this.filter = filter;
|
||||
this.isStreaming = false;
|
||||
updateNumChan(_nchan);
|
||||
if(this.dataType.equals("TimeSeries")){
|
||||
buffer = ByteBuffer.allocate(4*numChan);
|
||||
}else{
|
||||
@@ -996,7 +1013,7 @@ class Stream extends Thread{
|
||||
}
|
||||
}
|
||||
/* LSL Stream */
|
||||
Stream(String dataType, String streamName, String streamType, int nChanLSL, int filter){
|
||||
Stream(String dataType, String streamName, String streamType, int nChanLSL, int filter, int _nchan){
|
||||
this.protocol = "LSL";
|
||||
this.dataType = dataType;
|
||||
this.streamName = streamName;
|
||||
@@ -1004,6 +1021,7 @@ class Stream extends Thread{
|
||||
this.nChanLSL = nChanLSL;
|
||||
this.filter = filter;
|
||||
this.isStreaming = false;
|
||||
updateNumChan(_nchan);
|
||||
try{
|
||||
closeNetwork(); //make sure everything is closed!
|
||||
}catch (Exception e){
|
||||
@@ -1011,7 +1029,7 @@ class Stream extends Thread{
|
||||
}
|
||||
|
||||
// Serial Stream %%%%%
|
||||
Stream(String dataType, String portName, int baudRate, int filter, PApplet _this){
|
||||
Stream(String dataType, String portName, int baudRate, int filter, PApplet _this, int _nchan){
|
||||
// %%%%%
|
||||
this.protocol = "Serial";
|
||||
this.dataType = dataType;
|
||||
@@ -1020,6 +1038,7 @@ class Stream extends Thread{
|
||||
this.filter = filter;
|
||||
this.isStreaming = false;
|
||||
this.pApplet = _this;
|
||||
updateNumChan(_nchan);
|
||||
if(this.dataType.equals("TimeSeries")){
|
||||
buffer = ByteBuffer.allocate(4*numChan);
|
||||
}else{
|
||||
@@ -1060,8 +1079,10 @@ class Stream extends Thread{
|
||||
sendFFTData();
|
||||
}else if (this.dataType.equals("EMG")){
|
||||
sendEMGData();
|
||||
}else if (this.dataType.equals("PowerBands")){
|
||||
}else if (this.dataType.equals("BandPower")){
|
||||
sendPowerBandData();
|
||||
}else if (this.dataType.equals("Focus")){
|
||||
sendFocusData();
|
||||
}else if (this.dataType.equals("WIDGET")){
|
||||
sendWidgetData();
|
||||
}
|
||||
@@ -1090,8 +1111,10 @@ class Stream extends Thread{
|
||||
sendFFTData();
|
||||
}else if (this.dataType.equals("EMG")){
|
||||
sendEMGData();
|
||||
}else if (this.dataType.equals("PowerBands")){
|
||||
}else if (this.dataType.equals("BandPower")){
|
||||
sendPowerBandData();
|
||||
}else if (this.dataType.equals("Focus")){
|
||||
sendFocusData();
|
||||
}else if (this.dataType.equals("WIDGET")){
|
||||
sendWidgetData();
|
||||
}
|
||||
@@ -1109,7 +1132,9 @@ class Stream extends Thread{
|
||||
return dataProcessing.newDataToSend;
|
||||
}else if (this.dataType.equals("EMG")){
|
||||
return dataProcessing.newDataToSend;
|
||||
}else if (this.dataType.equals("PowerBands")){
|
||||
}else if (this.dataType.equals("BandPower")){
|
||||
return dataProcessing.newDataToSend;
|
||||
}else if (this.dataType.equals("Focus")){
|
||||
return dataProcessing.newDataToSend;
|
||||
}else if (this.dataType.equals("WIDGET")){
|
||||
/* ENTER YOUR WIDGET "NEW DATA" RETURN FUNCTION */
|
||||
@@ -1124,7 +1149,9 @@ class Stream extends Thread{
|
||||
dataProcessing.newDataToSend = false;
|
||||
}else if (this.dataType.equals("EMG")){
|
||||
dataProcessing.newDataToSend = false;
|
||||
}else if (this.dataType.equals("PowerBands")){
|
||||
}else if (this.dataType.equals("BandPower")){
|
||||
dataProcessing.newDataToSend = false;
|
||||
}else if (this.dataType.equals("Focus")){
|
||||
dataProcessing.newDataToSend = false;
|
||||
}else if (this.dataType.equals("WIDGET")){
|
||||
/* ENTER YOUR WIDGET "NEW DATA" RETURN FUNCTION */
|
||||
@@ -1136,7 +1163,7 @@ class Stream extends Thread{
|
||||
if(filter==0){
|
||||
// OSC
|
||||
if(this.protocol.equals("OSC")){
|
||||
for(int i=0;i<bufferLen;i++){
|
||||
for(int i=0;i<nPointsPerUpdate;i++){
|
||||
msg.clearArguments();
|
||||
for(int j=0;j<numChan;j++){
|
||||
msg.add(yLittleBuff_uV[j][i]);
|
||||
@@ -1149,24 +1176,33 @@ class Stream extends Thread{
|
||||
}
|
||||
// UDP
|
||||
}else if (this.protocol.equals("UDP")){
|
||||
for(int i=0;i<bufferLen;i++){
|
||||
buffer.rewind();
|
||||
for(int j=0;j<numChan;j++){
|
||||
buffer.putFloat(yLittleBuff_uV[j][i]);
|
||||
for(int i=0;i<nPointsPerUpdate;i++){
|
||||
String outputter = "{\"type\":\"eeg\",\"data\":[";
|
||||
for (int j = 0; j < numChan; j++){
|
||||
outputter += str(yLittleBuff_uV[j][i]);
|
||||
if (j != numChan - 1) {
|
||||
outputter += ",";
|
||||
} else {
|
||||
outputter += "]}\r\n";
|
||||
}
|
||||
}
|
||||
this.udp.send(buffer.array(),this.ip,this.port);
|
||||
}
|
||||
// LSL
|
||||
}else if (this.protocol.equals("LSL")){
|
||||
for(int i=0;i<bufferLen;i++){
|
||||
for(int j=0;j<numChan;j++){
|
||||
dataToSend[j] = yLittleBuff_uV[j][i];
|
||||
try {
|
||||
this.udp.send(outputter, this.ip, this.port);
|
||||
} catch (Exception e) {
|
||||
println(e);
|
||||
}
|
||||
outlet_data.push_sample(dataToSend);
|
||||
}
|
||||
// LSL
|
||||
} else if (this.protocol.equals("LSL")) {
|
||||
for (int i=0; i<nPointsPerUpdate;i++){
|
||||
for(int j=0;j<numChan;j++){
|
||||
dataToSend[j+numChan*i] = yLittleBuff_uV[j][i];
|
||||
}
|
||||
}
|
||||
outlet_data.push_chunk(dataToSend);
|
||||
// SERIAL
|
||||
}else if (this.protocol.equals("Serial")){ // Serial Output unfiltered
|
||||
for(int i=0;i<bufferLen;i++){
|
||||
for(int i=0;i<nPointsPerUpdate;i++){
|
||||
serialMessage = "["; //clear message
|
||||
for(int j=0;j<numChan;j++){
|
||||
float chan_uV = yLittleBuff_uV[j][i];//get chan uV float value and truncate to 3 decimal places
|
||||
@@ -1190,7 +1226,7 @@ class Stream extends Thread{
|
||||
// TIME SERIES FILTERED
|
||||
}else if (filter==1){
|
||||
if (this.protocol.equals("OSC")){
|
||||
for(int i=0;i<bufferLen;i++){
|
||||
for(int i=0;i<nPointsPerUpdate;i++){
|
||||
msg.clearArguments();
|
||||
for(int j=0;j<numChan;j++){
|
||||
msg.add(dataBuffY_filtY_uV[j][start+i]);
|
||||
@@ -1201,23 +1237,32 @@ class Stream extends Thread{
|
||||
println(e);
|
||||
}
|
||||
}
|
||||
}else if (this.protocol.equals("UDP")){
|
||||
for(int i=0;i<bufferLen;i++){
|
||||
buffer.rewind();
|
||||
for(int j=0;j<numChan;j++){
|
||||
buffer.putFloat(dataBuffY_filtY_uV[j][start+i]);
|
||||
} else if (this.protocol.equals("UDP")){
|
||||
for(int i=0;i<nPointsPerUpdate;i++){
|
||||
String outputter = "{\"type\":\"eeg\",\"data\":[";
|
||||
for (int j = 0; j < numChan; j++){
|
||||
outputter += str(dataBuffY_filtY_uV[j][start+i]);
|
||||
if (j != numChan - 1) {
|
||||
outputter += ",";
|
||||
} else {
|
||||
outputter += "]}\r\n";
|
||||
}
|
||||
}
|
||||
this.udp.send(buffer.array(),this.ip,this.port);
|
||||
}
|
||||
}else if (this.protocol.equals("LSL")){
|
||||
for(int i=0;i<bufferLen;i++){
|
||||
for(int j=0;j<numChan;j++){
|
||||
dataToSend[j] = dataBuffY_filtY_uV[j][i];
|
||||
try {
|
||||
this.udp.send(outputter, this.ip, this.port);
|
||||
} catch (Exception e) {
|
||||
println(e);
|
||||
}
|
||||
outlet_data.push_sample(dataToSend);
|
||||
}
|
||||
}else if (this.protocol.equals("LSL")){
|
||||
for (int i=0; i<nPointsPerUpdate;i++){
|
||||
for(int j=0;j<numChan;j++){
|
||||
dataToSend[j+numChan*i] = dataBuffY_filtY_uV[j][i];
|
||||
}
|
||||
}
|
||||
outlet_data.push_chunk(dataToSend);
|
||||
}else if (this.protocol.equals("Serial")){
|
||||
for(int i=0;i<bufferLen;i++){
|
||||
for(int i=0;i<nPointsPerUpdate;i++){
|
||||
serialMessage = "["; //clear message
|
||||
for(int j=0;j<numChan;j++){
|
||||
float chan_uV_filt = dataBuffY_filtY_uV[j][start+i];//get chan uV float value and truncate to 3 decimal places
|
||||
@@ -1258,28 +1303,28 @@ class Stream extends Thread{
|
||||
}
|
||||
// UDP
|
||||
}else if (this.protocol.equals("UDP")){
|
||||
for (int i=0;i<numChan;i++){
|
||||
buffer.rewind();
|
||||
buffer.putFloat(i+1);
|
||||
for (int j=0;j<125;j++){
|
||||
buffer.putFloat(fftBuff[i].getBand(j));
|
||||
String outputter = "{\"type\":\"fft\",\"data\":[[";
|
||||
for (int i = 0;i < numChan; i++){
|
||||
for (int j = 0; j < 125; j++) {
|
||||
outputter += str(fftBuff[i].getBand(j));
|
||||
if (j != 125 - 1) {
|
||||
outputter += ",";
|
||||
}
|
||||
}
|
||||
try{
|
||||
this.udp.send(buffer.array(),this.ip,this.port);
|
||||
}catch (Exception e){
|
||||
println(e);
|
||||
if (i != numChan - 1) {
|
||||
outputter += "],[";
|
||||
} else {
|
||||
outputter += "]]}\r\n";
|
||||
}
|
||||
}
|
||||
try {
|
||||
this.udp.send(outputter, this.ip, this.port);
|
||||
} catch (Exception e) {
|
||||
println(e);
|
||||
}
|
||||
// LSL
|
||||
}else if (this.protocol.equals("LSL")){
|
||||
// if(filter==0){
|
||||
// for(int i=0;i<bufferLen;i++){
|
||||
// for(int j=0;j<numChan;j++){
|
||||
// dataToSend[j] = fftBuff[j][i];
|
||||
// }
|
||||
// outlet_data.push_sample(dataToSend);
|
||||
// }
|
||||
// }
|
||||
/* */
|
||||
}else if (this.protocol.equals("Serial")){
|
||||
// Send FFT Data over Serial ... %%%%%
|
||||
// println("Sending FFT data over Serial...");
|
||||
@@ -1307,7 +1352,7 @@ class Stream extends Thread{
|
||||
|
||||
void sendPowerBandData(){
|
||||
// UNFILTERED & FILTERED ... influenced globally by the FFT filters dropdown ... just like the FFT data
|
||||
int numPowerBands = 5; //DELTA, THETA, ALPHA, BETA, GAMMA
|
||||
int numBandPower = 5; //DELTA, THETA, ALPHA, BETA, GAMMA
|
||||
|
||||
if(this.filter==0 || this.filter==1){
|
||||
// OSC
|
||||
@@ -1315,8 +1360,7 @@ class Stream extends Thread{
|
||||
for (int i=0;i<numChan;i++){
|
||||
msg.clearArguments();
|
||||
msg.add(i+1);
|
||||
for (int j=0;j<numPowerBands;j++){
|
||||
// msg.add(fftBuff[i].getBand(j));
|
||||
for (int j=0;j<numBandPower;j++){
|
||||
msg.add(dataProcessing.avgPowerInBins[i][j]); // [CHAN][BAND]
|
||||
}
|
||||
try{
|
||||
@@ -1327,36 +1371,44 @@ class Stream extends Thread{
|
||||
}
|
||||
// UDP
|
||||
}else if (this.protocol.equals("UDP")){
|
||||
for (int i=0;i<numChan;i++){
|
||||
buffer.rewind();
|
||||
buffer.putFloat(i+1);
|
||||
for (int j=0;j<numPowerBands;j++){
|
||||
buffer.putFloat(dataProcessing.avgPowerInBins[i][j]); //[CHAN][BAND]
|
||||
// DELTA, THETA, ALPHA, BETA, GAMMA
|
||||
String outputter = "{\"type\":\"bandPower\",\"data\":[[";
|
||||
for (int i = 0;i < numChan; i++){
|
||||
for (int j=0;j<numBandPower;j++){
|
||||
outputter += str(dataProcessing.avgPowerInBins[i][j]); //[CHAN][BAND]
|
||||
if (j != numBandPower - 1) {
|
||||
outputter += ",";
|
||||
}
|
||||
}
|
||||
try{
|
||||
this.udp.send(buffer.array(),this.ip,this.port);
|
||||
}catch (Exception e){
|
||||
println(e);
|
||||
if (i != numChan - 1) {
|
||||
outputter += "],[";
|
||||
} else {
|
||||
outputter += "]]}\r\n";
|
||||
}
|
||||
}
|
||||
try {
|
||||
this.udp.send(outputter, this.ip, this.port);
|
||||
} catch (Exception e) {
|
||||
println(e);
|
||||
}
|
||||
// LSL
|
||||
}else if (this.protocol.equals("LSL")){
|
||||
// if(filter==0){
|
||||
// for(int i=0;i<bufferLen;i++){
|
||||
// for(int j=0;j<numChan;j++){
|
||||
// dataToSend[j] = fftBuff[j][i];
|
||||
// }
|
||||
// outlet_data.push_sample(dataToSend);
|
||||
// }
|
||||
// }
|
||||
|
||||
float[] avgPowerLSL = new float[numChan*numBandPower];
|
||||
for (int i=0; i<numChan;i++){
|
||||
for(int j=0;j<numBandPower;j++){
|
||||
dataToSend[j+numChan*i] = dataProcessing.avgPowerInBins[i][j];
|
||||
}
|
||||
}
|
||||
outlet_data.push_chunk(dataToSend);
|
||||
}else if (this.protocol.equals("Serial")){
|
||||
for (int i=0;i<numChan;i++){
|
||||
serialMessage = "[" + (i+1) + ","; //clear message
|
||||
for (int j=0;j<numPowerBands;j++){
|
||||
for (int j=0;j<numBandPower;j++){
|
||||
float power_band = dataProcessing.avgPowerInBins[i][j];
|
||||
String power_band_3dec = String.format("%.3f", power_band);
|
||||
serialMessage += power_band_3dec;
|
||||
if(j < numPowerBands-1){
|
||||
if(j < numBandPower-1){
|
||||
serialMessage += ","; //add a comma to serialMessage to separate chan values, as long as it isn't last value...
|
||||
}
|
||||
}
|
||||
@@ -1383,7 +1435,7 @@ class Stream extends Thread{
|
||||
msg.add(i+1);
|
||||
//ADD NORMALIZED EMG CHANNEL DATA
|
||||
msg.add(w_emg.motorWidgets[i].output_normalized);
|
||||
println(i + " | " + w_emg.motorWidgets[i].output_normalized);
|
||||
// println(i + " | " + w_emg.motorWidgets[i].output_normalized);
|
||||
try{
|
||||
this.osc.send(msg,this.netaddress);
|
||||
}catch (Exception e){
|
||||
@@ -1391,28 +1443,29 @@ class Stream extends Thread{
|
||||
}
|
||||
}
|
||||
// UDP
|
||||
}else if (this.protocol.equals("UDP")){
|
||||
for (int i=0;i<numChan;i++){
|
||||
buffer.rewind();
|
||||
buffer.putFloat(i+1);
|
||||
//ADD NORMALIZED EMG CHANNEL DATA
|
||||
buffer.putFloat(w_emg.motorWidgets[i].output_normalized);
|
||||
try{
|
||||
this.udp.send(buffer.array(),this.ip,this.port);
|
||||
}catch (Exception e){
|
||||
println(e);
|
||||
} else if (this.protocol.equals("UDP")) {
|
||||
String outputter = "{\"type\":\"emg\",\"data\":[";
|
||||
for (int i = 0;i < numChan; i++){
|
||||
outputter += str(w_emg.motorWidgets[i].output_normalized);
|
||||
if (i != numChan - 1) {
|
||||
outputter += ",";
|
||||
} else {
|
||||
outputter += "]}\r\n";
|
||||
}
|
||||
}
|
||||
try {
|
||||
this.udp.send(outputter, this.ip, this.port);
|
||||
} catch (Exception e) {
|
||||
println(e);
|
||||
}
|
||||
// LSL
|
||||
}else if (this.protocol.equals("LSL")){
|
||||
// if(filter==0){
|
||||
// for(int i=0;i<bufferLen;i++){
|
||||
// for(int j=0;j<numChan;j++){
|
||||
// dataToSend[j] = fftBuff[j][i];
|
||||
// }
|
||||
// outlet_data.push_sample(dataToSend);
|
||||
// }
|
||||
// }
|
||||
if(filter==0){
|
||||
for(int j=0;j<numChan;j++){
|
||||
dataToSend[j] = w_emg.motorWidgets[j].output_normalized;
|
||||
}
|
||||
outlet_data.push_sample(dataToSend);
|
||||
}
|
||||
}else if (this.protocol.equals("Serial")){ // Send NORMALIZED EMG CHANNEL Data over Serial ... %%%%%
|
||||
for (int i=0;i<numChan;i++){
|
||||
serialMessage = "[" + (i+1) + ","; //clear message
|
||||
@@ -1420,7 +1473,7 @@ class Stream extends Thread{
|
||||
String emg_normalized_3dec = String.format("%.3f", emg_normalized);
|
||||
serialMessage += emg_normalized_3dec + "]";
|
||||
try{
|
||||
println(serialMessage);
|
||||
// println(serialMessage);
|
||||
this.serial_networking.write(serialMessage);
|
||||
}catch (Exception e){
|
||||
println(e);
|
||||
@@ -1430,6 +1483,55 @@ class Stream extends Thread{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void sendFocusData(){
|
||||
// UNFILTERED & FILTERED ... influenced globally by the FFT filters dropdown ... just like the FFT data
|
||||
|
||||
if(this.filter==0 || this.filter==1){
|
||||
// OSC
|
||||
if (this.protocol.equals("OSC")){
|
||||
msg.clearArguments();
|
||||
//ADD Focus Data
|
||||
msg.add(w_focus.isFocused);
|
||||
println(w_focus.isFocused);
|
||||
try{
|
||||
this.osc.send(msg,this.netaddress);
|
||||
}catch (Exception e){
|
||||
println(e);
|
||||
}
|
||||
// UDP
|
||||
}else if (this.protocol.equals("UDP")){
|
||||
String outputter = "{\"type\":\"focus\",\"data\":";
|
||||
outputter += str(w_focus.isFocused ? 1.0 : 0.0);
|
||||
outputter += "]}\r\n";
|
||||
try {
|
||||
this.udp.send(outputter, this.ip, this.port);
|
||||
} catch (Exception e) {
|
||||
println(e);
|
||||
}
|
||||
// LSL
|
||||
}else if (this.protocol.equals("LSL")){
|
||||
// convert boolean to float and only sends the first data
|
||||
float temp = w_focus.isFocused ? 1.0 : 0.0;
|
||||
dataToSend[0] = temp;
|
||||
outlet_data.push_chunk(dataToSend);
|
||||
// Serial
|
||||
}else if (this.protocol.equals("Serial")){ // Send NORMALIZED EMG CHANNEL Data over Serial ... %%%%%
|
||||
for (int i=0;i<numChan;i++){
|
||||
serialMessage = ""; //clear message
|
||||
String isFocused = Boolean.toString(w_focus.isFocused);
|
||||
serialMessage += isFocused;
|
||||
try{
|
||||
println(serialMessage);
|
||||
this.serial_networking.write(serialMessage);
|
||||
}catch (Exception e){
|
||||
println(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sendWidgetData(){
|
||||
/* INSERT YOUR CODE HERE */
|
||||
}
|
||||
@@ -1472,18 +1574,18 @@ class Stream extends Thread{
|
||||
this.msg = new OscMessage(this.address);
|
||||
}else if (this.protocol.equals("UDP")){
|
||||
this.udp = new UDP(this);
|
||||
this.udp.setBuffer(1024);
|
||||
this.udp.setBuffer(20000);
|
||||
this.udp.listen(false);
|
||||
this.udp.log(false);
|
||||
println("UDP successfully connected");
|
||||
output("UDP successfully connected");
|
||||
}else if (this.protocol.equals("LSL")){
|
||||
String stream_id = "q4asdgdsg";
|
||||
String stream_id = "openbcieeg12345";
|
||||
info_data = new LSL.StreamInfo(
|
||||
this.streamName,
|
||||
this.streamType,
|
||||
this.nChanLSL,
|
||||
openBCI.get_fs_Hz(),
|
||||
getSampleRateSafe(),
|
||||
LSL.ChannelFormat.float32,
|
||||
stream_id
|
||||
);
|
||||
|
||||
@@ -282,6 +282,10 @@ class Widget{
|
||||
|
||||
}
|
||||
|
||||
void mouseDragged(){
|
||||
|
||||
}
|
||||
|
||||
void setTitle(String _widgetTitle){
|
||||
widgetTitle = _widgetTitle;
|
||||
}
|
||||
@@ -313,8 +317,8 @@ class Widget{
|
||||
;
|
||||
}
|
||||
catch (Exception e) {
|
||||
println(e.getMessage());
|
||||
println("widgetOptions List not built yet...");
|
||||
// println(e.getMessage());
|
||||
// println("widgetOptions List not built yet..."); AJK 8/22/17 because this is annoyance
|
||||
}
|
||||
|
||||
for(int i = 0; i < dropdowns.size(); i++){
|
||||
|
||||
@@ -14,46 +14,96 @@ int navHeight = 22;
|
||||
// MAKE YOUR WIDGET GLOBALLY
|
||||
W_timeSeries w_timeSeries;
|
||||
W_fft w_fft;
|
||||
W_headPlot w_headPlot;
|
||||
W_accelerometer w_accelerometer;
|
||||
W_networking w_networking;
|
||||
W_BandPower w_bandPower;
|
||||
W_accelerometer w_accelerometer;
|
||||
W_ganglionImpedance w_ganglionImpedance;
|
||||
W_headPlot w_headPlot;
|
||||
W_template w_template1;
|
||||
W_emg w_emg;
|
||||
W_openBionics w_openbionics;
|
||||
W_Focus w_focus;
|
||||
W_PulseSensor w_pulsesensor;
|
||||
W_AnalogRead w_analogRead;
|
||||
W_DigitalRead w_digitalRead;
|
||||
W_MarkerMode w_markermode;
|
||||
|
||||
|
||||
//ADD YOUR WIDGET TO WIDGETS OF WIDGETMANAGER
|
||||
void setupWidgets(PApplet _this, ArrayList<Widget> w){
|
||||
// println(" setupWidgets start -- " + millis());
|
||||
|
||||
w_timeSeries = new W_timeSeries(_this);
|
||||
w_timeSeries.setTitle("Time Series");
|
||||
addWidget(w_timeSeries, w);
|
||||
// println(" setupWidgets time series -- " + millis());
|
||||
|
||||
|
||||
w_fft = new W_fft(_this);
|
||||
w_fft.setTitle("FFT Plot");
|
||||
addWidget(w_fft, w);
|
||||
// println(" setupWidgets fft -- " + millis());
|
||||
|
||||
w_accelerometer = new W_accelerometer(_this);
|
||||
w_accelerometer.setTitle("Accelerometer");
|
||||
w_networking = new W_networking(_this);
|
||||
w_networking.setTitle("Networking");
|
||||
|
||||
//only instantiate this widget if you are using a Ganglion board for live streaming
|
||||
if(nchan == 4 && eegDataSource == DATASOURCE_GANGLION){
|
||||
w_ganglionImpedance = new W_ganglionImpedance(_this);
|
||||
w_ganglionImpedance.setTitle("Ganglion Signal");
|
||||
addWidget(w_ganglionImpedance, w);
|
||||
addWidget(w_networking, w);
|
||||
addWidget(w_accelerometer, w);
|
||||
} else {
|
||||
addWidget(w_accelerometer, w);
|
||||
addWidget(w_networking, w);
|
||||
}
|
||||
|
||||
w_bandPower = new W_BandPower(_this);
|
||||
w_bandPower.setTitle("Band Power");
|
||||
addWidget(w_bandPower, w);
|
||||
// println(" setupWidgets band power -- " + millis());
|
||||
|
||||
|
||||
w_headPlot = new W_headPlot(_this);
|
||||
w_headPlot.setTitle("Head Plot");
|
||||
addWidget(w_headPlot, w);
|
||||
// println(" setupWidgets head plot -- " + millis());
|
||||
|
||||
w_accelerometer = new W_accelerometer(_this);
|
||||
w_accelerometer.setTitle("Accelerometer");
|
||||
addWidget(w_accelerometer, w);
|
||||
|
||||
w_networking = new W_networking(_this);
|
||||
w_networking.setTitle("Networking");
|
||||
addWidget(w_networking, w);
|
||||
|
||||
w_emg = new W_emg(_this);
|
||||
w_emg.setTitle("EMG");
|
||||
addWidget(w_emg, w);
|
||||
// println(" setupWidgets emg -- " + millis());
|
||||
|
||||
|
||||
w_focus = new W_Focus(_this);
|
||||
w_focus.setTitle("Focus Widget");
|
||||
addWidget(w_focus, w);
|
||||
// println(" setupWidgets focus widget -- " + millis());
|
||||
|
||||
//only instantiate this widget if you are using a Cyton board for live streaming
|
||||
if(eegDataSource != DATASOURCE_GANGLION){
|
||||
w_pulsesensor = new W_PulseSensor(_this);
|
||||
w_pulsesensor.setTitle("Pulse Sensor");
|
||||
addWidget(w_pulsesensor, w);
|
||||
// println(" setupWidgets pulse sensor -- " + millis());
|
||||
|
||||
w_digitalRead = new W_DigitalRead(_this);
|
||||
w_digitalRead.setTitle("Digital Read");
|
||||
addWidget(w_digitalRead, w);
|
||||
|
||||
w_analogRead = new W_AnalogRead(_this);
|
||||
w_analogRead.setTitle("Analog Read");
|
||||
addWidget(w_analogRead, w);
|
||||
|
||||
w_markermode = new W_MarkerMode(_this);
|
||||
w_markermode.setTitle("Marker Mode");
|
||||
addWidget(w_markermode, w);
|
||||
|
||||
}
|
||||
|
||||
w_template1 = new W_template(_this);
|
||||
w_template1.setTitle("Widget Template 1");
|
||||
@@ -104,12 +154,14 @@ class WidgetManager{
|
||||
int currentContainerLayout; //this is the Layout structure for the main body of the GUI ... refer to [PUT_LINK_HERE] for layouts/numbers image
|
||||
ArrayList<Layout> layouts = new ArrayList<Layout>(); //this holds all of the different layouts ...
|
||||
|
||||
public boolean isWMInitialized = false;
|
||||
private boolean visible = true;
|
||||
private boolean updating = true;
|
||||
|
||||
WidgetManager(PApplet _this){
|
||||
widgets = new ArrayList<Widget>();
|
||||
widgetOptions = new ArrayList<String>();
|
||||
isWMInitialized = false;
|
||||
|
||||
//DO NOT re-order the functions below
|
||||
setupLayouts();
|
||||
@@ -123,6 +175,10 @@ class WidgetManager{
|
||||
currentContainerLayout = 4; //default layout ... tall container left and 2 shorter containers stacked on the right
|
||||
setNewContainerLayout(currentContainerLayout); //sets and fills layout with widgets in order of widget index, to reorganize widget index, reorder the creation in setupWidgets()
|
||||
}
|
||||
|
||||
delay(1000);
|
||||
|
||||
isWMInitialized = true;
|
||||
}
|
||||
public boolean isVisible() {
|
||||
return visible;
|
||||
@@ -214,6 +270,14 @@ class WidgetManager{
|
||||
}
|
||||
}
|
||||
|
||||
void mouseDragged(){
|
||||
for(int i = 0; i < widgets.size(); i++){
|
||||
if(widgets.get(i).isActive){
|
||||
widgets.get(i).mouseDragged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setupLayouts(){
|
||||
//refer to [PUT_LINK_HERE] for layouts/numbers image
|
||||
//note that the order you create/add these layouts matters... if you reorganize these, the LayoutSelector will be out of order
|
||||
|
||||
|
Antes Largura: | Altura: | Tamanho: 26 KiB Depois Largura: | Altura: | Tamanho: 26 KiB |
|
Antes Largura: | Altura: | Tamanho: 23 KiB Depois Largura: | Altura: | Tamanho: 23 KiB |