parent
af814363d7
commit
e178414c66
@ -1,257 +0,0 @@ |
||||
import { program } from 'commander' |
||||
import { |
||||
Feedback, |
||||
INSTRUCTIONS, |
||||
CARD_PRESENT, |
||||
GUEST_PLAYING, |
||||
REGISTR_ABORT, |
||||
RENEW_CARD, |
||||
THANKS_FOR_PLAYING, |
||||
} from './statuslogger2.js' |
||||
import { YACE } from './yacardemu.js' |
||||
import { wmmt3 } from './statemachine.js' |
||||
import { ICCard } from './nfc.js' |
||||
|
||||
program |
||||
.option('-i, --i2c-bus <num>', 'i2c bus to use for LCD display') |
||||
.option('--noLCD', 'don\'t use LCD display, not recommended for production use') |
||||
.option('-e, --emulator-port <num>', 'port of the card emulator') |
||||
.option('-c, --cards-location <path>', 'path where card data will be saved') |
||||
|
||||
program.parse(); |
||||
|
||||
const opts = program.opts() |
||||
|
||||
const saves = opts.cardsLocation || '/home/dietpi/YACards/' |
||||
const logger = Feedback({ bus: opts.i2cBus, noLCD: opts.noLCD }) |
||||
|
||||
if (opts.noLCD) { |
||||
logger.info('[EABU]: Skipping LCD initialization on user\'s request') |
||||
} else if (opts.i2cBus) { |
||||
logger.initLCD() |
||||
} else { |
||||
logger.error('[EABU]: No i2c bus is specified, nor --noLCD is used, aborting') |
||||
process.exit(1) |
||||
} |
||||
|
||||
if (!(opts.emulatorPort > 0)) { |
||||
logger.error('[EABU]: Emulator port not configured!') |
||||
process.exit(1) |
||||
} |
||||
|
||||
const port = opts.emulatorPort |
||||
|
||||
logger.info('[EABU]: Starting up') |
||||
|
||||
const sm = wmmt3() |
||||
const status = { |
||||
card: null, |
||||
countdown: 0, |
||||
countdownCallback: null |
||||
} |
||||
|
||||
const countDown = (secs, callback) => { |
||||
status.countdown = secs |
||||
status.countdownCallback = callback |
||||
const func = () => { |
||||
console.debug(status.countdown) |
||||
logger.setCountdown(status.countdown) |
||||
if (status.countdown > 0) { |
||||
status.countdown-- |
||||
status.countdownTimeoutId = setTimeout(func, 1000) |
||||
} else { |
||||
if (status.countdownCallback) { |
||||
status.countdownCallback() |
||||
status.countdownCallback = null |
||||
} |
||||
} |
||||
} |
||||
func() |
||||
} |
||||
|
||||
logger.info('[EABU]: Starting YACE api') |
||||
const yace = YACE(port) |
||||
yace.once('error', () => setTimeout(() => { |
||||
yace.startPolling() |
||||
logger.error('[YACE]: Emulator unavailable, retrying in 1 sec') |
||||
}, 1000)) |
||||
yace.startPolling() |
||||
|
||||
const onInit = () => { |
||||
logger.info('[YACE]: Ready for new play.') |
||||
|
||||
yace.once('shutter-opened', () => { |
||||
sm.shutterOpen() |
||||
}) |
||||
} |
||||
|
||||
logger.info('[EABU]: Initializing NFC (if this hangs check if pcscd is running)') |
||||
const nfc = ICCard(logger, saves) |
||||
|
||||
nfc.alt.on('new-card', () => { |
||||
if (sm.cannot('new-card')) { |
||||
logger.warn('[YACE]: Cannot prepare new card when game\'s not ready for it!') |
||||
switch (sm.state) { |
||||
case 'playing': |
||||
logger.setMessage(CARD_PRESENT) |
||||
break |
||||
case 'guest-playing': |
||||
logger.setMessage(GUEST_PLAYING) |
||||
break |
||||
default: |
||||
logger.setMessage(INSTRUCTIONS) |
||||
} |
||||
} |
||||
}) |
||||
|
||||
nfc.alt.on('old-card', () => { |
||||
if (sm.cannot('old-card')) { |
||||
logger.warn('[YACE]: Cannot insert card when game\'s not ready for it!') |
||||
switch (sm.state) { |
||||
case 'playing': |
||||
logger.setMessage(CARD_PRESENT) |
||||
break |
||||
case 'guest-playing': |
||||
logger.setMessage(GUEST_PLAYING) |
||||
break |
||||
default: |
||||
logger.setMessage(INSTRUCTIONS) |
||||
} |
||||
} |
||||
}) |
||||
|
||||
onInit() |
||||
sm.observe('onIdle', onInit) |
||||
|
||||
sm.observe('onLeaveState', () => { |
||||
status.countdown = 0 |
||||
status.countdownCallback = null |
||||
clearTimeout(status.countdownTimeoutId) |
||||
yace.removeAllListeners() |
||||
nfc.removeAllListeners() |
||||
}) |
||||
|
||||
sm.observe('onEnterState', (state) => { |
||||
console.log(state.to) |
||||
logger.setState(state.to) |
||||
}) |
||||
|
||||
sm.observe('onReadyCard', () => { |
||||
logger.info('[YACE]: Shutter opened!') |
||||
|
||||
// countDown(10, () => {
|
||||
yace.once('shutter-closed', () => { |
||||
logger.info('[YACE]: No card scanned!') |
||||
sm.timeout() |
||||
}) |
||||
|
||||
nfc.once('new-card', (id) => { |
||||
yace.prepareCard(id) |
||||
logger.setCardId(id) |
||||
sm.newCard() |
||||
}) |
||||
|
||||
nfc.once('old-card', (id) => { |
||||
yace.insertCard(id) |
||||
logger.setCardId(id) |
||||
sm.oldCard() |
||||
}) |
||||
|
||||
}) |
||||
|
||||
// GUEST PLAY
|
||||
sm.observe('onGpWaitDispense', () => { |
||||
countDown(30, () => { |
||||
logger.info('[YACE]: No card dispensed, assuming real guest play.') |
||||
sm.timeout() |
||||
}) |
||||
|
||||
yace.once('card-dispensed', () => { |
||||
logger.info('[YACE]: Guest card dispensed') |
||||
sm.dispense() |
||||
}) |
||||
}) |
||||
|
||||
sm.observe('onGuestPlaying', () => { |
||||
logger.warn('[YACE]: Guest game in progress') |
||||
|
||||
yace.once('card-ejected', () => { |
||||
logger.info('[YACE]: Card ejected, guest game over.') |
||||
logger.setMessage(THANKS_FOR_PLAYING) |
||||
sm.eject() |
||||
yace.prepareCard('dummy') |
||||
logger.setCardId('') |
||||
}) |
||||
}) |
||||
|
||||
// REGISTERED CARD
|
||||
sm.observe('onOcCheckRenewal', () => { |
||||
logger.info('[YACE]: Card inserted, waiting in case renewal is needed') |
||||
|
||||
yace.once('card-ejected', () => { |
||||
logger.info('[YACE]: Card ejected, renewal detected.') |
||||
logger.setMessage(RENEW_CARD) |
||||
sm.eject() |
||||
}) |
||||
|
||||
countDown(20, () => { |
||||
logger.info('[YACE]: No card ejected, no renewal detected.') |
||||
sm.timeout() |
||||
}) |
||||
}) |
||||
|
||||
sm.observe('onOcWaitDispense', () => { |
||||
logger.info('[YACE]: Present card ejected (and discarded ;_;), waiting for new card to be dispensed') |
||||
|
||||
yace.once('card-dispensed', () => { |
||||
logger.info('[YACE]: Renewed card dispensed') |
||||
sm.dispense() |
||||
}) |
||||
|
||||
yace.once('card-ejected', () => { |
||||
logger.warn('[YACE]: Card ejected again, not sure what\'s going on, resetting...') |
||||
sm.goto('idle') |
||||
}) |
||||
|
||||
countDown(30, () => { |
||||
logger.warn('[YACE]: Error: not dispensed within 30 seconds, assuming illegal state and resetting') |
||||
sm.goto('idle') |
||||
}) |
||||
}) |
||||
|
||||
// UNREGISTERED CARD
|
||||
sm.observe('onNcWaitShutter', () => { |
||||
logger.info('[YACE]: Waiting for shutter to close') |
||||
|
||||
yace.once('shutter-closed', () => { |
||||
sm.shutterClose() |
||||
}) |
||||
}) |
||||
|
||||
sm.observe('onNcWaitDispense', () => { |
||||
logger.info('[YACE]: "No card" is selected, waiting for card purchase') |
||||
|
||||
yace.once('card-dispensed', () => { |
||||
logger.info('[YACE]: New card dispensed!') |
||||
sm.dispense() |
||||
}) |
||||
|
||||
countDown(30, () => { |
||||
logger.warn('[YACE]: Error: not dispensed within 30 seconds, assuming guest play') |
||||
logger.setMessage(REGISTR_ABORT) |
||||
sm.timeout() |
||||
}) |
||||
}) |
||||
|
||||
// CARD GAME OVER
|
||||
sm.observe('onPlaying', () => { |
||||
logger.info('[YACE]: Game in progress') |
||||
|
||||
yace.once('card-ejected', () => { |
||||
logger.warn('[YACE]: Card ejected, game over.') |
||||
yace.prepareCard('dummy') |
||||
logger.setCardId('') |
||||
logger.setMessage(THANKS_FOR_PLAYING) |
||||
sm.eject() |
||||
}) |
||||
}) |
@ -1,235 +0,0 @@ |
||||
import chalk from 'chalk' |
||||
import LCD from 'raspberrypi-liquid-crystal' |
||||
let lcd |
||||
|
||||
const err = chalk.bold.redBright |
||||
const inf = chalk.blueBright |
||||
const wrn = chalk.yellowBright |
||||
|
||||
const SPIN_CHARS = [ |
||||
[0x14,0x1c,0x15,0x1,0x6,0x0,0x00,0x0], |
||||
[0x14,0x1c,0x15,0x1,0x6,0x0,0x10,0x0], |
||||
[0x14,0x1c,0x15,0x1,0x6,0x0,0x14,0x0], |
||||
[0x14,0x1c,0x15,0x1,0x6,0x0,0x15,0x0] |
||||
] |
||||
const SPINNER = SPIN_CHARS.map((c, i) => i) |
||||
|
||||
const STATUS_LINE = [ |
||||
'Status: ______' |
||||
] |
||||
|
||||
const states = { |
||||
idle: [ |
||||
'Waiting for new game', |
||||
'Scan the card for ', |
||||
'instructions. ', |
||||
], |
||||
'ready-card': [ |
||||
' ', |
||||
'Scan your card now!!', |
||||
' ', |
||||
], |
||||
'nc-wait-shutter': [ |
||||
'New card detected! ', |
||||
'Please select the ' , |
||||
'"No card" option ', |
||||
], |
||||
'nc-wait-dispense': [ |
||||
'New card detected! ', |
||||
'Waiting for game to ' , |
||||
'dispense a new card ', |
||||
], |
||||
'playing': [ |
||||
'Game in progress, ', |
||||
'GLHF! Card id: ', |
||||
'____________________', |
||||
], |
||||
'gp-wait-dispense': [ |
||||
'No card? Ask owner ', |
||||
'for one! Game data ', |
||||
'will be lost! ' |
||||
], |
||||
'guest-playing': [ |
||||
'No card? Ask owner ', |
||||
'for one! Game data ', |
||||
'will be lost! ' |
||||
], |
||||
'oc-check-renewal': [ |
||||
'Welcome back! ', |
||||
'Checking if card ' , |
||||
'needs renewal... ', |
||||
], |
||||
'oc-wait-dispense': [ |
||||
'Card renewed! ', |
||||
'Waiting for game to ' , |
||||
'dispense a new card ', |
||||
] |
||||
} |
||||
|
||||
export const INSTRUCTIONS = [ |
||||
'1) Step on the gas ', |
||||
'2) Scan your card ', |
||||
'3) (Register card) ', |
||||
] |
||||
|
||||
export const CARD_PRESENT = [ |
||||
'Already scanned a ', |
||||
'card, please wait ', |
||||
'until new game. ', |
||||
] |
||||
|
||||
export const GUEST_PLAYING = [ |
||||
'Guest is currently ', |
||||
'playing, please wait', |
||||
'until new game. ', |
||||
] |
||||
|
||||
export const REGISTR_ABORT = [ |
||||
'Card registration is', |
||||
'aborted - time run ', |
||||
'out. Try again later', |
||||
] |
||||
|
||||
export const RENEW_CARD = [ |
||||
'Card renewed! ', |
||||
'Present or discarded', |
||||
'card data is lost :(', |
||||
] |
||||
|
||||
export const THANKS_FOR_PLAYING = [ |
||||
'Thanks for playing! ', |
||||
'Hope you had a good ', |
||||
'time. t. hj', |
||||
] |
||||
|
||||
export const Feedback = ({ bus, noLCD }) => { |
||||
const status = { |
||||
hasLCD: false, |
||||
spinnerIndex: 0, |
||||
spinner: SPINNER[0], |
||||
|
||||
state: 'idle', |
||||
|
||||
emuCard: false, |
||||
emuOpen: false, |
||||
|
||||
cardId: '', |
||||
|
||||
messageCountdown: 0, |
||||
message: null, |
||||
|
||||
countdown: 0, |
||||
} |
||||
|
||||
const quipSelect = () => { |
||||
if (status.messageCountdown > 0) { |
||||
return status.message || [ |
||||
'All your base ', |
||||
' Are belong to us!!', |
||||
' (invalid message) ', |
||||
] |
||||
} else { |
||||
const statusQuip = states[status.state] |
||||
if (status.state === 'playing') { |
||||
statusQuip[2] = status.cardId.padEnd(20, ' ') |
||||
} |
||||
return statusQuip |
||||
} |
||||
} |
||||
|
||||
const statusLineGenerator = () => { |
||||
console.log(status.countdown) |
||||
let countdownI = status.countdown > 0 |
||||
? `${status.countdown}`.padStart(3, ' ') |
||||
: ' ' |
||||
let emuCardI = status.emuCard ? 'C' : '_' |
||||
let emuOpenI = status.emuOpen ? 'O' : '#' |
||||
if (status.emuCard === 'error') { |
||||
emuCardI = 'E' |
||||
emuOpenI = 'E' |
||||
} |
||||
return countdownI + emuCardI + emuOpenI + status.spinner |
||||
} |
||||
|
||||
const lineGen = () => { |
||||
return STATUS_LINE[0].replace('______', statusLineGenerator()) |
||||
} |
||||
|
||||
const bufferGen = () => [ lineGen(), ...quipSelect() ] |
||||
|
||||
return { |
||||
setState(state) { |
||||
status.state = state |
||||
}, |
||||
setEmuCard(emuCard) { |
||||
status.emuCard = emuCard |
||||
}, |
||||
setEmuOpen(emuOpen) { |
||||
status.emuOpen = emuOpen |
||||
}, |
||||
setMessage(message) { |
||||
status.message = message |
||||
status.messageCountdown = 10 * 2 |
||||
}, |
||||
setCountdown(countdown) { |
||||
status.countdown = countdown |
||||
}, |
||||
setCardId(cardId) { |
||||
status.cardId = cardId |
||||
}, |
||||
warn(...string) { |
||||
console.warn(wrn('[!]') + ' ' + string.join(' ')) |
||||
}, |
||||
info(...string) { |
||||
console.info(inf('[ ]') + ' ' + string.join(' ')) |
||||
}, |
||||
error(...string) { |
||||
console.error(err('[!]') + ' ' + string.join(' ')) |
||||
}, |
||||
initLCD() { |
||||
this.info('Initializing LCD...') |
||||
|
||||
const loop = () => { |
||||
const buffer = bufferGen() |
||||
|
||||
this.advanceSpinner() |
||||
//lcd.clearSync()
|
||||
buffer.forEach((line, i) => lcd.printLineSync(i, line.substring(0, 20))) |
||||
if (status.messageCountdown > 0) { |
||||
status.messageCountdown-- |
||||
if (status.messageCountdown === 0) { |
||||
status.messageType = null |
||||
} |
||||
} |
||||
|
||||
setTimeout(loop, 100) |
||||
} |
||||
|
||||
lcd = new LCD(Number(bus), 0x27, 20, 4); |
||||
lcd |
||||
.begin() |
||||
.then(() => { |
||||
status.hasLCD = true |
||||
this.info('LCD Initialized!') |
||||
lcd.clearSync() |
||||
lcd.printLineSync(0, 'Wangan Midnight ') |
||||
lcd.printLineSync(1, ' Maximum Tune 3DX+') |
||||
lcd.printLineSync(2, ' IC card support ') |
||||
lcd.printLineSync(3, ' by HJ (and GXTX)') |
||||
SPIN_CHARS.forEach((c, i) => { |
||||
lcd.createCharSync(i, c) |
||||
}) |
||||
|
||||
setTimeout(loop, 3000) |
||||
}) |
||||
.catch((e) => { |
||||
status.hasLCD = false |
||||
this.warn('Error initializing LCD: ', e) |
||||
}) |
||||
}, |
||||
advanceSpinner() { |
||||
if (status.spinnerIndex >= SPINNER.length) status.spinnerIndex = 0 |
||||
status.spinner = LCD.getChar(status.spinnerIndex++) |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue