@@ -0,0 +1,9 | |||
|
1 | from django.conf.urls import url | |
|
2 | ||
|
3 | from . import consumers | |
|
4 | ||
|
5 | websocket_urlpatterns = [ | |
|
6 | url(r'^ws/main/$', consumers.MainConsumer), | |
|
7 | url(r'^ws/realtime/(?P<code>[^/]+)/(?P<plot>[^/]+)/$', consumers.PlotConsumer), | |
|
8 | url(r'^ws/database/(?P<code>[^/]+)/(?P<plot>[^/]+)/$', consumers.PlotConsumer), | |
|
9 | ] No newline at end of file |
@@ -1,16 +1,16 | |||
|
1 |
FROM python: |
|
|
1 | FROM python:3-slim | |
|
2 | 2 | RUN mkdir /app |
|
3 | 3 | WORKDIR /app |
|
4 | 4 | ADD requirements.txt ./requirements.txt |
|
5 | 5 | RUN apt-get clean && apt-get update && apt-get install -y --no-install-recommends \ |
|
6 | 6 | g++ \ |
|
7 | 7 | gcc \ |
|
8 | 8 | tzdata \ |
|
9 | 9 | && ln -snf /usr/share/zoneinfo/America/Lima /etc/localtime \ |
|
10 | 10 | && echo "America/Lima" > /etc/timezone \ |
|
11 | 11 | && dpkg-reconfigure -f noninteractive tzdata \ |
|
12 | 12 | && pip install -r requirements.txt \ |
|
13 | 13 | && apt-get purge -y --auto-remove gcc g++ \ |
|
14 | 14 | && rm -rf /var/lib/apt/lists/* |
|
15 | 15 | COPY . /app/ |
|
16 | 16 | No newline at end of file |
@@ -1,57 +1,57 | |||
|
1 | 1 | version: '2' |
|
2 | 2 | |
|
3 | 3 | services: |
|
4 | 4 | web: |
|
5 | 5 | container_name: 'realtime' |
|
6 | 6 | build: . |
|
7 | 7 | restart: always |
|
8 | 8 | image: realtime |
|
9 | 9 | command: python manage.py runserver 0.0.0.0:8000 |
|
10 | 10 | env_file: .env |
|
11 | 11 | ports: |
|
12 | 12 | - "8000:8000" |
|
13 | 13 | links: |
|
14 | 14 | - redis |
|
15 | 15 | - mongo |
|
16 | 16 | volumes: |
|
17 | 17 | - './:${APP_DIR}' |
|
18 | 18 | depends_on: |
|
19 | 19 | - redis |
|
20 | 20 | - mongo |
|
21 | 21 | |
|
22 | 22 | zmq_server: |
|
23 | 23 | container_name: 'realtime_zmq' |
|
24 | 24 | restart: always |
|
25 | 25 | image: 'realtime' |
|
26 | 26 | ports: |
|
27 | 27 | - '4444:4444' |
|
28 | 28 | command: 'python -u scripts/server.py' |
|
29 | 29 | env_file: .env |
|
30 | 30 | links: |
|
31 | 31 | - redis |
|
32 | 32 | - mongo |
|
33 | 33 | volumes: |
|
34 | 34 | - './:${APP_DIR}' |
|
35 | 35 | depends_on: |
|
36 | 36 | - web |
|
37 | 37 | |
|
38 | 38 | redis: |
|
39 | 39 | container_name: 'realtime_redis' |
|
40 | 40 | image: 'redis:3.2-alpine' |
|
41 | 41 | ports: |
|
42 | 42 | - '127.0.0.1:6379:6379' |
|
43 | 43 | volumes: |
|
44 | 44 | - 'redisdata:/data' |
|
45 | 45 | |
|
46 | 46 | mongo: |
|
47 | 47 | container_name: 'realtime_mongo' |
|
48 |
image: 'mongo: |
|
|
48 | image: 'mongo:4.0' | |
|
49 | 49 | command: '--storageEngine wiredTiger' |
|
50 | 50 | ports: |
|
51 | 51 | - '127.0.0.1:27017:27017' |
|
52 | 52 | volumes: |
|
53 | 53 | - 'mongodata:/data/db' |
|
54 | 54 | |
|
55 | 55 | volumes: |
|
56 | 56 | redisdata: |
|
57 | 57 | mongodata: No newline at end of file |
@@ -1,81 +1,172 | |||
|
1 | 1 | #!/usr/bin/python |
|
2 | 2 | # -*- coding: UTF-8 -*- |
|
3 | 3 | import os |
|
4 | 4 | import json |
|
5 | 5 | |
|
6 | 6 | from datetime import datetime, timedelta |
|
7 | 7 | |
|
8 | 8 | from pymongo import MongoClient |
|
9 | import mongoengine | |
|
10 | from asgiref.sync import async_to_sync | |
|
11 | from channels.generic.websocket import WebsocketConsumer | |
|
12 | ||
|
13 | from plotter.models import Experiment, ExpDetail, PlotMeta, PlotData | |
|
9 | 14 | |
|
10 | from channels.handler import AsgiHandler | |
|
11 | from channels.auth import channel_session_user | |
|
12 | from channels import Group | |
|
13 | 15 | # Here we create the db named "dbplots" |
|
14 | 16 | host = os.environ.get('HOST_MONGO', 'localhost') |
|
15 | CLIENT = MongoClient('{}:27017'.format(host)) | |
|
16 | DB = CLIENT['dbplots'] | |
|
17 | ||
|
18 | # Connected to websocket.connect | |
|
19 | def ws_connect(message, id, code, plot): | |
|
20 | ||
|
21 | if id == 'main': | |
|
22 | Group('main').add(message.reply_channel) | |
|
23 | print('New main connection') | |
|
24 | elif id == 'realtime': | |
|
25 | Group('{}_{}'.format(code, plot)).add(message.reply_channel) | |
|
26 | print('New connection from: {}, Group: {}_{}'.format(message.content['client'][0], code, plot)) | |
|
17 | mongoengine.connect('dbplots', host=host, port=27017) | |
|
18 | ||
|
19 | # CLIENT = MongoClient('{}:27017'.format(host)) | |
|
20 | # DB = CLIENT['dbplots'] | |
|
21 | ||
|
22 | ||
|
23 | class MainConsumer(WebsocketConsumer): | |
|
24 | ||
|
25 | def connect(self): | |
|
26 | self.group_name = 'main' | |
|
27 | async_to_sync(self.channel_layer.group_add)( | |
|
28 | self.group_name, | |
|
29 | self.channel_name | |
|
30 | ) | |
|
31 | self.accept() | |
|
32 | ||
|
33 | def disconnect(self, close_code): | |
|
34 | async_to_sync(self.channel_layer.group_discard)( | |
|
35 | self.group_name, | |
|
36 | self.channel_name | |
|
37 | ) | |
|
38 | ||
|
39 | def receive(self, text_data): | |
|
40 | pass | |
|
41 | ||
|
42 | def zmq_message(self, event): | |
|
43 | # Send message to WebSocket | |
|
44 | self.send(text_data=event['message']) | |
|
45 | ||
|
46 | class PlotConsumer(WebsocketConsumer): | |
|
47 | ||
|
48 | def connect(self): | |
|
49 | ||
|
50 | if 'realtime' in self.scope['path']: | |
|
51 | self.realtime = True | |
|
52 | self.group_name = '{}_{}'.format( | |
|
53 | self.scope['url_route']['kwargs']['code'], | |
|
54 | self.scope['url_route']['kwargs']['plot'], | |
|
55 | ) | |
|
56 | ||
|
57 | async_to_sync(self.channel_layer.group_add)( | |
|
58 | self.group_name, | |
|
59 | self.channel_name | |
|
60 | ) | |
|
61 | else: | |
|
62 | self.realtime = False | |
|
63 | self.accept() | |
|
64 | ||
|
65 | def disconnect(self, close_code): | |
|
66 | ||
|
67 | if self.realtime: | |
|
68 | async_to_sync(self.channel_layer.group_discard)( | |
|
69 | self.group_name, | |
|
70 | self.channel_name | |
|
71 | ) | |
|
72 | ||
|
73 | def receive(self, text_data): | |
|
74 | ret = {} | |
|
75 | dt = datetime.strptime(text_data, '%d-%m-%Y') | |
|
76 | code = self.scope['url_route']['kwargs']['code'] | |
|
77 | plot = self.scope['url_route']['kwargs']['plot'] | |
|
78 | # exp = DB.experiment.find_one({'code': int(code)}) | |
|
79 | # det0 = DB.exp_detail.find_one({'experiment': exp['_id'], 'date': dt-timedelta(days=1)}) | |
|
80 | # det1 = DB.exp_detail.find_one({'experiment': exp['_id'], 'date': dt}) | |
|
81 | exp = Experiment.objects.get(code=code) | |
|
82 | det0 = ExpDetail.objects(experiment=exp, date=dt-timedelta(days=1)) | |
|
83 | det1 = ExpDetail.objects(experiment=exp, date=dt) | |
|
84 | ||
|
85 | if det1: | |
|
86 | meta1 = PlotMeta.objects(exp_detail=det1[0], plot=plot) | |
|
87 | if meta1: | |
|
88 | if plot == 'spc': | |
|
89 | datas = PlotData.objects(plot=meta1[0]).order_by('-time').first() | |
|
90 | ret['time'] = [datas['time']] | |
|
91 | ret['spc'] = datas['data'] | |
|
92 | ret['meta'] = dict(meta1[0].metadata) | |
|
93 | meta = PlotMeta.objects(exp_detail=det1[0], plot='rti') | |
|
94 | if meta: | |
|
95 | data = PlotData.objects(plot=meta[0], time=ret['time'][0]) | |
|
96 | if data: | |
|
97 | ret['rti'] = data[0]['data'] | |
|
98 | ||
|
99 | meta = PlotMeta.objects(exp_detail=det1[0], plot='noise') | |
|
100 | if meta: | |
|
101 | data = PlotData.objects(plot=meta[0], time=ret['time'][0]) | |
|
102 | if data: | |
|
103 | ret['meta']['titles'] = ['{} dB'.format(x) for x in data[0]['data']] | |
|
27 | 104 | else: |
|
28 | print('New connection from: {}, history, id: {}'.format(message.content['client'][0], id)) | |
|
29 | message.reply_channel.send({ | |
|
30 | 'accept': True | |
|
31 | }) | |
|
105 | last = det1[0]['last_time'] | |
|
106 | metas = [meta1[0]] | |
|
107 | if det0: | |
|
108 | meta0 = PlotMeta.objects(exp_detail=det0[0], plot=plot) | |
|
109 | if meta0: | |
|
110 | metas.append(meta0[0]) | |
|
111 | datas = PlotData.objects(plot__in=metas, time__gt=last-12*60*60).limit(720) | |
|
112 | dum = [(d['time'], d['data']) for d in datas] | |
|
113 | ret['time'] = [d[0] for d in dum] | |
|
114 | dum = [d[1] for d in dum] | |
|
115 | ret[plot] = [t for t in map(list, list(zip(*dum)))] | |
|
116 | ret['meta'] = metas[0].metadata | |
|
117 | ||
|
118 | # exp.pop('date', None) | |
|
119 | # exp.pop('_id', None) | |
|
120 | self.send(json.dumps(ret)) | |
|
121 | else: | |
|
122 | self.send(json.dumps({'interval': 0})) | |
|
123 | ||
|
124 | def zmq_message(self, event): | |
|
125 | # Send message to WebSocket | |
|
126 | self.send(text_data=event['message']) | |
|
32 | 127 | |
|
33 | 128 | def ws_message(message, id, code, plot): |
|
34 | 129 | # Accept the incoming connection |
|
35 | 130 | dt = datetime.strptime(str(json.loads(message.content['text'])['date']), '%d/%m/%Y') |
|
36 | 131 | exp0 = DB.exp_meta.find_one({'code': int(code), 'date': dt-timedelta(days=1)}) |
|
37 | 132 | exp = DB.exp_meta.find_one({'code': int(code), 'date': dt}) |
|
38 | print('New request for id={}'.format(id)) | |
|
133 | print(('New request for id={}'.format(id))) | |
|
39 | 134 | if exp and plot in exp['plots']: |
|
40 | 135 | if plot == 'spc': |
|
41 | 136 | datas = DB.exp_data.find({'expmeta': exp['_id']}, ['time', 'data']).sort('time', -1).limit(1)[0] |
|
42 | 137 | exp['time'] = [datas['time']] |
|
43 | 138 | exp['spc'] = datas['data']['spc'] |
|
44 | 139 | exp['rti'] = datas['data']['rti'] |
|
45 | 140 | exp['noise'] = datas['data']['noise'] |
|
46 | 141 | else: |
|
47 | 142 | last = DB.exp_data.find_one({'expmeta': exp['_id']}, ['time'], sort=[('time', -1)]) |
|
48 | 143 | if exp0: |
|
49 | 144 | datas = DB.exp_data.find( |
|
50 | 145 | { |
|
51 | 146 | 'expmeta': {'$in': [exp0['_id'], exp['_id']]}, |
|
52 | 147 | 'time': {'$gt': last['time']-12*60*60} |
|
53 | 148 | }, |
|
54 | 149 | ['time', 'data'], |
|
55 | 150 | sort=[('time', 1)], |
|
56 | 151 | limit=720 |
|
57 | 152 | ) |
|
58 | 153 | else: |
|
59 | 154 | datas = DB.exp_data.find( |
|
60 | 155 | { |
|
61 | 156 | 'expmeta': exp['_id'], |
|
62 | 157 | 'time': {'$gt': last['time']-12*60*60} |
|
63 | 158 | }, |
|
64 | 159 | ['time', 'data'], |
|
65 | 160 | sort=[('time', 1)], |
|
66 | 161 | limit=720 |
|
67 | 162 | ) |
|
68 | 163 | dum = [(d['time'], d['data'][plot]) for d in datas] |
|
69 | 164 | exp['time'] = [d[0] for d in dum] |
|
70 | 165 | dum = [d[1] for d in dum] |
|
71 | exp[plot] = [t for t in map(list, zip(*dum))] | |
|
166 | exp[plot] = [t for t in map(list, list(zip(*dum)))] | |
|
72 | 167 | |
|
73 | 168 | exp.pop('date', None) |
|
74 | 169 | exp.pop('_id', None) |
|
75 | 170 | message.reply_channel.send({'text': json.dumps(exp)}) |
|
76 | 171 | else: |
|
77 | 172 | message.reply_channel.send({'text': json.dumps({'interval': 0})}) |
|
78 | ||
|
79 | # Connected to websocket.disconnect | |
|
80 | def ws_disconnect(message, id, code, plot): | |
|
81 | Group('{}_{}'.format(code, plot)).discard(message.reply_channel) |
@@ -1,33 +1,31 | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | from __future__ import unicode_literals | |
|
3 | 2 | |
|
4 | 3 | from django.db import models |
|
5 | from mongoengine import * | |
|
4 | from mongoengine import Document, IntField, FloatField, StringField, DictField, ListField, DateTimeField, ReferenceField | |
|
6 | 5 | |
|
7 | 6 | class Experiment(Document): |
|
8 | 7 | code = IntField(unique=True) |
|
9 | 8 | name = StringField(max_length=40) |
|
10 | 9 | |
|
11 |
class Exp |
|
|
12 | code = IntField() | |
|
10 | class ExpDetail(Document): | |
|
11 | experiment = ReferenceField(Experiment) | |
|
13 | 12 | date = DateTimeField() |
|
14 | pairs = ListField(default=list) | |
|
15 | yrange = ListField(FloatField()) | |
|
16 | xrange = ListField(FloatField()) | |
|
17 | interval = FloatField() | |
|
18 | plots = ListField(StringField()) | |
|
19 | localtime = BooleanField() | |
|
13 | last_time = FloatField() | |
|
20 | 14 | |
|
21 | meta = { | |
|
22 | 'indexes': [[("code", 1), ("date", 1)]] | |
|
23 | } | |
|
15 | def plots(self): | |
|
16 | return PlotMeta.objects(exp_detail=self) | |
|
17 | ||
|
18 | class PlotMeta(Document): | |
|
19 | exp_detail = ReferenceField(ExpDetail) | |
|
20 | metadata = DictField() | |
|
21 | plot = StringField() | |
|
24 | 22 | |
|
25 |
class |
|
|
26 |
|
|
|
23 | class PlotData(Document): | |
|
24 | plot = ReferenceField(PlotMeta) | |
|
27 | 25 | time = FloatField() |
|
28 |
data = |
|
|
26 | data = ListField() | |
|
29 | 27 | |
|
30 | 28 | meta = { |
|
31 |
'indexes': [" |
|
|
29 | 'indexes': ["plot", "+time"] | |
|
32 | 30 | } |
|
33 | 31 |
@@ -1,657 +1,673 | |||
|
1 | 1 | |
|
2 | 2 | var icon = { |
|
3 | 3 | 'width': 20, |
|
4 | 4 | 'path': 'M18.303,4.742l-1.454-1.455c-0.171-0.171-0.475-0.171-0.646,0l-3.061,3.064H2.019c-0.251,0-0.457,0.205-0.457,0.456v9.578c0,0.251,0.206,0.456,0.457,0.456h13.683c0.252,0,0.457-0.205,0.457-0.456V7.533l2.144-2.146C18.481,5.208,18.483,4.917,18.303,4.742 M15.258,15.929H2.476V7.263h9.754L9.695,9.792c-0.057,0.057-0.101,0.13-0.119,0.212L9.18,11.36h-3.98c-0.251,0-0.457,0.205-0.457,0.456c0,0.253,0.205,0.456,0.457,0.456h4.336c0.023,0,0.899,0.02,1.498-0.127c0.312-0.077,0.55-0.137,0.55-0.137c0.08-0.018,0.155-0.059,0.212-0.118l3.463-3.443V15.929z M11.241,11.156l-1.078,0.267l0.267-1.076l6.097-6.091l0.808,0.808L11.241,11.156z', |
|
5 | 5 | 'ascent': 20, |
|
6 | 6 | 'descent': 2, |
|
7 | 7 | }; |
|
8 | 8 | |
|
9 | 9 | function list2dict(values) { |
|
10 | 10 | |
|
11 | 11 | var o = {}; |
|
12 | 12 | $.each(values, function () { |
|
13 | 13 | o[this.name] = this.value; |
|
14 | 14 | }); |
|
15 | 15 | return o; |
|
16 | 16 | }; |
|
17 | 17 | /* In this class is defined all the function to RTI plot */ |
|
18 | 18 | class PcolorBuffer { |
|
19 | 19 | constructor({ div, data, key, props }) { |
|
20 | 20 | this.div = document.getElementById(div); |
|
21 | 21 | this.n = 0; |
|
22 | 22 | this.divs = []; |
|
23 | 23 | this.wait = false; |
|
24 | 24 | this.key = key; |
|
25 | 25 | this.timer = (props.throttle || 30) * 1000; |
|
26 | 26 | this.lastRan = Date.now(); |
|
27 | 27 | this.lastFunc = null; |
|
28 | 28 | this.zbuffer = []; |
|
29 | 29 | this.xbuffer = []; |
|
30 | this.empty = Array(data.yrange.length).fill(null); | |
|
30 | this.empty = Array(data.meta.yrange.length).fill(null); | |
|
31 | 31 | this.props = props; |
|
32 | 32 | this.setup(data); |
|
33 | 33 | } |
|
34 | 34 | /* This function is used to plot all the data that have the DB and just is used when is loaded or reloaded*/ |
|
35 | 35 | setup(data) { |
|
36 | 36 | this.last = data.time.slice(-1); |
|
37 | 37 | if (data.time.length == 1) { |
|
38 | 38 | var values = { 'time': data.time, 'data': data[this.key].map(function (x) { return [x] }) }; |
|
39 | 39 | } else { |
|
40 | var values = this.fill_gaps(data.time, data[this.key], data.interval, data[this.key].length); | |
|
40 | var values = this.fill_gaps(data.time, data[this.key], data.meta.interval, data[this.key].length); | |
|
41 | 41 | } |
|
42 | 42 | var t = values.time.map(function (x) { |
|
43 | 43 | var a = new Date(x * 1000); |
|
44 | 44 | // This condition is used to change from UTC to LT |
|
45 | 45 | if (data.localtime == true){ |
|
46 | 46 | a.setTime( a.getTime() + a.getTimezoneOffset()*60*1000 ); |
|
47 | 47 | } |
|
48 | 48 | return a; |
|
49 | 49 | }); |
|
50 | 50 | |
|
51 | 51 | var label; |
|
52 | 52 | if (data.localtime == true){ |
|
53 | 53 | label = "[LT]"; |
|
54 | 54 | |
|
55 | 55 | } |
|
56 | 56 | else{ |
|
57 | 57 | label = "[UTC]"; |
|
58 | 58 | } |
|
59 | 59 | |
|
60 | 60 | for (var i = 0; i < data[this.key].length; i++) { |
|
61 | 61 | var layout = { |
|
62 | 62 | height: 350, |
|
63 | 63 | xaxis: { |
|
64 | 64 | title: 'Time ' + label, |
|
65 | 65 | showgrid: false, |
|
66 | 66 | linewidth: 2, |
|
67 | 67 | size: 12, |
|
68 | 68 | mirror: true, |
|
69 | 69 | }, |
|
70 | 70 | yaxis: { |
|
71 | 71 | title: 'km', |
|
72 | 72 | showgrid: false, |
|
73 | 73 | linewidth: 2, |
|
74 | 74 | size: 12, |
|
75 | 75 | mirror: true, |
|
76 | 76 | }, |
|
77 | 77 | titlefont: { |
|
78 | 78 | size: 16, |
|
79 | 79 | }, |
|
80 | 80 | margin: { |
|
81 | 81 | t: 30, |
|
82 | 82 | } |
|
83 | 83 | }; |
|
84 | 84 | var iDiv = document.createElement('div'); |
|
85 | 85 | iDiv.id = 'plot-' + i; |
|
86 | 86 | //iDiv.className += iDiv.className ? ' col-lg-6 col-md-6 col-sm-12' : 'col-lg-6 col-md-12 col-sm-12'; |
|
87 | 87 | this.zbuffer.push([]); |
|
88 | 88 | this.n = this.n + 1; |
|
89 | 89 | this.div.appendChild(iDiv); |
|
90 | 90 | this.divs.push(iDiv.id); |
|
91 | 91 | var trace = { |
|
92 | 92 | z: values.data[i], |
|
93 | 93 | x: t, |
|
94 | 94 | y: data.yrange, |
|
95 | 95 | colorscale: this.props.colormap || 'Jet', |
|
96 | 96 | transpose: true, |
|
97 | 97 | type: 'heatmap' |
|
98 | 98 | }; |
|
99 | 99 | |
|
100 | 100 | if (this.props.zmin) { trace.zmin = this.props.zmin } |
|
101 | 101 | if (this.props.zmax) { trace.zmax = this.props.zmax } |
|
102 | 102 | |
|
103 | 103 | layout.title = 'Ch ' + i + ' - ' + t.slice(-1).toLocaleString(); |
|
104 | 104 | var conf = { |
|
105 | 105 | modeBarButtonsToRemove: ['sendDataToCloud', 'autoScale2d', 'hoverClosestCartesian', 'hoverCompareCartesian', 'lasso2d', 'select2d', 'zoomIn2d', 'zoomOut2d', 'toggleSpikelines'], |
|
106 | 106 | modeBarButtonsToAdd: [{ |
|
107 | 107 | name: 'Edit plot', |
|
108 | 108 | icon: icon, |
|
109 | 109 | click: function (gd) { |
|
110 | 110 | var div = gd.id; |
|
111 | 111 | $('input[id=id_plotdiv]').val(div); |
|
112 | 112 | $('#setup').modal('show'); |
|
113 | 113 | } |
|
114 | 114 | }], |
|
115 | 115 | displaylogo: false, |
|
116 | 116 | showTips: true |
|
117 | 117 | }; |
|
118 | 118 | Plotly.newPlot('plot-' + i, [trace], layout, conf); |
|
119 | 119 | } |
|
120 | 120 | } |
|
121 | 121 | |
|
122 | 122 | getSize() { |
|
123 | 123 | var div = document.getElementById(this.divs[0]); |
|
124 | 124 | var t = this.xbuffer.slice(-1)[0]; |
|
125 | 125 | var n = 0; |
|
126 | 126 | var timespan = (this.props.timespan || 12) * 1000 * 60 * 60; //If i dont put timespan in rti.html, by default is 8hs |
|
127 | 127 | |
|
128 | 128 | while ((t - div.data[0].x[n]) > timespan) { |
|
129 | 129 | n += 1; |
|
130 | 130 | } |
|
131 | 131 | return n; |
|
132 | 132 | } |
|
133 | 133 | |
|
134 | 134 | fill_gaps(xBuffer, zBuffer, interval, N) { |
|
135 | 135 | |
|
136 | 136 | var x = [xBuffer[0]]; |
|
137 | 137 | var z = []; |
|
138 | 138 | var last; |
|
139 | 139 | |
|
140 | 140 | for (var j = 0; j < N; j++) { |
|
141 | 141 | z.push([zBuffer[j][0]]); |
|
142 | 142 | } |
|
143 | 143 | |
|
144 | 144 | for (var i = 1; i < xBuffer.length; i++) { |
|
145 | 145 | var cnt = 0; |
|
146 | 146 | last = x.slice(-1)[0]; |
|
147 | 147 | while (Math.abs(parseFloat(xBuffer[i]) - last ) > 1.5 * parseFloat(interval)) { |
|
148 | 148 | cnt += 1; |
|
149 | 149 | last = last + interval; |
|
150 | 150 | x.push(last); |
|
151 | 151 | for (var j = 0; j < N; j++) { |
|
152 | 152 | z[j].push(this.empty); |
|
153 | 153 | } |
|
154 | 154 | // Avoid infinite loop |
|
155 | 155 | if (cnt == 100) { break; } |
|
156 | 156 | } |
|
157 | 157 | x.push(xBuffer[i]); |
|
158 | 158 | for (var j = 0; j < N; j++) { |
|
159 | 159 | z[j].push(zBuffer[j][i]); |
|
160 | 160 | } |
|
161 | 161 | } |
|
162 | 162 | return { 'time': x, 'data': z }; |
|
163 | 163 | } |
|
164 | 164 | |
|
165 | 165 | plot() { |
|
166 | 166 | // add new data to plots and empty buffers |
|
167 | 167 | var N = this.getSize(); |
|
168 | 168 | console.log('Plotting...'); |
|
169 | 169 | for (var i = 0; i < this.n; i++) { |
|
170 | 170 | var div = document.getElementById(this.divs[i]); |
|
171 | 171 | if (N > 0) { |
|
172 | 172 | div.data[0].z = div.data[0].z.slice(N, ) |
|
173 | 173 | div.data[0].x = div.data[0].x.slice(N, ) |
|
174 | 174 | } |
|
175 | 175 | Plotly.extendTraces(div, { |
|
176 | 176 | z: [this.zbuffer[i]], |
|
177 | 177 | x: [this.xbuffer] |
|
178 | 178 | }, [0]); |
|
179 | 179 | this.zbuffer[i] = []; |
|
180 | 180 | } |
|
181 | 181 | this.xbuffer = []; |
|
182 | 182 | } |
|
183 | 183 | //This function just add the last data and is used if previously was used setup() |
|
184 | 184 | update(obj) { |
|
185 | 185 | |
|
186 | 186 | // fill data gaps |
|
187 | 187 | var cnt = 0; |
|
188 | 188 | |
|
189 | 189 | while (Math.abs(parseFloat(obj.time[0]) - this.last) > 1.5 * parseFloat(obj.interval)) { |
|
190 | 190 | cnt += 1; |
|
191 | 191 | this.last += obj.interval; |
|
192 | 192 | var newt = new Date((this.last) * 1000); |
|
193 | 193 | // This condition is used to change from UTC to LT |
|
194 | 194 | if (obj.localtime == true){ |
|
195 | 195 | newt.setTime( newt.getTime() + newt.getTimezoneOffset()*60*1000 ); |
|
196 | 196 | } |
|
197 | 197 | this.xbuffer.push(newt); |
|
198 | 198 | for (var i = 0; i < obj[this.key].length; i++) { |
|
199 | 199 | this.zbuffer[i].push(this.empty); |
|
200 | 200 | } |
|
201 | 201 | // Avoid infinite loop |
|
202 | 202 | if (cnt == 100) { break; } |
|
203 | 203 | } |
|
204 | 204 | |
|
205 | 205 | // update buffers |
|
206 | 206 | this.last = parseFloat(obj.time[0]); |
|
207 | 207 | var t = new Date(obj.time[0] * 1000); |
|
208 | 208 | // This condition is used to change from UTC to LT |
|
209 | 209 | if (obj.localtime == true){ |
|
210 | 210 | t.setTime( t.getTime() + t.getTimezoneOffset()*60*1000 ); |
|
211 | 211 | } |
|
212 | 212 | this.xbuffer.push(t); |
|
213 | 213 | for (var i = 0; i < obj[this.key].length; i++) { |
|
214 | 214 | this.zbuffer[i].push(obj[this.key][i]); |
|
215 | 215 | var div = document.getElementById(this.divs[i]); |
|
216 | 216 | Plotly.relayout(div, { |
|
217 | 217 | title: 'Ch ' + i + ' - ' + t.toLocaleString(), |
|
218 | 218 | }); |
|
219 | 219 | } |
|
220 | 220 | |
|
221 | 221 | // plot when ready (every 10 secs) |
|
222 | 222 | if (!this.wait) { |
|
223 | 223 | this.plot(); |
|
224 | 224 | this.wait = true; |
|
225 | 225 | } else { |
|
226 | 226 | clearTimeout(this.lastFunc) |
|
227 | 227 | this.lastFunc = setTimeout(function (scope) { |
|
228 | 228 | if ((Date.now() - scope.lastRan) >= scope.timer) { |
|
229 | 229 | scope.plot() |
|
230 | 230 | scope.lastRan = Date.now() |
|
231 | 231 | } |
|
232 | 232 | }, this.timer - (Date.now() - this.lastRan), this) |
|
233 | 233 | } |
|
234 | 234 | } |
|
235 | 235 | // With this function You can change parameters in your plot |
|
236 | 236 | restyle(values) { |
|
237 | 237 | |
|
238 | 238 | var values = list2dict(values); |
|
239 | 239 | var div = document.getElementById(values.plotdiv); |
|
240 | 240 | |
|
241 | 241 | Plotly.relayout(div, { |
|
242 | 242 | yaxis: { |
|
243 | 243 | range: [values.ymin, values.ymax], |
|
244 | 244 | title: 'km', |
|
245 | 245 | linewidth: 2, |
|
246 | 246 | size: 12, |
|
247 | 247 | mirror: true, |
|
248 | 248 | } |
|
249 | 249 | |
|
250 | 250 | }); |
|
251 | 251 | |
|
252 | 252 | Plotly.restyle(div, { |
|
253 | 253 | zmin: values.zmin, |
|
254 | 254 | zmax: values.zmax, |
|
255 | 255 | colorscale: values.colormap |
|
256 | 256 | }); |
|
257 | 257 | } |
|
258 | 258 | } |
|
259 | 259 | /* In this class is defined all the function to SPC plot */ |
|
260 | 260 | class Pcolor { |
|
261 | 261 | constructor({ div, data, props }) { |
|
262 | 262 | this.div = document.getElementById(div); |
|
263 | 263 | this.n = 0; |
|
264 | 264 | this.divs = []; |
|
265 | 265 | this.props = props; |
|
266 | 266 | this.setup(data); |
|
267 | 267 | } |
|
268 | 268 | /* This function is used to plot all the data that have the DB and just is used when is loaded or reloaded*/ |
|
269 | 269 | setup(data) { |
|
270 | 270 | for (var i = 0; i < data.spc.length; i++) { |
|
271 | 271 | var layout = { |
|
272 | 272 | margin: { |
|
273 | 273 | t:30, |
|
274 | 274 | }, |
|
275 | 275 | height: 320, |
|
276 | 276 | xaxis: { |
|
277 | 277 | title: 'Velocity', |
|
278 | 278 | showgrid: false, |
|
279 | 279 | zeroline: false, |
|
280 | domain: [0, 0.7], | |
|
281 | 280 | linewidth: 2, |
|
282 | 281 | mirror: true, |
|
283 | 282 | size: 12, |
|
284 | 283 | }, |
|
285 | 284 | yaxis: { |
|
286 | 285 | title: 'km', |
|
287 | 286 | showgrid: false, |
|
288 | 287 | linewidth: 2, |
|
289 | 288 | mirror: 'all', |
|
290 | 289 | size: 12, |
|
291 | range: [data.yrange[0], data.yrange.slice(-1)[0]], | |
|
290 | //range: [data.meta.yrange[0], data.meta.yrange.slice(-1)[0]], | |
|
292 | 291 | }, |
|
293 | xaxis2: { | |
|
294 | title: 'dB', | |
|
295 | domain: [0.75, 1], | |
|
296 | ticks: 'outside', | |
|
297 | linewidth: 2, | |
|
298 | mirror: true, | |
|
299 | size: 12, | |
|
300 | }, | |
|
301 | ||
|
302 | 292 | titlefont: { |
|
303 | 293 | size: 14 |
|
304 | 294 | }, |
|
305 | 295 | }; |
|
306 | 296 | var iDiv = document.createElement('div'); |
|
307 | 297 | iDiv.id = 'plot-' + i; |
|
308 | 298 | iDiv.className += iDiv.className ? ' col-md-5' : 'col-md-5'; |
|
309 | 299 | this.n = this.n + 1; |
|
310 | 300 | this.div.appendChild(iDiv); |
|
311 | 301 | this.divs.push(iDiv.id); |
|
312 | 302 | var iDiv = document.createElement('div'); |
|
313 | 303 | iDiv.className = 'col-md-1'; |
|
314 | 304 | this.div.appendChild(iDiv); |
|
315 | 305 | var trace1 = { |
|
316 | 306 | z: data.spc[i], |
|
317 |
|
|
|
307 | y: data.meta.yrange, | |
|
308 | x: data.meta.xrange, | |
|
318 | 309 | colorscale: this.props.colormap || 'Jet', |
|
319 | 310 | transpose: true, |
|
320 | 311 | type: 'heatmap' |
|
321 | 312 | }; |
|
322 | 313 | |
|
314 | if ('rti' in data){ | |
|
315 | layout.xaxis.domain = [0, 0.7]; | |
|
316 | layout.xaxis2 = { | |
|
317 | title: 'dB', | |
|
318 | domain: [0.75, 1], | |
|
319 | ticks: 'outside', | |
|
320 | linewidth: 2, | |
|
321 | mirror: true, | |
|
322 | size: 12, | |
|
323 | }; | |
|
323 | 324 | var trace2 = { |
|
324 | 325 | x: data.rti[i], |
|
326 | y: data.meta.yrange, | |
|
325 | 327 | xaxis: 'x2', |
|
326 | 328 | type: 'scatter', |
|
327 | 329 | }; |
|
330 | } | |
|
328 | 331 | |
|
329 | 332 | if (this.props.zmin) { |
|
330 | 333 | trace1.zmin = this.props.zmin |
|
331 | 334 | } |
|
332 | 335 | if (this.props.zmax) { |
|
333 | 336 | trace1.zmax = this.props.zmax; |
|
337 | if ('rti' in data){ | |
|
334 | 338 | layout.xaxis2.range = [this.props.zmin, this.props.zmax] |
|
335 | 339 | } |
|
340 | } | |
|
336 | 341 | |
|
337 | 342 | var t = new Date(data.time * 1000); |
|
338 | 343 | // This condition is used to change from UTC to LT |
|
339 | 344 | if (data.localtime == true){ |
|
340 | 345 | t.setTime( t.getTime() + t.getTimezoneOffset()*60*1000 ); |
|
341 | 346 | } |
|
342 | layout.title = 'Ch ' + i + ': ' + data.noise[i] + 'dB - ' + t.toLocaleString(); | |
|
347 | if ('titles' in data.meta){ | |
|
348 | layout.title = 'Ch ' + i + ': ' + data.meta.titles[i] + ' ' + t.toLocaleString(); | |
|
349 | }else{ | |
|
350 | layout.title = 'Ch ' + i + ': ' + t.toLocaleString(); | |
|
351 | } | |
|
343 | 352 | var conf = { |
|
344 | 353 | modeBarButtonsToRemove: ['sendDataToCloud', 'autoScale2d', 'hoverClosestCartesian', 'hoverCompareCartesian', 'lasso2d', 'select2d', 'zoomIn2d', 'zoomOut2d', 'toggleSpikelines'], |
|
345 | 354 | modeBarButtonsToAdd: [{ |
|
346 | 355 | name: 'Edit plot', |
|
347 | 356 | icon: icon, |
|
348 | 357 | click: function (gd) { |
|
349 | 358 | var div = gd.id; |
|
350 | 359 | $('input[id=id_plotdiv]').val(div); |
|
351 | 360 | $('#setup').modal('show'); |
|
352 | 361 | } |
|
353 | 362 | }], |
|
354 | 363 | displaylogo: false, |
|
355 | 364 | showTips: true |
|
356 | 365 | }; |
|
357 | Plotly.newPlot('plot-' + i, [trace1, trace2], layout, conf); | |
|
366 | if ('rti' in data){ | |
|
367 | var traces = [trace1, trace2] | |
|
368 | }else{ | |
|
369 | var traces = [trace1] | |
|
370 | } | |
|
371 | Plotly.newPlot('plot-' + i, traces, layout, conf); | |
|
358 | 372 | } |
|
359 | 373 | } |
|
360 | 374 | |
|
361 | 375 | plot(obj) { |
|
362 | 376 | // this.data = obj; |
|
363 | 377 | // add new data to plots and empty buffers |
|
364 | 378 | console.log('Plotting...'); |
|
365 | 379 | var t = new Date(obj.time[0] * 1000); |
|
366 | 380 | // This condition is used to change from UTC to LT |
|
367 | 381 | if (obj.localtime == true){ |
|
368 | 382 | t.setTime( t.getTime() + t.getTimezoneOffset()*60*1000 ); |
|
369 | 383 | } |
|
370 | 384 | for (var i = 0; i < this.n; i++) { |
|
371 | 385 | var div = document.getElementById(this.divs[i]); |
|
372 | 386 | Plotly.relayout(div, { |
|
373 | 387 | title: 'Ch ' + i + ': ' + obj.noise[i] + 'dB - ' + t.toLocaleString(), |
|
374 | 388 | |
|
375 | 389 | }); |
|
376 | 390 | Plotly.restyle(div, { |
|
377 | 391 | z: [obj.spc[i], null], |
|
378 | 392 | x: [obj.xrange, obj.rti[i]] |
|
379 | 393 | }, [0, 1]); |
|
380 | 394 | } |
|
381 | 395 | } |
|
382 | 396 | |
|
383 | 397 | update(data) { |
|
384 | 398 | this.plot(data); |
|
385 | 399 | } |
|
386 | 400 | |
|
387 | 401 | // With this function You can change parameters in your plot |
|
388 | 402 | restyle(values) { |
|
389 | 403 | |
|
390 | 404 | var values = list2dict(values); |
|
391 | 405 | var div = document.getElementById(values.plotdiv); |
|
392 | 406 | |
|
393 | 407 | Plotly.relayout(div, { |
|
394 | 408 | yaxis: { |
|
395 | 409 | title: 'km', |
|
396 | 410 | linewidth: 2, |
|
397 | 411 | mirror: 'all', |
|
398 | 412 | range: [values.ymin, values.ymax] |
|
399 | 413 | }, |
|
400 | 414 | xaxis: { |
|
401 | 415 | title: 'Velocity', |
|
402 | 416 | linewidth: 2, |
|
403 | 417 | mirror: true, |
|
404 | 418 | domain: [0, .7], |
|
405 | 419 | range: [values.xmin, values.xmax] |
|
406 | 420 | }, |
|
407 | 421 | xaxis2: { |
|
408 | 422 | title: 'dB', |
|
409 | 423 | linewidth: 2, |
|
410 | 424 | mirror: true, |
|
411 | 425 | domain: [0.75, 1], |
|
412 | 426 | range: [values.zmin, values.zmax] |
|
413 | 427 | } |
|
414 | 428 | |
|
415 | 429 | }); |
|
416 | 430 | |
|
417 | 431 | Plotly.restyle(div, { |
|
418 | 432 | zmin: values.zmin, |
|
419 | 433 | zmax: values.zmax, |
|
420 | 434 | colorscale: values.colormap |
|
421 | 435 | }); |
|
422 | 436 | } |
|
423 | 437 | } |
|
424 | 438 | /* In this class is defined all the function to Scatter(noise) plot */ |
|
425 | 439 | class Scatter { |
|
426 | 440 | constructor({ div, data, key, props }) { |
|
427 | 441 | this.div = document.getElementById(div); |
|
428 | 442 | this.n = 0; |
|
429 | 443 | this.key = key; |
|
430 | 444 | this.wait = false; |
|
431 | 445 | this.timer = (props.throttle || 30) * 1000; |
|
432 | 446 | this.lastRan = Date.now(); |
|
433 | 447 | this.lastFunc = null; |
|
434 | 448 | this.ybuffer = []; |
|
435 | 449 | this.xbuffer = []; |
|
436 | 450 | this.props = props; |
|
437 | 451 | this.setup(data); |
|
438 | 452 | } |
|
439 | 453 | /* This function is used to plot all the data that have the DB and just is used when is loaded or reloaded*/ |
|
440 | 454 | setup(data) { |
|
441 | 455 | |
|
442 | 456 | this.data = data; //le agrego juan carlos para ver la data en mi consola |
|
443 | 457 | var traces = []; |
|
444 | 458 | this.last = data.time.slice(-1); |
|
445 | 459 | if (data.time.length == 1) { |
|
446 | 460 | var values = { 'time': data.time, 'data': data[this.key] }; |
|
447 | 461 | } else { |
|
448 | var values = this.fill_gaps(data.time, data[this.key], data.interval, data[this.key].length); | |
|
462 | var values = this.fill_gaps(data.time, data[this.key], data.meta.interval, data[this.key].length); | |
|
449 | 463 | } |
|
450 | 464 | |
|
451 | 465 | var t = values.time.map(function (x) { |
|
452 | 466 | var a = new Date(x * 1000); |
|
453 | 467 | // This condition is used to change from UTC to LT |
|
454 | 468 | if (data.localtime == true){ |
|
455 | 469 | a.setTime( a.getTime() + a.getTimezoneOffset()*60*1000 ); |
|
456 | 470 | } |
|
457 | 471 | return a; |
|
458 | 472 | }); |
|
459 | 473 | |
|
460 | 474 | for (var i = 0; i < data[this.key].length; i++) { |
|
461 | 475 | |
|
462 | 476 | this.n = this.n + 1; |
|
463 | 477 | this.ybuffer.push([]); |
|
464 | 478 | var trace = { |
|
465 | 479 | x: t, |
|
466 | 480 | y: values.data[i], |
|
467 | 481 | mode: 'lines', |
|
468 | 482 | type: 'scatter', |
|
469 | 483 | name: 'Channel ' + i, |
|
470 | 484 | connectgaps: false, |
|
471 | 485 | }; |
|
472 | 486 | |
|
473 | 487 | traces.push(trace); |
|
474 | 488 | } |
|
475 | 489 | |
|
476 | 490 | var label; |
|
477 | 491 | if (data.localtime == true){ |
|
478 | 492 | label = "[LT]"; |
|
479 | 493 | |
|
480 | 494 | } |
|
481 | 495 | else{ |
|
482 | 496 | label = "[UTC]"; |
|
483 | 497 | } |
|
484 | 498 | |
|
485 | 499 | var layout = { |
|
486 | 500 | height: 300, |
|
487 | 501 | title: t.slice(-1).toLocaleString(), |
|
488 | 502 | font: { |
|
489 | 503 | size: 12, |
|
490 | 504 | }, |
|
491 | 505 | xaxis: { |
|
492 | 506 | title: 'Time ' + label, |
|
493 | 507 | size: 12, |
|
494 | 508 | linewidth: 2, |
|
495 | 509 | mirror: true, |
|
496 | 510 | }, |
|
497 | 511 | yaxis: { |
|
498 | 512 | title: this.props.ylabel || 'dB', |
|
499 | 513 | linewidth: 2, |
|
500 | 514 | mirror: true, |
|
501 | 515 | }, |
|
502 | 516 | titlefont: { |
|
503 | 517 | size: 16, |
|
504 | 518 | }, |
|
505 | 519 | margin: { |
|
506 | 520 | t: 30, |
|
507 | 521 | } |
|
508 | 522 | }; |
|
509 | 523 | |
|
510 | 524 | if (this.props.ymin) { layout.yaxis.range = [this.props.ymin, this.props.ymax] } |
|
511 | 525 | |
|
512 | 526 | var conf = { |
|
513 | 527 | modeBarButtonsToRemove: ['sendDataToCloud', 'autoScale2d', 'hoverClosestCartesian', 'hoverCompareCartesian', 'lasso2d', 'select2d', 'zoomIn2d', 'zoomOut2d', 'toggleSpikelines'], |
|
514 | 528 | modeBarButtonsToAdd: [{ |
|
515 | 529 | name: 'Edit plot', |
|
516 | 530 | icon: icon, |
|
517 | 531 | click: function (gd) { |
|
518 | 532 | $('#setup').modal('show'); |
|
519 | 533 | } |
|
520 | 534 | }], |
|
521 | 535 | displaylogo: false, |
|
522 | 536 | showTips: true |
|
523 | 537 | }; |
|
524 | 538 | Plotly.newPlot('plot', traces, layout, conf); |
|
525 | 539 | } |
|
526 | 540 | |
|
527 | 541 | getSize() { |
|
528 | 542 | var t = this.xbuffer.slice(-1)[0]; |
|
529 | 543 | var n = 0; |
|
530 | 544 | var timespan = (this.props.timespan || 12) * 1000 * 60 * 60; |
|
531 | 545 | |
|
532 | 546 | while ((t - this.div.data[0].x[n]) > timespan) { |
|
533 | 547 | n += 1; |
|
534 | 548 | } |
|
535 | 549 | return n; |
|
536 | 550 | } |
|
537 | 551 | |
|
538 | 552 | fill_gaps(xBuffer, yBuffer, interval, N) { |
|
539 | 553 | |
|
540 | 554 | var x = [xBuffer[0]]; |
|
541 | 555 | var y = []; |
|
542 | 556 | |
|
543 | 557 | for (var j = 0; j < N; j++) { |
|
544 | 558 | y.push([yBuffer[j][0]]); |
|
545 | 559 | } |
|
546 | 560 | |
|
547 | 561 | var last; |
|
548 | 562 | |
|
549 | 563 | for (var i = 1; i < xBuffer.length; i++) { |
|
550 | 564 | var cnt = 0; |
|
551 | 565 | last = x.slice(-1)[0]; |
|
566 | console.log(Math.abs(parseFloat(xBuffer[i]) - last) + ' '+ parseFloat(interval)); | |
|
552 | 567 | while (Math.abs(parseFloat(xBuffer[i]) - last) > 1.5 * parseFloat(interval)) { |
|
553 | 568 | cnt += 1; |
|
554 | 569 | last = last + interval; |
|
555 | 570 | x.push(last); |
|
571 | console.log('missing ' + new Date(last*1000)); | |
|
556 | 572 | for (var j = 0; j < N; j++) { |
|
557 | 573 | y[j].push(null); |
|
558 | 574 | } |
|
559 | 575 | // Avoid infinite loop |
|
560 | 576 | if (cnt == 50) { break; } |
|
561 | 577 | } |
|
562 | 578 | x.push(xBuffer[i]); |
|
563 | 579 | |
|
564 | 580 | for (var j = 0; j < N; j++) { |
|
565 | 581 | y[j].push(yBuffer[j][i]); |
|
566 | 582 | } |
|
567 | 583 | } |
|
568 | 584 | return { 'time': x, 'data': y }; |
|
569 | 585 | } |
|
570 | 586 | |
|
571 | 587 | plot() { |
|
572 | 588 | // add new data to plots and empty buffers |
|
573 | 589 | var xvalues = []; |
|
574 | 590 | var yvalues = []; |
|
575 | 591 | var traces = []; |
|
576 | 592 | var N = this.getSize(); |
|
577 | 593 | console.log('Plotting...'); |
|
578 | 594 | for (var i = 0; i < this.n; i++) { |
|
579 | 595 | if (N > 0) { |
|
580 | 596 | this.div.data[i].y = this.div.data[i].y.slice(N, ) |
|
581 | 597 | this.div.data[i].x = this.div.data[i].x.slice(N, ) |
|
582 | 598 | } |
|
583 | 599 | yvalues.push(this.ybuffer[i]); |
|
584 | 600 | xvalues.push(this.xbuffer); |
|
585 | 601 | traces.push(i); |
|
586 | 602 | this.ybuffer[i] = []; |
|
587 | 603 | } |
|
588 | 604 | Plotly.extendTraces(this.div, { |
|
589 | 605 | y: yvalues, |
|
590 | 606 | x: xvalues |
|
591 | 607 | }, traces); |
|
592 | 608 | this.xbuffer = []; |
|
593 | 609 | } |
|
594 | 610 | //This function just add the last data and is used if previously was used setup() |
|
595 | 611 | update(obj) { |
|
596 | 612 | |
|
597 | 613 | // fill data gaps |
|
598 | 614 | var cnt = 0; |
|
599 | 615 | while (Math.abs(parseFloat(obj.time[0]) - this.last ) > 1.5 * parseFloat(obj.interval)) { |
|
600 | 616 | cnt += 1; |
|
601 | 617 | this.last += obj.interval; |
|
602 | 618 | var newt = new Date((this.last) * 1000); |
|
603 | 619 | // This condition is used to change from UTC to LT |
|
604 | 620 | if (obj.localtime == true){ |
|
605 | 621 | newt.setTime( newt.getTime() + newt.getTimezoneOffset()*60*1000 ); |
|
606 | 622 | } |
|
607 | 623 | this.xbuffer.push(newt); |
|
608 | 624 | for (var i = 0; i < this.n; i++) { |
|
609 | 625 | this.ybuffer[i].push(null); |
|
610 | 626 | } |
|
611 | 627 | // Avoid infinite loop |
|
612 | 628 | if (cnt == 100) { break; } |
|
613 | 629 | } |
|
614 | 630 | |
|
615 | 631 | // update buffers |
|
616 | 632 | this.last = parseFloat(obj.time[0]); |
|
617 | 633 | var t = new Date(obj.time[0] * 1000); |
|
618 | 634 | // This condition is used to change from UTC to LT |
|
619 | 635 | if (obj.localtime == true){ |
|
620 | 636 | t.setTime( t.getTime() + t.getTimezoneOffset()*60*1000 ); |
|
621 | 637 | } |
|
622 | 638 | this.xbuffer.push(t); |
|
623 | 639 | for (var i = 0; i < this.n; i++) { |
|
624 | 640 | this.ybuffer[i].push(obj[this.key][i][0]); |
|
625 | 641 | } |
|
626 | 642 | |
|
627 | 643 | Plotly.relayout(this.div, { |
|
628 | 644 | title: t.toLocaleString(), |
|
629 | 645 | }); |
|
630 | 646 | |
|
631 | 647 | // plot when ready (every 10 secs) |
|
632 | 648 | if (!this.wait) { |
|
633 | 649 | this.plot(); |
|
634 | 650 | this.wait = true; |
|
635 | 651 | } else { |
|
636 | 652 | clearTimeout(this.lastFunc) |
|
637 | 653 | this.lastFunc = setTimeout(function (scope) { |
|
638 | 654 | if ((Date.now() - scope.lastRan) >= scope.timer) { |
|
639 | 655 | scope.plot() |
|
640 | 656 | scope.lastRan = Date.now() |
|
641 | 657 | } |
|
642 | 658 | }, this.timer - (Date.now() - this.lastRan), this) |
|
643 | 659 | } |
|
644 | 660 | } |
|
645 | 661 | // With this function You can change parameters in your plot |
|
646 | 662 | restyle(values) { |
|
647 | 663 | |
|
648 | 664 | var values = list2dict(values); |
|
649 | 665 | Plotly.relayout(this.div, { |
|
650 | 666 | yaxis: { |
|
651 | 667 | range: [values.ymin, values.ymax], |
|
652 | 668 | title: this.props.ylabel || 'dB' |
|
653 | 669 | } |
|
654 | 670 | |
|
655 | 671 | }); |
|
656 | 672 | } |
|
657 | 673 | } |
@@ -1,52 +1,53 | |||
|
1 | 1 | {% extends 'base.html' %} |
|
2 | 2 | {% load static%} |
|
3 | 3 | {% block extra-header %} |
|
4 | 4 | {% endblock %} |
|
5 | 5 | {% block content %} |
|
6 | 6 | <div class="text-center p-2 text-igp"> |
|
7 | 7 | <h1>Realtime Experiments at JRO</h1> |
|
8 | 8 | </div> |
|
9 | 9 | {% include 'cartas.html' %} |
|
10 | 10 | {% endblock content %} |
|
11 | 11 | {% block script %} |
|
12 | 12 | <script> |
|
13 | 13 | function pad(num) { |
|
14 | 14 | return ("0"+num).slice(-2); |
|
15 | 15 | } |
|
16 | 16 | function hhmmss(secs) { |
|
17 | 17 | var minutes = Math.floor(secs / 60); |
|
18 | 18 | secs = secs%60; |
|
19 | 19 | var hours = Math.floor(minutes/60) |
|
20 | 20 | minutes = minutes%60; |
|
21 | 21 | return pad(hours)+":"+pad(minutes)+":"+pad(secs); |
|
22 | 22 | } |
|
23 | 23 | $("#loader").css("display", "none"); |
|
24 | 24 | |
|
25 | 25 | /* This part create a new socket named "socket" to comunicate |
|
26 | 26 | if there is new data we could be able to change some attributes of a class*/ |
|
27 |
var socket = new WebSocket('ws://' + window.location.host +'/main |
|
|
27 | var socket = new WebSocket('ws://' + window.location.host +'/ws/main/'); | |
|
28 | 28 | socket.onopen = function open() { |
|
29 | 29 | console.log('Main WebSockets connection created.'); |
|
30 | 30 | }; |
|
31 | 31 | |
|
32 |
socket.onmessage = function |
|
|
32 | socket.onmessage = function(event) { | |
|
33 | 33 | var data = JSON.parse(event.data); |
|
34 | 34 | console.log(data); |
|
35 |
code = data |
|
|
36 | value = data.value; | |
|
37 | time = moment(new Date(data.time*1000)).format('hh:mm:ss a'); | |
|
35 | var code = data['code']; | |
|
36 | console.log(code); | |
|
37 | var value = data['value']; | |
|
38 | var time = moment(new Date(data['time']*1000)).format('hh:mm:ss a'); | |
|
38 | 39 | |
|
39 | 40 | /*This conditional ask if value(send by server) is online, and if it is then |
|
40 | 41 | change value to online in div with id="#alert_"+code*/ |
|
41 | 42 | |
|
42 | 43 | $("#date_"+code).text("Last data: "+time); |
|
43 | 44 | $("#card_"+code).removeClass().addClass("card mb-4 box-shadow text-"+value+" border-"+value); |
|
44 | 45 | $("#card_header_"+code).removeClass().addClass("card-header text-white bg-"+value); |
|
45 | 46 | $("#card_body_"+code).find("a").removeClass().addClass("btn btn-outline-"+value); |
|
46 | 47 | }; |
|
47 | 48 | |
|
48 | 49 | if (socket.readyState == WebSocket.OPEN) { |
|
49 | 50 | socket.onopen(); |
|
50 | 51 | }; |
|
51 | 52 | </script> |
|
52 | 53 | {% endblock script %} |
@@ -1,92 +1,92 | |||
|
1 | 1 | {% extends 'base.html' %} {% load static %} {% load bootstrap4 %} {% block content %} |
|
2 | 2 | |
|
3 | 3 | <div class="p-4 text-igp"> |
|
4 | 4 | <h3>{{title}} <small> {{subtitle}}</small></h3> |
|
5 | 5 | </div> |
|
6 | 6 | <div class="row justify-content-center"> |
|
7 | 7 | <div id="loader" class="loader mt-5"></div> |
|
8 | 8 | </div> |
|
9 | 9 | <div id="plot" {%if plot == 'spc' %}class="row"{%endif%}></div> |
|
10 | 10 | |
|
11 | 11 | {% endblock content %} {% block modal %} |
|
12 | 12 | <!-- Modal --> |
|
13 | 13 | <div class="modal fade" id="setup" tabindex="-1" role="dialog" aria-labelledby="myModalLabel"> |
|
14 | 14 | <div class="modal-dialog modal-sm" role="document"> |
|
15 | 15 | <div class="modal-content"> |
|
16 | 16 | <div class="modal-header"> |
|
17 | 17 | <button type="button" class="close" data-dismiss="modal" aria-label="Close"> |
|
18 | 18 | <span aria-hidden="true">×</span> |
|
19 | 19 | </button> |
|
20 | 20 | <h4 class="modal-title" id="myModalLabel">Setup plot</h4> |
|
21 | 21 | </div> |
|
22 | 22 | <div class="modal-body"> |
|
23 | 23 | {% if code and plot %} |
|
24 | 24 | <form id="form_setup"> |
|
25 | 25 | {% bootstrap_form setup_form layout='grid' size='small' %} |
|
26 | 26 | </form> |
|
27 | 27 | {% endif %} |
|
28 | 28 | </div> |
|
29 | 29 | <div class="modal-footer"> |
|
30 | 30 | <button id="bt_update" type="button" class="btn btn-primary">Update</button> |
|
31 | 31 | </div> |
|
32 | 32 | </div> |
|
33 | 33 | </div> |
|
34 | 34 | </div> |
|
35 | 35 | {% endblock modal%} {% block script %} |
|
36 | 36 | <script src="{% static 'js/jroplots.js' %}"></script> |
|
37 | 37 | <script> |
|
38 | 38 | /* This conditional is used to know if we have to setup the data |
|
39 | 39 | or just update the last data*/ |
|
40 | 40 | $("#loader").css("display", "block"); |
|
41 | 41 | {% if realtime %} |
|
42 | var socket = new WebSocket('ws://' + window.location.host + '/realtime/{{code}}/{{plot}}/'); | |
|
42 | var socket = new WebSocket('ws://' + window.location.host + '/ws/realtime/{{code}}/{{plot}}/'); | |
|
43 | 43 | {% else %} |
|
44 |
var socket = new WebSocket('ws://' + window.location.host + '/ |
|
|
44 | var socket = new WebSocket('ws://' + window.location.host + '/ws/database/{{code}}/{{plot}}/'); | |
|
45 | 45 | {% endif %} |
|
46 | 46 | socket.onopen = function open() { |
|
47 | 47 | console.log('WebSockets connection created: ' + socket.url); |
|
48 |
socket.send('{ |
|
|
48 | socket.send('{{date}}') | |
|
49 | 49 | }; |
|
50 | 50 | |
|
51 | 51 | socket.onmessage = function message(event) { |
|
52 | 52 | var data = JSON.parse(event.data); |
|
53 | 53 | console.log(data); |
|
54 | 54 | if (data.interval == 0) { |
|
55 | 55 | $("#loader").removeClass("loader").addClass("no-data"); |
|
56 | 56 | $("#loader").html("No data found"); |
|
57 | 57 | } else { |
|
58 | 58 | var first = plot(data); |
|
59 | 59 | if (first == true) { |
|
60 | 60 | $("#loader").css("display", "none"); |
|
61 | 61 | } |
|
62 | 62 | } |
|
63 | 63 | } |
|
64 | 64 | |
|
65 | 65 | if (socket.readyState == WebSocket.OPEN) { |
|
66 | 66 | socket.onopen(); |
|
67 | 67 | } |
|
68 | 68 | |
|
69 | 69 | let flag = true; |
|
70 | 70 | function plot(data) { |
|
71 | 71 | if (flag === true) { |
|
72 | 72 | flag = false; |
|
73 | 73 | plt = new {{ fn_plot }} ({ |
|
74 | 74 | div: 'plot', |
|
75 | 75 | data: data, |
|
76 | 76 | key: '{{plot}}', |
|
77 | 77 | props: { throttle: 30, timespan: 12, colormap: 'Jet' }, |
|
78 | 78 | }); |
|
79 | 79 | return true; |
|
80 | 80 | } else { |
|
81 | 81 | plt.update(data); |
|
82 | 82 | return false; |
|
83 | 83 | } |
|
84 | 84 | } |
|
85 | 85 | /*It is the button to make changes in my plot parameters defined in block modal*/ |
|
86 | 86 | $("#bt_update").click(function () { |
|
87 | 87 | $("#setup").modal('hide'); |
|
88 | 88 | var values = $("#form_setup").serializeArray(); |
|
89 | 89 | plt.restyle(values); |
|
90 | 90 | }); |
|
91 | 91 | |
|
92 | 92 | </script> {% endblock script %} No newline at end of file |
@@ -1,156 +1,156 | |||
|
1 | 1 | #!/usr/bin/python |
|
2 | 2 | # -*- coding: UTF-8 -*- |
|
3 | from __future__ import unicode_literals | |
|
3 | ||
|
4 | 4 | |
|
5 | 5 | import os |
|
6 | 6 | import time |
|
7 | 7 | from datetime import datetime |
|
8 | 8 | |
|
9 | 9 | from django import forms |
|
10 | 10 | from django.contrib import messages |
|
11 | 11 | from django.utils.safestring import mark_safe |
|
12 | 12 | from django.shortcuts import render |
|
13 | 13 | |
|
14 | 14 | import mongoengine |
|
15 | 15 | |
|
16 |
from plotter.models import Experiment, Exp |
|
|
16 | from plotter.models import Experiment, ExpDetail, PlotMeta, PlotData | |
|
17 | 17 | |
|
18 | 18 | host = os.environ.get('HOST_MONGO', 'localhost') |
|
19 | 19 | mongoengine.connect('dbplots', host=host, port=27017) |
|
20 | 20 | |
|
21 | 21 | |
|
22 | 22 | # Forms |
|
23 | 23 | class SearchForm(forms.Form): |
|
24 | 24 | |
|
25 | 25 | experiment = forms.ChoiceField() |
|
26 | 26 | plot = forms.ChoiceField() |
|
27 | 27 | |
|
28 | 28 | def __init__(self, *args, **kwargs): |
|
29 | 29 | |
|
30 | 30 | exp_choices = kwargs.pop('exp_choices', []) |
|
31 | 31 | plt_choices = kwargs.pop('plt_choices', []) |
|
32 | 32 | super(SearchForm, self).__init__(*args, **kwargs) |
|
33 | 33 | self.fields['experiment'].choices = [(0, 'Select Experiment')] + exp_choices |
|
34 | 34 | self.fields['plot'].choices = [(0, 'Select Plot')] + plt_choices |
|
35 | 35 | # we use this class to change the parameter in Scatter plot using the function plotly.restyle in jroplot.js |
|
36 | 36 | class ScatterSetupForm(forms.Form): |
|
37 | 37 | |
|
38 | 38 | plotdiv = forms.CharField(widget=forms.HiddenInput()) |
|
39 | 39 | ymax = forms.CharField(initial=30) |
|
40 | 40 | ymin = forms.CharField(initial=10) |
|
41 | 41 | |
|
42 | 42 | # we use this class to change the parameter in RTI plot using the function plotly.restyle in jroplot.js |
|
43 | 43 | class RTISetupForm(forms.Form): |
|
44 | 44 | |
|
45 | 45 | plotdiv = forms.CharField(widget=forms.HiddenInput()) |
|
46 | 46 | colormap = forms.ChoiceField(choices=[('Jet', 'Jet'), ('Viridis', 'Viridis'), ('RdBu', 'RdBu')]) |
|
47 | 47 | zmax = forms.CharField(initial=30) |
|
48 | 48 | zmin = forms.CharField(initial=10) |
|
49 | 49 | ymax = forms.CharField(initial=180) |
|
50 | 50 | ymin = forms.CharField(initial=80) |
|
51 | 51 | |
|
52 | 52 | # we use this class to change the parameter in SPC plot using the function plotly.restyle in jroplot.js |
|
53 | 53 | class SPCSetupForm(forms.Form): |
|
54 | 54 | |
|
55 | 55 | plotdiv = forms.CharField(widget=forms.HiddenInput()) |
|
56 | 56 | colormap = forms.ChoiceField(choices=[('Jet', 'Jet'), ('Viridis', 'Viridis'), ('RdBu', 'RdBu')]) |
|
57 | 57 | #como es un perfil xmin y xmax deben ser iguales a zmin y zmax |
|
58 | 58 | xmax = forms.CharField(initial=30) |
|
59 | 59 | xmin = forms.CharField(initial=10) |
|
60 | 60 | #x2max = forms.CharField(initial=30) |
|
61 | 61 | #x2min = forms.CharField(initial=10) |
|
62 | 62 | ymax = forms.CharField(initial=180) |
|
63 | 63 | ymin = forms.CharField(initial=80) |
|
64 | 64 | zmax = forms.CharField(initial=30) |
|
65 | 65 | zmin = forms.CharField(initial=10) |
|
66 | 66 | |
|
67 | 67 | # Create your views here. |
|
68 | 68 | def main(request): |
|
69 | 69 | |
|
70 | 70 | kwargs = {} |
|
71 |
date = request.GET.get('date', datetime.now().strftime('%d |
|
|
72 |
exps = Exp |
|
|
71 | date = request.GET.get('date', datetime.now().strftime('%d-%m-%Y')) | |
|
72 | exps = ExpDetail.objects(date=datetime.strptime(date, '%d-%m-%Y')) | |
|
73 | 73 | experiments = [] |
|
74 | 74 | |
|
75 | 75 | for exp in exps: |
|
76 | 76 | dum = {} |
|
77 | dum['code'] = exp.code | |
|
78 | dum['plots'] = exp.plots | |
|
79 |
dum['name'] = |
|
|
77 | dum['code'] = exp.experiment.code | |
|
78 | dum['plots'] = [plot.plot for plot in exp.plots()] | |
|
79 | dum['name'] = exp.experiment.name | |
|
80 | 80 | dt = datetime.now() |
|
81 | data = ExpData.objects(expmeta=exp).order_by('-time')[0] #Get the time from the last data | |
|
82 | 81 | |
|
83 | 82 | t = time.mktime(dt.timetuple()) |
|
84 | 83 | |
|
85 | if exp['localtime'] == True: #Ask which type of time is coming: LT o UTC | |
|
84 | if exp.plots()[0]['metadata']['localtime'] == True: #Ask which type of time is coming: LT o UTC | |
|
86 | 85 | t -= 5*60*60 |
|
87 | 86 | # COnditionals to know which state are my clients |
|
88 |
if (t- |
|
|
87 | if (t-exp['last_time']) > 10*60: | |
|
89 | 88 | status = 'Offline' |
|
90 | 89 | clase = 'alertas-offline' |
|
91 | 90 | style = 'danger' |
|
92 |
lastDataDate = |
|
|
93 |
elif (t- |
|
|
91 | lastDataDate = exp['last_time'] | |
|
92 | elif (t-exp['last_time']) > 5*60: | |
|
94 | 93 | status = 'Delayed' |
|
95 | 94 | clase = 'alertas-delayed' |
|
96 | 95 | style = 'warning' |
|
97 |
lastDataDate = |
|
|
96 | lastDataDate = exp['last_time'] | |
|
98 | 97 | else: |
|
99 | 98 | status = 'Online' |
|
100 | 99 | clase = 'alertas-online' |
|
101 | 100 | style = 'success' |
|
102 |
lastDataDate = |
|
|
101 | lastDataDate = exp['last_time'] | |
|
103 | 102 | |
|
104 | 103 | dum['status'] = status |
|
105 | 104 | dum['class'] = clase |
|
106 | 105 | dum['style']= style |
|
107 | 106 | dum['date']= datetime.utcfromtimestamp(lastDataDate) |
|
108 | 107 | |
|
109 | 108 | experiments.append(dum) |
|
110 | 109 | |
|
111 | 110 | kwargs['date'] = date |
|
112 | 111 | kwargs['experiments'] = experiments |
|
113 | 112 | |
|
114 | 113 | return render(request, 'home.html', kwargs) |
|
115 | 114 | |
|
116 | 115 | |
|
117 | 116 | def plot(request, code=None, plot=None): |
|
118 | 117 | ''' |
|
119 | 118 | ''' |
|
120 | 119 | |
|
121 | 120 | realtime = False |
|
122 | 121 | date = request.GET.get('date', None) |
|
123 | 122 | if date is None: |
|
124 |
date = datetime.now().strftime('%d |
|
|
123 | date = datetime.now().strftime('%d-%m-%Y') | |
|
125 | 124 | realtime = True |
|
126 | 125 | exp = Experiment.objects.get(code=int(code)) |
|
127 |
|
|
|
126 | detail = ExpDetail.objects.get(experiment=exp, date=datetime.strptime(date, '%d-%m-%Y')) | |
|
127 | meta = PlotMeta.objects.get(exp_detail=detail, plot=plot) | |
|
128 | 128 | |
|
129 | 129 | kwargs = { |
|
130 | 130 | 'code': code, |
|
131 | 131 | 'plot': plot, |
|
132 | 132 | 'date': date, |
|
133 |
'id': |
|
|
133 | 'id': meta.pk, | |
|
134 | 134 | 'realtime': realtime, |
|
135 | 135 | 'title': exp.name, |
|
136 | 136 | } |
|
137 | 137 | # Logic to show my views |
|
138 | 138 | if plot == 'rti': |
|
139 | 139 | kwargs['setup_form'] = RTISetupForm() |
|
140 | 140 | kwargs['fn_plot'] = 'PcolorBuffer' |
|
141 | 141 | kwargs['subtitle'] = 'RTI plot' |
|
142 | 142 | return render(request, 'plot.html', kwargs) |
|
143 | 143 | elif plot == 'spc': |
|
144 | 144 | kwargs['setup_form'] = SPCSetupForm() |
|
145 | 145 | kwargs['fn_plot'] = 'Pcolor' |
|
146 | 146 | kwargs['subtitle'] = 'Spectra plot' |
|
147 | 147 | return render(request, 'plot.html', kwargs) |
|
148 | 148 | elif plot == 'noise': |
|
149 | 149 | kwargs['setup_form'] = ScatterSetupForm() |
|
150 | 150 | kwargs['fn_plot'] = 'Scatter' |
|
151 | 151 | kwargs['subtitle'] = 'Noise plot' |
|
152 | 152 | return render(request, 'plot.html', kwargs) |
|
153 | 153 | else: |
|
154 | 154 | return render(request, 'home.html', {}) |
|
155 | 155 | |
|
156 | 156 | No newline at end of file |
@@ -1,8 +1,11 | |||
|
1 |
from channels. |
|
|
2 | from plotter.consumers import ws_connect, ws_disconnect, ws_message | |
|
1 | from channels.auth import AuthMiddlewareStack | |
|
2 | from channels.routing import ProtocolTypeRouter, URLRouter | |
|
3 | import plotter.routing | |
|
3 | 4 | |
|
4 | channel_routing = [ | |
|
5 | route("websocket.connect", ws_connect, path=r'^/(?P<id>[a-z]+)/(?P<code>[0-9]+)/(?P<plot>[a-z]+)/$'), | |
|
6 | route("websocket.receive", ws_message, path=r'^/(?P<id>[a-z]+)/(?P<code>[0-9]+)/(?P<plot>[a-z]+)/$'), | |
|
7 | route("websocket.disconnect", ws_disconnect, path=r'^/(?P<id>[a-z]+)/(?P<code>[0-9]+)/(?P<plot>[a-z]+)/$'), | |
|
8 | ] No newline at end of file | |
|
5 | application = ProtocolTypeRouter({ | |
|
6 | 'websocket': AuthMiddlewareStack( | |
|
7 | URLRouter( | |
|
8 | plotter.routing.websocket_urlpatterns | |
|
9 | ) | |
|
10 | ), | |
|
11 | }) No newline at end of file |
@@ -1,136 +1,137 | |||
|
1 | 1 | """ |
|
2 | 2 | Django settings for realtime project. |
|
3 | 3 | |
|
4 | 4 | Generated by 'django-admin startproject' using Django 1.11.7. |
|
5 | 5 | |
|
6 | 6 | For more information on this file, see |
|
7 | 7 | https://docs.djangoproject.com/en/1.11/topics/settings/ |
|
8 | 8 | |
|
9 | 9 | For the full list of settings and their values, see |
|
10 | 10 | https://docs.djangoproject.com/en/1.11/ref/settings/ |
|
11 | 11 | """ |
|
12 | 12 | |
|
13 | 13 | import os |
|
14 | 14 | |
|
15 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) |
|
16 | 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
|
17 | 17 | |
|
18 | 18 | |
|
19 | 19 | # Quick-start development settings - unsuitable for production |
|
20 | 20 | # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ |
|
21 | 21 | |
|
22 | 22 | # SECURITY WARNING: keep the secret key used in production secret! |
|
23 | 23 | SECRET_KEY = '-rgo(&lgs^!4jn6atk_^=!a)+jtt%%h48a_w5-csgn7jc@iao5' |
|
24 | 24 | |
|
25 | 25 | # SECURITY WARNING: don't run with debug turned on in production! |
|
26 | 26 | DEBUG = True |
|
27 | 27 | |
|
28 | 28 | ALLOWED_HOSTS = ['*'] #YONG |
|
29 | 29 | |
|
30 | 30 | |
|
31 | 31 | # Application definition |
|
32 | 32 | |
|
33 | 33 | INSTALLED_APPS = [ |
|
34 | 34 | 'django.contrib.admin', |
|
35 | 35 | 'django.contrib.auth', |
|
36 | 36 | 'django.contrib.contenttypes', |
|
37 | 37 | 'django.contrib.sessions', |
|
38 | 38 | 'django.contrib.messages', |
|
39 | 39 | 'django.contrib.staticfiles', |
|
40 | 40 | 'bootstrap4', |
|
41 | 41 | 'channels', |
|
42 | 42 | 'plotter', |
|
43 | 43 | ] |
|
44 | 44 | |
|
45 | 45 | MIDDLEWARE = [ |
|
46 | 46 | 'django.middleware.security.SecurityMiddleware', |
|
47 | 47 | 'django.contrib.sessions.middleware.SessionMiddleware', |
|
48 | 48 | 'django.middleware.common.CommonMiddleware', |
|
49 | 49 | 'django.middleware.csrf.CsrfViewMiddleware', |
|
50 | 50 | 'django.contrib.auth.middleware.AuthenticationMiddleware', |
|
51 | 51 | 'django.contrib.messages.middleware.MessageMiddleware', |
|
52 | 52 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', |
|
53 | 53 | ] |
|
54 | 54 | |
|
55 | 55 | ROOT_URLCONF = 'realtime.urls' |
|
56 | 56 | |
|
57 | 57 | TEMPLATES = [ |
|
58 | 58 | { |
|
59 | 59 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', |
|
60 | 60 | 'DIRS': [], |
|
61 | 61 | 'APP_DIRS': True, |
|
62 | 62 | 'OPTIONS': { |
|
63 | 63 | 'context_processors': [ |
|
64 | 64 | 'django.template.context_processors.debug', |
|
65 | 65 | 'django.template.context_processors.request', |
|
66 | 66 | 'django.contrib.auth.context_processors.auth', |
|
67 | 67 | 'django.contrib.messages.context_processors.messages', |
|
68 | 68 | ], |
|
69 | 69 | }, |
|
70 | 70 | }, |
|
71 | 71 | ] |
|
72 | 72 | |
|
73 | 73 | WSGI_APPLICATION = 'realtime.wsgi.application' |
|
74 | 74 | |
|
75 | 75 | |
|
76 | 76 | # Database |
|
77 | 77 | # https://docs.djangoproject.com/en/1.11/ref/settings/#databases |
|
78 | 78 | |
|
79 | 79 | DATABASES = { |
|
80 | 80 | 'default': { |
|
81 | 81 | 'ENGINE': 'django.db.backends.sqlite3', |
|
82 | 82 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), |
|
83 | 83 | } |
|
84 | 84 | } |
|
85 | 85 | |
|
86 | 86 | |
|
87 | 87 | # Password validation |
|
88 | 88 | # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators |
|
89 | 89 | |
|
90 | 90 | AUTH_PASSWORD_VALIDATORS = [ |
|
91 | 91 | { |
|
92 | 92 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', |
|
93 | 93 | }, |
|
94 | 94 | { |
|
95 | 95 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', |
|
96 | 96 | }, |
|
97 | 97 | { |
|
98 | 98 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', |
|
99 | 99 | }, |
|
100 | 100 | { |
|
101 | 101 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', |
|
102 | 102 | }, |
|
103 | 103 | ] |
|
104 | 104 | |
|
105 | 105 | |
|
106 | 106 | # Internationalization |
|
107 | 107 | # https://docs.djangoproject.com/en/1.11/topics/i18n/ |
|
108 | 108 | |
|
109 | 109 | LANGUAGE_CODE = 'en-us' |
|
110 | 110 | |
|
111 | 111 | TIME_ZONE = os.environ.get('TZ', 'UTC') |
|
112 | 112 | |
|
113 | 113 | USE_I18N = True |
|
114 | 114 | |
|
115 | 115 | USE_L10N = True |
|
116 | 116 | |
|
117 | 117 | USE_TZ = True |
|
118 | 118 | |
|
119 | 119 | |
|
120 | 120 | # Static files (CSS, JavaScript, Images) |
|
121 | 121 | # https://docs.djangoproject.com/en/1.11/howto/static-files/ |
|
122 | 122 | |
|
123 | 123 | STATIC_URL = '/static/' |
|
124 | 124 | |
|
125 | 125 | #======================== YONG ================================ |
|
126 | 126 | host = os.environ.get('HOST_REDIS', '127.0.0.1') |
|
127 | 127 | |
|
128 | 128 | CHANNEL_LAYERS = { |
|
129 | 129 | "default": { |
|
130 |
|
|
|
130 | 'BACKEND': 'channels_redis.core.RedisChannelLayer', | |
|
131 | 131 | "CONFIG": { |
|
132 | 132 | "hosts": [(host, 6379)], |
|
133 | 133 | }, |
|
134 | "ROUTING": "realtime.routing.channel_routing", | |
|
135 | 134 | }, |
|
136 | 135 | } |
|
136 | ||
|
137 | ASGI_APPLICATION = "realtime.routing.application" |
@@ -1,10 +1,10 | |||
|
1 | asgi-redis==1.4.3 | |
|
2 | Django==1.11.7 | |
|
1 | Django | |
|
3 | 2 | django-bootstrap4 |
|
4 |
channels |
|
|
5 | mongoengine==0.15.0 | |
|
6 | pymongo==3.5.1 | |
|
7 | pyzmq==16.0.3 | |
|
8 | redis==2.10.6 | |
|
9 | requests==2.18.4 | |
|
10 | simplejson==3.12.0 | |
|
3 | channels | |
|
4 | channels_redis | |
|
5 | mongoengine | |
|
6 | pymongo | |
|
7 | pyzmq | |
|
8 | redis | |
|
9 | requests | |
|
10 | simplejson No newline at end of file |
@@ -1,104 +1,110 | |||
|
1 | 1 | #!/usr/bin/python |
|
2 | 2 | # -*- coding: UTF-8 -*- |
|
3 | 3 | |
|
4 | 4 | import time |
|
5 | 5 | from datetime import datetime, timedelta |
|
6 | 6 | import zmq |
|
7 | 7 | import json |
|
8 | 8 | import numpy as np |
|
9 | 9 | from threading import Thread |
|
10 | 10 | import argparse |
|
11 | 11 | |
|
12 | 12 | REQUEST_TIMEOUT = 5000 |
|
13 | 13 | RETRIES = 5 |
|
14 | 14 | SERVER_ENDPOINT = 'tcp://localhost:4444' |
|
15 | 15 | |
|
16 | context = zmq.Context() | |
|
17 | socket = context.socket(zmq.REQ) | |
|
18 | socket.connect (SERVER_ENDPOINT) | |
|
19 | poll = zmq.Poller() | |
|
20 | poll.register(socket, zmq.POLLIN) | |
|
16 | ||
|
21 | 17 | |
|
22 | 18 | def send(dato): |
|
23 | 19 | ''' |
|
24 | 20 | Function to send data to server |
|
25 | 21 | ''' |
|
26 | global socket, poll | |
|
22 | context = zmq.Context() | |
|
23 | socket = context.socket(zmq.REQ) | |
|
24 | socket.connect (SERVER_ENDPOINT) | |
|
25 | poll = zmq.Poller() | |
|
26 | poll.register(socket, zmq.POLLIN) | |
|
27 | 27 | retries = RETRIES |
|
28 | 28 | while True: |
|
29 | 29 | socket.send_json(dato) |
|
30 | 30 | socks = dict(poll.poll(REQUEST_TIMEOUT)) |
|
31 | 31 | if socks.get(socket) == zmq.POLLIN: |
|
32 | 32 | reply = socket.recv_string() |
|
33 | 33 | if reply == 'ok': |
|
34 | print('Server replied (%s)' % reply) | |
|
34 | print(('Server replied (%s)' % reply)) | |
|
35 | 35 | break |
|
36 | 36 | else: |
|
37 | print('Malformed reply from server: %s' % reply) | |
|
37 | print(('Malformed reply from server: %s' % reply)) | |
|
38 | 38 | else: |
|
39 | print('No response from server, retries left {}'.format(retries)) | |
|
39 | print(('No response from server, retries left {}'.format(retries))) | |
|
40 | 40 | socket.setsockopt(zmq.LINGER, 0) |
|
41 | 41 | socket.close() |
|
42 | 42 | poll.unregister(socket) |
|
43 | 43 | retries -= 1 |
|
44 | 44 | if retries == 0: |
|
45 | 45 | print('Server seems to be offline...') |
|
46 | 46 | socket = context.socket(zmq.REQ) |
|
47 | 47 | socket.connect(SERVER_ENDPOINT) |
|
48 | 48 | poll.register(socket, zmq.POLLIN) |
|
49 | 49 | break |
|
50 | 50 | # Create new connection |
|
51 | 51 | socket = context.socket(zmq.REQ) |
|
52 | 52 | socket.connect(SERVER_ENDPOINT) |
|
53 | 53 | poll.register(socket, zmq.POLLIN) |
|
54 | 54 | |
|
55 | 55 | def main(realtime, code, date=None, interval=30): |
|
56 | 56 | ''' |
|
57 | 57 | Simulate data to be sended to server |
|
58 | 58 | ''' |
|
59 | 59 | |
|
60 | 60 | if realtime: |
|
61 | 61 | dt = datetime.now() |
|
62 | 62 | else: |
|
63 | 63 | dt = date |
|
64 | 64 | |
|
65 | data = { | |
|
66 | 'spc': np.round(np.random.rand(4, 60, 100)*5 + 10, 2).tolist(), | |
|
67 | 'rti': np.round(np.random.rand(4, 100)*5 + 10, 2).tolist(), | |
|
68 | 'noise': np.round(np.random.rand(4) + np.array([10,11,12,13]), 2).tolist() | |
|
69 | } | |
|
70 | ||
|
65 | 71 | while True: |
|
66 | 72 | |
|
67 | print('Sending {} - {}'.format(code, dt)) | |
|
73 | print(('Sending {} - {}'.format(code, dt))) | |
|
68 | 74 | |
|
69 | 75 | dato = { |
|
70 | 76 | 'time': time.mktime(dt.timetuple()), |
|
71 | 'yrange': np.arange(100).tolist(), | |
|
77 | 'metadata':{ | |
|
78 | 'yrange': np.arange(80, 120, 40/100.).tolist(), | |
|
72 | 79 | 'xrange': np.arange(-30, 30).tolist(), |
|
73 | 80 | 'localtime': True, |
|
74 |
'interval': interval |
|
|
81 | 'interval': interval | |
|
82 | }, | |
|
75 | 83 | 'exp_code': code, |
|
76 | 'data': { | |
|
77 | 'noise': np.round(np.random.rand(4) + np.array([10,11,12,13]), 2).tolist(), | |
|
78 | 'rti': np.round(np.random.rand(4, 100)*5 + 10, 2).tolist(), | |
|
79 | 'spc': np.round(np.random.rand(4, 60, 100)*5 + 10, 2).tolist(), | |
|
80 | } | |
|
81 | 84 |
|
|
82 | 85 | |
|
83 | 86 | dt = dt + timedelta(seconds=interval) |
|
87 | for plot, d in data.items(): | |
|
88 | dato['plot'] = plot | |
|
89 | dato['data'] = d | |
|
84 | 90 | t = Thread(target=send, args=(dato, )) |
|
85 | 91 | t.start() |
|
86 | 92 | if realtime: |
|
87 | 93 | time.sleep(interval) |
|
88 | 94 | else: |
|
89 | 95 | time.sleep(5) |
|
90 | 96 | |
|
91 | 97 | if __name__ == '__main__': |
|
92 | 98 | parser = argparse.ArgumentParser(description='This is a Client for realtime app') |
|
93 | 99 | parser.add_argument('--date', action='store', default=None, help='format: 2018/02/13 12:23:00') |
|
94 | 100 | parser.add_argument('-r', action='store_true', dest='realtime', default=None) |
|
95 |
parser.add_argument('-c', action='store', dest='code', default='17 |
|
|
101 | parser.add_argument('-c', action='store', dest='code', default='172') | |
|
96 | 102 | parser.add_argument('-i', action='store', dest='interval', type=int, default=30) |
|
97 | 103 | |
|
98 | 104 | results = parser.parse_args() |
|
99 | 105 | if not results.realtime: |
|
100 | 106 | try: |
|
101 | 107 | results.date = datetime.strptime(results.date, '%Y/%m/%d %H:%M:%S') |
|
102 | 108 | except: |
|
103 |
raise |
|
|
109 | raise NameError('You must specify a date (--date) for non-realtime experiment') | |
|
104 | 110 | main(results.realtime, results.code, results.date, results.interval) |
@@ -1,153 +1,175 | |||
|
1 | 1 | #!/usr/bin/python |
|
2 | 2 | # -*- coding: UTF-8 -*- |
|
3 | 3 | |
|
4 | 4 | import os |
|
5 | 5 | import sys |
|
6 | 6 | import json |
|
7 | 7 | import simplejson |
|
8 | 8 | from datetime import datetime |
|
9 | 9 | import time |
|
10 | 10 | import zmq |
|
11 | import redis | |
|
12 | import asgi_redis | |
|
13 | 11 | import mongoengine |
|
14 | 12 | from threading import Thread |
|
15 | 13 | |
|
16 | 14 | sys.path.append(os.environ.get('APP_DIR', '../')) |
|
17 | 15 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "realtime.settings") |
|
18 | 16 | |
|
19 |
from plotter.models import Experiment, Exp |
|
|
17 | from plotter.models import Experiment, ExpDetail, PlotMeta, PlotData | |
|
20 | 18 | |
|
21 | 19 | host_mongo = os.environ.get('HOST_MONGO', 'localhost') |
|
22 | 20 | mongoengine.connect('dbplots', host=host_mongo, port=27017) |
|
23 | 21 | |
|
24 | host_redis = os.environ.get('HOST_REDIS', 'localhost') | |
|
25 | channel = asgi_redis.RedisChannelLayer(hosts=[(host_redis, 6379)]) | |
|
22 | import channels.layers | |
|
23 | from asgiref.sync import async_to_sync | |
|
24 | ||
|
25 | channel = channels.layers.get_channel_layer() | |
|
26 | 26 | |
|
27 | 27 | def loaddata(): |
|
28 | 28 | print('Loading Experiments...') |
|
29 | 29 | if os.environ.get('APP_DIR', None) is not None: |
|
30 | 30 | file_exp = os.path.join(os.environ.get('APP_DIR'), 'scripts', 'experiments.json') |
|
31 | 31 | else: |
|
32 | 32 | file_exp = './experiments.json' |
|
33 | 33 | for tup in json.load(open(file_exp)): |
|
34 |
print(tup |
|
|
34 | print(tup) | |
|
35 | 35 | exp = Experiment.objects(code=tup['code']).modify( |
|
36 | 36 | upsert=True, # To add a new row |
|
37 | 37 | new=True, |
|
38 | 38 | set__code=tup['code'], |
|
39 | 39 | set__name=tup['name'], |
|
40 | 40 | ) |
|
41 | 41 | exp.save() |
|
42 | 42 | |
|
43 | 43 | #============== funcion para modificar datos en la tabla ============== |
|
44 | 44 | def update(buffer): |
|
45 | 45 | dt = datetime.utcfromtimestamp(buffer['time']) |
|
46 | print('Updating code={} date={} {}'.format(buffer['exp_code'], dt, datetime.now())) | |
|
47 | exp = ExpMeta.objects(code=buffer['exp_code'], date=dt.date()).modify( | |
|
48 | upsert=True, # To add a new row | |
|
46 | exp = Experiment.objects.get(code=buffer['exp_code']) | |
|
47 | ||
|
48 | detail = ExpDetail.objects(experiment=exp, date=dt.date()).modify( | |
|
49 | upsert=True, | |
|
49 | 50 |
new=True, |
|
50 | set__code=buffer['exp_code'], | |
|
51 | set__experiment=exp, | |
|
51 | 52 | set__date=dt.date(), |
|
52 |
set__ |
|
|
53 | set__xrange = buffer['xrange'], | |
|
54 | set__interval = buffer['interval'], | |
|
55 | set__localtime = buffer['localtime'], | |
|
56 | set__plots = buffer['data'].keys() | |
|
53 | set__last_time = buffer['time'] | |
|
57 | 54 |
|
|
58 | exp.save() | |
|
59 | 55 | |
|
60 |
|
|
|
56 | plot = PlotMeta.objects(exp_detail=detail, plot=buffer['plot']).modify( | |
|
57 | upsert=True, | |
|
58 | new=True, | |
|
59 | set__metadata = buffer['metadata'] | |
|
60 | ) | |
|
61 | #plot.save() | |
|
62 | ||
|
63 | data = PlotData.objects(plot=plot, time=buffer['time']).modify( | |
|
61 | 64 | upsert=True, # To add a new row |
|
62 | 65 | new=True, |
|
63 | set__expmeta = exp, | |
|
64 | 66 | set__time = buffer['time'], |
|
65 | 67 | set__data = buffer['data'] |
|
66 | 68 | ) |
|
67 | 69 | |
|
68 | data.save() | |
|
70 | #data.save() | |
|
69 | 71 | |
|
70 | 72 | if datetime.now().date() == dt.date(): |
|
71 | 73 | return True |
|
72 | 74 | |
|
73 | 75 | return False |
|
74 | 76 | |
|
75 | 77 | # Function that is checking the state of my clients every 30s |
|
76 | 78 | def check_times(): |
|
77 | 79 | |
|
78 | 80 | while True: |
|
79 | 81 | dt = datetime.now() |
|
80 |
exps = Exp |
|
|
81 | ||
|
82 | for exp in exps: | |
|
83 | data = ExpData.objects(expmeta=exp).order_by('-time')[0] #me quedo con el ultimo time del ultimo valor | |
|
82 | exps = ExpDetail.objects(date=dt.date()) | |
|
84 | 83 |
|
|
84 | for detail in exps: | |
|
85 | code = detail.experiment.code | |
|
86 | plot = detail.plots()[0] | |
|
87 | data_time = detail['last_time'] | |
|
85 | 88 | t = time.mktime(dt.timetuple()) |
|
86 | 89 | |
|
87 | if exp['localtime'] == True: #Consulto que tipode time me esta llegando: LT o UTC | |
|
90 | if plot['metadata']['localtime'] == True: | |
|
88 | 91 | t -= 5*60*60 |
|
89 | 92 | |
|
90 | if exp['localtime'] == True: #Consulto que tipode time me esta llegando: LT o UTC | |
|
91 |
data_time = d |
|
|
93 | if plot['metadata']['localtime'] == True: | |
|
94 | data_time = detail['last_time'] + 5*60*60 | |
|
92 | 95 | |
|
93 | if (t-data['time']) > 6*exp['interval']: | |
|
94 | channel.send_group(u'main', {'text': json.dumps({'code': exp['code'], 'value': 'danger', 'time': data_time})}) | |
|
95 | print ('{} {} {} {} {}'.format(exp.code, t, data['time'], (t-data['time']), 'offline')) | |
|
96 | elif (t-data['time']) > 3*exp['interval']: | |
|
97 | channel.send_group(u'main', {'text': json.dumps({'code': exp['code'], 'value': 'warning', 'time': data_time})}) | |
|
98 | print ('{} {} {} {} {}'.format(exp.code, t, data['time'], (t-data['time']), 'delayed')) | |
|
96 | message = { | |
|
97 | 'code': code, | |
|
98 | 'time': data_time | |
|
99 | } | |
|
100 | ||
|
101 | if (t-detail['last_time']) > 10*60: | |
|
102 | value = 'danger' | |
|
103 | print(('{} {} {} {} {}'.format(code, t, detail['last_time'], (t-detail['last_time']), 'offline'))) | |
|
104 | elif (t-detail['last_time']) > 5*60: | |
|
105 | value = 'warning' | |
|
106 | print(('{} {} {} {} {}'.format(code, t, detail['last_time'], (t-detail['last_time']), 'delayed'))) | |
|
99 | 107 | else: |
|
100 | channel.send_group(u'main', {'text': json.dumps({'code': exp['code'], 'value': 'success', 'time': data_time})}) | |
|
101 | print ('{} {} {} {} {}'.format(exp.code, t, data['time'], (t-data['time']), 'online')) | |
|
108 | value = 'success' | |
|
109 | ||
|
110 | message['value'] = value | |
|
111 | ||
|
112 | async_to_sync(channel.group_send)( | |
|
113 | 'main', | |
|
114 | { | |
|
115 | 'type': 'zmq_message', | |
|
116 | 'message': json.dumps(message) | |
|
117 | } | |
|
118 | ) | |
|
119 | ||
|
102 | 120 | time.sleep(30) |
|
103 | 121 | |
|
104 | 122 | def main(): |
|
105 | 123 | print('Starting ZMQ server...') |
|
106 | 124 | context = zmq.Context() |
|
107 | 125 | receiver = context.socket(zmq.REP) |
|
108 | 126 | receiver.bind("tcp://0.0.0.0:4444") |
|
109 | 127 | t = Thread(target=check_times) |
|
110 | 128 | t.start() |
|
111 | 129 | |
|
112 | 130 | while True: |
|
113 | 131 | |
|
114 | 132 | buffer = receiver.recv_json() |
|
115 | 133 | |
|
116 | if buffer['localtime'] == True: # Ask which type of time is coming: LT o UTC | |
|
134 | if buffer['metadata']['localtime'] == True: # Ask which type of time is coming: LT o UTC | |
|
117 | 135 | buffer['time'] -= 5*60*60 |
|
118 | 136 | |
|
119 | 137 | if not isinstance(buffer, dict): |
|
120 | 138 | print('*******************') |
|
121 | 139 | print(buffer) |
|
122 | 140 | continue |
|
123 | 141 | if not update(buffer): |
|
124 | 142 | receiver.send_string('ok') |
|
125 | 143 | continue |
|
126 | print("==================================") | |
|
127 | for plot in buffer['data']: | |
|
128 |
dum = buffer |
|
|
129 | dum['time'] = [buffer['time']] | |
|
130 | if plot=='noise': | |
|
131 | dum[plot] = [[x] for x in buffer['data'][plot]] | |
|
132 | elif plot=='spc': | |
|
133 |
dum[' |
|
|
134 |
dum[' |
|
|
135 | dum['rti'] = buffer['data']['rti'] | |
|
136 | else: | |
|
137 |
|
|
|
138 |
dum.pop(' |
|
|
139 | exp_code = dum.pop('exp_code') | |
|
140 |
channel. |
|
|
141 | u'{}_{}'.format(exp_code, plot), | |
|
142 | {'text': simplejson.dumps(dum, ignore_nan=True)} | |
|
143 | ) | |
|
144 | print('Sending to group {}_{} - {} bytes'.format(exp_code, plot, len(str(dum)))) | |
|
144 | # for plot in buffer['data']: | |
|
145 | # dum = buffer.copy() | |
|
146 | # dum['time'] = [buffer['time']] | |
|
147 | # if plot=='noise': | |
|
148 | # dum[plot] = [[x] for x in buffer['data'][plot]] | |
|
149 | # elif plot=='spc': | |
|
150 | # dum['noise'] = [[x] for x in buffer['data']['noise']] | |
|
151 | # dum['spc'] = buffer['data']['spc'] | |
|
152 | # dum['rti'] = buffer['data']['rti'] | |
|
153 | # else: | |
|
154 | # dum[plot] = buffer['data'][plot] | |
|
155 | # dum.pop('data') | |
|
156 | # exp_code = dum.pop('exp_code') | |
|
157 | # group = '{}_{}'.format(exp_code, plot) | |
|
158 | # async_to_sync(channel.group_send)( | |
|
159 | # group, | |
|
160 | # { | |
|
161 | # 'type': 'zmq_message', | |
|
162 | # 'message': simplejson.dumps(dum, ignore_nan=True) | |
|
163 | # } | |
|
164 | # ) | |
|
165 | ||
|
166 | # print('Sending to group {}_{} - {} bytes'.format(exp_code, plot, len(str(dum)))) | |
|
145 | 167 | |
|
146 | 168 | receiver.send_string('ok') |
|
147 | 169 | |
|
148 | 170 | receiver.close() |
|
149 | 171 | context.term() |
|
150 | 172 | |
|
151 | 173 | if __name__=='__main__': |
|
152 | 174 | loaddata() |
|
153 | 175 | main() |
General Comments 0
You need to be logged in to leave comments.
Login now