Coder Social home page Coder Social logo

biketracker's Introduction

Biketracker

Track your (desk) bikes! This program allows you to track how far users bike and show it in a decent HTML widget, ready to embed to your website.

Table of Contents

Get Started (to develop locally)

These instructions should work on Linux/macOS/Windows, but the quality of testing is in roughly that order.

Database

  • Install Dependencies
    • Install Rustup (https://rustup.rs/) and PostgreSQL
    • Install Migrant (cargo install migrant)
  • Create database:
    • createuser biketracker --password=feKui9Be (example password, use something else in prod)
    • createdb biketracker --owner=biketracker
  • Apply migrations:
    • pushd biketracker-server && migrant apply --all && popd

Server

  • Set up the database (see above)
  • Switch to the server folder:
    • cd biketracker-server
  • Compile and run:
    • cargo run

Agent

  • Set up the Server (see above)
  • Switch to the agent folder:
    • cd biketracker-agent
  • Compile and run:
    • cargo run

Notes

  • macOS/Windows: You’ll need to disable Bluetooth support when running the agent, do this by passing the passing cargo run the argument --no-default-features. Then you’ll need to use the fake bike by setting bike.type to Fake in ./biketracker-agent/Config.toml.
  • macOS: Currently the agent only works in Release mode on macOS. Enable this by giving cargo run the argument --release.

Dashboard

  • Set up the database (as above)
  • Install Grafana
  • Create a Datasource that connects to the PostgreSQL in the Grafana GUI
    • Note: Make sure to create a separate read-only user in prod, rather than reusing the server’s user
  • Import ./grafana-config.json in the Grafana GUI

Deploying To Prod

Server

The repository is set up so that Travis automatically deploys whenever a new commit is pushed. You can also do it manually by running git crypt unlock && ./deploy.sh.

Notes

  • Migrations are currently not applied automatically, you will have to do that yourself, with a local copy of Migrant.

Architecture

@startuml
rectangle "Agent (Raspbarry Pi)" as Agent
rectangle Bike
rectangle Server
rectangle "Dashboard (Grafana)" as Dashboard
database "Database (Postgres)" as Database

Agent --> Bike : Bluetooth LE (CSC Profile)
Agent --> Server : REST API
Server --> Database
Dashboard --> Database
@enduml

./Architecture.png

Agent

Connects to the bike, records how far you travel, and transmits it to the Server once the session is over. Also presents a GUI showing the user how far they’ve travelled, and which lets them log in/out.

This component should be cross-platform, but Bluetooth functionality only works on Linux.

Located in ./biketracker-agent.

Server

Listens for the Agent asking to record sessions, and saves them to the database.

Located in ./biketracker-server.

Grafana

Collects statistics from the database, and presents them to the user. Also provides embeddable widgets (to show off on the website, for example).

A third party component available at https://grafana.com/. Our configuration is located in ./grafana-config.json.

Database

A Postgres database with the data collected from the user. The schema is managed using Migrant.

Bike

A bike that supports the Bluetooth LE CSC (Cycling Speed + Cadence) profile. In theory any CSC bike should work (if the code is adjusted to connect to it), but we’ve only tested against a Deskbike.

There is also a fake bike for testing purposes.

Installing Devices (Agents and Dashboards)

  • For each device:
    • Install Arch Linux ARM to SD card
    • Boot Raspberry Pi
    • Connect wired networking (temporarily)
    • Connect to Wi-Fi and bootstrap:
# Find out the IP address
nmap 192.168.1.0/24 -p22 --open
# Bootstrap trust by copying your SSH key
# The password here will be "alarm"
ssh-copy-id [email protected]
# Connect and switch to root
ssh [email protected]
# The password here will be "root"
su
# Switch to Swedish keyboard layout
load-keys sv-latin1
# Connect to Wi-Fi
wifi-menu
# Enable Wi-Fi on boot
netctl enable wlan0-mirawireless
# Find out your Wi-Fi IP address
ip address
  • Feel free to disconnect wired networking
  • Adjust the Ansible Inventory (ansible/hosts.ini)
  • Run the Ansible Playbook: ansible-playbook -i ansible/hosts.ini ansible/playbook.yml
  • For each new Agent:
    • Install FBCP to enable the Pi-TFT:
ssh [email protected]
cd ~/biketracker/pkgsbuilds/rpi-fbcp
makepkg --install

Why Arch Linux (rather than Raspbian)

Raspbian 9 (Stretch, the current Stable) only ships BlueZ 5.43, but Blurz only supports 5.44 and newer. Raspbian 10 (Buster, the current Testing) ships BlueZ 5.50, but also ships an X server that doesn’t like to cooperate with the PiTFT.

Arch supports both BlueZ 5.50 and a working X server.

biketracker's People

Contributors

nightkr avatar jassyrbravo avatar

Stargazers

Adrian van Dongen avatar

Watchers

 avatar James Cloos avatar Daniel Winther avatar Lukas avatar  avatar  avatar

biketracker's Issues

Rewrite UI code around a Flux/Diode-like pattern

We already do this to a degree, but the current approach requires polling all "work" futures, rather than being notified. The current design also has a bit of a latency issue: when you press a button we update the State, but we only take the new State into account on the next render. Currently that's not a massive issue since we try to keep a constant 60 FPS, but in the future it would be nice to avoid renders when there are no new events.

My current thinking is that we could rearchitect it like this (this should be familiar if you've used Diode):

  • render(&State, Vec<InputEvent>) -> Vec<Action> (render asynchronously after any Actions are ran, as well as when there are new HID events, triggers Actions based on user events)
  • runAction(Action, &mut State) -> Option<Effect> (applies an Action, which may mutate the State and start an Effect)
  • runEffect(Effect) -> Future<Action> (applies an Effect, which performs an asynchronous side effect and eventually resolves to an Action with a user-visible effect)

This way we should be drive our UI from Futures, allowing us to completely avoid unnecessary polling from our side, while staying as responsive as possible (since we always know when the UI has to be re-rendered).

Separating Actions and Effects allows us to keep processing other events in the meantime, since we don't need to keep State locked while waiting for the Effect's Future to resolve. Another option would be to define runAction(Action, &mut State) -> Future<Option<Action>>, but that would still require FooDone actions to update the state after the operation finished, while making runAction's implementation more complex.

Another concern to keep in mind is Streams. The structure above would allow defining an Action/Effect loop based on their Future form (Stream<T>: Future<(T, Stream<T>)>), but that would be pretty verbose. Maybe a better option would be to make runEffect return a Stream<Action> instead, since Future<T>s are a strict subset of Stream<T>s.

Finally, we need to consider cancellation (which the current system handles pretty well, surprisingly). An in-flight Effect that is no longer relevant should be cancelled, but I'm not entirely sure about how to handle this, yet.

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.