Coder Social home page Coder Social logo

pinewood-derby's Introduction

PinewoodDerby

Code Climate

A web-based race manager and status board application for the Cub Scout pinewood derby

screenshot

Join our project chat for support, questions, requests, or just to let me know if you're using it.

Features

  • Show a status board (TV/projector) with the standings, most recent heat times, and upcoming heats, all updated in real-time
  • Register contestants, even after the derby has begun
  • Activate the start switch for each race with a button in the app
  • Swap out contestants in upcoming heats when someone is MIA or not ready to race
  • Redo the most recent heat when something goes wrong
  • Manage the derby, show the display board, and run the sensor on different devices if desired
  • Retire contestants that drop out of the derby
  • The welcome screen displays the URL at which other devices can access the application
  • Warnings are displayed when the USB device is unplugged or the sensor daemon is not running
  • Lineup order is automatically generated (using the rules below)
  • Race using a configurable number of lanes
  • Password authentication

Derby lineup rules

  • Each contestant will run exactly once in each lane
  • The winner is the contestant with the lowest cumulative/average time (no brackets/playoffs)
  • In the first round, contestants race in the order they were registered
  • Contestants will race against others they have not yet raced against when possible
  • Keeping the above constraints, racers with slower averages will race before those with faster averages
  • When swapping out or adding contestants, the upcoming 3 heats are not recalculated unless there are empty lanes

Missing features

  • Manual lineup - Races are lined up using the rules above and cannot yet be manually set
  • Customization - support for other devices and variations on the lineup rules
  • Deltas - indicate when a contestant moves up or down in rank with a green or red highlight
  • Non-finishers - handle people whose cars don't make it to the sensor in under 10 seconds; currently they don't get a time for that heat
  • Windows support - The application runs only on Linux and Mac OSX
  • Mobile layout - the responsive mobile layout sometimes isn't very pretty

Feel free to inquire (via issues) regarding how you can modify this app for your setup. I am happy to assist.

Supported Track Sensors

Adding support for any track sensor that communicates via serial port should be straightforward.

Initial Setup

  1. Install drivers for the USB serial connector

  2. Install Ruby >= 2.0.0, bundler, and node.js.

  3. Install build tools for your operating system: a. Mac OSX: Install the Xcode command line tools b. Ubuntu/Debian Linux: sudo apt-get install build-essential c. Fedora/CentOS/RedHat Linux: yum groupinstall "Development Tools"

  4. Install the required ruby dependencies

     bundle install
    
  5. Initialize the database

     rake db:setup RAILS_ENV=development
     rake db:setup RAILS_ENV=production
    
  6. Ensure the config/derby_config.yaml file is configured correctly for your setup

  7. Precompile assets for production mode (Do this every time you update javascript/css/images)

     rake assets:precompile RAILS_ENV=production
    

Starting the Derby

  1. Run the application and sensor daemon as root (for access to port 80) and wait a few seconds

     sudo foreman start   # or rvmsudo if using rvm
    
  2. Visit http://localhost/ - You should see the welcome screen, and it should report that the sensor is not plugged in

  3. Connect the sensor via USB, and turn it on. (DT-8000: Hit the reset button, and it should display "3 lanes". Press reset until the sensor display goes blank.)

  4. Verify that the "not plugged in" warning has gone away

  5. Connect with other devices to the URL on the welcome page (status board and/or other device to manage the derby)

  6. Visit the contestants page to register contestants

  7. Visit the Dashboard page to manage the races

Running a heat

  1. Have the contestants for the upcoming heat place their cars on the track. If a contestant is missing, click their name to swap in another contestant and try the missing person later.
  2. On the Dashboard page, click the "Start the race" button. The next race is highlighted in green.
  3. Simultaneously release the cars and release the sensor's start switch to start the timer. The race times will appear on the Dashboard after they pass the finish line.
  4. If needed, click the "redo" button to re-run the same heat.

Configuration

config/derby_config.yml contains several configuration options, including sensor type, device file location, and lane count.

Password Authentication

A password can be used to prevent others who access the app URL from doing things that only the derby master should.

The default config/derby_config.yml uses the environment variable DERBY\_ADMIN\_KEY to supply the password, for example:

DERBY_ADMIN_KEY="mySuperSecretPassword" sudo -E foreman start

Alternatively, the password can be stored directly in config/derby_config.yml:

admin_password: "mySuperSecretPassword"

When a password is set, the app locks itself down in read-only mode and creates a "Run the race" login button on the front page.

Revisit the login page to log out.

Running for Testing / Development

To simulate a track sensor when one is not plugged in, use the mock sensor:

bin/mock-sensor

mock-sensor will announce what device file (eg. /dev/ttys009) it is using.

Before staring the server/daemon, set the environment variable TRACK_SENSOR_DEVICE to this device file. Eg:

In production:

TRACK_SENSOR_DEVICE=/dev/ttys009 sudo -E foreman start   # or rvmsudo if using rvm

In development:

TRACK_SENSOR_DEVICE=/dev/ttys009 RAILS_ENV=development DEBUG=true sudo -E foreman start   # or rvmsudo if using rvm

Optionally set the environment variable DEBUG=true to get more output from the sensor_watch daemon, including sensor device status and data received.

Developing

This project uses Rails 4.2.

Contributing

This pinewood-derby application is Copyright 2015 Edward Anderson, and is distributed under the GNU Affero General Public License (see LICENSE). This license requires that you provide the source code to users of this application.

Please let me know (via issues) if you're interested in using this PinewoodDerby software or need help with it.

pinewood-derby's People

Contributors

dependabot[bot] avatar nilbus avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

pinewood-derby's Issues

Show pack number for contestants

Displaying the pack number in the standings for each contestant would be great for district events, so you can root for others in your pack or see how a pack is consistently faster.

Buffer partially read lines

Sometimes readline_nonblock is returning partial lines because IO blocks briefly during the read. Eg:

"Copyrigh Mi"
"cro Wizard 2001-2009 \r\n"
"K3 1.07A  Serial Number 23855\r\n"
"A=3.554! B=3.768\" C=3.948# D=4.105$"
" E=0.000  F=0.000  \r\n"

Any race result that gets split like this will not get read properly and will have to be re-run.

Buffering data read and returning it after a full line has been read should alleviate this problem.

Consider a car number field

Assigning numbers to cars can be helpful for organizing, judging, etc. Consider whether this should be a field associated with a contestant, or if the number should just be part of the contestant name.

Speed up button response: get update via ajax response

Although (on the raspberry pi) ajax requests usually respond in 200ms, button clicks that have an effect on the board take typically 1 second (rarely up to 6) before the board is visually updated. This is because the ajax response doesn't contain the dashboard update. Instead that gets sent via the Faye websocket connection.

In addition to the websocket updates which must still go out to everyone, respond to ajax requests with the dashboard data, and re-render the dashboard using that response.

To prevent multiple yellow flashes, have the JS client cache the most recent response, and only flash and render when subsequent updates differ from the prior.

Redo button works but causes an exception

NoMethodError (undefined method `runs' for nil:NilClass):
  app/controllers/races_controller.rb:31:in `block in redo'
  app/controllers/races_controller.rb:29:in `redo'

Potential deadlock detected in Ruby 2

Ruby 2 detects potential deadlocks that earlier versions did not. See https://bugs.ruby-lang.org/issues/7917. The daemon does not run under ruby 2 because of this error.

rake aborted!
can't be called from trap context
/Users/nilbus/.rvm/gems/ruby-2.0.0-p0/gems/thread_safe-0.1.0/lib/thread_safe/mri_cache_backend.rb:15:in `synchronize'
/Users/nilbus/.rvm/gems/ruby-2.0.0-p0/gems/thread_safe-0.1.0/lib/thread_safe/mri_cache_backend.rb:15:in `[]='
/Users/nilbus/.rvm/gems/ruby-2.0.0-p0/bundler/gems/rails-5458f509d9a3/activesupport/lib/active_support/callbacks.rb:384:in `block in __callback_runner_name_cache'
/Users/nilbus/.rvm/gems/ruby-2.0.0-p0/gems/thread_safe-0.1.0/lib/thread_safe/cache.rb:38:in `call'
/Users/nilbus/.rvm/gems/ruby-2.0.0-p0/gems/thread_safe-0.1.0/lib/thread_safe/cache.rb:38:in `[]'
/Users/nilbus/.rvm/gems/ruby-2.0.0-p0/bundler/gems/rails-5458f509d9a3/activesupport/lib/active_support/callbacks.rb:392:in `__callback_runner_name'
/Users/nilbus/.rvm/gems/ruby-2.0.0-p0/bundler/gems/rails-5458f509d9a3/activesupport/lib/active_support/callbacks.rb:367:in `__define_callbacks'
/Users/nilbus/.rvm/gems/ruby-2.0.0-p0/bundler/gems/rails-5458f509d9a3/activesupport/lib/active_support/callbacks.rb:77:in `run_callbacks'
/Users/nilbus/.rvm/gems/ruby-2.0.0-p0/bundler/gems/rails-5458f509d9a3/activerecord/lib/active_record/core.rb:198:in `init_with'
/Users/nilbus/.rvm/gems/ruby-2.0.0-p0/bundler/gems/rails-5458f509d9a3/activerecord/lib/active_record/persistence.rb:55:in `instantiate'
/Users/nilbus/.rvm/gems/ruby-2.0.0-p0/bundler/gems/rails-5458f509d9a3/activerecord/lib/active_record/querying.rb:47:in `block (2 levels) in find_by_sql'
/Users/nilbus/.rvm/gems/ruby-2.0.0-p0/bundler/gems/rails-5458f509d9a3/activerecord/lib/active_record/result.rb:21:in `block in each'
/Users/nilbus/.rvm/gems/ruby-2.0.0-p0/bundler/gems/rails-5458f509d9a3/activerecord/lib/active_record/result.rb:21:in `each'
/Users/nilbus/.rvm/gems/ruby-2.0.0-p0/bundler/gems/rails-5458f509d9a3/activerecord/lib/active_record/result.rb:21:in `each'
/Users/nilbus/.rvm/gems/ruby-2.0.0-p0/bundler/gems/rails-5458f509d9a3/activerecord/lib/active_record/querying.rb:47:in `map'
/Users/nilbus/.rvm/gems/ruby-2.0.0-p0/bundler/gems/rails-5458f509d9a3/activerecord/lib/active_record/querying.rb:47:in `block in find_by_sql'
/Users/nilbus/.rvm/gems/ruby-2.0.0-p0/bundler/gems/rails-5458f509d9a3/activerecord/lib/active_record/explain.rb:36:in `logging_query_plan'
/Users/nilbus/.rvm/gems/ruby-2.0.0-p0/bundler/gems/rails-5458f509d9a3/activerecord/lib/active_record/querying.rb:37:in `find_by_sql'
/Users/nilbus/.rvm/gems/ruby-2.0.0-p0/bundler/gems/rails-5458f509d9a3/activerecord/lib/active_record/relation.rb:565:in `exec_queries'
/Users/nilbus/.rvm/gems/ruby-2.0.0-p0/bundler/gems/rails-5458f509d9a3/activerecord/lib/active_record/relation.rb:456:in `block in load'
/Users/nilbus/.rvm/gems/ruby-2.0.0-p0/bundler/gems/rails-5458f509d9a3/activerecord/lib/active_record/explain.rb:36:in `logging_query_plan'
/Users/nilbus/.rvm/gems/ruby-2.0.0-p0/bundler/gems/rails-5458f509d9a3/activerecord/lib/active_record/relation.rb:456:in `load'
/Users/nilbus/.rvm/gems/ruby-2.0.0-p0/bundler/gems/rails-5458f509d9a3/activerecord/lib/active_record/relation.rb:197:in `to_a'
/Users/nilbus/.rvm/gems/ruby-2.0.0-p0/bundler/gems/rails-5458f509d9a3/activerecord/lib/active_record/relation/delegation.rb:53:in `keep_if'
/Users/nilbus/sync/Dropbox/git/pinewood-derby/app/models/dashboard.rb:22:in `contestant_times'
/Users/nilbus/sync/Dropbox/git/pinewood-derby/app/models/dashboard.rb:10:in `to_json'
/Users/nilbus/sync/Dropbox/git/pinewood-derby/app/models/dashboard.rb:5:in `to_json'
/Users/nilbus/sync/Dropbox/git/pinewood-derby/app/controllers/announce_controller.rb:4:in `update'
/Users/nilbus/sync/Dropbox/git/pinewood-derby/app/controllers/announce_controller.rb:30:in `block in <class:AnnounceController>'

The traceback suggests the issue may be with the old version of rails or threadsafe that we're using.

Make an empty lane cancelable

Postponing a contestant who is lined up for the next race may leave that lane empty when there is no suitable alternate who is not already lined up. Unfortunately it's also impossible to bring the swapped-out candidate back, since the empty lane has no text to click on. Thus if the contestant returns, they are still unable to race.

Consider using SQLite

Since the target audience for this app is less technical, a lot of setup effort could be eliminated by shipping with and using a SQLite database.

  • Make sure Contestant.next_suitable can be implemented using SQLite syntax
  • Write specs for Contestant.next_suitable
  • Switch the gems and database.yml to use SQLite
  • Implement Contestant.next_suitable using SQLite syntax
  • Change SingleValue's implementation to not use postgres hstore
  • Switch back to using schema.rb rather than structure.sql (94874ff#diff-b1fe55db50c712fef0673345e5b9c0d9R32)
  • Update previous migrations to use standard data types so that migrating up is not a problem
  • Move database.yml.example to database.yml and allow ENV variables to override any setting

Round averages to > 3 digits

The hardware only reports 3 digits, but averages will usually have more digits. This can matter a lot when differentiating a 3-digit tie.

Canceling heats

Once you press the start race button, it's not possible to cancel the heat or swap out contestants in that heat. A heat should be cancelable after its start.

Notably, once a race is started on a sensor like the DT-8000 that requires a start signal to enable the gate trigger, the start signal can't be undone. However if the cars go and times are recorded by the sensor, times will be ignored in the app when there is no active/started race.

I would not like to allow contestants to be swapped out in a heat that has been started. This might too easily be done accidentally, which would likely result in times being recorded for the wrong contestant.

Mobile layout

Make it look good and work well on a phone and iPad.

Use the Rails 4 `console` block to run the Faye reactor

Rails 4 provides a console hook that can be placed in config/application.rb to run any code necessary when the console has loaded.

This can be used to run the Faye reactor when the Rails console has booted instead of running it separately.

Opening display

Make a better looking starting state when the derby hasn't gotten started yet.

Dashboard redesign

Mockup

Design mockup

  • Create a redesign branch
  • Switch to Foundation Zurb for everything but the dashboard
  • Add min/max time statistics to Announcer events
  • Implement Javascript to calculate bar heights and colors based on relative times
  • Implement the markup
  • Update the javascript to work with the new markup
  • Style it with CSS
  • Add animatied transitions for updates

Enable spring

Spring used to not work with the daemon, so I think I disabled the spring binstubs. Now that we don't use a daemon, try re-enabling it for performance. Consider whether it really helps on the raspberry pi, or if it will consume too many resources.

Button lag

When clicking the Start Race (or Redo) button, there's a delay between when the current race is highlighted and when the start (or redo) button goes away.

Document the device globbing behaviour

This app scans several USB serial device files and tries to talk with all of them simultaneously. This behavior isn't common, so it should be documented both in the Readme and the implementing class.

Ending the derby

When the derby is over, show only the rankings. Have a button to mark the derby as done.

Ticker with standings

Add a scrolling marqee to show all the racers and their times across the bottom.
Sort of like ticker

Serial terminal parameters using a portable method

The TrackSensor objects currently use stty to shell out and set the terminal's serial parameters (baud, char width, stop bits). This only works on Linux. On OSX, the serial parameters revert back to the defaults as soon as the serial file is closed. Windows does not use serial device files but instead uses COM1.

Incidentally, using stty < device also does not work on OSX. stty defaults to using stdin, and this method replaces stdin with device. It works on Linux but hangs on OSX when using a real serial device. stty takes an argument to specify the file, which is -f on OSX and -F on Linux.

Using the cross-platform serialport gem should provide compatibility for all 3 platforms. Last year I was not able to get serialport to set the parameters correctly for the DT8000 on Linux, but I may not have been doing it right. It deserves another try.

Authentication

Require authentication to make any changes.

Make sure the (user and) password is configurable.

Package it up like an appliance

To make this easier for less technical people, I want to minimize the technical burden in setting this up.

Consider:

  • Raspberry Pi
  • VirtualBox VM
  • Bootable Linux USB drive
  • Docker

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.