Compare commits
20 Commits
v1.0.0-rc1
...
v1.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89ea9564c8 | ||
|
|
00c619ce34 | ||
|
|
8f22a6baee | ||
|
|
fa2c1cccb1 | ||
|
|
7058512ee0 | ||
|
|
325f1176c3 | ||
|
|
0a9e610dd7 | ||
|
|
43b5277923 | ||
|
|
5265ba9ae9 | ||
|
|
16e9c52194 | ||
|
|
c5db357cc0 | ||
|
|
b7eb29f78b | ||
|
|
1457655f16 | ||
|
|
a3f3558f7c | ||
|
|
bc72207187 | ||
|
|
7837f605cf | ||
|
|
3d1a925ba6 | ||
|
|
682fe20db2 | ||
|
|
949dd0bef2 | ||
|
|
58cb777935 |
48
README.md
48
README.md
@@ -1,23 +1,37 @@
|
|||||||
# TeraHz
|
|
||||||
|
|
||||||
[](https://terahz.readthedocs.io/en/latest/?badge=latest)
|
[](https://terahz.readthedocs.io/en/latest/?badge=latest)
|
||||||
|
# <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:
|
*Za slovensko različico se odpravite na <http://git.sckr-lab.tk/kristjank/TeraHz>*
|
||||||
+ [__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
|
|
||||||
|
|
||||||
## Why?
|
TeraHz is a low-cost portable spectrometer based on Raspberry Pi and a few
|
||||||
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.
|
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
|
## 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)
|
<img alt="TeraHz logo" src="http://www.sckr.si/documents/upload/konektor/logo/_SC.gif" width="200px">
|
||||||
- Jakob "D3m1j4ck" Kosec (frontend)
|
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.
|
||||||
|
|
||||||
|
|
||||||
I would also like to thank Juš "ANormalPerson" Dolžan, who decided to leave the
|
|
||||||
team, but helped me a lot with backend development.
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
# app.py - main backend program
|
# app.py - main backend program
|
||||||
'''Main TeraHz backend program'''
|
'''Main TeraHz backend program'''
|
||||||
# All code in this file is licensed under the ISC license, provided in LICENSE.txt
|
# All code in this file is licensed under the ISC license, provided in LICENSE.txt
|
||||||
from flask import Flask
|
from flask import Flask, jsonify
|
||||||
import flask
|
|
||||||
import sensors
|
import sensors
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
@@ -12,6 +11,6 @@ def sendData():
|
|||||||
s = sensors.Spectrometer(path='/dev/serial0', baudrate=115200, tout=1)
|
s = sensors.Spectrometer(path='/dev/serial0', baudrate=115200, tout=1)
|
||||||
u = sensors.UVSensor()
|
u = sensors.UVSensor()
|
||||||
l = sensors.LxMeter()
|
l = sensors.LxMeter()
|
||||||
response = flask.jsonify([s.getData(), l.getData(), u.getABI()])
|
response = jsonify([s.getData(), l.getData(), u.getABI()])
|
||||||
response.headers.add('Access-Control-Allow-Origin', '*')
|
response.headers.add('Access-Control-Allow-Origin', '*')
|
||||||
return response
|
return response
|
||||||
|
|||||||
@@ -65,8 +65,8 @@ class Spectrometer:
|
|||||||
raise Exception(
|
raise Exception(
|
||||||
'An exception occurred when polling for spectrometer data')
|
'An exception occurred when polling for spectrometer data')
|
||||||
else:
|
else:
|
||||||
responseorder = [i for i in 'RSTUVWGHIJKLABCDEF']
|
responseorder = list('RSTUVWGHIJKLABCDEF')
|
||||||
realorder = [i for i in 'ABCDEFGHRISJTUVWKL']
|
realorder = list('ABCDEFGHRISJTUVWKL')
|
||||||
response = pd.Series(
|
response = pd.Series(
|
||||||
[float(i) / 35.0 for i in rawresp[:-3].split(',')], index=responseorder)
|
[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']
|
return pd.DataFrame(response, index=realorder, columns=['uW/cm^2']).to_dict()['uW/cm^2']
|
||||||
@@ -160,7 +160,7 @@ class LxMeter:
|
|||||||
except:
|
except:
|
||||||
raise Exception('An exception occured fetching lux channels')
|
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:
|
if chB / chA <= 0.5 and chB / chA > 0:
|
||||||
lux = 0.0304 * chA - 0.062 * chA * (chB / chA)**1.4
|
lux = 0.0304 * chA - 0.062 * chA * (chB / chA)**1.4
|
||||||
elif chB / chA <= 0.61 and chB / chA > 0.5:
|
elif chB / chA <= 0.61 and chB / chA > 0.5:
|
||||||
@@ -187,6 +187,12 @@ class UVSensor:
|
|||||||
try:
|
try:
|
||||||
# enable the sensor and set the integration time
|
# enable the sensor and set the integration time
|
||||||
self.bus.write_byte_data(self.addr, 0x00, 0b00010000)
|
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:
|
except:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
'An exception occured when initalizing the UV Sensor')
|
'An exception occured when initalizing the UV Sensor')
|
||||||
@@ -203,39 +209,15 @@ class UVSensor:
|
|||||||
c2 = self.bus.read_word_data(self.addr, 0x0b)
|
c2 = self.bus.read_word_data(self.addr, 0x0b)
|
||||||
except:
|
except:
|
||||||
raise Exception('An exception occured when fetching raw UV data')
|
raise Exception('An exception occured when fetching raw UV data')
|
||||||
# scary computations ahead! refer to Vishay app note 84339 and Sparkfun
|
# Refer to Vishay app note 84339 and Sparkfun VEML6075 documentation.
|
||||||
# VEML6075 documentation.
|
# The computed values may be negative if UV is vastly weaker than
|
||||||
|
# visible and IR light. This is not a bug!
|
||||||
# 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
|
|
||||||
|
|
||||||
|
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]
|
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):
|
def on(self):
|
||||||
'''Turns the UV sensor on after shutdown.'''
|
'''Turns the UV sensor on after shutdown.'''
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -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
|
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
|
`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
|
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
|
external Wi-Fi adapter if Wi-Fi functionality is desired.
|
||||||
compiling Python on the first generation of Raspberry Pis is also very
|
|
||||||
questionable.
|
|
||||||
|
|
||||||
Sensors required for operation are:
|
TeraHz depends on three separate sensor boards:
|
||||||
- AS7265x
|
- AS7265x spectral chipset
|
||||||
- VEML6075
|
- VEML6075 UV sensor
|
||||||
- APDS-9301
|
- APDS-9301 lux-meter
|
||||||
|
|
||||||
They provide the spectrometry data, UV data and illuminance data, respectively.
|
|
||||||
They all support I2C, AS7265x supports UART in addition.
|
|
||||||
|
|
||||||
The sensors leech power from the GPIO connector, thus eliminating the need for a
|
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
|
separate power supply. The necessary power for the whole system is delivered through
|
||||||
|
|||||||
@@ -9,10 +9,12 @@ pip3 install numpy pandas flask smbus2 pyserial gunicorn
|
|||||||
cp -R hostapd/ /etc
|
cp -R hostapd/ /etc
|
||||||
chmod +rx /etc/hostapd/edit_ssid.sh
|
chmod +rx /etc/hostapd/edit_ssid.sh
|
||||||
cp dnsmasq.conf /etc
|
cp dnsmasq.conf /etc
|
||||||
|
|
||||||
cp rc.local /etc
|
cp rc.local /etc
|
||||||
|
chmod +rx /etc/rc.local
|
||||||
cp interfaces-terahz /etc/network/interfaces.d/
|
cp interfaces-terahz /etc/network/interfaces.d/
|
||||||
|
|
||||||
cp -R ../frontend/* /var/www/html
|
cp -R ../frontend/* /var/www/html/
|
||||||
mkdir -p /usr/local/lib/terahz
|
mkdir -p /usr/local/lib/terahz
|
||||||
cp -R ../backend/* /usr/local/lib/terahz
|
cp -R ../backend/* /usr/local/lib/terahz
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
|
||||||
0
etcs/rc.local
Normal file → Executable file
0
etcs/rc.local
Normal file → Executable file
@@ -1,7 +1,5 @@
|
|||||||
// All code in this file is licensed under the ISC license, provided in LICENSE.txt
|
// All code in this file is licensed under the ISC license, provided in LICENSE.txt
|
||||||
$('#update').click(function () {
|
$('#update').click(updateData);
|
||||||
updateData();
|
|
||||||
});
|
|
||||||
// jQuery event binder
|
// jQuery event binder
|
||||||
|
|
||||||
function updateData () {
|
function updateData () {
|
||||||
@@ -22,14 +20,14 @@ function graphSpectralData (obj, dom) {
|
|||||||
var graphPoints = [];
|
var graphPoints = [];
|
||||||
var graphXTicks = [];
|
var graphXTicks = [];
|
||||||
|
|
||||||
Object.keys(obj).forEach((element, index) => {
|
const specter = 'ABCDEFGHRISJTUVWKL';
|
||||||
graphPoints.push([index, obj[element]]); // build array of points
|
for (var i = 0; i < specter.length; i++) {
|
||||||
graphXTicks.push([index, element]); // build array of axis labels
|
graphPoints.push([i, obj[specter[i]]]);
|
||||||
});
|
graphXTicks.push([i, specter[i]]);
|
||||||
// console.log(graphPoints);
|
}
|
||||||
const options = {
|
const options = {
|
||||||
grid: {color: 'white'},
|
grid: { color: 'white' },
|
||||||
xaxis: {ticks: graphXTicks}
|
xaxis: { ticks: graphXTicks }
|
||||||
};
|
};
|
||||||
$.plot('#graph', [graphPoints], options);
|
$.plot('#graph', [graphPoints], options);
|
||||||
// flot expects an array of arrays (lines) of 2-element arrays (points)
|
// flot expects an array of arrays (lines) of 2-element arrays (points)
|
||||||
|
|||||||
@@ -6,20 +6,39 @@
|
|||||||
<link rel="stylesheet" href="lib/bootstrap.min.css">
|
<link rel="stylesheet" href="lib/bootstrap.min.css">
|
||||||
<link rel="stylesheet" href="stylesheet.css">
|
<link rel="stylesheet" href="stylesheet.css">
|
||||||
<title>TeraHz</title>
|
<title>TeraHz</title>
|
||||||
|
<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>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="container text-center">
|
<div class="container text-center">
|
||||||
<h1><img src="lib/logo-sq.png" height="64px">TeraHz</h1>
|
<h1><img src="lib/logo-sq.png" height="64px">TeraHz</h1>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<button id="update" class="btn btn-primary m-1 float-right">Get data</button>
|
<button id="update" onclick="updateData()" class="btn btn-primary">Get data</button>
|
||||||
<p id="debug">
|
<button id="download" class="btn btn-primary">Download</button>
|
||||||
</p>
|
<button id="debug" class="btn btn-danger">DEBUG</button>
|
||||||
<h3>Spectrogram</h3>
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm">
|
||||||
|
<h4>Spectrogram</h4>
|
||||||
<div id="graph" style="height:480px;width:720px"></div>
|
<div id="graph" style="height:480px;width:720px"></div>
|
||||||
<h3>Spectral readings</h3>
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm">
|
||||||
|
<h4>Visible+IR spectrum</h4>
|
||||||
<table class="table table-dark table-sm" id="specter">
|
<table class="table table-dark table-sm" id="specter">
|
||||||
<thead class="thead-dark">
|
<thead class="thead-dark">
|
||||||
<tr>
|
<tr>
|
||||||
@@ -119,9 +138,11 @@
|
|||||||
<td id="L">---</td>
|
<td id="L">---</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<br>
|
</div>
|
||||||
<h3>Lux and UV readings</h3>
|
|
||||||
<table class="table-dark table" id="luxuv">
|
<div class="col-sm">
|
||||||
|
<h4>UV+Illuminance</h4>
|
||||||
|
<table class="table-dark table table-sm" id="luxuv">
|
||||||
<thead class="thead-dark">
|
<thead class="thead-dark">
|
||||||
<tr>
|
<tr>
|
||||||
<th>Parameter</th>
|
<th>Parameter</th>
|
||||||
@@ -146,17 +167,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<script src="lib/bootstrap.bundle.min.js"></script>
|
</div>
|
||||||
<script src="lib/jquery-3.4.1.min.js"></script>
|
</div>
|
||||||
<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>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user