Home

Awesome

Fresh configurator

A re-written simplication of the Betaflight configurator

Pipeline status Coverage code style: prettier Storybook Api documation Schema documentation

<p align="center"> <img width="600" src="./docs/progress.png"> <img width="600" src="./docs/progress-dark.png"> </p>

What is this?

This is a from-scratch re-write of the betaflight configurator which is attempting to replace the software with more a modern and correct approach.

The current configurator is both written without a UI framework (apart from jQuery), and doesn't utilise any of the modern javascript tooling and package management which exist today.

The aim of this rewrite is to show how the software could be vastly improved and simplified, and that by doing so attract more contribution to the software.

Try it out!

What's happening right now?

At the moment functionality is very minimal, and lots is changing all the time.

What's the plan?

Idealy, to become feature complete with the current configurator. The overall goal of the project, however, is to simplify the requirements to develop your own configurator or customise an existing one.

Because @betaflight/msp is written separately it is published as its own package, available for anyone to build tools which can interact with flight controllers. As @betaflight/api is also separate, it provides the ability for people to publish tools which can integrate with betaflight, just by using the API.

Packages

ProjectDescription
@betaflight/configuratorThe betaflight configurator electron application, built using the rest of the libraries
@betaflight/api-graphA GraphQL schema to read data from betaflight flight controllers, built using the @betaflight/api
@betaflight/apiThe betaflight API, built using @betaflight/msp
@betaflight/mspA library for handling the MultiWii Serial Protocol for reading and writing data to flight controllers
@betaflight/api-serverA GraphQL server which implements @betaflight/api-graph

Developing

Requirements

If you have nvm run nvm use to use the correct node version

PLEASE NOTE, THIS SOFTWARE IS WORK IN PROGRESS AND THINGS ARE CHANGING ALL THE TIME

$ yarn

Running the application

$ yarn start

Running the application in a mocked device environment

$ yarn start:mocked

Component development environment

$ yarn storybook

Compile the application (for your current OS)

$ yarn build

Tests

Tests can be run across the entire codebase at once

$ yarn test

End To End tests can be executed against the built application to verify that the UI functions as expected

$ yarn e2e:production

Architecture

 - - - - - - - - - - - - - - - - - - - -
|          Electron Application         |
|                   |                   |
|     renderer      |        main       |
|                   |                   |
|   configurator <----->   api-graph    |
|                   |          |        |
|                   |          V        |
|                   |         api       |
|                   |          |        |
|                   |          V        |
|                   |         msp       |
|_ _ _ _ _ _ _ _ _ _|_ _ _ _ _ | _ _ _ _|
                               V
                       Flight Controller

This is a monorepo application, with the design being to allow a single feature to be able to make changes to the entire application stack, but for the packages to be separate enough to exist on their own.

By segrating logic into their own packages, code barriers are automatically added to discourage interdependency. Because parts of the application are publishable, they must be written to exist on their own and thus create even less interdependency.

The only package which cannot exist outside of the monorepo is @betaflight/configurator as it is the final export of this repo.

Heavy use of TypeScript and graphql-code-generator is made throughout the monorepo to ensure the datatypes transmitted between the flight controller and the configurator are consistent at compile time and graphql ensuring the datatypes are consistent at runtime. This means that there are many checks in place before even testing the application.

Configurator

The configurator is built with React and uses GraphQL as the core state of the application. @apollo/client is the magic behind all of this. It provides the hooks and rerender triggers for the components when application state changes, which means no render loop.

Client and Device state is held in the same graph, and can be queried from any component in the application. The client state graph is described in the client schema, and device state is described in the device schema.

Because retreiving state from the device is expensive, the client will cache all device state in its graph so that queries are only executed for data which is not available in the graph.

The schema of the client and device state are used to enforce that all code which uses the state is type-safe. This means that changes to the API will not compile unless the configurator and api-server schema is updated to match the types from the API.

Queries

Querying the device is as simple as describing the data you want to retreive:

...

import { useQuery } from "@apollo/client";
import useConnection from "../hooks/useConnection";

const FcStatusProvider: React.FC = () => {
  const { connection } = useConnection();
  const connection = useQuery(
    gql(/* GraphQL */
      `query Status($connection: ID!) {
          connection(connectionId: $connection) {
            device {
              status {
                cycleTime
                i2cError
                cpuload
              }
            }
          }
        }
      `
    ),
    {
      variables: {
        connection,
      },
      // Fetch this data every 100ms
      pollInterval: 100
    }
  );

  ...

  // display all 0 values while the device status is loading
  return (
    <StatusList>
      ...
      <li>I2C error: {device?.status.i2cError ?? 0}</li>
      <li>Cycle Time: {device?.status.cycleTime ?? 0}</li>
      <li>CPU Load: {device?.status.cpuload ?? 0}%</li>
    </StatusList>
  )
};

Running codegen (yarn codegen), will autoamtically ensure that the gql tag will return the exact type shape to match your query!