Initital commit

This commit is contained in:
assada 2023-07-04 19:57:54 +03:00
commit 4d96d3ca60
Signed by: assada
GPG Key ID: 8905E8CE5CC3000D
6 changed files with 300 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
__pycache__/

8
Dockerfile Normal file
View 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
View 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
View File

@ -0,0 +1,5 @@
import usb.core
d = usb.core.find(find_all=1)
print(list(d))

146
webserver.html Normal file
View 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
View 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)