-
Notifications
You must be signed in to change notification settings - Fork 6
Pull request to integrate Granboard #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
c077499
9aca35c
8a9acad
e185607
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,188 @@ | ||
| var debug = require('debug')('kcapp-granboard:board'); | ||
| var noble = require('@abandonware/noble'); | ||
|
|
||
| /** List containing all the messages send by the board. Used to translate to score and multiplier values. */ | ||
| const BOARD = ["4.0@", "8.0@", "3.3@", "3.4@", "3.5@", "3.6@", "2.3@", "2.4@", "2.5@", "2.6@", | ||
| "1.2@", "1.4@", "1.5@", "1.6@", "0.1@", "0.3@", "0.5@", "0.6@", "0.0@", "0.2@", | ||
| "0.4@", "4.5@", "1.0@", "1.1@", "1.3@", "4.4@", "2.0@", "2.1@", "2.2@", "4.3@", | ||
| "3.0@", "3.1@", "3.2@", "4.2@", "9.1@", "9.0@", "9.2@", "8.2@", "10.1@", "10.0@", | ||
| "10.2@", "8.3@", "7.1@", "7.0@", "7.2@", "8.4@", "6.1@", "6.0@", "6.3@", "8.5@", | ||
| "11.1@", "11.2@", "11.4@", "8.6@", "11.0@", "11.3@", "11.5@", "11.6@", "6.2@", "6.4@", | ||
| "6.5@", "6.6@", "7.3@", "7.4@", "7.5@", "7.6@", "10.3@", "10.4@", "10.5@", "10.6@", | ||
| "9.3@", "9.4@", "9.5@", "9.6@", "5.0@", "5.3@", "5.5@", "5.6@", "5.1@", "5.2@", "5.4@", "4.6@", "BTN@", "OUT@"]; | ||
|
|
||
| const VALUES = ["25-2", "25-0", "20-0", "20-3", "20-1", "20-2", "1-0", "1-3", "1-1", "1-2", | ||
| "18-0", "18-3", "18-1", "18-2", "4-0", "4-3", "4-1", "4-2", "13-0", "13-3", | ||
| "13-1", "13-2", "6-0", "6-3", "6-1", "6-2", "10-0", "10-3", "10-1", "10-2", | ||
| "15-0", "15-3", "15-1", "15-2", "2-0", "2-3", "2-1", "2-2", "17-0", "17-3", | ||
| "17-1", "17-2", "3-0", "3-3", "3-1", "3-2", "19-0", "19-3", "19-1", "19-2", | ||
| "7-0", "7-3", "7-1", "7-2", "16-0", "16-3", "16-1", "16-2", "8-0", "8-3", | ||
| "8-1", "8-2", "11-0", "11-3", "11-1", "11-2", "14-0", "14-3", "14-1", "14-2", | ||
| "9-0", "9-3", "9-1", "9-2", "12-0", "12-3", "12-1", "12-2", "5-0", "5-3", "5-1", "5-2", "BTN", "OUT"]; | ||
|
|
||
| /** Service containing board characteristics */ | ||
| const SERVICE_SCORING = "442f15708a009a28cbe1e1d4212d53eb"; | ||
| /** Characteristic to subscribe to throw notifications */ | ||
| const CHARACTERISTIC_THROW_NOTIFICATIONS = "442f15718a009a28cbe1e1d4212d53eb"; | ||
|
|
||
| /** | ||
| * Translate the given message | ||
| * @param {string} message - Message sent by board | ||
| */ | ||
| function translate(message) { | ||
| for (let i = 0; i < BOARD.length+1; i++) { | ||
| if (i == BOARD.length) { | ||
| return "ERROR"; | ||
| } else if (BOARD[i] === message) { | ||
| return VALUES[i]; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Start scanning for the board | ||
| */ | ||
| exports.startScan = () => { | ||
| debug("Started scanning for board"); | ||
| noble.startScanning(); | ||
| } | ||
|
|
||
| /** | ||
| * Connect to the dart board | ||
| * This method will add a callback to the discover method for bluetooth, | ||
| * and check all peripherals found until it finds one matching the UUID | ||
| * we are looking for | ||
| * | ||
| * @param {string} uuid - UUID of smartboard to connect to | ||
| * @param {function} callback - Callback when dart is thrown | ||
| */ | ||
| exports.connect = (uuid, callback) => { | ||
| this.discoverCallback = (peripheral) => { | ||
| if (peripheral.uuid === uuid) { | ||
| callback(peripheral); | ||
| debug("Found device, stopped scanning"); | ||
| noble.stopScanning(); | ||
| this.peripheral = peripheral; | ||
| } | ||
| }; | ||
| noble.on('discover', this.discoverCallback); | ||
| } | ||
|
|
||
| /** | ||
| * Initialize the dart board, by setting up notification listeners | ||
| * for darts thrown, and button presses | ||
| * | ||
| * @param {object} - Peripheral object to initialize | ||
| * @param {int} - Number next to the board button | ||
| * @param {function} - Callback when dart is thrown | ||
| * @param {function} - Callback when button is pressed | ||
| */ | ||
| exports.initialize = (peripheral, buttonNumber, throwCallback, playerChangeCallback) => { | ||
| peripheral.connect((error) => { | ||
| if (error) { | ||
| debug(`ERROR: ${error}`); | ||
| } | ||
| debug(`Connected to ${peripheral.advertisement.localName} (${peripheral.uuid})`); | ||
|
|
||
| // Get the scoring service | ||
| peripheral.discoverServices([SERVICE_SCORING], (error, services) => { | ||
| if (error) { | ||
| debug(`ERROR: ${error}`); | ||
| } | ||
|
|
||
| var scoringService = services[0]; | ||
| scoringService.discoverCharacteristics([CHARACTERISTIC_THROW_NOTIFICATIONS], (error, characteristics) => { | ||
| if (error) { | ||
| debug(`ERROR: ${error}`); | ||
| } | ||
|
|
||
| // Subscribe to throw notifications | ||
| var throwNotifyCharacteristic = characteristics[0]; | ||
| throwNotifyCharacteristic.subscribe((error) => { | ||
| if (error) { | ||
| debug(`ERROR: ${error}`); | ||
| } | ||
| debug('Subscribed to throw notifications!'); | ||
| }); | ||
|
|
||
| throwNotifyCharacteristic.on('data', (data, isNotification) => { | ||
| var rawValue = data.toString(); | ||
| var value = translate(rawValue); | ||
| if (value == "BTN") { | ||
| playerChangeCallback(); | ||
| } else if (value == "ERROR") { | ||
| debug(`Message from board could not be translated: ${rawValue}`); | ||
| } else { | ||
| var dart = { | ||
| score: parseInt(value.split("-")[0]), | ||
| multiplier: parseInt(value.split("-")[1]) | ||
| }; | ||
| throwCallback(dart); | ||
| } | ||
| }); | ||
| this.throwNotifyCharacteristic = throwNotifyCharacteristic; | ||
| }); | ||
| }); | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Disconnect from the connected peripheral | ||
| * @param {object} - Connected peripheral | ||
| * @param {function} - Callback onces disconnected | ||
| */ | ||
| exports.disconnect = (peripheral, callback) => { | ||
| debug(`Removing 'discover' callback`); | ||
| noble.removeListener('discover', this.discoverCallback); | ||
|
|
||
| if (this.throwNotifyCharacteristic) { | ||
| this.throwNotifyCharacteristic.unsubscribe((error) => { | ||
| if (error) { | ||
| debug(`ERROR: ${error}`); | ||
| } | ||
| debug(`Unsubscribed from throw notifications`); | ||
| }); | ||
| } | ||
| if (this.buttonCharacteristic) { | ||
| this.buttonCharacteristic.write(new Buffer([0x02]), true, (error) => { | ||
| if (error) { | ||
| debug(`ERROR: ${error}`); | ||
| } | ||
| debug(`Disabled listening on characteristic ${CHARACTERISTIC_BUTTON}`); | ||
| peripheral.disconnect((error) => { | ||
| if (error) { | ||
| debug(`ERROR: ${error}`); | ||
| } | ||
| debug(`Disconnected from ${peripheral.advertisement.localName}`); | ||
| if (callback) { | ||
| callback(); | ||
| } | ||
| }); | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| function interrupt() { | ||
| if (this.peripheral) { | ||
| debug("Caught interrupt signal, Disconnecting..."); | ||
|
|
||
| this.disconnect(() => { | ||
| process.exit(); | ||
| }); | ||
|
|
||
| // Give the board 3 seconds to disconnect before we die.... | ||
| setTimeout(() => { | ||
| process.exit(); | ||
| }, 3000); | ||
| } else { | ||
| process.exit(); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Configure the smartboard module | ||
| */ | ||
| module.exports = () => { | ||
| process.on('SIGINT', interrupt.bind(this)); | ||
| return this; | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,16 @@ | ||
| const debug = require('debug')('kcapp-smartboard:main'); | ||
| const smartboard = process.env.NODE_ENV === "prod" ? require('./smartboard')() : require("./smartboard-mock")(); | ||
| var smartboard; | ||
| if (process.env.NODE_ENV == "prod"){ | ||
| smartboard = require("./smartboard")(); | ||
| } else if (process.env.NODE_ENV == "gran"){ | ||
| smartboard = require("./granboard")(); | ||
| } else { | ||
| smartboard = require("./smartboard-mock")(); | ||
| } | ||
| const host = process.env.KCAPP_API || "localhost"; | ||
| const port = process.env.PORT || 3000; | ||
| const kcapp = require('kcapp-sio-client/kcapp')(host, port, 'smartboard', "http"); | ||
| const protocol = process.env.PROTOCOL || "http"; | ||
| const kcapp = require('kcapp-sio-client/kcapp')(host, port, 'smartboard', protocol); | ||
|
|
||
| const X01 = 1; | ||
| const SHOOTOUT = 2; | ||
|
|
@@ -25,14 +33,15 @@ function connectToMatch(data) { | |
| const match = data.match; | ||
| const legId = match.current_leg_id; | ||
| const config = match.venue.config; | ||
| const smartboard_uuid = process.env.UUID || config.smartboard_uuid; | ||
thordy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if (match.venue && config.has_smartboard) { | ||
| debug(`Connected to match ${match.id}`); | ||
| kcapp.connectLegNamespace(legId, (leg) => { | ||
| debug(`Connected to leg ${legId}`); | ||
| if (!this.connected) { | ||
| leg.emit('announce', { type: 'notify', message: 'Searching for smartboard ...' }); | ||
| smartboard.startScan(); | ||
| smartboard.connect(config.smartboard_uuid, (peripheral) => { | ||
| smartboard.connect(smartboard_uuid, (peripheral) => { | ||
| this.connected = true; | ||
| this.peripheral = peripheral; | ||
|
|
||
|
|
@@ -102,7 +111,67 @@ function connectToMatch(data) { | |
| } else { | ||
| debug("Already connected to board..."); | ||
| leg.emit('announce', { type: 'success', message: 'Already Connected' }); | ||
| leg.on('leg_finished', disconnectListener.bind(this)); | ||
| smartboard.initialize(peripheral, config.smartboard_button_number, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does the GranBoard really require us to reconnect here? If it does we should check here for which board currently is in use, and then only It also seems like this is exactly the same as lines 48:87, so we could also extract those out into a separate method to reduce code duplication
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, the majority of the code of the first branch is duplicated here. Reconnection does not seem necessary, but the listener is also in the initialize function and thus it was the easiest way of establishing this. It might work with less code, but except for one error thrown because we are already connected the code just works. It also allows to keep kcapp-smartboard running as a daemon which will connect to new legs and to the board if you switch it on again. No need to launch it for each session.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So you need to run the initialize function for each leg on the Granboard? Does it also keep track of legs played and stuff like that and needs a reset? Regarding reconnecting, this was solved by having the "Reconnect Smartboard" button in the Frontend
This button will only be available if a Could you see if this works for your case as well?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The reconnect button only triggers the message that the board has already been connected. In fact it is (showing a green light instead of red), but kcapp is not listening for any events from the board and thus not registering them. Besides on a smartphone the Change order menu is not usable as the pop-up is too large to be completely visible. Panning or zooming is also not possible. Are you sure it works with more than one leg on the Unicorn board? The score is in the object dart which is handled inside the initialize function. In fact the the if else only handles two options:
But in either case you should do the rest. To be honest I thus do not understand the original code, in my opinion there is something missing in the else part. |
||
| (dart) => { | ||
| const player = leg.currentPlayer; | ||
| if (dart.multiplier == 0) { | ||
| dart.multiplier = 1; | ||
| dart.zone = 'inner'; | ||
| } else if (dart.multiplier == 1) { | ||
| dart.zone = 'outer'; | ||
| } | ||
| debug(`Got throw ${JSON.stringify(dart)} for ${player.player.id}`); | ||
| leg.emitThrow(dart); | ||
|
|
||
| if (match.match_type.id == SHOOTOUT) { | ||
| player.current_score += dart.score * dart.multiplier; | ||
| const visits = leg.leg.visits.length; | ||
| if (visits > 0 && ((visits * 3 + leg.dartsThrown) % (9 * leg.leg.players.length) === 0)) { | ||
| debug("Match finished! sending visit"); | ||
| leg.emitVisit(); | ||
| } else if (leg.dartsThrown == 3) { | ||
| leg.emitVisit(); | ||
| } | ||
| } | ||
| else if (match.match_type.id == X01) { | ||
| player.current_score -= dart.score * dart.multiplier; | ||
|
|
||
| if (player.current_score === 0 && dart.multiplier === 2) { | ||
| debug("Player Checkout! sending visit"); | ||
| leg.emit('announce', { type: 'confirm_checkout', message: "" }); | ||
| } else if (player.current_score <= 1) { | ||
| debug("Player busted, sending visit"); | ||
| leg.emitVisit(); | ||
| } else if (leg.dartsThrown == 3) { | ||
| leg.emitVisit(); | ||
| } | ||
| } else { | ||
| if (leg.dartsThrown == 3) { | ||
| leg.emitVisit(); | ||
| } | ||
| } | ||
| }, | ||
| () => { | ||
| debug("Button pressed, sending visit"); | ||
| leg.emitVisit(); | ||
| } | ||
| ); | ||
|
|
||
| leg.on('leg_finished', (data) => { | ||
| debug(`Got leg_finished event!`); | ||
| const match = data.match; | ||
| if (match.is_finished) { | ||
| debug("Match is finished, disconnecting from board"); | ||
| disconnectListener.bind(this)(data); | ||
| } else { | ||
| debug("Leg is finished"); | ||
| } | ||
| }); | ||
|
|
||
| leg.on('cancelled', (data) => { | ||
| debug("Leg cancelled, disconnecting from board"); | ||
| disconnectListener.bind(this)(); | ||
| }); | ||
| } | ||
| }); | ||
| } | ||
|
|
||


Uh oh!
There was an error while loading. Please reload this page.