En su día creamos 3 scripts para incrementar, decrementar y poner a cero el contador de turnos. Hoy empezamos añadiendo un script nuevo que nos permitirá dar un valor concreto, que entrará como parámetro, al marcador:
# cat pon_turno.sh
#!/bin/bash
num=$1
test "$num" == "" && num="00"
servidor="172.X.Y.Z"
topic="numero"
if num=$(printf "%02d" $num 2> /dev/null)
then #Si $num no es un numero, falla el comando anterior
mosquitto_pub -t $topic -h $servidor -m "A-$num" -r
fi
Esto será útil si hay cualquier fallo que descontrole el marcador y necesitemos ir hasta un número directamente.Para continuar, se me planteó la siguiente necesidad: controlar los turnos desde una aplicación normal y corriente como alternativa al método de la entrada anterior del blog (la cual usaba un ratón y triggerhappy como sistema para incrementar/decrementar el control del marcador de turnos). Podría escribir una aplicación de escritorio en Python y GTK, ya que con bash sería mas complicado, pero al final me he ido a lo mas sencillo y he optado por usar una aplicación web con Javascript+Mosquitto, usando la misma tecnología con que hice la pantalla que muestra el sistema de turnos del post anterior.
El aspecto de la aplicación será:
Al pulsar "Definir Turno" nos permitirán introducir un turno nuevo directamente:
Todo esto se implementa con una página HTML con mucho CSS y Javascript, ahí va:
# cat /var/www/turnos/GestionTurnos.html
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html><head>
<meta content="text/html; charset=ISO-8859-1" http-equiv="content-type">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sistema de Gestión de Turnos</title>
<style type="text/css">
html, body {
margin: 0px;
height: 100%;
background-color: #000099;
}
@font-face{
font-family: 'Antonio';
src: url('Antonio-Regular.ttf') format('truetype');
}
#Pagina {
font-family: 'Antonio', Arial, sans-serif;
margin: 0px;
height: 100%;
}
#cabecera {
font-style: normal;
font-weight: bold;
background-color: #000099;
font-size:7vw;
width: 100%;
color: white;
text-align: center;
background-image: url(img/logo_guadalupe.png);
background-position: left center;
background-repeat: no-repeat;
}
#cuerpo {
color: yellow;
text-align: center;
background-color: black;
font-size:22vw;
font-weight: bold;
width: 100%;
height: 60%;
}
#pie {
font-size:7vw;
background-color: #000099;
font-weight: bold;
width: 100%;
height: 15%;
color: white;
font-style: normal;
text-align: center;
}
.botonAvanza {
-moz-box-shadow:inset 0px -3px 7px 0px #29bbff;
-webkit-box-shadow:inset 0px -3px 7px 0px #29bbff;
box-shadow:inset 0px -3px 7px 0px #29bbff;
background:-webkit-gradient(linear, left top, left bottom, color-stop(0.05, #2dabf9), color-stop(1, #0688fa));
background:-moz-linear-gradient(top, #2dabf9 5%, #0688fa 100%);
background:-webkit-linear-gradient(top, #2dabf9 5%, #0688fa 100%);
background:-o-linear-gradient(top, #2dabf9 5%, #0688fa 100%);
background:-ms-linear-gradient(top, #2dabf9 5%, #0688fa 100%);
background:linear-gradient(to bottom, #2dabf9 5%, #0688fa 100%);
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#2dabf9', endColorstr='#0688fa',GradientType=0);
background-color:#2dabf9;
-moz-border-radius:12px;
-webkit-border-radius:12px;
border-radius:12px;
border:1px solid #0b0e07;
display:inline-block;
cursor:pointer;
color:#ffffff;
font-family:Arial;
font-size:28px;
padding:22px 48px;
text-decoration:none;
text-shadow:0px 1px 0px #263666;
vertical-align:middle;
}
.botonAvanza:hover {
background:-webkit-gradient(linear, left top, left bottom, color-stop(0.05, #0688fa), color-stop(1, #2dabf9));
background:-moz-linear-gradient(top, #0688fa 5%, #2dabf9 100%);
background:-webkit-linear-gradient(top, #0688fa 5%, #2dabf9 100%);
background:-o-linear-gradient(top, #0688fa 5%, #2dabf9 100%);
background:-ms-linear-gradient(top, #0688fa 5%, #2dabf9 100%);
background:linear-gradient(to bottom, #0688fa 5%, #2dabf9 100%);
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#0688fa', endColorstr='#2dabf9',GradientType=0);
background-color:#0688fa;
}
.botonAvanza:active {
position:relative;
top:1px;
}
.botonRetrocede {
-moz-box-shadow:inset 0px -3px 7px 0px #29bbff;
-webkit-box-shadow:inset 0px -3px 7px 0px #29bbff;
box-shadow:inset 0px -3px 7px 0px #29bbff;
background:-webkit-gradient(linear, left top, left bottom, color-stop(0.05, #2dabf9), color-stop(1, #0688fa));
background:-moz-linear-gradient(top, #2dabf9 5%, #0688fa 100%);
background:-webkit-linear-gradient(top, #2dabf9 5%, #0688fa 100%);
background:-o-linear-gradient(top, #2dabf9 5%, #0688fa 100%);
background:-ms-linear-gradient(top, #2dabf9 5%, #0688fa 100%);
background:linear-gradient(to bottom, #2dabf9 5%, #0688fa 100%);
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#2dabf9', endColorstr='#0688fa',GradientType=0);
background-color:#2dabf9;
-moz-border-radius:12px;
-webkit-border-radius:12px;
border-radius:12px;
border:1px solid #0b0e07;
display:inline-block;
cursor:pointer;
color:#ffffff;
font-family:Arial;
font-size:28px;
padding:22px 48px;
text-decoration:none;
text-shadow:0px 1px 0px #263666;
vertical-align:middle;
}
.botonRetrocede:hover {
background:-webkit-gradient(linear, left top, left bottom, color-stop(0.05, #0688fa), color-stop(1, #2dabf9));
background:-moz-linear-gradient(top, #0688fa 5%, #2dabf9 100%);
background:-webkit-linear-gradient(top, #0688fa 5%, #2dabf9 100%);
background:-o-linear-gradient(top, #0688fa 5%, #2dabf9 100%);
background:-ms-linear-gradient(top, #0688fa 5%, #2dabf9 100%);
background:linear-gradient(to bottom, #0688fa 5%, #2dabf9 100%);
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#0688fa', endColorstr='#2dabf9',GradientType=0);
background-color:#0688fa;
}
.botonRetrocede:active {
position:relative;
top:1px;
}
.botonSet {
-moz-box-shadow:inset 0px -3px 7px 0px #29bbff;
-webkit-box-shadow:inset 0px -3px 7px 0px #29bbff;
box-shadow:inset 0px -3px 7px 0px #29bbff;
background:-webkit-gradient(linear, left top, left bottom, color-stop(0.05, #2dabf9), color-stop(1, #0688fa));
background:-moz-linear-gradient(top, #2dabf9 5%, #0688fa 100%);
background:-webkit-linear-gradient(top, #2dabf9 5%, #0688fa 100%);
background:-o-linear-gradient(top, #2dabf9 5%, #0688fa 100%);
background:-ms-linear-gradient(top, #2dabf9 5%, #0688fa 100%);
background:linear-gradient(to bottom, #2dabf9 5%, #0688fa 100%);
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#2dabf9', endColorstr='#0688fa',GradientType=0);
background-color:#2dabf9;
-moz-border-radius:12px;
-webkit-border-radius:12px;
border-radius:12px;
border:1px solid #0b0e07;
display:inline-block;
cursor:pointer;
color:#ffffff;
font-family:Arial;
font-size:28px;
padding:22px 48px;
text-decoration:none;
text-shadow:0px 1px 0px #263666;
vertical-align:middle;
}
.botonSet:hover {
background:-webkit-gradient(linear, left top, left bottom, color-stop(0.05, #0688fa), color-stop(1, #2dabf9));
background:-moz-linear-gradient(top, #0688fa 5%, #2dabf9 100%);
background:-webkit-linear-gradient(top, #0688fa 5%, #2dabf9 100%);
background:-o-linear-gradient(top, #0688fa 5%, #2dabf9 100%);
background:-ms-linear-gradient(top, #0688fa 5%, #2dabf9 100%);
background:linear-gradient(to bottom, #0688fa 5%, #2dabf9 100%);
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#0688fa', endColorstr='#2dabf9',GradientType=0);
background-color:#0688fa;
}
.botonSet:active {
position:relative;
top:1px;
}
.inputturno {
-moz-box-shadow:inset 0px -3px 7px 0px #29bbff;
-webkit-box-shadow:inset 0px -3px 7px 0px #29bbff;
box-shadow:inset 0px -3px 7px 0px #29bbff;
background:-webkit-gradient(linear, left top, left bottom, color-stop(0.05, #2dabf9), color-stop(1, #0688fa));
background:-moz-linear-gradient(top, #2dabf9 5%, #0688fa 100%);
background:-webkit-linear-gradient(top, #2dabf9 5%, #0688fa 100%);
background:-o-linear-gradient(top, #2dabf9 5%, #0688fa 100%);
background:-ms-linear-gradient(top, #2dabf9 5%, #0688fa 100%);
background:linear-gradient(to bottom, #2dabf9 5%, #0688fa 100%);
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#2dabf9', endColorstr='#0688fa',GradientType=0);
background-color:#2dabf9;
-moz-border-radius:12px;
-webkit-border-radius:12px;
border-radius:12px;
border:1px solid #0b0e07;
display:inline-block;
cursor:pointer;
color:#ffffff;
font-family:Arial;
font-size:28px;
padding:22px 48px;
text-decoration:none;
text-shadow:0px 1px 0px #263666;
vertical-align:middle;
width: 30px;
}
.labelturno {
display:inline-block;
cursor:pointer;
color:#ffffff;
font-family:Arial;
font-size:28px;
padding:22px 2px;
text-decoration:none;
text-shadow:0px 1px 0px #263666;
vertical-align:middle;
}
</style>
<script src="http://tercero/turnos/mqttws31.js" type="text/javascript"></script>
<script src="http://tercero/turnos/jquery.min.js" type="text/javascript"></script>
<script src="http://tercero/turnos/config.js" type="text/javascript"></script>
<script type="text/javascript">
var valor;
var mqtt;
var reconnectTimeout = 2000;
function MQTTconnect() {
if (typeof path == "undefined") {
path = '/mqtt';
}
mqtt = new Paho.MQTT.Client(
host,
port,
path,
"web_" + parseInt(Math.random() * 100, 10)
);
var options = {
timeout: 3,
useSSL: useTLS,
cleanSession: cleansession,
onSuccess: onConnect,
// mqttVersion: 3,
onFailure: function (message) {
$('#status').val("Connection failed: " + message.errorMessage + "Retrying");
setTimeout(MQTTconnect, reconnectTimeout);
}
};
mqtt.onConnectionLost = onConnectionLost;
mqtt.onMessageArrived = onMessageArrived;
if (username != null) {
options.userName = username;
options.password = password;
}
console.log("Host="+ host + ", port=" + port + ", path=" + path + " TLS = " + useTLS + " username=" + username + " password=" + password);
mqtt.connect(options);
}
function onConnect() {
$('#status').val('Connected to ' + host + ':' + port + path);
// Connection succeeded; subscribe to our topic
mqtt.subscribe(topic, {qos: 0});
$('#topic').val(topic);
}
function onConnectionLost(response) {
setTimeout(MQTTconnect, reconnectTimeout);
$('#status').val("connection lost: " + responseObject.errorMessage + ". Reconnecting");
};
function onMessageArrived(message) {
var topic = message.destinationName;
var payload = message.payloadString;
valor=payload;
$('#cuerpo').html(payload);
};
$(document).ready(function() {
MQTTconnect();
var caja=$('#id_turno');
caja.hide();
var label=$('#id_label');
label.hide();
});
function subirTurno () {
var numero=valor.substring(2);
if (numero==99) numero=0;
else numero++;
topicMessage = new Paho.MQTT.Message("A-"+("00" + numero).slice (-2)); //Para añadir padZero a la izquierda del numero
topicMessage.destinationName = "numero";
topicMessage.retained = true;
mqtt.send(topicMessage);
}
function bajarTurno () {
var numero=valor.substring(2);
if (numero==0) numero=0;
else numero--;
topicMessage = new Paho.MQTT.Message("A-"+("00" + numero).slice (-2)); //Para añadir padZero a la izquierda del numero
topicMessage.destinationName = "numero";
topicMessage.retained = true;
mqtt.send(topicMessage);
}
function verCaja () {
var caja=$('#id_turno');
caja.show();
var numero=valor.substring(2);
caja.val(numero);
var label=$('#id_label');
label.show();
var boton=$('#id_boton');
boton.hide();
}
function ponerTurno () {
var caja=$('#id_turno');
var numero=caja.val();
if ( !! numero && ! isNaN(numero) ) { // !!numero-> string no vacio.
caja.hide();
var label=$('#id_label');
label.hide();
var boton=$('#id_boton');
boton.show();muchomucho
if ( numero<0 || numero > 99) numero=valor.substring(2);
topicMessage = new Paho.MQTT.Message("A-"+("00" + numero).slice (-2)); //Para añadir padZero a la izquierda del numero
topicMessage.destinationName = "numero";
topicMessage.retained = true;
mqtt.send(topicMessage);
}
}
</script>
</head>
<body>
<div id="Pagina">
<input type='text' id='topic' disabled hidden/>
<input type='text' id='status' size="80" disabled hidden/>
<div id="cabecera">IES VIRGEN DE GUADALUPE</div>
<div id="cuerpo">TURNO</div>
<div id="pie">
<a href="#" onclick="subirTurno();" class="botonAvanza">Avanza Turno</a>
<a href="#" onclick="bajarTurno();" class="botonRetrocede">Retrocede Turno</a>
<a href="#" onclick="verCaja();" class="botonSet" id="id_boton">Definir Turno</a>
<label class="labelturno" id="id_label">Introduzca nuevo turno ==></label>
<input id="id_turno" class="inputturno" onclick="this.select()" onKeyDown="if (event.keyCode==13) ponerTurno();" value="" />
</div>
</div>
</body>
</html>
Todos los ficheros necesarios ya los enlacé en entrada anterior del blog. Simplemente habría que añadir GestionTurnos.html en el mismo sitio web donde pusimos SuTurno.htmlAl estar basada en Mosquitto la página web GestionTurnos.html se actualizará en tiempo real y si está abierta en varias ubicaciones (por ejemplo todos los PC de administración) cualquier cambio se propagará a todas los demás de forma inmediata.
Una vez la tenemos funcionando se me ocurre dar una vuelta de tuerca más y convertir la aplicación web en una aplicación independiente de escritorio. De esta manera correrá en una ventana autónoma del navegador que podrá minimizarse y maximizarse a voluntad y será mas sencilla de usar. Hay muchas herramientas para realizar esta conversión web->escritorio, pero me ha gustado por su sencillez Nativifier. Lo que haremos será:
# cat /etc/apt/sources.list.d/nodesource.list
deb https://deb.nodesource.com/node_0.10 trusty main
deb-src https://deb.nodesource.com/node_0.10 trusty main
# apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 1655A0AB68576280
# apt-get update
# apt-get install nodejs
# npm install nativefier -g
# nativefier -n "Sistema de Gestión de Turnos" http://172.X.Y.Z/turnos/GestionTurnos.html
Esto crea un directorio con un fichero ejecutable estándar de Linux que al lanzarlo abre la aplicación Web de Gestión de Turnos como si fuera una aplicación de escritorio. Bastará con poner un acceso directo en el escritorio del usuario y quedará todo simple y accesible.Si queremos que la aplicación sea usada por todos los usuarios de la máquina lo mas sencillo será llevarla a /opt y dar permisos 777 a todo el directorio donde está.
Bueno, pues ya solo nos falta hacer algún día una aplicación para el móvil...