Initital commit
This commit is contained in:
commit
4d96d3ca60
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
__pycache__/
|
8
Dockerfile
Normal file
8
Dockerfile
Normal file
@ -0,0 +1,8 @@
|
||||
FROM python:3.11-bullseye
|
||||
WORKDIR /app
|
||||
RUN apt-get update && apt-get install -y libglib2.0-dev usbutils
|
||||
RUN pip3 install 'radiacode[examples]' --upgrade
|
||||
RUN apt-get install -y libusb-1.0-0-dev
|
||||
COPY . .
|
||||
RUN pip3 install pyusb
|
||||
CMD ["python3", "webserver.py"]
|
27
docker-compose.yml
Normal file
27
docker-compose.yml
Normal file
@ -0,0 +1,27 @@
|
||||
version: '3.7'
|
||||
services:
|
||||
radia:
|
||||
environment:
|
||||
- UDEV=1
|
||||
devices:
|
||||
- '/dev:/dev'
|
||||
# restart: always
|
||||
privileged: true
|
||||
ports:
|
||||
- 8080:8080
|
||||
build: .
|
||||
container_name: radia
|
||||
volumes:
|
||||
- /dev/bus/usb:/dev/bus/usb
|
||||
- /run/udev/control:/run/udev/control
|
||||
- ./:/app
|
||||
networks:
|
||||
- caddy
|
||||
labels:
|
||||
caddy: kyiv.dead.guru
|
||||
caddy.tls: "assada+ua@gmail.com"
|
||||
caddy.reverse_proxy: "{{upstreams 8080}}"
|
||||
|
||||
networks:
|
||||
caddy:
|
||||
external: true
|
5
usb_test.py
Normal file
5
usb_test.py
Normal file
@ -0,0 +1,5 @@
|
||||
import usb.core
|
||||
|
||||
|
||||
d = usb.core.find(find_all=1)
|
||||
print(list(d))
|
146
webserver.html
Normal file
146
webserver.html
Normal file
@ -0,0 +1,146 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Kyiv Radioactive!</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue-apexcharts"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
|
||||
<style>
|
||||
#app > div {
|
||||
margin: 5px auto;
|
||||
width: 80%;
|
||||
text-align: center;
|
||||
padding: 5px;
|
||||
border: 1px #aaa dashed;
|
||||
}
|
||||
#app fieldset {
|
||||
display: inline-block;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin-left: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<h1 class="title">
|
||||
неРадіоактивна ситуація в Києві.
|
||||
</h1>
|
||||
<h2 class="subtitle">В реальному часі.</h2>
|
||||
<div id="app">
|
||||
<div>
|
||||
<apexchart type="bar" height="350" :options="spectrumChartOptions" :series="spectrum_series"></apexchart>
|
||||
<div>
|
||||
<fieldset>
|
||||
<input type="checkbox" id="spectrum_x_accum" v-model="spectrum_accum">
|
||||
<label for="spectrum_x_accum">Акумульований</label>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<input type="radio" id="spectrum_x_channel" v-bind:value="false" v-model="spectrum_energy">
|
||||
<label for="spectrum_x_channel">Канал</label>
|
||||
|
||||
<input type="radio" id="spectrum_x_energy" v-bind:value="true" v-model="spectrum_energy">
|
||||
<label for="spectrum_x_energy">Енергія</label>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<input type="radio" id="spectrum_linear" v-bind:value="false" v-model="spectrum_logarithmic">
|
||||
<label for="spectrum_linear">Лінійне</label>
|
||||
<input type="radio" id="spectrum_log" v-bind:value="true" v-model="spectrum_logarithmic">
|
||||
<label for="spectrum_log">Логарифмічне</label>
|
||||
</fieldset>
|
||||
</div>
|
||||
<button @click="updateSpectrum">Оновити спектр</button>
|
||||
</div>
|
||||
<div>
|
||||
<apexchart type="line" height="350" :options="ratesChartOptions" :series="rates_series"></apexchart>
|
||||
<button @click="rates_autoupdate = !rates_autoupdate">Автооновлення: {{ rates_autoupdate ? "ВКЛ" : "ВИКЛ" }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<script>
|
||||
const common_options = {
|
||||
chart: {
|
||||
animations: {enabled: false},
|
||||
zoom: {autoScaleYaxis: true},
|
||||
},
|
||||
tooltip: {intersect: false},
|
||||
grid: {xaxis: {lines: {show: true}}},
|
||||
dataLabels: {enabled: false},
|
||||
};
|
||||
var app = new Vue({
|
||||
el: '#app',
|
||||
components: {
|
||||
apexchart: VueApexCharts,
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
ws: null,
|
||||
spectrum_duration: 0,
|
||||
rates_autoupdate: true,
|
||||
rates_series: [],
|
||||
spectrum_accum: false,
|
||||
spectrum_series: [],
|
||||
spectrum_coef: [0, 0, 0],
|
||||
spectrum_logarithmic: true,
|
||||
spectrum_energy: true,
|
||||
ratesChartOptions: {
|
||||
...common_options,
|
||||
title: {text: 'Активнсть подій (подій в секунду) і доза'},
|
||||
xaxis: {type: 'datetime'},
|
||||
yaxis: [
|
||||
{seriesName: 'Подій', title: {text: 'ПНС'}, labels: {formatter:(v) => v.toFixed(2) + ' ПНС'}},
|
||||
{seriesName: 'Доза', title: {text: 'мк3в/г'}, labels: {formatter:(v) => v.toFixed(4) + ' мк3в/г'}, opposite: true},
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
spectrum_accum() {
|
||||
this.updateSpectrum();
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
spectrumChartOptions() {
|
||||
const a0 = this.spectrum_coef[0], a1 = this.spectrum_coef[1], a2 = this.spectrum_coef[2];
|
||||
const fmt = this.spectrum_energy ? ((c) => (a0 + a1*c + a2*c*c).toFixed(0)) : undefined;
|
||||
const title = this.spectrum_energy ? 'кеВ' : 'канал';
|
||||
return{
|
||||
...common_options,
|
||||
title: {text: `Спектр, ${this.spectrum_duration} секунд`},
|
||||
xaxis: {type: 'numeric', title: {text: title}, tickAmount: 25, labels: {formatter:fmt}},
|
||||
yaxis: {logarithmic: this.spectrum_logarithmic, decimalsInFloat: 0},
|
||||
plotOptions: {bar: {columnWidth: '95%'}},
|
||||
};
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.ws = new WebSocket('wss://' + window.location.host + '/ws')
|
||||
this.ws.onmessage = this.onmessage;
|
||||
this.updateSpectrum();
|
||||
},
|
||||
beforeDestroy: function() {
|
||||
this.ws.close();
|
||||
},
|
||||
methods: {
|
||||
onmessage(ev) {
|
||||
if (!this.rates_autoupdate) {
|
||||
return;
|
||||
}
|
||||
const d = JSON.parse(ev.data);
|
||||
this.rates_series = d.series;
|
||||
},
|
||||
updateSpectrum() {
|
||||
fetch(`/spectrum?accum=${this.spectrum_accum}`)
|
||||
.then(response => response.json())
|
||||
.then(data => (this.spectrum_duration=data.duration, this.spectrum_coef=data.coef, this.spectrum_series=data.series));
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
113
webserver.py
Normal file
113
webserver.py
Normal file
@ -0,0 +1,113 @@
|
||||
import argparse
|
||||
import asyncio
|
||||
import json
|
||||
import pathlib
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
from radiacode import CountRate, DoseRate, RadiaCode
|
||||
|
||||
|
||||
print('LOL')
|
||||
|
||||
file = open(pathlib.Path(__file__).parent.absolute() / 'webserver.html', 'r')
|
||||
contecnt = file.read()
|
||||
|
||||
|
||||
async def handle_index(request):
|
||||
return web.Response(content_type='text/html', text=contecnt)
|
||||
|
||||
|
||||
async def handle_ws(request):
|
||||
ws = web.WebSocketResponse()
|
||||
await ws.prepare(request)
|
||||
request.app.ws_clients.append(ws)
|
||||
async for _ in ws: # noqa: WPS328
|
||||
pass
|
||||
request.app.ws_clients.remove(ws)
|
||||
return ws
|
||||
|
||||
|
||||
async def handle_spectrum(request):
|
||||
cn = request.app.rc_conn
|
||||
accum = request.query.get('accum') == 'true'
|
||||
spectrum = cn.spectrum_accum() if accum else cn.spectrum()
|
||||
# apexcharts can't handle 0 in logarithmic view
|
||||
spectrum_data = [
|
||||
(channel, cnt if cnt > 0 else 0.5)
|
||||
for channel, cnt in enumerate(spectrum.counts)
|
||||
]
|
||||
print('Spectrum updated')
|
||||
return web.json_response(
|
||||
{
|
||||
'coef': [spectrum.a0, spectrum.a1, spectrum.a2],
|
||||
'duration': spectrum.duration.total_seconds(),
|
||||
'series': [{'name': 'spectrum', 'data': spectrum_data}],
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
async def process(app):
|
||||
max_history_size = 128
|
||||
countrate_history, doserate_history = [], []
|
||||
while True: # noqa: WPS457
|
||||
databuf = app.rc_conn.data_buf()
|
||||
for v in databuf:
|
||||
if isinstance(v, CountRate):
|
||||
countrate_history.append(v)
|
||||
elif isinstance(v, DoseRate):
|
||||
doserate_history.append(v)
|
||||
|
||||
countrate_history.sort(key=lambda x: x.dt)
|
||||
countrate_history = countrate_history[-max_history_size:]
|
||||
doserate_history.sort(key=lambda x: x.dt)
|
||||
doserate_history = doserate_history[-max_history_size:]
|
||||
jdata = json.dumps(
|
||||
{
|
||||
'series': [
|
||||
{
|
||||
'name': 'Події',
|
||||
'data': [(int(1000 * x.dt.timestamp()), x.count_rate) for x in countrate_history],
|
||||
},
|
||||
{
|
||||
'name': 'Доза',
|
||||
'data': [(int(1000 * x.dt.timestamp()), 10000 * x.dose_rate) for x in doserate_history],
|
||||
},
|
||||
],
|
||||
},
|
||||
)
|
||||
print(f'Rates updated, sending to {len(app.ws_clients)} connected clients')
|
||||
await asyncio.gather(*[ws.send_str(jdata) for ws in app.ws_clients], asyncio.sleep(1.0))
|
||||
|
||||
|
||||
async def on_startup(app):
|
||||
asyncio.create_task(process(app))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--bluetooth-mac', type=str, required=False, help='bluetooth MAC address of radiascan device')
|
||||
parser.add_argument('--listen-host', type=str, required=False, default='0.0.0.0', help='listen host for webserver')
|
||||
parser.add_argument('--listen-port', type=int, required=False, default=8080, help='listen port for webserver')
|
||||
args = parser.parse_args()
|
||||
|
||||
app = web.Application()
|
||||
|
||||
|
||||
app.ws_clients = []
|
||||
if args.bluetooth_mac:
|
||||
print('will use Bluetooth connection')
|
||||
app.rc_conn = RadiaCode(bluetooth_mac=args.bluetooth_mac)
|
||||
else:
|
||||
print('will use USB connection')
|
||||
app.rc_conn = RadiaCode()
|
||||
|
||||
app.on_startup.append(on_startup)
|
||||
app.add_routes(
|
||||
[
|
||||
web.get('/', handle_index),
|
||||
web.get('/spectrum', handle_spectrum),
|
||||
web.get('/ws', handle_ws),
|
||||
],
|
||||
)
|
||||
web.run_app(app, host=args.listen_host, port=args.listen_port)
|
Loading…
Reference in New Issue
Block a user