70 Commits

Author SHA1 Message Date
Kristjan Komlosi
ec8d3ca94c rename init to app change camel case to snake case, added __name__ conditional 2020-08-27 14:22:35 +02:00
Kristjan Komlosi
88687124f2 fixing packaging #1 2020-08-27 13:52:24 +02:00
Kristjan Komlosi
97233f27c3 fixing packaging #1 2020-08-27 11:14:01 +02:00
Kristjan Komlosi
b35aacad3f add travis config 2020-07-22 11:54:46 +02:00
Kristjan Komlosi
2136406a28 changed JS to match 1c49490 2020-07-14 11:12:43 +02:00
Kristjan Komlosi
ff13d8eeeb Add a semistandard badge to readme 2020-07-14 10:54:06 +02:00
Kristjan Komlosi
fece181c6f added an ESLint config file 2020-07-14 10:38:17 +02:00
Kristjan Komlosi
85d7be4117 refactor updateData to use simpler $.get method 2020-07-14 10:37:57 +02:00
Kristjan Komlosi
56e8282a82 specter --> spectrum 2020-07-14 10:02:18 +02:00
Kristjan Komlosi
1c4949020b change element IDs for irradiance values and table rows 2020-07-14 10:01:13 +02:00
Kristjan Komlosi
a71d8565c4 Change HTML indent to 4 2020-07-14 09:59:04 +02:00
Kristjan Komlosi
7e09a1e51f modify index.html into Jinja2 rendering template 2020-07-13 14:28:09 +02:00
Kristjan Komlosi
92fac02a9e formatting 2020-07-13 14:01:19 +02:00
Kristjan Komlosi
320bcae78e Merge branch 'development' of github.com:cls-02/TeraHz into developmen 2020-07-13 13:56:30 +02:00
Kristjan Komlosi
d6a0efd69b Merge branch 'development' of github.com:cls-02/TeraHz into development 2020-07-13 13:55:44 +02:00
Kristjan Komlosi
40a79af9af moved static files to conform with Flask 2020-07-13 13:54:30 +02:00
Kristjan Komlosi
229719cc5c moved static files to conform with Flask 2020-07-13 13:47:13 +02:00
Kristjan Komlosi
ac6345c5b5 modify gitignore 2020-07-13 13:42:08 +02:00
Kristjan Komlosi
7773788e20 Merge branch 'development' of github.com:cls-02/TeraHz into development 2020-07-13 13:28:36 +02:00
Kristjan Komlosi
ac154e6770 moved templates 2020-07-13 13:28:32 +02:00
Kristjan Komlosi
a4ec93f412 moved templates 2020-07-13 13:23:24 +02:00
Kristjan Komlosi
16d7624154 update copyright and add a root route 2020-07-13 13:13:01 +02:00
Kristjan Komlosi
54b93cb05c cleaning up 2020-07-13 13:11:10 +02:00
Kristjan Komlosi
3d3254c4a7 formatting 2020-07-13 13:01:34 +02:00
Kristjan Komlosi
cfe6e3c286 remove old-unused files 2020-07-13 12:13:31 +02:00
Kristjan Komlosi
7a3877b34b remove unused FastCGI configuration file 2020-07-13 11:55:21 +02:00
Kristjan Komlosi
f20dd30292 remove unused config file 2020-07-13 11:54:35 +02:00
Kristjan Komlosi
ef5c87d8d2 removed janky and unused storage module 2020-07-13 11:54:20 +02:00
Kristjan Komlosi
4878df9ca8 Shortened error messages 2020-07-13 11:52:22 +02:00
Kristjan Komlosi
89ea9564c8 fixed onclick binding 2020-06-02 10:45:16 +02:00
Kristjan Komlosi
00c619ce34 fixed typo 2020-06-02 09:53:09 +02:00
Kristjan Komlosi
8f22a6baee removed lighttpd configuration as the defaults work fine 2020-06-02 09:52:40 +02:00
Kristjan Komlosi
fa2c1cccb1 removed old pipfiles 2020-06-02 09:52:08 +02:00
Kristjan Komlosi
7058512ee0 tidied comments, condensed UV calculation, removed redundant getA/B/I() 2020-04-24 16:49:09 +02:00
Kristjan Komlosi
325f1176c3 tidied translation tables 2020-04-24 16:47:43 +02:00
Kristjan Komlosi
0a9e610dd7 pruned import 2020-04-24 16:46:53 +02:00
Kristjan Komlosi
43b5277923 pipenv update 2020-04-24 16:46:27 +02:00
Kristjan Komlosi
5265ba9ae9 hotfix #2 2020-02-15 22:02:36 +01:00
Kristjan Komlosi
16e9c52194 rc.local hotfix 2020-02-15 21:59:41 +01:00
Kristjan Komlosi
c5db357cc0 fixed lighttpd root directory 2020-02-15 21:50:03 +01:00
Kristjan Komlosi
b7eb29f78b made rc.local executable 2020-02-15 21:46:12 +01:00
Kristjan Komlosi
1457655f16 GUI redesign 2020-02-15 15:56:28 +01:00
Kristjan Komlosi
a3f3558f7c fixed wavelength order in spectrogram 2020-02-14 14:18:38 +01:00
Kristjan Komlosi
bc72207187 fixed broken git headers 2019-11-18 17:19:18 +01:00
Kristjan Komlosi
7837f605cf Merge branch 'master' of github.com:cls-02/TeraHz 2019-11-17 22:23:28 +01:00
Kristjan Komlosi
3d1a925ba6 added school logo 2019-11-17 22:23:09 +01:00
Kristjan Komlosi
682fe20db2 added school logo 2019-11-17 22:21:54 +01:00
Kristjan Komlosi
949dd0bef2 Updated the main readme 2019-11-17 22:04:57 +01:00
Kristjan Komlosi
58cb777935 Documentation updates 2019-11-17 19:07:47 +01:00
Kristjan Komlosi
6ffc4f8aa2 Updated documentation pages titles 2019-11-12 21:30:48 +01:00
Kristjan Komlosi
87bd420d31 fixed a broken markdown list 2019-11-12 21:30:00 +01:00
Kristjan Komlosi
3e1a43c10f updated documentation 2019-11-12 21:28:27 +01:00
Kristjan Komlosi
ed0b21a496 Shortened the build guide significantly 2019-11-12 21:20:28 +01:00
Kristjan Komlosi
b93358dc32 made install.sh directory independent 2019-11-12 20:29:34 +01:00
Kristjan Komlosi
7f8e69fcf3 fixed gitignore to reinclude wrongly ignored frontend libraries 2019-11-12 20:23:24 +01:00
Kristjan Komlosi
751095a5a1 removed old sensor documentation 2019-11-12 20:03:03 +01:00
Kristjan Komlosi
665cf6050b removed old utils virtualenv 2019-11-12 19:56:22 +01:00
Kristjan Komlosi
8e7435ff8f cleared previously tracked gitignored files 2019-11-12 19:43:50 +01:00
Kristjan Komlosi
08310e074d .gitignore update 2019-11-12 19:40:02 +01:00
Kristjan Komlosi
598352f526 fixed install.sh to make edit_ssid executable 2019-11-12 19:29:32 +01:00
Kristjan Komlosi
e88171b31c fixed configuration files 2019-11-12 19:23:57 +01:00
Kristjan Komlosi
a5b393a879 made edit_ssid.sh executable 2019-11-12 19:13:07 +01:00
Kristjan Komlosi
9b4e733209 added interfaces file 2019-11-11 19:06:46 +01:00
Kristjan Komlosi
4cf102946e update ssid at each boot 2019-11-11 17:54:41 +01:00
Kristjan Komlosi
c72c0800fc made edit_ssid.sh path-independent 2019-11-11 17:41:55 +01:00
Kristjan Komlosi
095a95c1eb fixed relative path problems in run.sh 2019-11-11 17:34:18 +01:00
Kristjan Komlosi
fd5e482de9 made backend startup script executable 2019-11-11 17:14:16 +01:00
Kristjan Komlosi
ccbeb20f70 Merge branch 'buster' of github.com:cls-02/TeraHz into buster 2019-11-11 16:59:06 +01:00
Kristjan Komlosi
a4dca60892 removed lighttpd config files, as the frontend now uses defaults 2019-11-11 16:58:58 +01:00
Kristjan Komlosi
95e22b5d1f added gunicorn to pip3 dependencies 2019-11-10 18:37:55 +01:00
6074 changed files with 669 additions and 1354500 deletions

Binary file not shown.

5
.eslintrc Normal file
View File

@@ -0,0 +1,5 @@
parserOptions:
ecmaVersion: 2019
env:
browser: true
jquery: true

133
.gitignore vendored
View File

@@ -1 +1,132 @@
site/
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# Include frontend ibraries though
!terahz/templates/lib

1
.travis.yml Normal file
View File

@@ -0,0 +1 @@
language: python

15
Pipfile Normal file
View File

@@ -0,0 +1,15 @@
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
[packages]
flask = "*"
smbus2 = "*"
pyserial = "*"
pandas = "*"
[requires]
python_version = "3.8"

184
Pipfile.lock generated Normal file
View File

@@ -0,0 +1,184 @@
{
"_meta": {
"hash": {
"sha256": "7fb3ae0afbe0d0c2af92f618aa1c54442566daeb561480eee2c2a299f1c2bee7"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.8"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"click": {
"hashes": [
"sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
"sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"
],
"version": "==7.1.2"
},
"flask": {
"hashes": [
"sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060",
"sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557"
],
"index": "pypi",
"version": "==1.1.2"
},
"itsdangerous": {
"hashes": [
"sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19",
"sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"
],
"version": "==1.1.0"
},
"jinja2": {
"hashes": [
"sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0",
"sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"
],
"version": "==2.11.2"
},
"markupsafe": {
"hashes": [
"sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
"sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
"sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
"sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
"sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42",
"sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
"sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
"sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
"sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
"sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
"sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
"sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b",
"sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
"sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15",
"sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
"sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
"sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
"sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905",
"sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735",
"sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d",
"sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e",
"sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d",
"sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c",
"sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21",
"sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2",
"sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5",
"sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b",
"sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
"sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
"sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
"sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2",
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7",
"sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"
],
"version": "==1.1.1"
},
"numpy": {
"hashes": [
"sha256:082f8d4dd69b6b688f64f509b91d482362124986d98dc7dc5f5e9f9b9c3bb983",
"sha256:1bc0145999e8cb8aed9d4e65dd8b139adf1919e521177f198529687dbf613065",
"sha256:309cbcfaa103fc9a33ec16d2d62569d541b79f828c382556ff072442226d1968",
"sha256:3673c8b2b29077f1b7b3a848794f8e11f401ba0b71c49fbd26fb40b71788b132",
"sha256:480fdd4dbda4dd6b638d3863da3be82873bba6d32d1fc12ea1b8486ac7b8d129",
"sha256:56ef7f56470c24bb67fb43dae442e946a6ce172f97c69f8d067ff8550cf782ff",
"sha256:5a936fd51049541d86ccdeef2833cc89a18e4d3808fe58a8abeb802665c5af93",
"sha256:5b6885c12784a27e957294b60f97e8b5b4174c7504665333c5e94fbf41ae5d6a",
"sha256:667c07063940e934287993366ad5f56766bc009017b4a0fe91dbd07960d0aba7",
"sha256:7ed448ff4eaffeb01094959b19cbaf998ecdee9ef9932381420d514e446601cd",
"sha256:8343bf67c72e09cfabfab55ad4a43ce3f6bf6e6ced7acf70f45ded9ebb425055",
"sha256:92feb989b47f83ebef246adabc7ff3b9a59ac30601c3f6819f8913458610bdcc",
"sha256:935c27ae2760c21cd7354402546f6be21d3d0c806fffe967f745d5f2de5005a7",
"sha256:aaf42a04b472d12515debc621c31cf16c215e332242e7a9f56403d814c744624",
"sha256:b12e639378c741add21fbffd16ba5ad25c0a1a17cf2b6fe4288feeb65144f35b",
"sha256:b1cca51512299841bf69add3b75361779962f9cee7d9ee3bb446d5982e925b69",
"sha256:b8456987b637232602ceb4d663cb34106f7eb780e247d51a260b84760fd8f491",
"sha256:b9792b0ac0130b277536ab8944e7b754c69560dac0415dd4b2dbd16b902c8954",
"sha256:c9591886fc9cbe5532d5df85cb8e0cc3b44ba8ce4367bd4cf1b93dc19713da72",
"sha256:cf1347450c0b7644ea142712619533553f02ef23f92f781312f6a3553d031fc7",
"sha256:de8b4a9b56255797cbddb93281ed92acbc510fb7b15df3f01bd28f46ebc4edae",
"sha256:e1b1dc0372f530f26a03578ac75d5e51b3868b9b76cd2facba4c9ee0eb252ab1",
"sha256:e45f8e981a0ab47103181773cc0a54e650b2aef8c7b6cd07405d0fa8d869444a",
"sha256:e4f6d3c53911a9d103d8ec9518190e52a8b945bab021745af4939cfc7c0d4a9e",
"sha256:ed8a311493cf5480a2ebc597d1e177231984c818a86875126cfd004241a73c3e",
"sha256:ef71a1d4fd4858596ae80ad1ec76404ad29701f8ca7cdcebc50300178db14dfc"
],
"version": "==1.19.1"
},
"pandas": {
"hashes": [
"sha256:01b1e536eb960822c5e6b58357cad8c4b492a336f4a5630bf0b598566462a578",
"sha256:0246c67cbaaaac8d25fed8d4cf2d8897bd858f0e540e8528a75281cee9ac516d",
"sha256:0366150fe8ee37ef89a45d3093e05026b5f895e42bbce3902ce3b6427f1b8471",
"sha256:16ae070c47474008769fc443ac765ffd88c3506b4a82966e7a605592978896f9",
"sha256:1acc2bd7fc95e5408a4456897c2c2a1ae7c6acefe108d90479ab6d98d34fcc3d",
"sha256:391db82ebeb886143b96b9c6c6166686c9a272d00020e4e39ad63b792542d9e2",
"sha256:41675323d4fcdd15abde068607cad150dfe17f7d32290ee128e5fea98442bd09",
"sha256:53328284a7bb046e2e885fd1b8c078bd896d7fc4575b915d4936f54984a2ba67",
"sha256:57c5f6be49259cde8e6f71c2bf240a26b071569cabc04c751358495d09419e56",
"sha256:84c101d0f7bbf0d9f1be9a2f29f6fcc12415442558d067164e50a56edfb732b4",
"sha256:88930c74f69e97b17703600233c0eaf1f4f4dd10c14633d522724c5c1b963ec4",
"sha256:8c9ec12c480c4d915e23ee9c8a2d8eba8509986f35f307771045c1294a2e5b73",
"sha256:a81c4bf9c59010aa3efddbb6b9fc84a9b76dc0b4da2c2c2d50f06a9ef6ac0004",
"sha256:d9644ac996149b2a51325d48d77e25c911e01aa6d39dc1b64be679cd71f683ec",
"sha256:e4b6c98f45695799990da328e6fd7d6187be32752ed64c2f22326ad66762d179",
"sha256:fe6f1623376b616e03d51f0dd95afd862cf9a33c18cf55ce0ed4bbe1c4444391"
],
"index": "pypi",
"version": "==1.1.1"
},
"pyserial": {
"hashes": [
"sha256:6e2d401fdee0eab996cf734e67773a0143b932772ca8b42451440cfed942c627",
"sha256:e0770fadba80c31013896c7e6ef703f72e7834965954a78e71a3049488d4d7d8"
],
"index": "pypi",
"version": "==3.4"
},
"python-dateutil": {
"hashes": [
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
"sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
],
"version": "==2.8.1"
},
"pytz": {
"hashes": [
"sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed",
"sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"
],
"version": "==2020.1"
},
"six": {
"hashes": [
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
],
"version": "==1.15.0"
},
"smbus2": {
"hashes": [
"sha256:210e66eebe4d0b1fe836b3ec2751841942e1c4918c0b429b20a0e20a222228b4"
],
"index": "pypi",
"version": "==0.3.0"
},
"werkzeug": {
"hashes": [
"sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43",
"sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c"
],
"version": "==1.0.1"
}
},
"develop": {}
}

View File

@@ -1,23 +1,38 @@
# TeraHz
[![Documentation Status](https://readthedocs.org/projects/terahz/badge/?version=latest)](https://terahz.readthedocs.io/en/latest/?badge=latest)
[![js-semistandard-style](https://img.shields.io/badge/code%20style-semistandard-brightgreen.svg?style=flat-square)](https://github.com/standard/semistandard)
# <img alt="TeraHz logo" src="docs/imgs/logo-sq.png" width="200px"> TeraHz
TeraHz is a low-cost spectrometer based on a Raspberry Pi 3 or 3 B+ and three sensors:
+ [__AS7265x__](https://www.tindie.com/products/onehorse/compact-as7265x-spectrometer/)
is a 18 channel spectrometer chipset that provides the device with spectral data
+ [__VEML6075__](https://www.sparkfun.com/products/15089) is an
UVA/UVB sensor
+ [__APDS-9301__](https://www.sparkfun.com/products/14350) is a calibrated illuminance (lux) meter that provides the device with reliable readings
*Za slovensko različico se odpravite na <http://git.sckr-lab.tk/kristjank/TeraHz>*
## Why?
Because people and institutions could use an affordable and accurate light-analysing device that is also portable, easy to use and simple to assemble. TeraHz was started as an answer to our high school not being able to afford a commercially available solution. One TeraHz spectrometer costs around 150$ in parts, which makes it a competitive alternative to other solutions on the market today.
TeraHz is a low-cost portable spectrometer based on Raspberry Pi and a few
commonly available sensor breakout boards. It's designed to bring low-cost
scientific exploration of the light spectrum to educational institutions that
cannot afford the options available on the current market. It costs less than
200€ with money to spare and uses only free, libre and open-source software
(FLOSS). It is free to use under the ISC license, a spiritual successor to the
classic 3-clause BSD license.
## How to install
Stable releases are available under the releases tab and can be installed either
by flashing a **preinstalled** ready to boot image with a stable version of
TeraHz preinstalled or by flashing a **preconfigured** image of DietPi and
installing TeraHz manually, which is useful if you want to test bleeding edge
releases. For more information, check out the [Build
guide](https://terahz.readthedocs.io/en/latest/build/).
## Docs
TeraHz usually works out of the box. A wireless network named `TeraHz_XXXXXX`
(XXXXXX = the last half of the MAC address) will appear. Password is
`terahertz`. After connection, open a web browser and visit `terahz.site`.
The UI will appear. To fetch data from the sensors, press the 'Get Data' button.
The readings are then plotted and written into the tables below the graph.
More documentation is available at <https://terahz.readthedocs.io>
## Development team
Copyright 2018, 2019
- Kristjan Komloši (cls-02) - Project leader and main programmer
- Jakob Kosec (D3m1j4ck) - Frontend designer
- Juš Dolžan (ANormalPerson) - Math double-checker
- Kristjan "cls-02" Komloši (electronics, sensor drivers, backend)
- Jakob "D3m1j4ck" Kosec (frontend)
I would also like to thank Juš "ANormalPerson" Dolžan, who decided to leave the
team, but helped me a lot with backend development.
<img alt="TeraHz logo" src="http://www.sckr.si/documents/upload/konektor/logo/_SC.gif" width="200px">
TeraHz has been developed under guidance and financial support of Šolski Center Kranj (Kranj School Centre). Without their support, this project might have never been possible.

View File

@@ -1,17 +0,0 @@
# app.py - main backend program
'''Main TeraHz backend program'''
# All code in this file is licensed under the ISC license, provided in LICENSE.txt
from flask import Flask
import flask
import sensors
app = Flask(__name__)
@app.route('/data')
def sendData():
'''Responder function for /data route'''
s = sensors.Spectrometer(path='/dev/serial0', baudrate=115200, tout=1)
u = sensors.UVSensor()
l = sensors.LxMeter()
response = flask.jsonify([s.getData(), l.getData(), u.getABI()])
response.headers.add('Access-Control-Allow-Origin', '*')
return response

Binary file not shown.

View File

@@ -1,4 +0,0 @@
#!/bin/bash
# run.sh - run the backend server
sudo gunicorn app:app -b 0.0.0.0:5000 &
disown

Binary file not shown.

View File

@@ -1,37 +0,0 @@
# storage.py - storage backend for TeraHz
'''TeraHz storage backend'''
# Copyright Kristjan Komloši 2019
# All code in this file is licensed under the ISC license,
# provided in LICENSE.txt
import sqlite3
class jsonStorage:
'''Class for simple sqlite3 database of JSON entries'''
def __init__(self, dbFile):
'''Storage object constructor. Argument is filename'''
self.db = sqlite3.connect(dbFile)
def listJSONs(self):
'''Returns a list of all existing entries.'''
c = self.db.cursor()
c.execute('SELECT * FROM storage')
result = c.fetchall()
c.close()
return result
def storeJSON(self, jsonString, comment):
'''Stores a JSON entry along with a timestamp and a comment.'''
c = self.db.cursor()
c.execute(('INSERT INTO storage VALUES (datetime'
'(\'now\', \'localtime\'), ?, ?)'), (comment, jsonString))
c.close()
self.db.commit()
def retrieveJSON(self, timestamp):
'''Retrieves a JSON entry. Takes a timestamp string'''
c = self.db.cursor()
c.execute('SELECT * FROM storage WHERE timestamp = ?', (timestamp,))
result = c.fetchall()
c.close()
return result

View File

View File

@@ -1,7 +0,0 @@
#!/usr/bin/python3
# Minimal flup configuration for Flask
from flup.server.fcgi import WSGIServer
from app import app
if __name__ == '__main__':
WSGIServer(app, bindAddress='/var/www/api/terahz.sock').run()

View File

@@ -1,169 +1,39 @@
# TeraHz build guide
This document describes the process of building/installing TeraHz from the Git
repository.
In its early development phase, TeraHz was hard and time-consuming to compile and install.
This is not case now, as the more optimized DietPi Linux distribution allows
better performance and simpler configuration than formerly used Raspbian.
## Warning
The recommended way of getting TeraHz is the official Raspberry Pi SD card image
provided under the releases tab in the GitHub repository. Installing TeraHz from
source is a time consuming and painful process, even more so if you don't know
what you're doing, and whatever you end up building __will not be officially
supported__ (unless you're a core developer).
## Downloading the complete image
The easiest way to get TeraHz is to download the premade complete image and
write it to an SD card. It already has TeraHz installed and **will work out of
the box** with the correct hardware. The image is designed to run from a 16 GB
micro SD card, class 10 or higher is recommended for snappy performance. The
recommended image writer is Etcher
With this warning out of the way, let's begin.
Please note that while this installation process is the easiest to perform, it
does not guarantee the most recent TeraHz software. Complete images take time to
prepare and despite the developer's best efforts aren't guaranteed to be always
up-to-date.
## Getting the latest sources
The most reliable way to get working source code is by cloning the official
GitHub repository and checking out the `development-stable` tag. This tag marks
the latest confirmed working commit. Building from the master branch is somewhat
risky, and building from development branches is straight up stupid if you're
not a developer.
## Downloading the clean DietPi image and installing TeraHz manually
This process is a bit more involved, but the version of TeraHz installed is
guaranteed to be the latest one available from the repository.
Make sure that the repository is cloned into `/home/pi/TeraHz`, as Lighttpd
expects to find frontend files inside this directory.
The SD card image used in this case also contains some pre-configuration, but no
TeraHz code is installed. To install TeraHz, you'll need a console access to the
Raspberry Pi, preferably an SSH console over a wired network. DietPi is configured
to get an IP over DHCP. A tool such as arp-scan on linux is very helpful at determining
the device's IP address.
After cloning and checking out, check the documentation for module dependencies
and the required version of python in the `docs/dependencies.md` file.
## Installing Python
This step depends a lot on the platform you're using. TeraHz was developed with
Raspberry Pi and Raspbian in mind. If you're familiar with Raspbian enough,
you'll know that the latest version of Python available is `3.5`, which is too
obsolete to run TeraHz and the required modules consistently. This leaves us
with compiling Python from source. __This step is guaranteed to be slow,
overnight compiling with something like tmux is recommended.__
### Pre-requirements
Installing python without most C libraries will lead to a rather minimalistic
Python install, missing a lot of important modules. To prevent this, update
the system packages. After that, reboot.
After connecting to the Raspberry Pi with username `root` and password `terahz`,
TeraHz can be installed by cloning the Git repository and running the `etcs/install.sh`
script.
```
sudo apt update
sudo apt full-upgrade
sudo reboot
git clone https://github.com/cls-02/TeraHz.git
cd TeraHz/etcs
./install.sh
```
Install the required build tools, libraries and their headers.
```
sudo apt-get install build-essential tk-dev libncurses5-dev libncursesw5-dev \
libreadline6-dev libdb5.3-dev libgdbm-dev libsqlite3-dev libssl-dev libbz2-dev \
libexpat1-dev liblzma-dev zlib1g-dev
```
### Compiling
Compiling Python from source is, in fact pretty easy, just time-consuming. To combat
that, using tmux to detach and later reattach the session is advised.
Python is packaged in many forms, but you'll be using the most basic
of them all: a gzipped tarball. Download and decompress it, then cd into its
directory.
```
wget https://www.python.org/ftp/python/3.6.8/Python-3.6.8.tgz
tar -xzf Python-3.6.8.tgz
cd Python-3.6.8
```
Python's build process is pretty classic, a `.configure` script and a Makefile.
Using the `-j` option with Make can reduce the compile time significantly. Go
with as many threads as you have cores: `-j 4` works great on the Pi 3 B/B+.
```
./configure
make -j4
```
When the compilation ends, install your freshly built version of python.
```
sudo make altinstall
```
"Altinstall" means that the new version of Python will be installed beside the
existing version, and all related commands will use the full naming scheme:
think `python3.6` or `pip3.6` instead of the shorter `python3` or `pip3`.
### Modules
Another painfully slow part is the installation of all the required modules
needed by TeraHz. Luckily, `pip3.6` takes care of the entire installation
process. As before, using tmux is advised.
```
pip3.6 install smbus pyserial flask pandas
```
## Raspi-config
For some law-obeying reason, Raspbian locks down the Wi-Fi interface card until
the Wi-Fi country is set. This means that we will need to set it manually through
the `raspi-config` program. It requires superuser privileges.
```
sudo raspi-config
```
![1](imgs/raspi-config/1.png)
Configure the network hostname to something specific. If setting up multiple
TeraHz machines, make their hostnames unique so you can tell them apart.
![](imgs/raspi-config/3.png)
![](imgs/raspi-config/4.png)
Enable SSH and I2C interfaces.
![](imgs/raspi-config/15.png)
![](imgs/raspi-config/5.png)
![](imgs/raspi-config/6.png)
![](imgs/raspi-config/7.png)
![](imgs/raspi-config/19.png)
Expand the root filesystem along the entire SD card.
![](imgs/raspi-config/17.png)
![](imgs/raspi-config/8.png)
![](imgs/raspi-config/9.png)
Set the Wi-Fi country to the country you'll be using TeraHz in.
![](imgs/raspi-config/10.png)
![](imgs/raspi-config/11.png)
![](imgs/raspi-config/12.png)
![](imgs/raspi-config/13.png)
![](imgs/raspi-config/14.png)
Save and reboot to enable Wi-Fi
## Installing packages
In addition to what's already installed, TeraHz requires the following daemons
to run:
- Lighttpd - Frontend HTTP server
- Dnsmasq - DNS and DHCP server, used to redirect the `terahz.site` domain
- Hostapd - Wi-Fi access point
They are available from the Raspbian repository. Install it via `apt`.
```
apt install hostapd dnsmasq hostapd
```
## Configuring daemons
By default, the daemons we installed are disabled and start only manually. To
change that, enable them through systemctl. Hostapd conflicts with
wpa_supplicant, the solution is to disable wpa_supplicant (this will break your
wireless connections, so use wired ethernet).
```
sudo systemctl unmask hostapd
sudo systemctl stop wpa_supplicant
sudo systemctl disable wpa_supplicant
sudo systemctl enable dnsmasq hostapd lighttpd
```
## Copying configuration files
To simplify the process of configuring Raspbian to run TeraHz, sample
configuration file are provided in the `etcs` subdirectory of the Git
repository.
These files have been verified to work, but it's not a brilliant idea to just
copy them into your `/etc` directory. Use them carefully and more as a template
for your own configuration rather than as a _de facto_ way of configuring
TeraHz.
When the installation completes, reboot the Raspberry Pi and it will
automatically boot into TeraHz.

View File

@@ -1,26 +1,24 @@
# Development-stable dependencies
The current development version of TeraHz has been verified to work with:
- Raspbian Stretch (9)
- Python 3.6.8 (built from source code and altinstall'd)
- Module versions (direct `pip3.6 list` output):
# 1.0.0-rc1 Dependencies
The 1.0.0-rc1 version of TeraHz is confirmed to work with:
- DietPi 6.26.3
- Python 3.7.3
- Pip 18.1
- Following versions of pip3 packages:
```
Package Version
--------------- ---------
Click 7.0
Flask 1.0.3
itsdangerous 1.1.0
Jinja2 2.10.1
MarkupSafe 1.1.1
numpy 1.16.4
pandas 0.24.2
pip 18.1
pyserial 3.4
python-dateutil 2.8.0
pytz 2019.1
setuptools 40.6.2
six 1.12.0
smbus 1.1.post2
Werkzeug 0.15.4
Click 7.0
Flask 1.1.1
gunicorn 20.0.0
itsdangerous 1.1.0
Jinja2 2.10.3
MarkupSafe 1.1.1
numpy 1.17.4
pandas 0.25.3
pip 18.1
pyserial 3.4
python-dateutil 2.8.1
pytz 2019.3
setuptools 41.6.0
six 1.13.0
smbus2 0.3.0
Werkzeug 0.16.0
```

View File

@@ -7,17 +7,12 @@ TeraHz was developed on and for the Raspberry Pi 3 Model B+. Compatibility with
other Raspberries can probably be achieved by tweaking the device paths in the
`app.py` file, but isn't confirmed at this point. Theoretically, 3 Model B and
Zero W should work out of the box, but models without Wi-Fi will need an
external Wi-Fi adapter if Wi-Fi functionality is desired. The practicality of
compiling Python on the first generation of Raspberry Pis is also very
questionable.
external Wi-Fi adapter if Wi-Fi functionality is desired.
Sensors required for operation are:
+ AS7265x
+ VEML6075
+ APDS-9301
They provide the spectrometry data, UV data and illuminance data, respectively.
They all support I2C, AS7265x supports UART in addition.
TeraHz depends on three separate sensor boards:
- AS7265x spectral chipset
- VEML6075 UV sensor
- APDS-9301 lux-meter
The sensors leech power from the GPIO connector, thus eliminating the need for a
separate power supply. The necessary power for the whole system is delivered through

View File

@@ -21,13 +21,9 @@ project when basic testing will be done.
GPIO can be routed to the PCB with a standard old IDE disk cable, and terminated
with another 40-pin connector at the PCB. Sensor breakouts should be mounted
<<<<<<< HEAD
through standard 0.1" connectors, male on the sensor breakout and female on the
PCB. A shitty add-on header and a shitty add-on header v1.69bis can't hurt, either.
=======
through standard 0.1" connectors, male on the sensor brakout and female on the
PCB. A shitty addon header and a shitty addon header v1.69bis can't hurt, either.
>>>>>>> fd1f07d40dace3e003e49377d4771de53f8bdeb8
## SMBus sensors
SMBus is a well-defined version of the well-known I2C bus, widely used
@@ -39,11 +35,8 @@ Pins are familiarly marked as SDA and SCL, the same as with classic I2C. They
connect to the SDA and SCL pins on the VEML6075 and APDS-9301 sensor.
## UART sensor
<<<<<<< HEAD
Spectral sensor attaches through the UART port on the Raspberry pi (see picture).
=======
Spectrometry sensor attaches through the UART port on the Raspberry pi (see picture).
>>>>>>> fd1f07d40dace3e003e49377d4771de53f8bdeb8
The Tx and Rx lines must cross over, connecting the sensor's Tx line to the
computer's Rx line and vice versa.

View File

@@ -1,3 +0,0 @@
interface=wlan0
dhcp-range=192.168.1.10,192.168.1.100,255.255.255.0,24h
address=/terahz.site/192.168.1.1

View File

@@ -1,5 +0,0 @@
#!/bin/bash
# edit_ssid.sh - edits hostapd.conf and sets a MAC address-based SSID
ssid=`ip link | awk '/wlan0/ {getline; print $2}' | awk -v FS=':' '{printf("TeraHz_%s%s%s\n", $4, $5, $6)}'`
sed "/ssid=.*/s/ssid=.*/ssid=$ssid/" hostapd.conf > newconf
mv newconf hostapd.conf

View File

@@ -1,9 +0,0 @@
interface=wlan0
hw_mode=g
channel=8
wpa=2
wpa_key_mgmt=WPA-PSK
wpa_pairwise=TKIP
rsn_pairwise=CCMP
ssid=TeraHz
wpa_passphrase=terahertz

View File

@@ -1,19 +0,0 @@
# install.sh - install TeraHz onto a Raspbian or DietPi installation
apt -y update
apt -y full-upgrade
apt install -y python3 python3-pip lighttpd dnsmasq hostapd libatlas-base-dev
pip3 install numpy pandas flask smbus2 pyserial
cp -R hostapd/ /etc
cp -R lighttpd/ /etc
cp dnsmasq.conf /etc
cp rc.local /etc
cp -R ../frontend/* /var/www/html
mkdir -p /usr/local/lib/terahz
cp -R ../backend/* /usr/local/lib/terahz
cd /etc/hostapd
chmod +x edit_ssid.sh
./edit_ssid.sh
systemctl unmask dnsmasq hostapd lighttpd
systemctl enable dnsmasq hostapd lighttpd

View File

@@ -1,27 +0,0 @@
server.modules = (
"mod_access",
"mod_alias",
"mod_compress",
"mod_redirect"
)
server.document-root = "/var/www/html"
server.upload-dirs = ( "/var/cache/lighttpd/uploads" )
server.errorlog = "/var/log/lighttpd/error.log"
server.pid-file = "/var/run/lighttpd.pid"
server.username = "www-data"
server.groupname = "www-data"
server.port = 80
index-file.names = ( "index.php", "index.html", "index.lighttpd.html" )
url.access-deny = ( "~", ".inc" )
static-file.exclude-extensions = ( ".php", ".pl", ".fcgi" )
compress.cache-dir = "/var/cache/lighttpd/compress/"
compress.filetype = ( "application/javascript", "text/css", "text/html", "text/plain" )
# default listening port for IPv6 falls back to the IPv4 port
include_shell "/usr/share/lighttpd/use-ipv6.pl " + server.port
include_shell "/usr/share/lighttpd/create-mime.assign.pl"
include_shell "/usr/share/lighttpd/include-conf-enabled.pl"

View File

@@ -1,14 +0,0 @@
#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.
/usr/local/lib/terahz/run.sh &
disown

View File

@@ -1,46 +0,0 @@
// All code in this file is licensed under the ISC license, provided in LICENSE.txt
$('#update').click(function () {
updateData();
});
// jQuery event binder
function updateData () {
const url = 'http://' + window.location.hostname + ':5000/data';
$.ajax({ // spawn an AJAX request
url: url,
success: function (data, status) {
console.log(data);
graphSpectralData(data[0], 0);
fillTableData(data);
},
timeout: 2500 // this should be a pretty sane timeout
});
}
function graphSpectralData (obj, dom) {
// graph spectral data in obj into dom
var graphPoints = [];
var graphXTicks = [];
Object.keys(obj).forEach((element, index) => {
graphPoints.push([index, obj[element]]); // build array of points
graphXTicks.push([index, element]); // build array of axis labels
});
// console.log(graphPoints);
const options = {
grid: {color: 'white'},
xaxis: {ticks: graphXTicks}
};
$.plot('#graph', [graphPoints], options);
// flot expects an array of arrays (lines) of 2-element arrays (points)
}
function fillTableData (obj) {
// fill the obj data into HTML tables
Object.keys(obj[0])
.forEach((element) => { $('#' + element).text(obj[0][element]); });
$('#lx').text(obj[1]);
$('#uva').text(obj[2][0]);
$('#uvb').text(obj[2][1]);
$('#uvi').text(obj[2][2]);
}

View File

@@ -1,162 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf8">
<link rel="stylesheet" href="lib/bootstrap.min.css">
<link rel="stylesheet" href="stylesheet.css">
<title>TeraHz</title>
</head>
<body>
<div class="container text-center">
<h1><img src="lib/logo-sq.png" height="64px">TeraHz</h1>
</div>
<div class="container">
<button id="update" class="btn btn-primary m-1 float-right">Get data</button>
<p id="debug">
</p>
<h3>Spectrogram</h3>
<div id="graph" style="height:480px;width:720px"></div>
<h3>Spectral readings</h3>
<table class="table table-dark table-sm" id="specter">
<thead class="thead-dark">
<tr>
<th>Band</th>
<th>Wavelength [nm]</th>
<th>Irradiance [μW/cm²]</th>
</tr>
</thead>
<tr>
<td>A</td>
<td>410 nm</td>
<td id="A">---</td>
</tr>
<tr>
<td>B</td>
<td>435 nm</td>
<td id="B">---</td>
</tr>
<tr>
<td>C</td>
<td>460 nm</td>
<td id="C">---</td>
</tr>
<tr>
<td>D</td>
<td>485 nm</td>
<td id="D">---</td>
</tr>
<tr>
<td>E</td>
<td>510 nm</td>
<td id="E">---</td>
</tr>
<tr>
<td>F</td>
<td>535 nm</td>
<td id="F">---</td>
</tr>
<tr>
<td>G</td>
<td>560 nm</td>
<td id="G">---</td>
</tr>
<tr>
<td>H</td>
<td>585 nm</td>
<td id="H">---</td>
</tr>
<tr>
<td>R</td>
<td>610 nm</td>
<td id="R">---</td>
</tr>
<tr>
<td>I</td>
<td>645 nm</td>
<td id="I">---</td>
</tr>
<tr>
<td>S</td>
<td>680 nm</td>
<td id="S">---</td>
</tr>
<tr>
<td>J</td>
<td>705 nm</td>
<td id="J">---</td>
</tr>
<tr>
<td>T</td>
<td>730 nm</td>
<td id="T">---</td>
</tr>
<tr>
<td>U</td>
<td>760 nm</td>
<td id="U">---</td>
</tr>
<tr class="table-secondary">
<td>V</td>
<td>810 nm</td>
<td id="V">---</td>
</tr>
<tr class="table-secondary">
<td>W</td>
<td>860 nm</td>
<td id="W">---</td>
</tr>
<tr class="table-secondary">
<td>K</td>
<td>900 nm</td>
<td id="K">---</td>
</tr>
<tr class="table-secondary">
<td>L</td>
<td>940 nm</td>
<td id="L">---</td>
</tr>
</table>
<br>
<h3>Lux and UV readings</h3>
<table class="table-dark table" id="luxuv">
<thead class="thead-dark">
<tr>
<th>Parameter</th>
<th>Value</th>
</tr>
</thead>
<tr>
<td>Illuminance [lx]</td>
<td id="lx">---</td>
</tr>
<tr>
<td>UVA irradiance [μW/cm²]</td>
<td id="uva">---</td>
</tr>
<tr>
<td>UVB irradiance [μW/cm²]</td>
<td id="uvb">---</td>
</tr>
<tr>
<td>UVA/UVB average [μW/cm²]</td>
<td id="uvi">---</td>
</tr>
</table>
</div>
<script src="lib/bootstrap.bundle.min.js"></script>
<script src="lib/jquery-3.4.1.min.js"></script>
<script src="lib/flot/jquery.flot.js"></script>
<script src="frontend.js"></script>
<script src="lib/flot/jquery.canvaswrapper.js"></script>
<script src="lib/flot/jquery.colorhelpers.js"></script>
<script src="lib/flot/jquery.flot.js"></script>
<script src="lib/flot/jquery.flot.saturated.js"></script>
<script src="lib/flot/jquery.flot.browser.js"></script>
<script src="lib/flot/jquery.flot.drawSeries.js"></script>
<script src="lib/flot/jquery.flot.uiConstants.js"></script>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -5,5 +5,5 @@ nav:
- Advanced guides:
- Build guide: 'build.md'
- Developer's guide: 'dev-guide.md'
- Electrical connections: 'electrical.md'
- Electrical guide: 'electrical.md'
- Latest dependencies: 'dependencies.md'

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,131 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:p="http://www.evolus.vn/Namespace/Pencil" xmlns:pencil="http://www.evolus.vn/Namespace/Pencil" xmlns:html="http://www.w3.org/1999/xhtml" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rpt="http://openoffice.org/2005/report" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:rdfa="http://docs.oasis-open.org/opendocument/meta/rdfa#" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:regexp="http://exslt.org/regular-expressions" xmlns:em="http://exslt.org/math" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="1385" height="852" id="exportedSVG" version="1.1" pencil:version="1.2.2" sodipodi:docname="demo"><g inkscape:label="Untitled Page" inkscape:groupmode="layer" id="layer_untitled_page"><g><rect x="0" y="0" width="1385" height="852" fill="none"/><g p:type="Shape" p:def="Evolus.Sketchy.GUI:box" id="316ea25d69264185809ddf96933f1b9d" transform="matrix(1,0,0,1,479,239)"><p:metadata><p:property name="box">379,359</p:property><p:property name="textPadding">18.95,23.933333333333334</p:property><p:property name="fillColor">#FFFFFFFF</p:property><p:property name="strokeColor">#000000FF</p:property><p:property name="strokeStyle">1|</p:property><p:property name="textContent"/><p:property name="textFont">'Comic Sans MS'|normal|normal|12px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
<path style="stroke-linejoin: round; fill: rgb(255, 255, 255); fill-opacity: 1; stroke: rgb(0, 0, 0); stroke-opacity: 1; stroke-width: 1;" p:name="line1" id="9f2cd04595654a2daa5c78cc57fc3ad1" transform="translate(0.5,0.5)" d="M 0 0 C 126 -1 253 -1 379 0 C 381 120 381 239 379 359 C 253 360 126 360 0 359 C -2 239 -2 120 0 0 z"/>
<text p:name="text" id="22d3cf61288a4e6f8aea91fd9f11d090" transform="translate(190,180)" style="font-family: &quot;Comic Sans MS&quot;; font-size: 12px; font-weight: normal; font-style: normal; text-decoration: none; fill: rgb(0, 0, 0); fill-opacity: 1;"/>
</g><g p:type="Shape" p:def="Evolus.Common:Bitmap" id="f2992ac1faff43ef857d97b6097a3377" transform="matrix(1,0,0,1,545,295)"><p:metadata><p:property name="box">248,248</p:property><p:property name="imageData">1600,1600,ref://c549d0e3af32443d81f8de3a764ef050.png</p:property><p:property name="withBlur">false</p:property><p:property name="fillColor">#FFFFFF00</p:property><p:property name="strokeColor">#000000FF</p:property><p:property name="strokeStyle">0|</p:property></p:metadata>
<defs>
<filter height="1.2558399" y="-0.12792" width="1.06396" x="-0.03198" p:name="imageShading" id="47f2743a5502445daeea6763b1824d99">
<feGaussianBlur stdDeviation="1.3325" in="SourceAlpha"/>
</filter>
<g p:name="container" id="c807f25ef7cd4046b815700f4e8dd837">
<rect style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-opacity: 1; stroke-width: 0; fill-opacity: 0;" p:name="bgRect" id="442171111cd644568d10f9aaa8367c03" transform="translate(0,0)" width="248" height="248"/>
<g p:name="imageContainer" id="149b425645654fd99d93a63c39be92cd" transform="scale(0.155,0.155)">
<image x="0" y="0" p:name="image" id="6f19a34b96bf4773969f6d58610f56bd" xlink:href="file:///tmp/tmp-27399CzGZUH8OkSfx/refs/c549d0e3af32443d81f8de3a764ef050.png?token=1549194154000" width="1600" height="1600"/>
</g>
</g>
</defs>
<use xlink:href="#c807f25ef7cd4046b815700f4e8dd837" transform="translate(1, 1)" p:filter="url(#47f2743a5502445daeea6763b1824d99)" style="opacity: 0.6; visibility: hidden; display: none;" p:name="bgCopy" id="f490b3b5a7e340a89dd6f4e992753bb7"/>
<use xlink:href="#c807f25ef7cd4046b815700f4e8dd837"/>
</g><g p:type="Shape" p:def="Evolus.Common:PlainTextV2" p:sc="Label" id="d874a4977f474fdeb875d7e6bf5944ed" transform="matrix(1,0,0,1,596,289)"><p:metadata><p:property name="disabled">false</p:property><p:property name="width">100,0</p:property><p:property name="fixedWidth">false</p:property><p:property name="label">Raspberry Pi 3 B+</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textFont">'Comic Sans MS'|normal|normal|18px|none</p:property><p:property name="textAlign">0,0</p:property></p:metadata>
<rect x="0" y="0" style="fill: none; stroke: none; visibility: hidden; display: none;" p:name="bgRect" id="de7b105d7eec4d18b5eb971d7883baaa" width="147.640625" height="25.09375"/>
<text xml:space="preserve" p:name="text" id="949a08b7fd4044b794cc5b69b6f83422" style="fill: rgb(0, 0, 0); fill-opacity: 1; font-family: &quot;Comic Sans MS&quot;; font-size: 18px; font-weight: normal; font-style: normal; text-decoration: none;"><tspan x="0" y="0">Raspberry Pi 3 B+</tspan></text>
</g><g p:type="Shape" p:def="Evolus.Sketchy.GUI:box" id="4e6d928174bb44b2b165b10e439c8069" transform="matrix(1,0,0,1,215,144)"><p:metadata><p:property name="box">200,150</p:property><p:property name="textPadding">10,10</p:property><p:property name="fillColor">#FFFFFFFF</p:property><p:property name="strokeColor">#000000FF</p:property><p:property name="strokeStyle">1|</p:property><p:property name="textContent">AS7265X
Spektrometerski senzor za IR in vidno svetlobo</p:property><p:property name="textFont">'Comic Sans MS'|normal|normal|12px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
<path style="stroke-linejoin: round; fill: rgb(255, 255, 255); fill-opacity: 1; stroke: rgb(0, 0, 0); stroke-opacity: 1; stroke-width: 1;" p:name="line1" id="ba226d4a59e9445cb033b5f6cda2407c" transform="translate(0.5,0.5)" d="M 0 0 C 67 1 133 1 200 0 C 200 50 200 100 200 150 C 133 148 67 148 0 150 C -1 100 -1 50 0 0 z"/>
<text p:name="text" id="2f20051133e249f78ca5e893035376d7" transform="translate(10,64)" style="font-family: &quot;Comic Sans MS&quot;; font-size: 12px; font-weight: normal; font-style: normal; text-decoration: none; fill: rgb(0, 0, 0); fill-opacity: 1;"><tspan x="62.4609375" y="0">AS7265X</tspan><tspan x="5.109375" y="16">Spektrometerski senzor za IR</tspan><tspan x="43.7109375" y="32.390625">in vidno svetlobo</tspan></text>
</g><g p:type="Shape" p:def="Evolus.Sketchy.GUI:box" id="0788d286529c4856817fe87c11c8c576" transform="matrix(1,0,0,1,113,331)"><p:metadata><p:property name="box">200,150</p:property><p:property name="textPadding">10,10</p:property><p:property name="fillColor">#FFFFFFFF</p:property><p:property name="strokeColor">#000000FF</p:property><p:property name="strokeStyle">1|</p:property><p:property name="textContent">VEML6075
Senzor UV svetlobe</p:property><p:property name="textFont">'Comic Sans MS'|normal|normal|12px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
<path style="stroke-linejoin: round; fill: rgb(255, 255, 255); fill-opacity: 1; stroke: rgb(0, 0, 0); stroke-opacity: 1; stroke-width: 1;" p:name="line1" id="fb336ddc2a0e4285ab16ca92def9a93f" transform="translate(0.5,0.5)" d="M 0 0 C 67 2 133 2 200 0 C 199 50 199 100 200 150 C 133 151 67 151 0 150 C -1 100 -1 50 0 0 z"/>
<text p:name="text" id="5a27b79034a142a182fbba178737c976" transform="translate(10,72)" style="font-family: &quot;Comic Sans MS&quot;; font-size: 12px; font-weight: normal; font-style: normal; text-decoration: none; fill: rgb(0, 0, 0); fill-opacity: 1;"><tspan x="59.109375" y="0">VEML6075</tspan><tspan x="34.640625" y="16">Senzor UV svetlobe</tspan></text>
</g><g p:type="Shape" p:def="Evolus.Sketchy.GUI:box" id="fdd2989c419f44e49052af8a9433398b" transform="matrix(1,0,0,1,112,499)"><p:metadata><p:property name="box">200,150</p:property><p:property name="textPadding">10,10</p:property><p:property name="fillColor">#FFFFFFFF</p:property><p:property name="strokeColor">#000000FF</p:property><p:property name="strokeStyle">1|</p:property><p:property name="textContent">APDS-9301
Merilec osvetljenosti
(lux-meter)</p:property><p:property name="textFont">'Comic Sans MS'|normal|normal|12px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
<path style="stroke-linejoin: round; fill: rgb(255, 255, 255); fill-opacity: 1; stroke: rgb(0, 0, 0); stroke-opacity: 1; stroke-width: 1;" p:name="line1" id="fd47ea2f4fea4465b5145099e062a50f" transform="translate(0.5,0.5)" d="M 0 0 C 67 0 133 0 200 0 C 198 50 198 100 200 150 C 133 151 67 151 0 150 C 1 100 1 50 0 0 z"/>
<text p:name="text" id="3a1e6ef59bfe4e32a29f2d83e8a0eda4" transform="translate(10,64)" style="font-family: &quot;Comic Sans MS&quot;; font-size: 12px; font-weight: normal; font-style: normal; text-decoration: none; fill: rgb(0, 0, 0); fill-opacity: 1;"><tspan x="57.8125" y="0">APDS-9301</tspan><tspan x="30.8671875" y="16">Merilec osvetljenosti</tspan><tspan x="57.859375" y="32.484375">(lux-meter)</tspan></text>
</g><g p:type="Shape" p:def="Evolus.Sketchy.GUI:box" id="6a62643b2a9e40ad92de2807a3b4d499" transform="matrix(1,0,0,1,898,448)"><p:metadata><p:property name="box">200,150</p:property><p:property name="textPadding">10,10</p:property><p:property name="fillColor">#FFFFFFFF</p:property><p:property name="strokeColor">#000000FF</p:property><p:property name="strokeStyle">1|</p:property><p:property name="textContent">2600 mAh Powerbank
Napaja spektrometer</p:property><p:property name="textFont">'Comic Sans MS'|normal|normal|12px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
<path style="stroke-linejoin: round; fill: rgb(255, 255, 255); fill-opacity: 1; stroke: rgb(0, 0, 0); stroke-opacity: 1; stroke-width: 1;" p:name="line1" id="0e72ea3ae7374eae96f6d39616ad398b" transform="translate(0.5,0.5)" d="M 0 0 C 67 -1 133 -1 200 0 C 200 50 200 100 200 150 C 133 150 67 150 0 150 C 0 100 0 50 0 0 z"/>
<text p:name="text" id="e73d9b50f41c4e3eb2efffdb7a5f58d9" transform="translate(10,72)" style="font-family: &quot;Comic Sans MS&quot;; font-size: 12px; font-weight: normal; font-style: normal; text-decoration: none; fill: rgb(0, 0, 0); fill-opacity: 1;"><tspan x="29.6875" y="0">2600 mAh Powerbank</tspan><tspan x="30.125" y="16">Napaja spektrometer</tspan></text>
</g><g p:type="Shape" p:def="Evolus.Common:arrow" id="bc03848225fa44a3a7c3339794d1aa4e" transform="matrix(1,0,0,1,479,127)"><p:metadata><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="startPin" ns9889235:viay="92" ns9889235:viax="-54" ns9889235:connectedOutletId="middle-right" ns9889235:connectedShapeId="4e6d928174bb44b2b165b10e439c8069">-64,92</p:property><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="endPin" ns9889235:viay="102" ns9889235:viax="189.5" ns9889235:connectedOutletId="top-center" ns9889235:connectedShapeId="316ea25d69264185809ddf96933f1b9d">189.5,112</p:property><p:property name="withStartArrow">true</p:property><p:property name="withEndArrow">true</p:property><p:property name="mode">curvy</p:property><p:property name="detached">false</p:property><p:property name="strokeColor">#1B3280FF</p:property><p:property name="strokeStyle">2|</p:property><p:property name="textContent"/><p:property name="textFont">"Liberation Sans",Arial,sans-serif|normal|normal|13px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
<defs>
<path style="stroke-linejoin: round; fill: none;" p:name="path" id="590e362dc0a941558c8729fc4369cced" d="M -58 98 L -64 92 L -58 86 M -64 92 C -4 92 190 52 189.5 112 M 195 106 L 189.5 112 L 184 106"/>
</defs>
<use xlink:href="#590e362dc0a941558c8729fc4369cced" stroke-width="10" stroke-opacity="0" stroke="#FF0000"/>
<use xlink:href="#590e362dc0a941558c8729fc4369cced" p:name="outArrow1" id="9c47d44f84ff4e8c9c60f2427e5649e4" style="stroke: rgb(27, 50, 128); stroke-opacity: 1; stroke-width: 2;"/>
<text p:name="text" id="2027c38e59a7415ea356985839c3cb6a" style="fill: rgb(0, 0, 0); fill-opacity: 1; font-family: &quot;Liberation Sans&quot;, Arial, sans-serif; font-size: 13px; font-weight: normal; font-style: normal; text-decoration: none;">
<textPath xlink:href="#590e362dc0a941558c8729fc4369cced" startOffset="50%" text-anchor="middle" alignment-baseline="middle">
<tspan dy="-4.333333333333333" p:name="textSpan" id="7e3a712874e84610942b4fa041b7a43b" dx="0"/>
</textPath>
</text>
</g><g p:type="Shape" p:def="Evolus.Sketchy.GUI:box" id="3f86fad121f34239a0d9a0381b8e5293" transform="matrix(1,0,0,1,339,331)"><p:metadata><p:property name="box">76,316</p:property><p:property name="textPadding">3.8,21.066666666666663</p:property><p:property name="fillColor">#FFFFFFFF</p:property><p:property name="strokeColor">#000000FF</p:property><p:property name="strokeStyle">1|</p:property><p:property name="textContent">I2C
Vodilo</p:property><p:property name="textFont">'Comic Sans MS'|normal|normal|12px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
<path style="stroke-linejoin: round; fill: rgb(255, 255, 255); fill-opacity: 1; stroke: rgb(0, 0, 0); stroke-opacity: 1; stroke-width: 1;" p:name="line1" id="38738b385aac4e4f882b7957ae3baf95" transform="translate(0.5,0.5)" d="M 0 0 C 25 -1 51 -1 76 0 C 76 105 76 211 76 316 C 51 315 25 315 0 316 C -1 211 -1 105 0 0 z"/>
<text p:name="text" id="d829667556ac42128e839eeb4432aec3" transform="translate(4,155)" style="font-family: &quot;Comic Sans MS&quot;; font-size: 12px; font-weight: normal; font-style: normal; text-decoration: none; fill: rgb(0, 0, 0); fill-opacity: 1;"><tspan x="23.645312500000003" y="0">I2C</tspan><tspan x="17.145312500000003" y="16">Vodilo</tspan></text>
</g><g p:type="Shape" p:def="Evolus.Common:arrow" id="e6e45b080a4640e2af58d063d5abe1ef" transform="matrix(1,0,0,1,562,696)"><p:metadata><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="startPin" ns9889235:connectedShapeId="0788d286529c4856817fe87c11c8c576" ns9889235:connectedOutletId="middle-right" ns9889235:viax="-239" ns9889235:viay="-290">-249,-290</p:property><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="endPin" ns9889235:connectedShapeId="3f86fad121f34239a0d9a0381b8e5293" ns9889235:connectedOutletId="middle-left" ns9889235:viax="-233" ns9889235:viay="-207">-223,-207</p:property><p:property name="withStartArrow">true</p:property><p:property name="withEndArrow">true</p:property><p:property name="mode">curvy</p:property><p:property name="detached">false</p:property><p:property name="strokeColor">#1B3280FF</p:property><p:property name="strokeStyle">2|</p:property><p:property name="textContent"/><p:property name="textFont">"Liberation Sans",Arial,sans-serif|normal|normal|13px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
<defs>
<path style="stroke-linejoin: round; fill: none;" p:name="path" id="564ef2543bfd4d4f8bf59146b4632c7c" d="M -243 -284 L -249 -290 L -243 -296 M -249 -290 C -206 -290 -266 -207 -223 -207 M -229 -213 L -223 -207 L -229 -201"/>
</defs>
<use xlink:href="#564ef2543bfd4d4f8bf59146b4632c7c" stroke-width="10" stroke-opacity="0" stroke="#FF0000"/>
<use xlink:href="#564ef2543bfd4d4f8bf59146b4632c7c" p:name="outArrow1" id="9fd757e4b8d042a09378b6031cfe99e2" style="stroke: rgb(27, 50, 128); stroke-opacity: 1; stroke-width: 2;"/>
<text p:name="text" id="c73cdd327696416eab5fa6971dba53ef" style="fill: rgb(0, 0, 0); fill-opacity: 1; font-family: &quot;Liberation Sans&quot;, Arial, sans-serif; font-size: 13px; font-weight: normal; font-style: normal; text-decoration: none;">
<textPath xlink:href="#564ef2543bfd4d4f8bf59146b4632c7c" startOffset="50%" text-anchor="middle" alignment-baseline="middle">
<tspan dy="-4.333333333333333" p:name="textSpan" id="221213c9b3fc487facaa478640fecf0f" dx="0"/>
</textPath>
</text>
</g><g p:type="Shape" p:def="Evolus.Common:arrow" id="540bab7bb3f3442f8429144581700e47" transform="matrix(1,0,0,1,324,705)"><p:metadata><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="startPin" ns9889235:viay="-131" ns9889235:viax="-2" ns9889235:connectedOutletId="middle-right" ns9889235:connectedShapeId="fdd2989c419f44e49052af8a9433398b">-12,-131</p:property><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="endPin" ns9889235:connectedShapeId="3f86fad121f34239a0d9a0381b8e5293" ns9889235:connectedOutletId="middle-left" ns9889235:viax="5" ns9889235:viay="-216">15,-216</p:property><p:property name="withStartArrow">true</p:property><p:property name="withEndArrow">true</p:property><p:property name="mode">curvy</p:property><p:property name="detached">false</p:property><p:property name="strokeColor">#1B3280FF</p:property><p:property name="strokeStyle">2|</p:property><p:property name="textContent"/><p:property name="textFont">"Liberation Sans",Arial,sans-serif|normal|normal|13px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
<defs>
<path style="stroke-linejoin: round; fill: none;" p:name="path" id="b08f88ad83b84d5fad883f5b2d7152d9" d="M -6 -125 L -12 -131 L -6 -137 M -12 -131 C 33 -131 -30 -216 15 -216 M 9 -222 L 15 -216 L 9 -210"/>
</defs>
<use xlink:href="#b08f88ad83b84d5fad883f5b2d7152d9" stroke-width="10" stroke-opacity="0" stroke="#FF0000"/>
<use xlink:href="#b08f88ad83b84d5fad883f5b2d7152d9" p:name="outArrow1" id="81869e15f13e41609130f9b556eef9d3" style="stroke: rgb(27, 50, 128); stroke-opacity: 1; stroke-width: 2;"/>
<text p:name="text" id="2fa0348639be49eda6fa12a65623e9b6" style="fill: rgb(0, 0, 0); fill-opacity: 1; font-family: &quot;Liberation Sans&quot;, Arial, sans-serif; font-size: 13px; font-weight: normal; font-style: normal; text-decoration: none;">
<textPath xlink:href="#b08f88ad83b84d5fad883f5b2d7152d9" startOffset="50%" text-anchor="middle" alignment-baseline="middle">
<tspan dy="-4.333333333333333" p:name="textSpan" id="c544ec23fdcd4df998935ca8bb683723" dx="0"/>
</textPath>
</text>
</g><g p:type="Shape" p:def="Evolus.Common:arrow" id="0bc4ad3e83614786a0f2348a47ec40f2" transform="matrix(1,0,0,1,463,627)"><p:metadata><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="startPin" ns9889235:connectedShapeId="3f86fad121f34239a0d9a0381b8e5293" ns9889235:connectedOutletId="middle-right" ns9889235:viax="-38" ns9889235:viay="-138">-48,-138</p:property><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="endPin" ns9889235:connectedShapeId="316ea25d69264185809ddf96933f1b9d" ns9889235:connectedOutletId="middle-left" ns9889235:viax="5.999999999999972" ns9889235:viay="-208.5">16,-208</p:property><p:property name="withStartArrow">true</p:property><p:property name="withEndArrow">true</p:property><p:property name="mode">curvy</p:property><p:property name="detached">false</p:property><p:property name="strokeColor">#1B3280FF</p:property><p:property name="strokeStyle">2|</p:property><p:property name="textContent"/><p:property name="textFont">"Liberation Sans",Arial,sans-serif|normal|normal|13px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
<defs>
<path style="stroke-linejoin: round; fill: none;" p:name="path" id="6c567c02fe2642e19f2b63f417f770a9" d="M -42 -132 L -48 -138 L -42 -144 M -48 -138 C -1 -138 -31 -210 16 -208 M 11 -214 L 16 -208 L 10 -203"/>
</defs>
<use xlink:href="#6c567c02fe2642e19f2b63f417f770a9" stroke-width="10" stroke-opacity="0" stroke="#FF0000"/>
<use xlink:href="#6c567c02fe2642e19f2b63f417f770a9" p:name="outArrow1" id="cad90b7ac097400b8a9febcd1059b193" style="stroke: rgb(27, 50, 128); stroke-opacity: 1; stroke-width: 2;"/>
<text p:name="text" id="7256ea09df6f4b7ebf33202f1ced446d" style="fill: rgb(0, 0, 0); fill-opacity: 1; font-family: &quot;Liberation Sans&quot;, Arial, sans-serif; font-size: 13px; font-weight: normal; font-style: normal; text-decoration: none;">
<textPath xlink:href="#6c567c02fe2642e19f2b63f417f770a9" startOffset="50%" text-anchor="middle" alignment-baseline="middle">
<tspan dy="-4.333333333333333" p:name="textSpan" id="a3e251b119324498b6079771d87cf26b" dx="0"/>
</textPath>
</text>
</g><g p:type="Shape" p:def="Evolus.Common:RichTextBoxV2" id="f9704a7a815d4cdba445660a10c7ddc8" transform="matrix(1,0,0,1,479,601)"><p:metadata><p:property name="width">200,0</p:property><p:property name="fixedWidth">false</p:property><p:property name="textContent">&lt;span xmlns="http://www.w3.org/1999/xhtml" style="font-family: &amp;quot;Comic Sans MS&amp;quot;;"&gt;Računalnik na tiskanem vezju (SBC = single board computer)&lt;br xmlns="http://www.w3.org/1999/xhtml" /&gt;Opravlja prevzem in obdelavo podatkov s senzorja, in jih preda telefonu, ki upravlja spektrmeter&lt;/span&gt;</p:property><p:property name="textFont">"Liberation Sans",Arial,sans-serif|normal|normal|13px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="customStyle">
</p:property></p:metadata>
<foreignObject x="0" y="0" width="585" height="38" p:name="htmlObject" id="54de4aab4c144b8ead414b2e22841369" style="color: rgb(0, 0, 0); font-family: &quot;Liberation Sans&quot;, Arial, sans-serif; font-size: 13px; font-weight: normal; font-style: normal; text-decoration: none;">
<div xmlns="http://www.w3.org/1999/xhtml" p:name="textDiv" id="0cbc982da2c54d4aa9a65d4f6e9727c6" style="display: inline-block; white-space: nowrap; text-decoration: none;"><div><span style="font-family: &quot;Comic Sans MS&quot;;">Računalnik na tiskanem vezju (SBC = single board computer)<br />Opravlja prevzem in obdelavo podatkov s senzorja, in jih preda telefonu, ki upravlja spektrmeter</span></div></div>
</foreignObject>
</g><g p:type="Shape" p:def="Evolus.Common:arrow" id="c884a2093fdc477bb3a74e719a1c7c77" transform="matrix(1,0,0,1,951,527)"><p:metadata><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="startPin" ns9889235:connectedShapeId="316ea25d69264185809ddf96933f1b9d" ns9889235:connectedOutletId="middle-right" ns9889235:viax="-83" ns9889235:viay="-108.5">-93,-108</p:property><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="endPin" ns9889235:viay="-4" ns9889235:viax="-63.000000000000014" ns9889235:connectedOutletId="middle-left" ns9889235:connectedShapeId="6a62643b2a9e40ad92de2807a3b4d499">-53,-4</p:property><p:property name="withStartArrow">true</p:property><p:property name="withEndArrow">true</p:property><p:property name="mode">curvy</p:property><p:property name="detached">false</p:property><p:property name="strokeColor">#1B3280FF</p:property><p:property name="strokeStyle">2|</p:property><p:property name="textContent"/><p:property name="textFont">"Liberation Sans",Arial,sans-serif|normal|normal|13px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
<defs>
<path style="stroke-linejoin: round; fill: none;" p:name="path" id="384134701047435fac995fc6eaee5932" d="M -87 -102 L -93 -108 L -87 -114 M -93 -108 C -37 -108 -109 -4 -53 -4 M -59 -10 L -53 -4 L -59 2"/>
</defs>
<use xlink:href="#384134701047435fac995fc6eaee5932" stroke-width="10" stroke-opacity="0" stroke="#FF0000"/>
<use xlink:href="#384134701047435fac995fc6eaee5932" p:name="outArrow1" id="14d921eb06d44f7d9acd062d2b01551a" style="stroke: rgb(27, 50, 128); stroke-opacity: 1; stroke-width: 2;"/>
<text p:name="text" id="5485420a3edb40669c089909c03dc1f8" style="fill: rgb(0, 0, 0); fill-opacity: 1; font-family: &quot;Liberation Sans&quot;, Arial, sans-serif; font-size: 13px; font-weight: normal; font-style: normal; text-decoration: none;">
<textPath xlink:href="#384134701047435fac995fc6eaee5932" startOffset="50%" text-anchor="middle" alignment-baseline="middle">
<tspan dy="-4.333333333333333" p:name="textSpan" id="428895865f0145dca4a5ff61843e80ef" dx="0"/>
</textPath>
</text>
</g><g p:type="Shape" p:def="Evolus.Common:Bitmap" id="b6ed13921fdd40f9a3eb0e1e3d0ecbe6" transform="matrix(1,0,0,1,1103,146)"><p:metadata><p:property name="box">195,276</p:property><p:property name="imageData">318,450,ref://7214557d80314d2ea0269f3a449d0000.png</p:property><p:property name="withBlur">false</p:property><p:property name="fillColor">#FFFFFF00</p:property><p:property name="strokeColor">#000000FF</p:property><p:property name="strokeStyle">0|</p:property></p:metadata>
<defs>
<filter height="1.2558399" y="-0.12792" width="1.06396" x="-0.03198" p:name="imageShading" id="45b9efdc21654623a49f9d3d92738b31">
<feGaussianBlur stdDeviation="1.3325" in="SourceAlpha"/>
</filter>
<g p:name="container" id="dc93c38161c8442faea0e63e5cbf5843">
<rect style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-opacity: 1; stroke-width: 0; fill-opacity: 0;" p:name="bgRect" id="62e29bf3081e4449ae9fc80c6c967b71" transform="translate(0,0)" width="195" height="276"/>
<g p:name="imageContainer" id="6f7191c994c644f183d02acf013570d0" transform="scale(0.6132075471698113,0.6133333333333333)">
<image x="0" y="0" p:name="image" id="a6b907023988404f8a2a4c4daeb1b81e" xlink:href="file:///tmp/tmp-27399CzGZUH8OkSfx/refs/7214557d80314d2ea0269f3a449d0000.png?token=1549194986000" width="318" height="450"/>
</g>
</g>
</defs>
<use xlink:href="#dc93c38161c8442faea0e63e5cbf5843" transform="translate(1, 1)" p:filter="url(#45b9efdc21654623a49f9d3d92738b31)" style="opacity: 0.6; visibility: hidden; display: none;" p:name="bgCopy" id="02792f50e6104ccc99ad1f691c8d3a5e"/>
<use xlink:href="#dc93c38161c8442faea0e63e5cbf5843"/>
</g><g p:type="Shape" p:def="Evolus.Common:arrow" id="7b94aab3e1cd494696d3b5cf838dfde6" transform="matrix(1,0,0,1,858,286)"><p:metadata><p:property name="startPin">0,0</p:property><p:property name="endPin">282,-4</p:property><p:property name="withStartArrow">true</p:property><p:property name="withEndArrow">true</p:property><p:property name="mode">curvy</p:property><p:property name="detached">false</p:property><p:property name="strokeColor">#1B3280FF</p:property><p:property name="strokeStyle">4|3,3</p:property><p:property name="textContent"/><p:property name="textFont">"Liberation Sans",Arial,sans-serif|normal|normal|13px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
<defs>
<path style="stroke-linejoin: round; fill: none;" p:name="path" id="b8c0a2b9697f46288c408540e8af57fa" d="M 11 11 L 0 0 L 11 -11 M 0 0 C 60 0 222 -3 282 -4 M 271 -15 L 282 -4 L 271 7"/>
</defs>
<use xlink:href="#b8c0a2b9697f46288c408540e8af57fa" stroke-width="10" stroke-opacity="0" stroke="#FF0000"/>
<use xlink:href="#b8c0a2b9697f46288c408540e8af57fa" p:name="outArrow1" id="97846f435e74416e996116a08ef664e4" style="stroke: rgb(27, 50, 128); stroke-opacity: 1; stroke-width: 4; stroke-dasharray: 3, 3;"/>
<text p:name="text" id="987b10b79cfa4658917b6bb36a6005fb" style="fill: rgb(0, 0, 0); fill-opacity: 1; font-family: &quot;Liberation Sans&quot;, Arial, sans-serif; font-size: 13px; font-weight: normal; font-style: normal; text-decoration: none;">
<textPath xlink:href="#b8c0a2b9697f46288c408540e8af57fa" startOffset="50%" text-anchor="middle" alignment-baseline="middle">
<tspan dy="-4.333333333333333" p:name="textSpan" id="667515745b224bb79dbd2e134284f6d2" dx="0"/>
</textPath>
</text>
</g><g p:type="Shape" p:def="Evolus.Common:PlainTextV2" p:sc="Label" id="bb91f7fb7faf445a834363edc2cb287a" transform="matrix(1,0,0,1,1034,157)"><p:metadata><p:property name="disabled">false</p:property><p:property name="width">100,0</p:property><p:property name="fixedWidth">false</p:property><p:property name="label">Mobilni telefon preko WiFi povezave upravlja spektrometer</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textFont">'Comic Sans MS'|normal|normal|12px|none</p:property><p:property name="textAlign">0,0</p:property></p:metadata>
<rect x="0" y="0" style="fill: none; stroke: none; visibility: hidden; display: none;" p:name="bgRect" id="c2e343a9ad3246cdb047e5e01ee1fb42" width="332.390625" height="16.484375"/>
<text xml:space="preserve" p:name="text" id="357a8a3e3c4044bb8518698b7d607b60" style="fill: rgb(0, 0, 0); fill-opacity: 1; font-family: &quot;Comic Sans MS&quot;; font-size: 12px; font-weight: normal; font-style: normal; text-decoration: none;"><tspan x="0" y="0">Mobilni telefon preko WiFi povezave upravlja spektrometer</tspan></text>
</g></g></g></svg>

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1 +0,0 @@
# sysprep - How to prepare a Raspberry Pi 3 B+ to run TeraHz

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

22
setup.py Normal file
View File

@@ -0,0 +1,22 @@
import setuptools
with open("README.md", "r") as fh:
long_description = fh.read()
setuptools.setup(
name="terahz", # Replace with your own username
version="1.0.0",
author="Kristjan Komloši",
author_email="kristjan.komlosi@gmail.com",
description="Low cost spectrometry webapp",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/cls-02/TeraHz",
packages=setuptools.find_packages(),
classifiers=[
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
],
python_requires='>=3.6',
)

46
terahz/app.py Normal file
View File

@@ -0,0 +1,46 @@
# app.py - main backend program
'''Main TeraHz backend program'''
# All code in this file is licensed under the ISC license, provided in LICENSE.txt
from flask import Flask, jsonify, render_template
from . import terahz
def start_flaskapp():
'''Initialize global variables'''
global app, s, u, l
app = Flask(__name__)
s = terahz.Spectrometer(path='/dev/serial0')
u = terahz.UVSensor()
l = terahz.LxMeter()
@app.route('/data')
def sendData():
'''Responder function for /data route'''
response = jsonify([s.getData(), l.getData(), u.getABI()])
response.headers.add('Access-Control-Allow-Origin', '*')
return response
@app.route('/')
def renderTable():
''''Main page renderer'''
wavelengthDict = {
'A': '410 nm',
'B': '435 nm',
'C': '460 nm',
'D': '485 nm',
'E': '510 nm',
'F': '535 nm',
'G': '560 nm',
'H': '585 nm',
'R': '610 nm',
'I': '645 nm',
'S': '680 nm',
'J': '705 nm',
'T': '730 nm',
'U': '760 nm',
'V': '810 nm',
'W': '860 nm',
'K': '900 nm',
'L': '940 nm'}
data = [s.getData(), l.getData(), u.getABI()]
return render_template('index.html', data=data, wavelengths=wavelengthDict)

42
terahz/static/frontend.js Normal file
View File

@@ -0,0 +1,42 @@
// All code in this file is licensed under the ISC license, provided in LICENSE.txt
$('#update').click(updateData);
// jQuery event binder
function updateData () {
const url = '/data';
$.get(url, (data, status) => {
if (status === '200') {
graphSpectralData(data[0], 0);
fillTableData(data);
} else alert('Data request failed, please refresh page.');
});
}
function graphSpectralData (obj, dom) {
// graph spectral data in obj into dom
var graphPoints = [];
var graphXTicks = [];
const spectrum = 'ABCDEFGHRISJTUVWKL';
for (var i = 0; i < spectrum.length; i++) {
graphPoints.push([i, obj[spectrum[i]]]);
graphXTicks.push([i, spectrum[i]]);
}
const options = {
grid: { color: 'white' },
xaxis: { ticks: graphXTicks }
};
$.plot('#graph', [graphPoints], options);
// flot expects an array of arrays (lines) of 2-element arrays (points)
}
function fillTableData (obj) {
// fill the obj data into HTML tables
Object.keys(obj[0]).forEach((element) =>
{ $('#' + element + '_value').text(obj[0][element]); });
$('#lx').text(obj[1]);
$('#uva').text(obj[2][0]);
$('#uvb').text(obj[2][1]);
$('#uvi').text(obj[2][2]);
}

View File

Before

Width:  |  Height:  |  Size: 136 KiB

After

Width:  |  Height:  |  Size: 136 KiB

View File

@@ -0,0 +1,98 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf8">
<link rel="stylesheet" href="static/bootstrap.min.css">
<link rel="stylesheet" href="static/stylesheet.css">
<title>TeraHz</title>
<!-- script imports -->
<script src="static/bootstrap.bundle.min.js"></script>
<script src="static/jquery-3.4.1.min.js"></script>
<script src="static/flot/jquery.flot.js"></script>
<script src="static/frontend.js"></script>
<script src="static/flot/jquery.canvaswrapper.js"></script>
<script src="static/flot/jquery.colorhelpers.js"></script>
<script src="static/flot/jquery.flot.js"></script>
<script src="static/flot/jquery.flot.saturated.js"></script>
<script src="static/flot/jquery.flot.browser.js"></script>
<script src="static/flot/jquery.flot.drawSeries.js"></script>
<script src="static/flot/jquery.flot.uiConstants.js"></script>
</head>
<body>
<div class="container text-center">
<h1><img src="static/logo-sq.png" height="64px">TeraHz</h1>
</div>
<div class="container">
<button id="update" onclick="updateData()" class="btn btn-primary">Get data</button>
<button id="download" class="btn btn-primary">Download</button>
<button id="debug" class="btn btn-danger">DEBUG</button>
</div>
<div class="container">
<div class="row">
<div class="col-sm">
<h4>Spectrogram</h4>
<div id="graph" style="height:480px;width:720px"></div>
</div>
<div class="col-sm">
<h4>Visible+IR spectrum</h4>
<table class="table table-dark table-sm" id="specter">
<thead class="thead-dark">
<tr>
<th>Band</th>
<th>Wavelength [nm]</th>
<th>Irradiance [μW/cm²]</th>
</tr>
</thead>
{% for band, irr in data[0] %}
<tr id="{{band}}">
<td>
{{band}}
</td>
<td>
{{wld[band]}}
</td>
<td id="{{band}}_value">
{{irr}}
</td>
</tr>
{% endfor %}
</table>
</div>
<div class="col-sm">
<h4>UV+Illuminance</h4>
<table class="table-dark table table-sm" id="luxuv">
<thead class="thead-dark">
<tr>
<th>Parameter</th>
<th>Value</th>
</tr>
</thead>
<tr>
<td>Illuminance [lx]</td>
<td id="lx">{{data[1]}}</td>
</tr>
<tr>
<td>UVA irradiance [μW/cm²]</td>
<td id="uva">{{data[2][0]}}</td>
</tr>
<tr>
<td>UVB irradiance [μW/cm²]</td>
<td id="uvb">{{data[2][1]}}</td>
</tr>
<tr>
<td>UVA/UVB average [μW/cm²]</td>
<td id="uvi">{{data[2][2]}}</td>
</tr>
</table>
</div>
</div>
</div>
</body>
</html>

View File

@@ -1,6 +1,6 @@
# sensors.py - a module for interfacing to the sensors
# terahz.py - a module for interfacing to the sensors
'''Module for interfacing with TeraHz sensors'''
# Copyright 2019 Kristjan Komloši
# Copyright 2019, 2020 Kristjan Komloši
# All code in this file is licensed under the ISC license, provided in LICENSE.txt
import serial as ser
import pandas as pd
@@ -8,6 +8,7 @@ import smbus2
class Spectrometer:
'''Class representing the AS7265X specrometer'''
def initializeSensor(self):
'''confirm the sensor is responding and proceed\
with spectrometer initialization'''
@@ -23,8 +24,7 @@ class Spectrometer:
if rstring == 'ERROR':
raise Exception # sensor is in error state
except:
raise Exception(
'An exception ocurred when performing spectrometer handshake')
raise Exception('No AT command response')
def setParameters(self, parameters):
'''applies the parameters like LED light and gain to the spectrometer'''
@@ -53,8 +53,7 @@ class Spectrometer:
self.serialObject.write('ATLED3={}\n'.format(led).encode())
self.serialObject.readline()
except:
raise Exception(
'An exception occured during spectrometer initialization')
raise Exception('Error setting spectrometer parameters')
def getData(self):
'''Returns spectral data in a pandas DataFrame.'''
@@ -62,11 +61,10 @@ class Spectrometer:
self.serialObject.write(b'ATCDATA\n')
rawresp = self.serialObject.readline().decode()
except:
raise Exception(
'An exception occurred when polling for spectrometer data')
raise Exception('Error polling spectrometer data')
else:
responseorder = [i for i in 'RSTUVWGHIJKLABCDEF']
realorder = [i for i in 'ABCDEFGHRISJTUVWKL']
responseorder = list('RSTUVWGHIJKLABCDEF')
realorder = list('ABCDEFGHRISJTUVWKL')
response = pd.Series(
[float(i) / 35.0 for i in rawresp[:-3].split(',')], index=responseorder)
return pd.DataFrame(response, index=realorder, columns=['uW/cm^2']).to_dict()['uW/cm^2']
@@ -78,14 +76,14 @@ class Spectrometer:
try:
self.serialObject = ser.Serial(path, baudrate, timeout=tout)
except:
raise Exception(
'An exception occured when opening the serial port at {}'.format(path))
raise Exception('Error opening serial port: {}'.format(path))
else:
self.initializeSensor()
class LxMeter:
'''Class representing the APDS-9301 digital photometer.'''
def __init__(self, busNumber=1, addr=0x39):
self.addr = addr
try:
@@ -93,14 +91,14 @@ class LxMeter:
self.bus = smbus2.SMBus(busNumber)
except:
raise Exception(
'An exception occured opening the SMBus {}'.format(self.bus))
'Error opening SMBus {}'.format(self.bus))
try:
self.bus.write_byte_data(
self.addr, 0xa0, 0x03) # enable the sensor
self.setGain(16)
except:
raise Exception('An exception occured when enabling lux meter')
raise Exception('Error enabling lux meter')
def setGain(self, gain):
'''Set the sensor gain. Either 1 or 16.'''
@@ -109,15 +107,13 @@ class LxMeter:
temp = self.bus.read_byte_data(self.addr, 0xa1)
self.bus.write_byte_data(self.addr, 0xa1, 0xef & temp)
except:
raise Exception(
'An exception occured when setting lux meter gain')
raise Exception('Error setting lux meter gain')
if gain == 16:
try:
temp = self.bus.read_byte_data(self.addr, 0xa1)
self.bus.write_byte_data(self.addr, 0xa1, 0x10 | temp)
except:
raise Exception(
'An exception occured when setting lux meter gain')
raise Exception('Error setting lux meter gain')
else:
raise Exception('Invalid gain')
@@ -160,7 +156,7 @@ class LxMeter:
except:
raise Exception('An exception occured fetching lux channels')
# scary computations ahead! refer to the apds-9301 datasheet!
# Refer to APDS-9301 datasheet!
if chB / chA <= 0.5 and chB / chA > 0:
lux = 0.0304 * chA - 0.062 * chA * (chB / chA)**1.4
elif chB / chA <= 0.61 and chB / chA > 0.5:
@@ -176,6 +172,7 @@ class LxMeter:
class UVSensor:
'''Class representing VEML6075 UVA/B meter'''
def __init__(self, bus=1, addr=0x10):
self.addr = addr
try:
@@ -187,9 +184,16 @@ class UVSensor:
try:
# enable the sensor and set the integration time
self.bus.write_byte_data(self.addr, 0x00, 0b00010000)
# trigger a measurement to prevent bus errors at first read
self.bus.write_byte_data(self.addr, 0x00, 0b00010010) # UV_AF=1
self.bus.write_byte_data(self.addr, 0x00, 0b00010110) # UV_TRIG=1
self.bus.write_byte_data(
self.addr, 0x00, 0b00010000) # normal mode
except:
raise Exception(
'An exception occured when initalizing the UV Sensor')
'Error initalizing the UV Sensor')
def getABI(self):
'''Calculates the UVA and UVB irradiances,
@@ -202,40 +206,16 @@ class UVSensor:
c1 = self.bus.read_word_data(self.addr, 0x0a)
c2 = self.bus.read_word_data(self.addr, 0x0b)
except:
raise Exception('An exception occured when fetching raw UV data')
# scary computations ahead! refer to Vishay app note 84339 and Sparkfun
# VEML6075 documentation.
# compensate for visible and IR noise
aCorr = aRaw - 2.22 * c1 - 1.33 * c2
bCorr = bRaw - 2.95 * c1 - 1.74 * c2
# convert values into irradiances
a = aCorr * 1.06
b = bCorr * 0.48
# zero out negative results (readings with no uv)
if a < 0:
a = 0
if b < 0:
b = 0
# last, calculate the UV index
i = (a + b) / 2
raise Exception('Error fetching raw UV data')
# Refer to Vishay app note 84339 and Sparkfun VEML6075 documentation.
# The computed values may be negative if UV is vastly weaker than
# visible and IR light. This is not a bug!
a = (aRaw - 2.22 * c1 - 1.33 * c2) * 1.06
b = (bRaw - 2.95 * c1 - 1.74 * c2) * 0.48
i = (a + b) / 2 # calculate UV index
return [a, b, i]
def getA(self):
'''Returns UVA value. A getABI() wrapper.'''
return self.getABI()[0]
def getB(self):
'''Returns UVB value. A getABI() wrapper.'''
return self.getABI()[1]
def getI(self):
'''Returns UV index. A getABI() wrapper.'''
return self.getABI()[2]
def on(self):
'''Turns the UV sensor on after shutdown.'''
try:
@@ -244,7 +224,7 @@ class UVSensor:
self.bus.write_byte_data(self.addr, 0x00, 0x10)
except:
raise Exception(
'An exception occured when turning the UV sensor on')
'Error turning the UV sensor on')
def off(self):
'''Shuts the UV sensor down.'''
@@ -254,4 +234,4 @@ class UVSensor:
self.bus.write_byte_data(self.addr, 0x00, 0x11)
except:
raise Exception(
'An exception occured when shutting the UV sensor down')
'Error shutting the UV sensor down')

View File

@@ -1,45 +0,0 @@
# getcdata.py - fetch the calibrated data from the AS7265x module
# All code in this file is licensed under the ISC license, provided in LICENSE.txt
import serial as ser
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import time
#global variables
uartpath = '/dev/ttyUSB0'
uartbaud = 115200
uarttout = 5
wl = [410, 435, 460, 485, 510, 535, 560, 585, 610, 645, 680, 705, 730, 760, 810, 860, 900, 940]
responseorder = [i for i in 'RSTUVWGHIJKLABCDEF'] # works, do NOT touch!
realorder = [i for i in 'ABCDEFGHRISJTUVWKL']
print('getcdata')
print('This utility is part of the TeraHz project')
wavelens = pd.Series(realorder)
plt.ion()
win = plt.figure()
spectrum=win.add_subplot(111)
with ser.Serial(uartpath, uartbaud, timeout=uarttout) as sensor:
while True:
sensor.write(b'ATCDATA\n')
rawresp = sensor.readline().decode()
# parses, calculates and saves the data
response = pd.Series([float(i)/35.0 for i in rawresp[:-3].split(',')], index=responseorder)
data = pd.DataFrame(response, index=realorder, columns = ['uW/cm^2']) # puts data into a DataFrame
data.insert(0, 'wavelenght', wl) #inserts a legend
print(data)
spectrum.cla()
spectrum.plot(data['wavelenght'], data['uW/cm^2'])
spectrum.set_xlabel('Valovna dolžina')
spectrum.set_ylabel('uW/cm2')
win.canvas.draw()
time.sleep(0.1)

View File

@@ -1,12 +0,0 @@
import smbus2
bus = smbus2.SMBus(1)
result = bus.read_byte_data(0x39, 0x8a)
print('LUX Meter ID = {}'.format(result))
result = bus.read_word_data(0x10, 0x0c)
print('UV sensor ID = {}'.format(result))
result = bus.read_word_data(0x39, 0xec)
print('LUX chan 0 = {}'.format(result))

View File

@@ -1,22 +0,0 @@
from serial import Serial
import tkinter as tk
import pandas as pd
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
from matplotlib.backend_bases import key_press_handler
from matplotlib.figure import Figure
uartpath = '/dev/ttyUSB0'
uartbaud = 115200
uarttout = 5
wl = [410, 435, 460, 485, 510, 535, 560, 585, 610, 645, 680, 705, 730, 760, 810, 860, 900, 940]
responseorder = [i for i in 'RSTUVWGHIJKLABCDEF'] # works, do NOT touch!
realorder = [i for i in 'ABCDEFGHRISJTUVWKL']
root = tk.Tk()
root.wm_title('TeraHz Demo')
fig = Figure(figsize=(5, 4), dpi=100)
plot = fig.add_subplot(111)
canvas = FigureCanvasTkAgg(fig, master=root)

View File

@@ -1,76 +0,0 @@
# This file must be used with "source bin/activate" *from bash*
# you cannot run it directly
deactivate () {
# reset old environment variables
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
PATH="${_OLD_VIRTUAL_PATH:-}"
export PATH
unset _OLD_VIRTUAL_PATH
fi
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
export PYTHONHOME
unset _OLD_VIRTUAL_PYTHONHOME
fi
# This should detect bash and zsh, which have a hash command that must
# be called to get it to forget past commands. Without forgetting
# past commands the $PATH changes we made may not be respected
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
hash -r
fi
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
PS1="${_OLD_VIRTUAL_PS1:-}"
export PS1
unset _OLD_VIRTUAL_PS1
fi
unset VIRTUAL_ENV
if [ ! "$1" = "nondestructive" ] ; then
# Self destruct!
unset -f deactivate
fi
}
# unset irrelevant variables
deactivate nondestructive
VIRTUAL_ENV="/root/projekti/TeraHz/utils/venv"
export VIRTUAL_ENV
_OLD_VIRTUAL_PATH="$PATH"
PATH="$VIRTUAL_ENV/bin:$PATH"
export PATH
# unset PYTHONHOME if set
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
# could use `if (set -u; : $PYTHONHOME) ;` in bash
if [ -n "${PYTHONHOME:-}" ] ; then
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
unset PYTHONHOME
fi
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
_OLD_VIRTUAL_PS1="${PS1:-}"
if [ "x(venv) " != x ] ; then
PS1="(venv) ${PS1:-}"
else
if [ "`basename \"$VIRTUAL_ENV\"`" = "__" ] ; then
# special case for Aspen magic directories
# see http://www.zetadev.com/software/aspen/
PS1="[`basename \`dirname \"$VIRTUAL_ENV\"\``] $PS1"
else
PS1="(`basename \"$VIRTUAL_ENV\"`)$PS1"
fi
fi
export PS1
fi
# This should detect bash and zsh, which have a hash command that must
# be called to get it to forget past commands. Without forgetting
# past commands the $PATH changes we made may not be respected
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
hash -r
fi

View File

@@ -1,37 +0,0 @@
# This file must be used with "source bin/activate.csh" *from csh*.
# You cannot run it directly.
# Created by Davide Di Blasi <davidedb@gmail.com>.
# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; test "\!:*" != "nondestructive" && unalias deactivate'
# Unset irrelevant variables.
deactivate nondestructive
setenv VIRTUAL_ENV "/root/projekti/TeraHz/utils/venv"
set _OLD_VIRTUAL_PATH="$PATH"
setenv PATH "$VIRTUAL_ENV/bin:$PATH"
set _OLD_VIRTUAL_PROMPT="$prompt"
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
if ("venv" != "") then
set env_name = "venv"
else
if (`basename "VIRTUAL_ENV"` == "__") then
# special case for Aspen magic directories
# see http://www.zetadev.com/software/aspen/
set env_name = `basename \`dirname "$VIRTUAL_ENV"\``
else
set env_name = `basename "$VIRTUAL_ENV"`
endif
endif
set prompt = "[$env_name] $prompt"
unset env_name
endif
alias pydoc python -m pydoc
rehash

View File

@@ -1,75 +0,0 @@
# This file must be used with ". bin/activate.fish" *from fish* (http://fishshell.org)
# you cannot run it directly
function deactivate -d "Exit virtualenv and return to normal shell environment"
# reset old environment variables
if test -n "$_OLD_VIRTUAL_PATH"
set -gx PATH $_OLD_VIRTUAL_PATH
set -e _OLD_VIRTUAL_PATH
end
if test -n "$_OLD_VIRTUAL_PYTHONHOME"
set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
set -e _OLD_VIRTUAL_PYTHONHOME
end
if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
functions -e fish_prompt
set -e _OLD_FISH_PROMPT_OVERRIDE
functions -c _old_fish_prompt fish_prompt
functions -e _old_fish_prompt
end
set -e VIRTUAL_ENV
if test "$argv[1]" != "nondestructive"
# Self destruct!
functions -e deactivate
end
end
# unset irrelevant variables
deactivate nondestructive
set -gx VIRTUAL_ENV "/root/projekti/TeraHz/utils/venv"
set -gx _OLD_VIRTUAL_PATH $PATH
set -gx PATH "$VIRTUAL_ENV/bin" $PATH
# unset PYTHONHOME if set
if set -q PYTHONHOME
set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
set -e PYTHONHOME
end
if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
# fish uses a function instead of an env var to generate the prompt.
# save the current fish_prompt function as the function _old_fish_prompt
functions -c fish_prompt _old_fish_prompt
# with the original prompt function renamed, we can override with our own.
function fish_prompt
# Save the return status of the last command
set -l old_status $status
# Prompt override?
if test -n "(venv) "
printf "%s%s" "(venv) " (set_color normal)
else
# ...Otherwise, prepend env
set -l _checkbase (basename "$VIRTUAL_ENV")
if test $_checkbase = "__"
# special case for Aspen magic directories
# see http://www.zetadev.com/software/aspen/
printf "%s[%s]%s " (set_color -b blue white) (basename (dirname "$VIRTUAL_ENV")) (set_color normal)
else
printf "%s(%s)%s" (set_color -b blue white) (basename "$VIRTUAL_ENV") (set_color normal)
end
end
# Restore the return status of the previous command.
echo "exit $old_status" | .
_old_fish_prompt
end
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
end

View File

@@ -1,10 +0,0 @@
#!/root/projekti/TeraHz/utils/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from setuptools.command.easy_install import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(main())

View File

@@ -1,10 +0,0 @@
#!/root/projekti/TeraHz/utils/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from setuptools.command.easy_install import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(main())

View File

@@ -1,10 +0,0 @@
#!/root/projekti/TeraHz/utils/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from numpy.f2py.f2py2e import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(main())

View File

@@ -1,10 +0,0 @@
#!/root/projekti/TeraHz/utils/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from numpy.f2py.f2py2e import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(main())

View File

@@ -1,10 +0,0 @@
#!/root/projekti/TeraHz/utils/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from numpy.f2py.f2py2e import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(main())

View File

@@ -1,976 +0,0 @@
#!/root/projekti/TeraHz/utils/venv/bin/python3
#
# Very simple serial terminal
#
# This file is part of pySerial. https://github.com/pyserial/pyserial
# (C)2002-2015 Chris Liechti <cliechti@gmx.net>
#
# SPDX-License-Identifier: BSD-3-Clause
import codecs
import os
import sys
import threading
import serial
from serial.tools.list_ports import comports
from serial.tools import hexlify_codec
# pylint: disable=wrong-import-order,wrong-import-position
codecs.register(lambda c: hexlify_codec.getregentry() if c == 'hexlify' else None)
try:
raw_input
except NameError:
# pylint: disable=redefined-builtin,invalid-name
raw_input = input # in python3 it's "raw"
unichr = chr
def key_description(character):
"""generate a readable description for a key"""
ascii_code = ord(character)
if ascii_code < 32:
return 'Ctrl+{:c}'.format(ord('@') + ascii_code)
else:
return repr(character)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
class ConsoleBase(object):
"""OS abstraction for console (input/output codec, no echo)"""
def __init__(self):
if sys.version_info >= (3, 0):
self.byte_output = sys.stdout.buffer
else:
self.byte_output = sys.stdout
self.output = sys.stdout
def setup(self):
"""Set console to read single characters, no echo"""
def cleanup(self):
"""Restore default console settings"""
def getkey(self):
"""Read a single key from the console"""
return None
def write_bytes(self, byte_string):
"""Write bytes (already encoded)"""
self.byte_output.write(byte_string)
self.byte_output.flush()
def write(self, text):
"""Write string"""
self.output.write(text)
self.output.flush()
def cancel(self):
"""Cancel getkey operation"""
# - - - - - - - - - - - - - - - - - - - - - - - -
# context manager:
# switch terminal temporary to normal mode (e.g. to get user input)
def __enter__(self):
self.cleanup()
return self
def __exit__(self, *args, **kwargs):
self.setup()
if os.name == 'nt': # noqa
import msvcrt
import ctypes
class Out(object):
"""file-like wrapper that uses os.write"""
def __init__(self, fd):
self.fd = fd
def flush(self):
pass
def write(self, s):
os.write(self.fd, s)
class Console(ConsoleBase):
def __init__(self):
super(Console, self).__init__()
self._saved_ocp = ctypes.windll.kernel32.GetConsoleOutputCP()
self._saved_icp = ctypes.windll.kernel32.GetConsoleCP()
ctypes.windll.kernel32.SetConsoleOutputCP(65001)
ctypes.windll.kernel32.SetConsoleCP(65001)
self.output = codecs.getwriter('UTF-8')(Out(sys.stdout.fileno()), 'replace')
# the change of the code page is not propagated to Python, manually fix it
sys.stderr = codecs.getwriter('UTF-8')(Out(sys.stderr.fileno()), 'replace')
sys.stdout = self.output
self.output.encoding = 'UTF-8' # needed for input
def __del__(self):
ctypes.windll.kernel32.SetConsoleOutputCP(self._saved_ocp)
ctypes.windll.kernel32.SetConsoleCP(self._saved_icp)
def getkey(self):
while True:
z = msvcrt.getwch()
if z == unichr(13):
return unichr(10)
elif z in (unichr(0), unichr(0x0e)): # functions keys, ignore
msvcrt.getwch()
else:
return z
def cancel(self):
# CancelIo, CancelSynchronousIo do not seem to work when using
# getwch, so instead, send a key to the window with the console
hwnd = ctypes.windll.kernel32.GetConsoleWindow()
ctypes.windll.user32.PostMessageA(hwnd, 0x100, 0x0d, 0)
elif os.name == 'posix':
import atexit
import termios
import fcntl
class Console(ConsoleBase):
def __init__(self):
super(Console, self).__init__()
self.fd = sys.stdin.fileno()
self.old = termios.tcgetattr(self.fd)
atexit.register(self.cleanup)
if sys.version_info < (3, 0):
self.enc_stdin = codecs.getreader(sys.stdin.encoding)(sys.stdin)
else:
self.enc_stdin = sys.stdin
def setup(self):
new = termios.tcgetattr(self.fd)
new[3] = new[3] & ~termios.ICANON & ~termios.ECHO & ~termios.ISIG
new[6][termios.VMIN] = 1
new[6][termios.VTIME] = 0
termios.tcsetattr(self.fd, termios.TCSANOW, new)
def getkey(self):
c = self.enc_stdin.read(1)
if c == unichr(0x7f):
c = unichr(8) # map the BS key (which yields DEL) to backspace
return c
def cancel(self):
fcntl.ioctl(self.fd, termios.TIOCSTI, b'\0')
def cleanup(self):
termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old)
else:
raise NotImplementedError(
'Sorry no implementation for your platform ({}) available.'.format(sys.platform))
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
class Transform(object):
"""do-nothing: forward all data unchanged"""
def rx(self, text):
"""text received from serial port"""
return text
def tx(self, text):
"""text to be sent to serial port"""
return text
def echo(self, text):
"""text to be sent but displayed on console"""
return text
class CRLF(Transform):
"""ENTER sends CR+LF"""
def tx(self, text):
return text.replace('\n', '\r\n')
class CR(Transform):
"""ENTER sends CR"""
def rx(self, text):
return text.replace('\r', '\n')
def tx(self, text):
return text.replace('\n', '\r')
class LF(Transform):
"""ENTER sends LF"""
class NoTerminal(Transform):
"""remove typical terminal control codes from input"""
REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32) if unichr(x) not in '\r\n\b\t')
REPLACEMENT_MAP.update(
{
0x7F: 0x2421, # DEL
0x9B: 0x2425, # CSI
})
def rx(self, text):
return text.translate(self.REPLACEMENT_MAP)
echo = rx
class NoControls(NoTerminal):
"""Remove all control codes, incl. CR+LF"""
REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32))
REPLACEMENT_MAP.update(
{
0x20: 0x2423, # visual space
0x7F: 0x2421, # DEL
0x9B: 0x2425, # CSI
})
class Printable(Transform):
"""Show decimal code for all non-ASCII characters and replace most control codes"""
def rx(self, text):
r = []
for c in text:
if ' ' <= c < '\x7f' or c in '\r\n\b\t':
r.append(c)
elif c < ' ':
r.append(unichr(0x2400 + ord(c)))
else:
r.extend(unichr(0x2080 + ord(d) - 48) for d in '{:d}'.format(ord(c)))
r.append(' ')
return ''.join(r)
echo = rx
class Colorize(Transform):
"""Apply different colors for received and echo"""
def __init__(self):
# XXX make it configurable, use colorama?
self.input_color = '\x1b[37m'
self.echo_color = '\x1b[31m'
def rx(self, text):
return self.input_color + text
def echo(self, text):
return self.echo_color + text
class DebugIO(Transform):
"""Print what is sent and received"""
def rx(self, text):
sys.stderr.write(' [RX:{}] '.format(repr(text)))
sys.stderr.flush()
return text
def tx(self, text):
sys.stderr.write(' [TX:{}] '.format(repr(text)))
sys.stderr.flush()
return text
# other ideas:
# - add date/time for each newline
# - insert newline after: a) timeout b) packet end character
EOL_TRANSFORMATIONS = {
'crlf': CRLF,
'cr': CR,
'lf': LF,
}
TRANSFORMATIONS = {
'direct': Transform, # no transformation
'default': NoTerminal,
'nocontrol': NoControls,
'printable': Printable,
'colorize': Colorize,
'debug': DebugIO,
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
def ask_for_port():
"""\
Show a list of ports and ask the user for a choice. To make selection
easier on systems with long device names, also allow the input of an
index.
"""
sys.stderr.write('\n--- Available ports:\n')
ports = []
for n, (port, desc, hwid) in enumerate(sorted(comports()), 1):
sys.stderr.write('--- {:2}: {:20} {!r}\n'.format(n, port, desc))
ports.append(port)
while True:
port = raw_input('--- Enter port index or full name: ')
try:
index = int(port) - 1
if not 0 <= index < len(ports):
sys.stderr.write('--- Invalid index!\n')
continue
except ValueError:
pass
else:
port = ports[index]
return port
class Miniterm(object):
"""\
Terminal application. Copy data from serial port to console and vice versa.
Handle special keys from the console to show menu etc.
"""
def __init__(self, serial_instance, echo=False, eol='crlf', filters=()):
self.console = Console()
self.serial = serial_instance
self.echo = echo
self.raw = False
self.input_encoding = 'UTF-8'
self.output_encoding = 'UTF-8'
self.eol = eol
self.filters = filters
self.update_transformations()
self.exit_character = 0x1d # GS/CTRL+]
self.menu_character = 0x14 # Menu: CTRL+T
self.alive = None
self._reader_alive = None
self.receiver_thread = None
self.rx_decoder = None
self.tx_decoder = None
def _start_reader(self):
"""Start reader thread"""
self._reader_alive = True
# start serial->console thread
self.receiver_thread = threading.Thread(target=self.reader, name='rx')
self.receiver_thread.daemon = True
self.receiver_thread.start()
def _stop_reader(self):
"""Stop reader thread only, wait for clean exit of thread"""
self._reader_alive = False
if hasattr(self.serial, 'cancel_read'):
self.serial.cancel_read()
self.receiver_thread.join()
def start(self):
"""start worker threads"""
self.alive = True
self._start_reader()
# enter console->serial loop
self.transmitter_thread = threading.Thread(target=self.writer, name='tx')
self.transmitter_thread.daemon = True
self.transmitter_thread.start()
self.console.setup()
def stop(self):
"""set flag to stop worker threads"""
self.alive = False
def join(self, transmit_only=False):
"""wait for worker threads to terminate"""
self.transmitter_thread.join()
if not transmit_only:
if hasattr(self.serial, 'cancel_read'):
self.serial.cancel_read()
self.receiver_thread.join()
def close(self):
self.serial.close()
def update_transformations(self):
"""take list of transformation classes and instantiate them for rx and tx"""
transformations = [EOL_TRANSFORMATIONS[self.eol]] + [TRANSFORMATIONS[f]
for f in self.filters]
self.tx_transformations = [t() for t in transformations]
self.rx_transformations = list(reversed(self.tx_transformations))
def set_rx_encoding(self, encoding, errors='replace'):
"""set encoding for received data"""
self.input_encoding = encoding
self.rx_decoder = codecs.getincrementaldecoder(encoding)(errors)
def set_tx_encoding(self, encoding, errors='replace'):
"""set encoding for transmitted data"""
self.output_encoding = encoding
self.tx_encoder = codecs.getincrementalencoder(encoding)(errors)
def dump_port_settings(self):
"""Write current settings to sys.stderr"""
sys.stderr.write("\n--- Settings: {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits}\n".format(
p=self.serial))
sys.stderr.write('--- RTS: {:8} DTR: {:8} BREAK: {:8}\n'.format(
('active' if self.serial.rts else 'inactive'),
('active' if self.serial.dtr else 'inactive'),
('active' if self.serial.break_condition else 'inactive')))
try:
sys.stderr.write('--- CTS: {:8} DSR: {:8} RI: {:8} CD: {:8}\n'.format(
('active' if self.serial.cts else 'inactive'),
('active' if self.serial.dsr else 'inactive'),
('active' if self.serial.ri else 'inactive'),
('active' if self.serial.cd else 'inactive')))
except serial.SerialException:
# on RFC 2217 ports, it can happen if no modem state notification was
# yet received. ignore this error.
pass
sys.stderr.write('--- software flow control: {}\n'.format('active' if self.serial.xonxoff else 'inactive'))
sys.stderr.write('--- hardware flow control: {}\n'.format('active' if self.serial.rtscts else 'inactive'))
sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding))
sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding))
sys.stderr.write('--- EOL: {}\n'.format(self.eol.upper()))
sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters)))
def reader(self):
"""loop and copy serial->console"""
try:
while self.alive and self._reader_alive:
# read all that is there or wait for one byte
data = self.serial.read(self.serial.in_waiting or 1)
if data:
if self.raw:
self.console.write_bytes(data)
else:
text = self.rx_decoder.decode(data)
for transformation in self.rx_transformations:
text = transformation.rx(text)
self.console.write(text)
except serial.SerialException:
self.alive = False
self.console.cancel()
raise # XXX handle instead of re-raise?
def writer(self):
"""\
Loop and copy console->serial until self.exit_character character is
found. When self.menu_character is found, interpret the next key
locally.
"""
menu_active = False
try:
while self.alive:
try:
c = self.console.getkey()
except KeyboardInterrupt:
c = '\x03'
if not self.alive:
break
if menu_active:
self.handle_menu_key(c)
menu_active = False
elif c == self.menu_character:
menu_active = True # next char will be for menu
elif c == self.exit_character:
self.stop() # exit app
break
else:
#~ if self.raw:
text = c
for transformation in self.tx_transformations:
text = transformation.tx(text)
self.serial.write(self.tx_encoder.encode(text))
if self.echo:
echo_text = c
for transformation in self.tx_transformations:
echo_text = transformation.echo(echo_text)
self.console.write(echo_text)
except:
self.alive = False
raise
def handle_menu_key(self, c):
"""Implement a simple menu / settings"""
if c == self.menu_character or c == self.exit_character:
# Menu/exit character again -> send itself
self.serial.write(self.tx_encoder.encode(c))
if self.echo:
self.console.write(c)
elif c == '\x15': # CTRL+U -> upload file
self.upload_file()
elif c in '\x08hH?': # CTRL+H, h, H, ? -> Show help
sys.stderr.write(self.get_help_text())
elif c == '\x12': # CTRL+R -> Toggle RTS
self.serial.rts = not self.serial.rts
sys.stderr.write('--- RTS {} ---\n'.format('active' if self.serial.rts else 'inactive'))
elif c == '\x04': # CTRL+D -> Toggle DTR
self.serial.dtr = not self.serial.dtr
sys.stderr.write('--- DTR {} ---\n'.format('active' if self.serial.dtr else 'inactive'))
elif c == '\x02': # CTRL+B -> toggle BREAK condition
self.serial.break_condition = not self.serial.break_condition
sys.stderr.write('--- BREAK {} ---\n'.format('active' if self.serial.break_condition else 'inactive'))
elif c == '\x05': # CTRL+E -> toggle local echo
self.echo = not self.echo
sys.stderr.write('--- local echo {} ---\n'.format('active' if self.echo else 'inactive'))
elif c == '\x06': # CTRL+F -> edit filters
self.change_filter()
elif c == '\x0c': # CTRL+L -> EOL mode
modes = list(EOL_TRANSFORMATIONS) # keys
eol = modes.index(self.eol) + 1
if eol >= len(modes):
eol = 0
self.eol = modes[eol]
sys.stderr.write('--- EOL: {} ---\n'.format(self.eol.upper()))
self.update_transformations()
elif c == '\x01': # CTRL+A -> set encoding
self.change_encoding()
elif c == '\x09': # CTRL+I -> info
self.dump_port_settings()
#~ elif c == '\x01': # CTRL+A -> cycle escape mode
#~ elif c == '\x0c': # CTRL+L -> cycle linefeed mode
elif c in 'pP': # P -> change port
self.change_port()
elif c in 'sS': # S -> suspend / open port temporarily
self.suspend_port()
elif c in 'bB': # B -> change baudrate
self.change_baudrate()
elif c == '8': # 8 -> change to 8 bits
self.serial.bytesize = serial.EIGHTBITS
self.dump_port_settings()
elif c == '7': # 7 -> change to 8 bits
self.serial.bytesize = serial.SEVENBITS
self.dump_port_settings()
elif c in 'eE': # E -> change to even parity
self.serial.parity = serial.PARITY_EVEN
self.dump_port_settings()
elif c in 'oO': # O -> change to odd parity
self.serial.parity = serial.PARITY_ODD
self.dump_port_settings()
elif c in 'mM': # M -> change to mark parity
self.serial.parity = serial.PARITY_MARK
self.dump_port_settings()
elif c in 'sS': # S -> change to space parity
self.serial.parity = serial.PARITY_SPACE
self.dump_port_settings()
elif c in 'nN': # N -> change to no parity
self.serial.parity = serial.PARITY_NONE
self.dump_port_settings()
elif c == '1': # 1 -> change to 1 stop bits
self.serial.stopbits = serial.STOPBITS_ONE
self.dump_port_settings()
elif c == '2': # 2 -> change to 2 stop bits
self.serial.stopbits = serial.STOPBITS_TWO
self.dump_port_settings()
elif c == '3': # 3 -> change to 1.5 stop bits
self.serial.stopbits = serial.STOPBITS_ONE_POINT_FIVE
self.dump_port_settings()
elif c in 'xX': # X -> change software flow control
self.serial.xonxoff = (c == 'X')
self.dump_port_settings()
elif c in 'rR': # R -> change hardware flow control
self.serial.rtscts = (c == 'R')
self.dump_port_settings()
else:
sys.stderr.write('--- unknown menu character {} --\n'.format(key_description(c)))
def upload_file(self):
"""Ask user for filenname and send its contents"""
sys.stderr.write('\n--- File to upload: ')
sys.stderr.flush()
with self.console:
filename = sys.stdin.readline().rstrip('\r\n')
if filename:
try:
with open(filename, 'rb') as f:
sys.stderr.write('--- Sending file {} ---\n'.format(filename))
while True:
block = f.read(1024)
if not block:
break
self.serial.write(block)
# Wait for output buffer to drain.
self.serial.flush()
sys.stderr.write('.') # Progress indicator.
sys.stderr.write('\n--- File {} sent ---\n'.format(filename))
except IOError as e:
sys.stderr.write('--- ERROR opening file {}: {} ---\n'.format(filename, e))
def change_filter(self):
"""change the i/o transformations"""
sys.stderr.write('\n--- Available Filters:\n')
sys.stderr.write('\n'.join(
'--- {:<10} = {.__doc__}'.format(k, v)
for k, v in sorted(TRANSFORMATIONS.items())))
sys.stderr.write('\n--- Enter new filter name(s) [{}]: '.format(' '.join(self.filters)))
with self.console:
new_filters = sys.stdin.readline().lower().split()
if new_filters:
for f in new_filters:
if f not in TRANSFORMATIONS:
sys.stderr.write('--- unknown filter: {}\n'.format(repr(f)))
break
else:
self.filters = new_filters
self.update_transformations()
sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters)))
def change_encoding(self):
"""change encoding on the serial port"""
sys.stderr.write('\n--- Enter new encoding name [{}]: '.format(self.input_encoding))
with self.console:
new_encoding = sys.stdin.readline().strip()
if new_encoding:
try:
codecs.lookup(new_encoding)
except LookupError:
sys.stderr.write('--- invalid encoding name: {}\n'.format(new_encoding))
else:
self.set_rx_encoding(new_encoding)
self.set_tx_encoding(new_encoding)
sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding))
sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding))
def change_baudrate(self):
"""change the baudrate"""
sys.stderr.write('\n--- Baudrate: ')
sys.stderr.flush()
with self.console:
backup = self.serial.baudrate
try:
self.serial.baudrate = int(sys.stdin.readline().strip())
except ValueError as e:
sys.stderr.write('--- ERROR setting baudrate: {} ---\n'.format(e))
self.serial.baudrate = backup
else:
self.dump_port_settings()
def change_port(self):
"""Have a conversation with the user to change the serial port"""
with self.console:
try:
port = ask_for_port()
except KeyboardInterrupt:
port = None
if port and port != self.serial.port:
# reader thread needs to be shut down
self._stop_reader()
# save settings
settings = self.serial.getSettingsDict()
try:
new_serial = serial.serial_for_url(port, do_not_open=True)
# restore settings and open
new_serial.applySettingsDict(settings)
new_serial.rts = self.serial.rts
new_serial.dtr = self.serial.dtr
new_serial.open()
new_serial.break_condition = self.serial.break_condition
except Exception as e:
sys.stderr.write('--- ERROR opening new port: {} ---\n'.format(e))
new_serial.close()
else:
self.serial.close()
self.serial = new_serial
sys.stderr.write('--- Port changed to: {} ---\n'.format(self.serial.port))
# and restart the reader thread
self._start_reader()
def suspend_port(self):
"""\
open port temporarily, allow reconnect, exit and port change to get
out of the loop
"""
# reader thread needs to be shut down
self._stop_reader()
self.serial.close()
sys.stderr.write('\n--- Port closed: {} ---\n'.format(self.serial.port))
do_change_port = False
while not self.serial.is_open:
sys.stderr.write('--- Quit: {exit} | p: port change | any other key to reconnect ---\n'.format(
exit=key_description(self.exit_character)))
k = self.console.getkey()
if k == self.exit_character:
self.stop() # exit app
break
elif k in 'pP':
do_change_port = True
break
try:
self.serial.open()
except Exception as e:
sys.stderr.write('--- ERROR opening port: {} ---\n'.format(e))
if do_change_port:
self.change_port()
else:
# and restart the reader thread
self._start_reader()
sys.stderr.write('--- Port opened: {} ---\n'.format(self.serial.port))
def get_help_text(self):
"""return the help text"""
# help text, starts with blank line!
return """
--- pySerial ({version}) - miniterm - help
---
--- {exit:8} Exit program
--- {menu:8} Menu escape key, followed by:
--- Menu keys:
--- {menu:7} Send the menu character itself to remote
--- {exit:7} Send the exit character itself to remote
--- {info:7} Show info
--- {upload:7} Upload file (prompt will be shown)
--- {repr:7} encoding
--- {filter:7} edit filters
--- Toggles:
--- {rts:7} RTS {dtr:7} DTR {brk:7} BREAK
--- {echo:7} echo {eol:7} EOL
---
--- Port settings ({menu} followed by the following):
--- p change port
--- 7 8 set data bits
--- N E O S M change parity (None, Even, Odd, Space, Mark)
--- 1 2 3 set stop bits (1, 2, 1.5)
--- b change baud rate
--- x X disable/enable software flow control
--- r R disable/enable hardware flow control
""".format(version=getattr(serial, 'VERSION', 'unknown version'),
exit=key_description(self.exit_character),
menu=key_description(self.menu_character),
rts=key_description('\x12'),
dtr=key_description('\x04'),
brk=key_description('\x02'),
echo=key_description('\x05'),
info=key_description('\x09'),
upload=key_description('\x15'),
repr=key_description('\x01'),
filter=key_description('\x06'),
eol=key_description('\x0c'))
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# default args can be used to override when calling main() from an other script
# e.g to create a miniterm-my-device.py
def main(default_port=None, default_baudrate=9600, default_rts=None, default_dtr=None):
"""Command line tool, entry point"""
import argparse
parser = argparse.ArgumentParser(
description="Miniterm - A simple terminal program for the serial port.")
parser.add_argument(
"port",
nargs='?',
help="serial port name ('-' to show port list)",
default=default_port)
parser.add_argument(
"baudrate",
nargs='?',
type=int,
help="set baud rate, default: %(default)s",
default=default_baudrate)
group = parser.add_argument_group("port settings")
group.add_argument(
"--parity",
choices=['N', 'E', 'O', 'S', 'M'],
type=lambda c: c.upper(),
help="set parity, one of {N E O S M}, default: N",
default='N')
group.add_argument(
"--rtscts",
action="store_true",
help="enable RTS/CTS flow control (default off)",
default=False)
group.add_argument(
"--xonxoff",
action="store_true",
help="enable software flow control (default off)",
default=False)
group.add_argument(
"--rts",
type=int,
help="set initial RTS line state (possible values: 0, 1)",
default=default_rts)
group.add_argument(
"--dtr",
type=int,
help="set initial DTR line state (possible values: 0, 1)",
default=default_dtr)
group.add_argument(
"--ask",
action="store_true",
help="ask again for port when open fails",
default=False)
group = parser.add_argument_group("data handling")
group.add_argument(
"-e", "--echo",
action="store_true",
help="enable local echo (default off)",
default=False)
group.add_argument(
"--encoding",
dest="serial_port_encoding",
metavar="CODEC",
help="set the encoding for the serial port (e.g. hexlify, Latin1, UTF-8), default: %(default)s",
default='UTF-8')
group.add_argument(
"-f", "--filter",
action="append",
metavar="NAME",
help="add text transformation",
default=[])
group.add_argument(
"--eol",
choices=['CR', 'LF', 'CRLF'],
type=lambda c: c.upper(),
help="end of line mode",
default='CRLF')
group.add_argument(
"--raw",
action="store_true",
help="Do no apply any encodings/transformations",
default=False)
group = parser.add_argument_group("hotkeys")
group.add_argument(
"--exit-char",
type=int,
metavar='NUM',
help="Unicode of special character that is used to exit the application, default: %(default)s",
default=0x1d) # GS/CTRL+]
group.add_argument(
"--menu-char",
type=int,
metavar='NUM',
help="Unicode code of special character that is used to control miniterm (menu), default: %(default)s",
default=0x14) # Menu: CTRL+T
group = parser.add_argument_group("diagnostics")
group.add_argument(
"-q", "--quiet",
action="store_true",
help="suppress non-error messages",
default=False)
group.add_argument(
"--develop",
action="store_true",
help="show Python traceback on error",
default=False)
args = parser.parse_args()
if args.menu_char == args.exit_char:
parser.error('--exit-char can not be the same as --menu-char')
if args.filter:
if 'help' in args.filter:
sys.stderr.write('Available filters:\n')
sys.stderr.write('\n'.join(
'{:<10} = {.__doc__}'.format(k, v)
for k, v in sorted(TRANSFORMATIONS.items())))
sys.stderr.write('\n')
sys.exit(1)
filters = args.filter
else:
filters = ['default']
while True:
# no port given on command line -> ask user now
if args.port is None or args.port == '-':
try:
args.port = ask_for_port()
except KeyboardInterrupt:
sys.stderr.write('\n')
parser.error('user aborted and port is not given')
else:
if not args.port:
parser.error('port is not given')
try:
serial_instance = serial.serial_for_url(
args.port,
args.baudrate,
parity=args.parity,
rtscts=args.rtscts,
xonxoff=args.xonxoff,
do_not_open=True)
if not hasattr(serial_instance, 'cancel_read'):
# enable timeout for alive flag polling if cancel_read is not available
serial_instance.timeout = 1
if args.dtr is not None:
if not args.quiet:
sys.stderr.write('--- forcing DTR {}\n'.format('active' if args.dtr else 'inactive'))
serial_instance.dtr = args.dtr
if args.rts is not None:
if not args.quiet:
sys.stderr.write('--- forcing RTS {}\n'.format('active' if args.rts else 'inactive'))
serial_instance.rts = args.rts
serial_instance.open()
except serial.SerialException as e:
sys.stderr.write('could not open port {}: {}\n'.format(repr(args.port), e))
if args.develop:
raise
if not args.ask:
sys.exit(1)
else:
args.port = '-'
else:
break
miniterm = Miniterm(
serial_instance,
echo=args.echo,
eol=args.eol.lower(),
filters=filters)
miniterm.exit_character = unichr(args.exit_char)
miniterm.menu_character = unichr(args.menu_char)
miniterm.raw = args.raw
miniterm.set_rx_encoding(args.serial_port_encoding)
miniterm.set_tx_encoding(args.serial_port_encoding)
if not args.quiet:
sys.stderr.write('--- Miniterm on {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits} ---\n'.format(
p=miniterm.serial))
sys.stderr.write('--- Quit: {} | Menu: {} | Help: {} followed by {} ---\n'.format(
key_description(miniterm.exit_character),
key_description(miniterm.menu_character),
key_description(miniterm.menu_character),
key_description('\x08')))
miniterm.start()
try:
miniterm.join(True)
except KeyboardInterrupt:
pass
if not args.quiet:
sys.stderr.write("\n--- exit ---\n")
miniterm.join()
miniterm.close()
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
if __name__ == '__main__':
main()

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More