Browse Source

Sincronização de cronômetros em diferentes máquinas (#2894)

* Inicia sincronizacao de cronometros no painel do operador

* Correção do reset e melhorias de código

* Adiciona sincronização no painel eletrônico

* Remove código desnecessário

* Corrige sincronização no painel do operador
pull/2758/head
Cesar Augusto de Carvalho 5 years ago
committed by Edward
parent
commit
4fe2db18a0
  1. 7
      sapl/base/templatetags/common_tags.py
  2. 26
      sapl/painel/migrations/0010_auto_20190711_1030.py
  3. 20
      sapl/painel/migrations/0011_cronometro_last_stop_duration.py
  4. 21
      sapl/painel/models.py
  5. 44
      sapl/painel/views.py
  6. 7
      sapl/sessao/views.py
  7. 75
      sapl/templates/painel/index.html
  8. 201
      sapl/templates/sessao/painel.html

7
sapl/base/templatetags/common_tags.py

@ -4,6 +4,7 @@ import re
from django import template
from django.template.defaultfilters import stringfilter
from django.utils.safestring import mark_safe
from django.utils import timezone
from webpack_loader import utils
from sapl.base.models import AppConfig
@ -271,6 +272,12 @@ def cronometro_to_seconds(value):
def duration_to_seconds(cronometro_duration):
return cronometro_duration.seconds
@register.filter
def duration_difference(cronometro_duration, last_time):
difference_to_now = timezone.now()-last_time
if difference_to_now < cronometro_duration:
return (cronometro_duration-difference_to_now).seconds
return cronometro_duration.seconds
@register.filter
def to_list_pk(object_list):

26
sapl/painel/migrations/0010_auto_20190711_1030.py

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-07-11 13:30
from __future__ import unicode_literals
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('painel', '0009_painelconfig_mostrar_votos_antecedencia'),
]
operations = [
migrations.AddField(
model_name='cronometro',
name='ultima_alteracao_status',
field=models.DateTimeField(default=django.utils.timezone.now),
),
migrations.AlterField(
model_name='cronometro',
name='status',
field=models.CharField(choices=[('I', 'Start'), ('R', 'Reset'), ('S', 'Stop'), ('C', 'Increment')], default='R', max_length=1, verbose_name='Status do cronômetro'),
),
]

20
sapl/painel/migrations/0011_cronometro_last_stop_duration.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-07-11 14:30
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('painel', '0010_auto_20190711_1030'),
]
operations = [
migrations.AddField(
model_name='cronometro',
name='last_stop_duration',
field=models.DurationField(blank=True, null=True, verbose_name='Última duração salva em stop'),
),
]

21
sapl/painel/models.py

@ -2,7 +2,7 @@ import reversion
from django.db import models
from django.utils.translation import ugettext_lazy as _
from sapl.utils import YES_NO_CHOICES
from django.utils import timezone
@reversion.register()
class Painel(models.Model):
@ -24,12 +24,6 @@ class Painel(models.Model):
@reversion.register()
class Cronometro(models.Model):
CRONOMETRO_TYPES = (
('A', _('Aparte')),
('D', _('Discurso')),
('O', _('Ordem do dia')),
('C', _('Considerações finais'))
)
CRONOMETRO_STATUS = (
('I', 'Start'),
@ -42,17 +36,28 @@ class Cronometro(models.Model):
max_length=1,
verbose_name=_('Status do cronômetro'),
choices=CRONOMETRO_STATUS,
default='S')
default='R')
ultima_alteracao_status = models.DateTimeField(default=timezone.now)
last_stop_duration = models.DurationField(
blank=True,
null=True,
verbose_name=_("Última duração salva em stop"))
duracao_cronometro = models.DurationField(
verbose_name=_('Duração do cronômetro'))
tipo = models.CharField(
max_length=100,
verbose_name=_('Tipo Cronômetro'),
unique=True)
ativo = models.BooleanField(
default=False,
choices=YES_NO_CHOICES,
verbose_name=_('Ativo?'))
ordenacao = models.PositiveIntegerField(
blank=True,
null=True,

44
sapl/painel/views.py

@ -363,7 +363,28 @@ def switch_painel(request):
def verifica_painel(request):
sessao = SessaoPlenaria.objects.get(id=request.GET['pk_sessao'])
status = sessao.painel_aberto
resposta = JsonResponse(dict(status=status))
CRONOMETRO_STATUS = {
'I': 'start',
'R': 'reset',
'S': 'stop',
'C': 'increment'
}
dict_status_cronometros = dict(Cronometro.objects.filter(ativo=True).order_by('ordenacao').values_list('id', 'status'))
for key, value in dict_status_cronometros.items():
dict_status_cronometros[key] = CRONOMETRO_STATUS[dict_status_cronometros[key]]
dict_duracao_cronometros = dict(Cronometro.objects.filter(ativo=True).order_by('ordenacao').values_list('id', 'duracao_cronometro'))
for key, value in dict_duracao_cronometros.items():
dict_duracao_cronometros[key] = value.seconds
resposta = JsonResponse(dict(status=status,
cronometros=dict_status_cronometros,
duracao_cronometros=dict_duracao_cronometros)
)
return resposta
@ -395,22 +416,13 @@ def cronometro_painel(request):
cronometro_id = request.GET['tipo'].split('cronometro_')[1]
cronometro = Cronometro.objects.get(id=cronometro_id)
cronometro.status = CRONOMETRO_STATUS[acao]
cronometro.ultima_alteracao_status = timezone.now()
# Caso não seja stop, last_time virá como 0
cronometro.last_stop_duration = request.GET.get('last_time')
cronometro.save()
return HttpResponse({})
def get_cronometro_status(request, name):
logger = logging.getLogger(__name__)
username = request.user.username
try:
logger.debug("user=" + username + ". Tentando obter cronometro.")
cronometro = request.session[name]
except KeyError as e:
logger.error("user=" + username + ". Erro ao obter cronometro. Retornado como vazio. " + str(e))
cronometro = ''
return cronometro
def get_materia_aberta(pk):
return OrdemDia.objects.filter(
sessao_plenaria_id=pk, votacao_aberta=True).last()
@ -608,11 +620,17 @@ def get_dados_painel(request, pk):
for key, value in dict_status_cronometros.items():
dict_status_cronometros[key] = CRONOMETRO_STATUS[dict_status_cronometros[key]]
dict_duracao_cronometros = dict(Cronometro.objects.filter(ativo=True).order_by('ordenacao').values_list('id', 'duracao_cronometro'))
for key, value in dict_duracao_cronometros.items():
dict_duracao_cronometros[key] = value.seconds
response = {
'sessao_plenaria': str(sessao),
'sessao_plenaria_data': sessao.data_inicio.strftime('%d/%m/%Y'),
'sessao_plenaria_hora_inicio': sessao.hora_inicio,
'cronometros': dict_status_cronometros,
'duracao_cronometros': dict_duracao_cronometros,
'sessao_solene': sessao.tipo.nome == "Solene",
'sessao_finalizada': sessao.finalizada,
'tema_solene': sessao.tema_solene,

7
sapl/sessao/views.py

@ -1055,9 +1055,10 @@ class PainelView(PermissionRequiredForAppCrudMixin, TemplateView):
self.template_name = 'painel/index.html'
cronometros = Cronometro.objects.filter(ativo=True).order_by('ordenacao')
for cronometro in cronometros:
cronometro.status = 'S' # Stop
cronometro.save()
# for cronometro in cronometros:
# cronometro.status = 'S' # Stop
# cronometro.ultima_alteracao_status = timezone.now()
# cronometro.save()
return TemplateView.get(self, request, *args, **kwargs)

75
sapl/templates/painel/index.html

@ -170,6 +170,16 @@
audio.play();
}
function convertValueToDuration(value){
let h = Math.floor((value/1000) / 3600);
h = checkTime(h);
let m = Math.floor((value/1000) % 3600 / 60);
m = checkTime(m);
let s = Math.floor((value/1000) % 3600 % 60);
s = checkTime(s);
return h.toString() + ":" + m.toString() + ":" + s.toString();
}
$(document).ready(function() {
//TODO: replace by a fancy jQuery clock
startTime();
@ -186,23 +196,26 @@
{% for cron in cronometros %}
cronometros_previous.push(0);
$('#cronometro_' + "{{cron.id}}").runner({
autostart: false,
autostart: {% if cron.status == "I"%} true {% else %} false {% endif %},
countdown: true,
startAt: parseInt("{{cron.duracao_cronometro|duration_to_seconds}}") * 1000,
startAt:
{% if cron.status == "R"%}
parseInt("{{cron.duracao_cronometro|duration_to_seconds}}") * 1000
{% elif cron.status == "S"%}
{% if cron.last_stop_duration %}
parseInt("{{cron.last_stop_duration|duration_to_seconds}}") * 1000
{% else %}
parseInt("{{cron.duracao_cronometro|duration_to_seconds}}") * 1000
{% endif %}
{% elif cron.status == "I" %}
parseInt("{{cron.duracao_cronometro|duration_difference:cron.ultima_alteracao_status}}") * 1000
{% endif %},
stopAt: 0,
milliseconds: false,
format: function(value) {
let h = Math.floor((value/1000) / 3600);
h = checkTime(h);
let m = Math.floor((value/1000) % 3600 / 60);
m = checkTime(m);
let s = Math.floor((value/1000) % 3600 % 60);
s = checkTime(s);
return h.toString() + ":" + m.toString() + ":" + s.toString();
}
format: convertValueToDuration
}).on('runnerFinish', function(eventObject, info){
playAudioNumVezes(audioAlertFinish, num_vezes_toca_audio);
})
});
{% endfor %}
var tempo_disparo_antecedencia = "{{ painel_config.tempo_disparo_antecedencia }}"
@ -329,27 +342,55 @@
$("#votacao").append('<center>Não há votação, pois não há nenhuma matéria aberta ou já votada.</center>');
};
// obtém todos os ids em uma lista e
// converte os dados do status dos cronômetros de dicionário para uma lista
var ids = [];
var status_cronometros = []
var status_cronometros = [];
for (var key in data['cronometros']) {
if (data['cronometros'].hasOwnProperty(key)) {
ids.push(key);
status_cronometros.push(data['cronometros'][key])
status_cronometros.push(data['cronometros'][key]);
}
}
// converte os dados de dicionário para uma lista
var duracao_cronometros = [];
for (let id of ids) {
duracao_cronometros.push(data['duracao_cronometros'][id]);
}
for(let i=0; i<status_cronometros.length; i++){
if (!cronometros_previous[i])
if (!cronometros_previous[i]){
cronometros_previous[i] = ''
}
// se houve alteração de status
if (status_cronometros[i] != cronometros_previous[i]) {
if(status_cronometros[i] == 'reset'){
// é necessário recriar o cronômetro com o valor da duração original devido a limitações da API
$('#cronometro_' + ids[i]).runner({
autostart: false,
countdown: true,
startAt: parseInt(duracao_cronometros[i]) * 1000,
stopAt: 0,
milliseconds: false,
format: convertValueToDuration
}).on('runnerFinish', function(eventObject, info){
playAudioNumVezes(audioAlertFinish, num_vezes_toca_audio);
});
}
// ações de start e stop
else{
$('#cronometro_' + ids[i]).runner(status_cronometros[i]);
}
cronometros_previous[i] = status_cronometros[i];
}
if($('#cronometro_' + ids[i]).runner('info').formattedTime == tempo_disparo_antecedencia &&
"{{ painel_config.disparo_cronometro }}" == "True" )
// Dispara aviso prévio se estiver configurado
if($('#cronometro_' + ids[i]).runner('info').formattedTime == tempo_disparo_antecedencia
&& "{{ painel_config.disparo_cronometro }}" == "True" ){
audioAlertFinish.play();
}
}

201
sapl/templates/sessao/painel.html

@ -115,31 +115,22 @@ function playAudioNumVezes(audio, times, ended) {
audio.play();
}
function convertValueToDuration(value){
let h = Math.floor((value/1000) / 3600);
h = checkTime(h);
let m = Math.floor((value/1000) % 3600 / 60);
m = checkTime(m);
let s = Math.floor((value/1000) % 3600 % 60);
s = checkTime(s);
return h.toString() + ":" + m.toString() + ":" + s.toString();
}
$(document).ready(function(){
let pk_sessao = parseInt("{{root_pk}}");
let botao_abrir = $('#id_abrir_painel');
let botao_fechar = $('#id_fechar_painel');
$.ajax({
data: {pk_sessao: pk_sessao},
type: 'GET',
dataType: 'json',
url: "{% url 'sapl.painel:verifica_painel' %}",
error: function () {
alert("Erro ao verificar o Painel");
},
success: function (data) {
if (data['status']) {
botao_abrir.hide();
botao_fechar.show();
} else {
botao_abrir.show();
botao_fechar.hide();
}
},
});
startTime();
let audioAlertFinish = document.getElementById("audio");
@ -149,78 +140,190 @@ $(document).ready(function(){
duracao_disparo = parseInt(tmp[0])*3600 + parseInt(tmp[1])*60 + parseInt(tmp[2]);
let num_vezes_toca_audio = Math.round(duracao_disparo/audioAlertFinish.duration);
var cronometros_previous = [];
{% for cron in cronometros %}
cronometros_previous.push(0);
$('#cronometro_' + "{{cron.id}}").prop('disabled', false);
{% if cron.status == 'I' %}
$('#cronometro_' + "{{cron.id}}" + '_Reset').hide();
$('#cronometro_' + "{{cron.id}}").runner('start');
$('#cronometro_' + "{{cron.id}}" + '_Start').text('Parar');
{% else %}
$('#cronometro_' + "{{cron.id}}" + '_Reset').show();
$('#cronometro_' + "{{cron.id}}").runner('stop');
$('#cronometro_' + "{{cron.id}}" + '_Start').text('Iniciar');
{% endif %}
$('#cronometro_' + "{{cron.id}}").runner({
autostart: false,
autostart: {% if cron.status == "I"%} true {% else %} false {% endif %},
countdown: true,
startAt: parseInt("{{cron.duracao_cronometro|duration_to_seconds}}") * 1000,
startAt:
{% if cron.status == "R"%}
parseInt("{{cron.duracao_cronometro|duration_to_seconds}}") * 1000
{% elif cron.status == "S"%}
{% if cron.last_stop_duration %}
parseInt("{{cron.last_stop_duration|duration_to_seconds}}") * 1000
{% else %}
parseInt("{{cron.duracao_cronometro|duration_to_seconds}}") * 1000
{% endif %}
{% elif cron.status == "I" %}
parseInt("{{cron.duracao_cronometro|duration_difference:cron.ultima_alteracao_status}}") * 1000
{% endif %},
stopAt: 0,
milliseconds: false,
format: function(value) {
let h = Math.floor((value/1000) / 3600);
h = checkTime(h);
let m = Math.floor((value/1000) % 3600 / 60);
m = checkTime(m);
let s = Math.floor((value/1000) % 3600 % 60);
s = checkTime(s);
return h.toString() + ":" + m.toString() + ":" + s.toString();
}
format: convertValueToDuration
}).on('runnerFinish', function(eventObject, info){
$.get('/painel/cronometro', { tipo: 'cronometro_' + "{{cron.id}}", action: 'stop' } );
$.get('/painel/cronometro', { tipo: 'cronometro_' + "{{cron.id}}", action: 'stop', last_time: $('#cronometro_' + "{{c.id}}").val() } );
playAudioNumVezes(audioAlertFinish, num_vezes_toca_audio);
$('#cronometro_' + "{{cron.id}}" + '_Reset').show();
$('#cronometro_' + "{{cron.id}}").runner('stop');
$('#cronometro_' + "{{cron.id}}" + '_Start').text('Iniciar');
})
});
{% if painel_config.cronometro_ordem and cron.tipo == "Cronômetro da Questão de Ordem" %}
$('#cronometro_' + "{{cron.id}}" + '_Start').click(function() {
// Como o botão de start e stop está sendo reaproveitado (é o mesmo botão, que fica mudando de texto),
// deve-se checar se é um start ou um stop
// Ação de start
if ($('#cronometro_' + "{{cron.id}}" + '_Start').text() == 'Iniciar'){
// Cronômetro da questão de ordem é tratado separadamente
// porque pode parar os demais quando inicia
{% if painel_config.cronometro_ordem and cron.tipo == "Cronômetro da Questão de Ordem" %}
// cronometro da questão de ordem
{% for c in cronometros %}
{% if c.tipo == "Cronômetro da Questão de Ordem" %}
$.get('/painel/cronometro', { tipo: 'cronometro_' + "{{c.id}}", action: 'start' } );
$.get('/painel/cronometro', { tipo: 'cronometro_' + "{{c.id}}", action: 'start', last_time: '0' } );
$('#cronometro_' + "{{c.id}}" + '_Reset').hide();
$('#cronometro_' + "{{c.id}}").runner('start');
$('#cronometro_' + "{{c.id}}" + '_Start').text('Parar');
{% else %}
$.get('/painel/cronometro', { tipo: 'cronometro_' + "{{c.id}}", action: 'stop' } );
$.get('/painel/cronometro', { tipo: 'cronometro_' + "{{c.id}}", action: 'stop', last_time: $('#cronometro_' + "{{c.id}}").val() } );
$('#cronometro_' + "{{c.id}}" + '_Reset').show();
$('#cronometro_' + "{{c.id}}").runner('stop');
$('#cronometro_' + "{{c.id}}" + '_Start').text('Iniciar');
{% endif %}
{% endfor %}
} else{
$.get('/painel/cronometro', { tipo: 'cronometro_' + "{{cron.id}}", action: 'stop' } );
$('#cronometro_' + "{{cron.id}}" + '_Reset').show();
$('#cronometro_' + "{{cron.id}}").runner('stop');
$('#cronometro_' + "{{cron.id}}" + '_Start').text('Iniciar');
}
});
// Demais cronômetros
{% else %}
$('#cronometro_' + "{{cron.id}}" + '_Start').click(function() {
if ($('#cronometro_' + "{{cron.id}}" + '_Start').text() == 'Iniciar'){
$.get('/painel/cronometro', { tipo: 'cronometro_' + "{{cron.id}}", action: 'start' } );
$.get('/painel/cronometro', { tipo: 'cronometro_' + "{{cron.id}}", action: 'start', last_time: '0' } );
$('#cronometro_' + "{{cron.id}}" + '_Reset').hide();
$('#cronometro_' + "{{cron.id}}").runner('start');
$('#cronometro_' + "{{cron.id}}" + '_Start').text('Parar');
{% endif %}
// Ação de stop é igual para todos os cronômetros
} else{
$.get('/painel/cronometro', { tipo: 'cronometro_' + "{{cron.id}}", action: 'stop' } );
$.get('/painel/cronometro', { tipo: 'cronometro_' + "{{cron.id}}", action: 'stop', last_time: $('#cronometro_' + "{{cron.id}}").val() } );
$('#cronometro_' + "{{cron.id}}" + '_Reset').show();
$('#cronometro_' + "{{cron.id}}").runner('stop');
$('#cronometro_' + "{{cron.id}}" + '_Start').text('Iniciar');
}
});
{% endif %}
// Ação de reset é igual para todos os cronômetros
$('#cronometro_' + "{{cron.id}}" + '_Reset').click(function() {
$.get('/painel/cronometro', { tipo: 'cronometro_' + "{{cron.id}}", action: 'reset' } );
$.get('/painel/cronometro', { tipo: 'cronometro_' + "{{cron.id}}", action: 'reset', last_time: '0' } );
// é necessário recriar o cronômetro com o valor da duração original devido a limitações da API
$('#cronometro_' + "{{cron.id}}").runner({
autostart: false,
countdown: true,
startAt: parseInt("{{cron.duracao_cronometro|duration_to_seconds}}") * 1000,
stopAt: 0,
milliseconds: false,
format: convertValueToDuration
}).on('runnerFinish', function(eventObject, info){
$.get('/painel/cronometro', { tipo: 'cronometro_' + "{{cron.id}}", action: 'stop', last_time: $('#cronometro_' + "{{c.id}}").val() } );
playAudioNumVezes(audioAlertFinish, num_vezes_toca_audio);
$('#cronometro_' + "{{cron.id}}" + '_Reset').show();
$('#cronometro_' + "{{cron.id}}").runner('stop');
$('#cronometro_' + "{{cron.id}}").runner('reset');
$('#cronometro_' + "{{cron.id}}" + '_Start').text('Iniciar');
});
});
{% endfor %}
(function poll() {
$.ajax({
data: {pk_sessao: pk_sessao},
type: 'GET',
dataType: 'json',
url: "{% url 'sapl.painel:verifica_painel' %}",
error: function () {
alert("Erro ao verificar o Painel");
},
success: function (data) {
if (data['status']) {
botao_abrir.hide();
botao_fechar.show();
} else {
botao_abrir.show();
botao_fechar.hide();
}
// ajustes do painel para caso haja dois controladores
// obtém todos os ids em uma lista e
// converte os dados do status dos cronômetros de dicionário para uma lista
var ids = [];
var status_cronometros = [];
for (var key in data['cronometros']) {
if (data['cronometros'].hasOwnProperty(key)) {
ids.push(key);
status_cronometros.push(data['cronometros'][key]);
}
}
// converte os dados de dicionário para uma lista
var duracao_cronometros = [];
for (let id of ids) {
duracao_cronometros.push(data['duracao_cronometros'][id]);
}
for(let i=0; i<status_cronometros.length; i++){
if (!cronometros_previous[i]){
cronometros_previous[i] = ''
}
// se houve alteração de status
if (status_cronometros[i] != cronometros_previous[i]) {
if(status_cronometros[i] == 'reset'){
// é necessário recriar o cronômetro com o valor da duração original devido a limitações da API
$('#cronometro_' + ids[i]).runner({
autostart: false,
countdown: true,
startAt: parseInt(duracao_cronometros[i]) * 1000,
stopAt: 0,
milliseconds: false,
format: convertValueToDuration
}).on('runnerFinish', function(eventObject, info){
playAudioNumVezes(audioAlertFinish, num_vezes_toca_audio);
});
}
// ações de start e stop
else if(status_cronometros[i] == 'stop'){
$('#cronometro_' + ids[i] + '_Reset').show();
$('#cronometro_' + ids[i]).runner('stop');
$('#cronometro_' + ids[i] + '_Start').text('Iniciar');
}
else{
$('#cronometro_' + ids[i] + '_Reset').hide();
$('#cronometro_' + ids[i]).runner('start');
$('#cronometro_' + ids[i] + '_Start').text('Parar');
}
cronometros_previous[i] = status_cronometros[i];
}
// Dispara aviso prévio se estiver configurado
//if($('#cronometro_' + ids[i]).runner('info').formattedTime == tempo_disparo_antecedencia
// && "{{ painel_config.disparo_cronometro }}" == "True" ){
// audioAlertFinish.play();
//}
}
},
complete: setTimeout(function() {poll()}, 500),
timeout: 20000 // TODO: decrease
});
})();
});

Loading…
Cancel
Save