You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
271 lines
6.2 KiB
271 lines
6.2 KiB
import { program } from 'commander'
|
|
import chalk from 'chalk'
|
|
import {
|
|
DisplayController,
|
|
Logger,
|
|
INSTRUCTIONS,
|
|
CARD_PRESENT,
|
|
GUEST_PLAYING,
|
|
REGISTR_ABORT,
|
|
RENEW_CARD,
|
|
THANKS_FOR_PLAYING,
|
|
} from './statuslogger.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
|
|
const logger = Logger(chalk.green('[EABU]'))
|
|
const lcd = DisplayController({ bus: opts.i2cBus, noLCD: opts.noLCD })
|
|
|
|
if (opts.noLCD) {
|
|
logger.info('Skipping LCD initialization on user\'s request')
|
|
} else if (opts.i2cBus) {
|
|
lcd.initLCD()
|
|
} else {
|
|
logger.error('No i2c bus is specified, nor --noLCD is used, aborting')
|
|
process.exit(1)
|
|
}
|
|
|
|
if (!(opts.emulatorPort > 0)) {
|
|
logger.error('Emulator port not configured!')
|
|
process.exit(1)
|
|
}
|
|
|
|
if (!(opts.cardsLocation.length > 0)) {
|
|
logger.error('Saves location not set!')
|
|
process.exit(1)
|
|
}
|
|
|
|
const port = opts.emulatorPort
|
|
|
|
logger.info('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 = () => {
|
|
lcd.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('Starting YACE api')
|
|
const yace = YACE(port)
|
|
yace.once('error', () => setTimeout(() => {
|
|
yace.startPolling()
|
|
logger.error('Emulator unavailable, retrying in 1 sec')
|
|
}, 1000))
|
|
yace.startPolling()
|
|
|
|
const onInit = () => {
|
|
logger.info('Ready for new play.')
|
|
|
|
yace.once('shutter-opened', () => {
|
|
sm.shutterOpen()
|
|
})
|
|
}
|
|
|
|
logger.info('Initializing NFC (if this hangs check if pcscd is running)')
|
|
const nfc = ICCard(saves)
|
|
|
|
nfc.alt.on('new-card', () => {
|
|
if (sm.cannot('new-card')) {
|
|
logger.warn('Cannot prepare new card when game\'s not ready for it!')
|
|
switch (sm.state) {
|
|
case 'playing':
|
|
lcd.setMessage(CARD_PRESENT)
|
|
break
|
|
case 'guest-playing':
|
|
lcd.setMessage(GUEST_PLAYING)
|
|
break
|
|
default:
|
|
lcd.setMessage(INSTRUCTIONS)
|
|
}
|
|
}
|
|
})
|
|
|
|
nfc.alt.on('old-card', () => {
|
|
if (sm.cannot('old-card')) {
|
|
logger.warn('Cannot insert card when game\'s not ready for it!')
|
|
switch (sm.state) {
|
|
case 'playing':
|
|
lcd.setMessage(CARD_PRESENT)
|
|
break
|
|
case 'guest-playing':
|
|
lcd.setMessage(GUEST_PLAYING)
|
|
break
|
|
default:
|
|
lcd.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) => {
|
|
lcd.setState(state.to)
|
|
})
|
|
|
|
sm.observe('onReadyCard', () => {
|
|
logger.info('Shutter opened, assuming game started!')
|
|
|
|
yace.once('shutter-closed', () => {
|
|
logger.info('No card scanned!')
|
|
sm.timeout()
|
|
})
|
|
|
|
countDown(20, () => {
|
|
logger.info('Shutter still open after 20 seconds, assuming invalid state and resetting...')
|
|
sm.goto('idle')
|
|
})
|
|
|
|
nfc.once('new-card', (id) => {
|
|
yace.prepareCard(id)
|
|
lcd.setCardId(id)
|
|
sm.newCard()
|
|
})
|
|
|
|
nfc.once('old-card', (id) => {
|
|
yace.insertCard(id)
|
|
lcd.setCardId(id)
|
|
sm.oldCard()
|
|
})
|
|
|
|
})
|
|
|
|
// GUEST PLAY
|
|
sm.observe('onGpWaitDispense', () => {
|
|
countDown(30, () => {
|
|
logger.info('No card dispensed, assuming real guest play.')
|
|
sm.timeout()
|
|
})
|
|
|
|
yace.once('card-dispensed', () => {
|
|
logger.info('Guest card dispensed')
|
|
sm.dispense()
|
|
})
|
|
})
|
|
|
|
sm.observe('onGuestPlaying', () => {
|
|
logger.warn('Guest game in progress')
|
|
|
|
yace.once('card-ejected', () => {
|
|
logger.info('Card ejected, guest game over.')
|
|
lcd.setMessage(THANKS_FOR_PLAYING)
|
|
sm.eject()
|
|
yace.prepareCard('dummy')
|
|
lcd.setCardId('')
|
|
})
|
|
})
|
|
|
|
// REGISTERED CARD
|
|
sm.observe('onOcCheckRenewal', () => {
|
|
logger.info('Card inserted, waiting in case renewal is needed')
|
|
|
|
// XXX if card is corrupt or whatever it will be ejected outright.
|
|
// this wouldn't be a problem if we knew beforehand if renewal is needed
|
|
// or not.
|
|
|
|
yace.once('card-ejected', () => {
|
|
logger.info('Card ejected, renewal detected.')
|
|
lcd.setMessage(RENEW_CARD)
|
|
sm.eject()
|
|
})
|
|
|
|
countDown(60, () => {
|
|
logger.info('No card ejected, no renewal detected.')
|
|
sm.timeout()
|
|
})
|
|
})
|
|
|
|
sm.observe('onOcWaitDispense', () => {
|
|
logger.info('Present card ejected (and discarded ;_;), waiting for new card to be dispensed')
|
|
|
|
yace.once('card-dispensed', () => {
|
|
logger.info('Renewed card dispensed')
|
|
sm.dispense()
|
|
})
|
|
|
|
yace.once('card-ejected', () => {
|
|
logger.warn('Card ejected again, not sure what\'s going on, resetting...')
|
|
sm.goto('idle')
|
|
})
|
|
|
|
countDown(30, () => {
|
|
logger.warn('Error: not dispensed within 30 seconds, assuming illegal state and resetting')
|
|
sm.goto('idle')
|
|
})
|
|
})
|
|
|
|
// UNREGISTERED CARD
|
|
sm.observe('onNcWaitShutter', () => {
|
|
logger.info('Waiting for shutter to close')
|
|
|
|
yace.once('shutter-closed', () => {
|
|
sm.shutterClose()
|
|
})
|
|
})
|
|
|
|
sm.observe('onNcWaitDispense', () => {
|
|
logger.info('"No card" is selected, waiting for card purchase')
|
|
|
|
yace.once('card-dispensed', () => {
|
|
logger.info('New card dispensed!')
|
|
sm.dispense()
|
|
})
|
|
|
|
countDown(30, () => {
|
|
logger.warn('Error: not dispensed within 30 seconds, assuming guest play')
|
|
lcd.setMessage(REGISTR_ABORT)
|
|
sm.timeout()
|
|
})
|
|
})
|
|
|
|
// CARD GAME OVER
|
|
sm.observe('onPlaying', () => {
|
|
logger.info('Game in progress')
|
|
|
|
yace.once('card-ejected', () => {
|
|
logger.warn('Card ejected, game over.')
|
|
yace.prepareCard('dummy')
|
|
lcd.setCardId('')
|
|
lcd.setMessage(THANKS_FOR_PLAYING)
|
|
sm.eject()
|
|
})
|
|
})
|
|
|