274 lines
8.0 KiB
JavaScript
274 lines
8.0 KiB
JavaScript
const { QLog, undefinedOrNull } = require('quanto-commons');
|
|
const player = require('node-wav-player');
|
|
const { Telegraf } = require('telegraf')
|
|
if(process.platform !== 'darwin') var gpio = require('rpi-gpio');
|
|
|
|
const telegramBotToken = '5472909337:AAGH269wNGa9u99ekJqHqXEHfi0knpV7IFU';
|
|
const GlobalLog = QLog.scope('Global');
|
|
const TelLog = QLog.scope('Telegram');
|
|
|
|
GlobalLog.enableLogs(['warn']);
|
|
TelLog.enableLogs(['warn']);
|
|
|
|
GlobalLog.headPadding = 30;
|
|
TelLog.headPadding = 30;
|
|
|
|
const bot = new Telegraf(telegramBotToken);
|
|
|
|
// debounce der buttons in ms
|
|
const bounceMS = 400;
|
|
|
|
// ist an wenn im kind mode (erw LED is aus)
|
|
const childLED = 37;
|
|
// ist and wenn im erwachsenen Mode (kind LED ist aus)
|
|
const erwLED = 38;
|
|
// status : ist an wenn in ruhe position, blinkt, wenn gestartet, ist aus bei fail
|
|
const statusLED = 40;
|
|
// setze auf HIGH wenn fail (klingel relais)
|
|
const failSwitch = 35;
|
|
|
|
// warte auf spielstart wenn auf HIGH
|
|
const startButton = 32;
|
|
// beende spiel wenn auf HIGH
|
|
// const finishButton = 31;
|
|
const finishButton = 22;
|
|
// faile spiel wenn auf HIGH
|
|
// const failButton = 29;
|
|
const failButton = 21;
|
|
// wechsle kind/erwachen Modus + LED
|
|
const toggleButton = 33;
|
|
|
|
// wird je nach modus gesetzt und abgespielt wenn start
|
|
const backgroundGrown = "northsouth.wav";
|
|
const backgroundChild = "northsouth.wav";
|
|
// wird zufallig zu zufaelligen momenten abgespielt (erw mode)
|
|
const erschreckSounds = [ "ziege.wav", "scream.wav", "lough.wav" ];
|
|
// wird nach fester zeit + random oder spaeter nach der besten zeit abgespielt (erw mode)
|
|
const stresserSound = "sharks.wav";
|
|
// wird bei fail abgespielt (zonk)
|
|
const failSound = "zonk.wav";
|
|
// hurra wird bei finish abgespielt
|
|
const finishSound = "kidscheering.wav";
|
|
|
|
const GROWN=1;
|
|
const CHILD=2;
|
|
|
|
const STATES = {
|
|
STARTED: 'started',
|
|
FINISH: 'finshed',
|
|
FAIL: 'failed'
|
|
};
|
|
|
|
const best = {
|
|
grown: 25,
|
|
child: 37
|
|
}
|
|
|
|
const bounce = {};
|
|
|
|
var STATE = STATES.FINISH;
|
|
var MODE = GROWN;
|
|
var GAME = {};
|
|
|
|
var botCTX;
|
|
|
|
async function sendBotMsg(msg) {
|
|
if(botCTX) {
|
|
botCTX.reply(msg);
|
|
}
|
|
}
|
|
async function sendBotImg(img) {
|
|
if(botCTX) {
|
|
botCTX.replyWithPhoto({ source: img});
|
|
}
|
|
}
|
|
|
|
async function setup() {
|
|
GlobalLog.start('Running');
|
|
bot.start((ctx) => {
|
|
let message = `Bot aktiv und beobachtet. Benutze /stats um die Bestenliste zu sehen`;
|
|
ctx.reply(message);
|
|
TelLog.note('Bot started');
|
|
botCTX=ctx;
|
|
});
|
|
bot.command('stats', async (ctx) => {
|
|
await ctx.replyWithPhoto({ source: '/root/hotwire-js/img/bestgrown.png' });
|
|
await ctx.reply(`Bester Erwachsener mit nur ${best.grown} sek`);
|
|
await ctx.replyWithPhoto({ source: '/root/hotwire-js/img/bestchild.png' })
|
|
await ctx.reply(`Bestes Kind mit nur ${best.child} sek`);
|
|
});
|
|
bot.launch();
|
|
|
|
GlobalLog.note('Setting up GPIOs');
|
|
|
|
if(process.platform === 'darwin') {
|
|
setTimeout(start, 500);
|
|
setTimeout(fail, 3500);
|
|
setTimeout(start, 4000);
|
|
setTimeout(finish, 9000);
|
|
setTimeout(fail, 10000);
|
|
setTimeout(finish, 11000);
|
|
return;
|
|
}
|
|
gpio.setup(failSwitch, gpio.OUT, () => {
|
|
gpio.output(failSwitch, false);
|
|
});
|
|
gpio.setup(statusLED, gpio.OUT, () => {
|
|
gpio.output(statusLED, true);
|
|
});
|
|
gpio.setup(childLED, gpio.OUT, () => {
|
|
gpio.output(childLED, true);
|
|
});
|
|
gpio.setup(erwLED, gpio.OUT, () => {
|
|
gpio.output(erwLED, false);
|
|
});
|
|
|
|
gpio.setup(finishButton, gpio.DIR_IN, gpio.EDGE_RISING);
|
|
gpio.setup(toggleButton, gpio.DIR_IN, gpio.EDGE_RISING);
|
|
gpio.setup(startButton, gpio.DIR_IN, gpio.EDGE_RISING);
|
|
gpio.setup(failButton, gpio.DIR_IN, gpio.EDGE_RISING);
|
|
|
|
gpio.on('change', (channel, value) => {
|
|
if(bounce[channel] === true) return;
|
|
GlobalLog.debug('Change',channel, value);
|
|
bounce[channel] = true;
|
|
setTimeout(() => { bounce[channel] = false; GlobalLog.debug('Debounced', channel); }, bounceMS);
|
|
switch(channel) {
|
|
case startButton:
|
|
start(value);
|
|
break;
|
|
case finishButton:
|
|
finish(value);
|
|
break;
|
|
case failButton:
|
|
fail(value);
|
|
break;
|
|
case toggleButton:
|
|
toggle(value);
|
|
break;
|
|
default:
|
|
GlobalLog.warn('Alert! Unknown button change',channel);
|
|
}
|
|
});
|
|
GlobalLog.note('Done. Game logic started. Waiting for input');
|
|
}
|
|
|
|
function gameTimer() {
|
|
GlobalLog.debug('game timer called');
|
|
GAME.events.forEach((el) => {
|
|
if(el.start === GAME.runTime) {
|
|
GlobalLog.debug('Event :', el);
|
|
playSound(el.sound);
|
|
}
|
|
});
|
|
GAME.runTime++;
|
|
}
|
|
|
|
function getRandomInt(max) {
|
|
return Math.floor(Math.random() * max);
|
|
}
|
|
|
|
function start(value) {
|
|
GAME = {
|
|
events: [],
|
|
runTime: 0
|
|
};
|
|
if(STATE === STATES.FINISH || STATE === STATES.FAIL) {
|
|
STATE = STATES.STARTED;
|
|
// set all params for next game, then start counter and event timer
|
|
if(MODE === GROWN) {
|
|
GAME.events.push({ sound: backgroundGrown, start: 0});
|
|
GAME.events.push({ sound: stresserSound, start: 30+getRandomInt(10) });
|
|
GAME.events.push({ sound: erschreckSounds[getRandomInt(2)], start: getRandomInt(10) });
|
|
GAME.events.push({ sound: erschreckSounds[getRandomInt(2)], start: 10+getRandomInt(10) });
|
|
GAME.events.push({ sound: erschreckSounds[getRandomInt(2)], start: 20+getRandomInt(10) });
|
|
GAME.mode = GROWN;
|
|
GAME.fail = failSound;
|
|
GAME.finish = finishSound;
|
|
GAME.runtime = 0;
|
|
}
|
|
if(MODE === CHILD) {
|
|
GAME.events.push({ sound: backgroundChild, start: 0});
|
|
GAME.events.push({ sound: stresserSound, start: 40+getRandomInt(10)});
|
|
GAME.events.push({ sound: erschreckSounds[getRandomInt(2)], start: 10+getRandomInt(10)});
|
|
GAME.mode = CHILD;
|
|
GAME.fail = failSound;
|
|
GAME.finish = finishSound;
|
|
GAME.runtime = 0;
|
|
}
|
|
GlobalLog.note('Starting new game', JSON.stringify(GAME, 2, null));
|
|
GAME.countTimer = setInterval(gameTimer, 1000);
|
|
sendBotMsg('new game startet');
|
|
} else {
|
|
GlobalLog.warn('START not in state', STATE);
|
|
}
|
|
}
|
|
|
|
async function finish(value) {
|
|
GlobalLog.debug('Finish');
|
|
if(STATE === STATES.STARTED) {
|
|
GlobalLog.note('You made it in', GAME.runTime, 'seconds');
|
|
playSound(GAME.finish);
|
|
STATE = STATES.FINISH;
|
|
clearInterval(GAME.countTimer)
|
|
GAME.events = [];
|
|
await sendBotImg('/tmp/img.png');
|
|
await sendBotMsg(`Gewinner in nur ${GAME.runTime} Sekunden!`);
|
|
if(MODE === GROWN) {
|
|
if(GAME.runTime < best.grown) {
|
|
await sendBotMsg(`Neue Bestzeit bei den Erwachsenen!`);
|
|
best.grown = GAME.runTime
|
|
// todo copy best img
|
|
}
|
|
} else {
|
|
if(GAME.runTime < best.child) {
|
|
await sendBotMsg(`Neue Bestzeit bei den Kindern!`);
|
|
best.child = GAME.runTime
|
|
// todo copy best img
|
|
}
|
|
}
|
|
} else {
|
|
GlobalLog.warn('FINISH not in state', STATE);
|
|
}
|
|
}
|
|
|
|
function fail(value) {
|
|
if(STATE === STATES.STARTED) {
|
|
GlobalLog.note('You failed after', GAME.runTime, 'seconds');
|
|
playSound(GAME.fail);
|
|
STATE = STATES.FAIL;
|
|
clearInterval(GAME.countTimer)
|
|
GAME.events = [];
|
|
} else {
|
|
GlobalLog.debug('FAIL not in state', STATE);
|
|
}
|
|
}
|
|
|
|
function toggle(value) {
|
|
if(STATE === STATES.FINISH || STATE === STATES.FAIL) {
|
|
GlobalLog.info('Toggle');
|
|
if(MODE === CHILD) {
|
|
MODE = GROWN;
|
|
// set all values for GROWN mode
|
|
}
|
|
if(MODE === GROWN) {
|
|
MODE = CHILD;
|
|
// set all values for CHILD mode
|
|
}
|
|
} else {
|
|
GlobalLog.debug('Toggle not in state', STATE);
|
|
}
|
|
}
|
|
|
|
function playSound(file){
|
|
GlobalLog.info("Play sound : ", file);
|
|
if(GAME.playing) {
|
|
GlobalLog.debug("Stopping old soundfile", GAME.playing);
|
|
player.stop();
|
|
}
|
|
GAME.playing = file;
|
|
player.play({ path: `wav/${file}`, sync: true }).then(() => { delete GAME.playing })
|
|
}
|
|
|
|
setup();
|