Sync edgetx to Gitea
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
# CLI Commands
|
||||
|
||||
Summary of the commands available in CLI mode.
|
||||
|
||||
## Always available commands
|
||||
|
||||
| Function | Parameters (if any) | Description |
|
||||
|--|--|--|
|
||||
| `beep` |`[<frequency>] [<duration>]`| Play a beep sound. |
|
||||
| `ls` | `<directory>`| List files in a directory. |
|
||||
| `read` |`<filename>` | Read a file. |
|
||||
| `readsd` |`<start sector> <sectors count> <read buffer size (sectors)>`| Read sectors from SD card. |
|
||||
| `testsd` | | Test SD card. |
|
||||
| `play` | `<filename>` | Play a file. |
|
||||
| `reboot` | `[wdt]` | Reboot the device. |
|
||||
| `set` | `<what> <value>` | Set a configuration parameter. |
|
||||
| `trigger_watchdog_reset` | | Trigger a watchdog reset. |
|
||||
| `help` | `[<command>]` | Show help for a command. |
|
||||
|
||||
## If `ENABLE_SERIAL_PASSTHROUGH` set
|
||||
|
||||
| Function | Parameters (if any) | Description |
|
||||
|--|--|--|
|
||||
| `serialpassthrough` | `<port type> [<port number>] [<baudrate>]` | Enable serial passthrough. |
|
||||
|
||||
|
||||
## If `DEBUG` set
|
||||
|
||||
| Function | Parameters (if any) | Description |
|
||||
|--|--|--|
|
||||
| `print` | `<address> [<size>] \| <what>` | Print a value. |
|
||||
| `p` | `<address> [<size>] \| <what>` | Print a value. |
|
||||
| `stackinfo` | | Show stack info. |
|
||||
| `meminfo` | | Show memory info. |
|
||||
| `test` | `new \| graphics \| memspd` | Run a test. |
|
||||
| `trace` | `on \| off` | Enable/disable tracing. |
|
||||
| `debugvars` | | Show debug variables. |
|
||||
| `repeat` | `<interval> <command>` | Repeat a command. |
|
||||
| `testfatfs` | | Test FatFS SD card. |
|
||||
|
||||
|
||||
## If `JITTER_MEASURE` set
|
||||
|
||||
| Function | Parameters (if any) | Description |
|
||||
|--|--|--|
|
||||
| `jitter` | | Measure jitter. |
|
||||
|
||||
|
||||
## If `INTERNAL_GPS` set
|
||||
|
||||
| Function | Parameters (if any) | Description |
|
||||
|--|--|--|
|
||||
| `gps` | `<baudrate>\|$<command>\|trace` | Send a command to the GPS. |
|
||||
|
||||
|
||||
## If `SPACEMOUSE` set
|
||||
|
||||
| Function | Parameters (if any) | Description |
|
||||
|--|--|--|
|
||||
| `spacemouse` | `poll \| tare \| startstreaming \| stopstreaming \| trace` | Control the SpaceMouse device. |
|
||||
|
||||
|
||||
## If `BLUETOOTH` set
|
||||
|
||||
| Function | Parameters (if any) | Description |
|
||||
|--|--|--|
|
||||
| `bt` | `<baudrate>\|<command>` | Send a command to the Bluetooth module. |
|
||||
|
||||
|
||||
## If `TP_GT911` set
|
||||
|
||||
| Function | Parameters (if any) | Description |
|
||||
|--|--|--|
|
||||
| `reset_gt911` | | Reset the GT911 touchscreen controller. |
|
||||
|
||||
|
||||
## If `ACCESS_DENIED` && `DEBUG_CRYPT` set
|
||||
|
||||
| Function | Parameters (if any) | Description |
|
||||
|--|--|--|
|
||||
| `crypt` | `<string to be encrypted>` | Encrypt a string. |
|
||||
@@ -0,0 +1,122 @@
|
||||
# Control Inputs Refactor
|
||||
|
||||
This page documents the concepts and ideas implemented in [#2631](https://github.com/EdgeTX/edgetx/pull/2631).
|
||||
|
||||
## General
|
||||
|
||||
In `YAML`, only canonical names are used. The UI uses however translated labels
|
||||
that can be overridden with custom labels.
|
||||
|
||||
|
||||
## Main Controls
|
||||
|
||||
Sometimes called sticks, gimbals, wheel and throttle: whatever is used to control
|
||||
the vehicle.
|
||||
|
||||
To be able to implement the stick modes used usually in RC aircrafts, it is
|
||||
necessary to map the physical controls to canonical labels.
|
||||
|
||||
Canonical names shall be used for physical reference:
|
||||
- calibration data,
|
||||
|
||||
Canonical labels are used for logical references:
|
||||
- inputs
|
||||
- mixer lines
|
||||
- etc
|
||||
|
||||
Corresponding to the canonical labels, UI labels are used to present translated
|
||||
names to the user. These UI labels can be overidden by the user by defining custom labels.
|
||||
|
||||
### Physical main controls
|
||||
|
||||
- Radios with 2 gimbals:
|
||||
- `LH`: left gimbal, vertical axis
|
||||
- `LV`: left gimbal, horizontal axis
|
||||
- `RV`: right gimbal, vertical axis
|
||||
- `RH`: right gimbal, horizontal axis
|
||||
|
||||
- Surface radios:
|
||||
- `WH`: steering wheel
|
||||
- `TR`: throttle trigger
|
||||
|
||||
### Canonical names
|
||||
|
||||
- Radios with 2 gimbals:
|
||||
- `Rud`
|
||||
- `Ele`
|
||||
- `Thr`
|
||||
- `Ail`
|
||||
|
||||
- Surface radios:
|
||||
- `Whl`
|
||||
- `Thr`
|
||||
|
||||
|
||||
## Additional controls
|
||||
|
||||
Additional analog controls have canonical names equal to the canonical labels.
|
||||
|
||||
On top of these, labels can be defined to be used in the UI and thus correspond
|
||||
to the real labels printed on the radio case on in the user manual. These labels can be
|
||||
overidden by the user by defining custom labels.
|
||||
|
||||
### Potentiometers, sliders, joystick
|
||||
|
||||
Canonical / physical names:
|
||||
- `P1`, `P2`, etc
|
||||
- `SL1`, `SL2`, etc
|
||||
- `JSx` and `JSy`
|
||||
|
||||
### Switches
|
||||
|
||||
Canonical names:
|
||||
- `SA` to `SZ`
|
||||
|
||||
|
||||
## APIs
|
||||
|
||||
We use different APIs for the different types of names and labels.
|
||||
|
||||
All APIs used the same types as follows:
|
||||
|
||||
```
|
||||
enum {
|
||||
ADC_INPUT_MAIN,
|
||||
ADC_INPUT_POT,
|
||||
ADC_INPUT_AXIS,
|
||||
ADC_INPUT_VBAT,
|
||||
ADC_INPUT_RTC_BAT
|
||||
};
|
||||
```
|
||||
|
||||
### Physical names
|
||||
|
||||
Physical names are manipulated by using the following functions:
|
||||
|
||||
```
|
||||
const char* analogGetPhysicalName(uint8_t type, uint8_t idx);
|
||||
int analogLookupPhysicalIdx(uint8_t type, const char* name, size_t len);
|
||||
```
|
||||
|
||||
### Canonical names
|
||||
|
||||
Canonical names are retrieved by using the following function:
|
||||
|
||||
```
|
||||
const char* analogGetCanonicalName(uint8_t type, uint8_t idx);
|
||||
int analogLookupCanonicalIdx(uint8_t type, const char* name, size_t len);
|
||||
```
|
||||
|
||||
### UI labels
|
||||
|
||||
UI labels are retrieved using the following functions:
|
||||
|
||||
```
|
||||
# Retrievs the name of the input (custom or canonical)
|
||||
const char* getAnalogLabel(uint8_t type, uint8_t idx);
|
||||
|
||||
# Custom labels
|
||||
void analogSetCustomLabel(uint8_t type, uint8_t idx, const char* str, size_t len);
|
||||
const char* analogGetCustomLabel(uint8_t type, uint8_t idx);
|
||||
bool analogHasCustomLabel(uint8_t type, uint8_t idx);
|
||||
```
|
||||
@@ -0,0 +1,37 @@
|
||||
# External Module Protocols
|
||||
|
||||
# PPM
|
||||
|
||||

|
||||
|
||||
# PXX (FrSky XJT)
|
||||
|
||||

|
||||
|
||||
# ACCESS (FrSky RM900 ACCESS)
|
||||
|
||||

|
||||
|
||||
# DSM2 (Spektrum hacked module)
|
||||
|
||||
125000 bauds 8N1 - No parity
|
||||
|
||||

|
||||
|
||||
# SBUS (Normal mode)
|
||||
|
||||
100000 bauds - Inverted - Even parity
|
||||
|
||||

|
||||
|
||||
# Multimodule
|
||||
|
||||
100000 bauds - Even parity - 2 stops bit
|
||||
|
||||

|
||||
|
||||
# Crossfire
|
||||
|
||||
400000 bauds (or reduced depending on radio capabilities) - Inverted
|
||||
|
||||

|
||||
@@ -0,0 +1,38 @@
|
||||
# Mixer Synchronisation
|
||||
|
||||
The EdgeTX mixer can be synchronised with some modules.
|
||||
|
||||
Here is a short list of synchronised modules/protocols:
|
||||
- any module using `CRSF` (Crossfire, Tracer, ExpressLRS)
|
||||
- `GHST`
|
||||
- `MPM`
|
||||
- PXX1 / PXX2 (based on FrSky's heartbeat mechanism)
|
||||
|
||||
## Mixer task
|
||||
|
||||
The mixer task is responsible for mainly 3 things:
|
||||
- trigger the ADC conversions
|
||||
- compute channel values
|
||||
- send the channels to the module(s)
|
||||
|
||||
## Mixer scheduler
|
||||
|
||||
The mixer task is triggered cycle-by-cycle with the help of a scheduler controlled by a timer. This timer is either set to a fixed (non-synchronised modules) or an adjustable value.
|
||||
|
||||
Synchronised modules are able to adjust the value of this timer by implementing a regulation loop that allows to optimise the timing for some metrics. Most of the time the module wants to optimise the latency. At the same time, it allows to prevent sampling aliasing caused by the ADC sample frequency being different from the frequency at which channels are sent over the air (historical reason for implementing the mixer scheduler in the first place).
|
||||
|
||||
[](../assets/images/mixersync.png)
|
||||
|
||||
If we look at the regulation loop, there are indeed two parameters that you want to be able to set:
|
||||
- frequency
|
||||
- phase
|
||||
|
||||
The mixer scheduler will take these two parameters as input to set the timer for the next cycle and try to adjust accordingly.
|
||||
|
||||
## Regulation loop
|
||||
|
||||
The synchronisation mechanism can be seen as a classical regulation loop. The set point in this regulation loop is the mixer scheduler timer. The feedback is given by the module to directly adjust the set point. Please note that the regulation is actually living in the module: this means that the module is responsible for adjusting the set point according to its needs (in most cases, minimising latency).
|
||||
|
||||
## Heartbeat mechanism
|
||||
|
||||
FrSky implements the synchronisation in a different way, so the mixer scheduler is by-passed. When a heartbeat is detected, the mixer is triggered and the safety timer is reset.
|
||||
@@ -0,0 +1,275 @@
|
||||
# YAML Parser Generator
|
||||
|
||||
The YAML storage used in EdgeTX since version `2.6` relies on the radio side on a parser
|
||||
generator that creates a structure in flash memory to be used by the parser to
|
||||
determine a precise mapping `attribute -> [bit address, bit length]`.
|
||||
|
||||
This is done by using `libclang` Python bindings to parse `ModelData` and `RadioData`
|
||||
as well as related structures to compute precise bit-offsets for each attribute.
|
||||
|
||||
## Generating YAML parsers for existing radios
|
||||
|
||||
As the Python script used only works for now with certain versions of libclang,
|
||||
it is highly recommended to use the EdgeTX dev container to generate YAML parsers.
|
||||
|
||||
The master script used will generate the parsers for all different radios:
|
||||
```shell
|
||||
docker run -it --rm -v $(pwd):/src \
|
||||
ghcr.io/edgetx/edgetx-dev \
|
||||
/src/tools/generate-yaml.sh
|
||||
```
|
||||
|
||||
It is also possible to generate only for certain radios by using the `FLAVOR` variable:
|
||||
```shell
|
||||
docker run -it --rm -v $(pwd):/src \
|
||||
-e "FLAVOR=tx16s;x12s;nv14;x7;x9d;x9dp"
|
||||
ghcr.io/edgetx/edgetx-dev \
|
||||
/src/tools/generate-yaml.sh
|
||||
```
|
||||
|
||||
Please check `tools/generate-yaml.sh` for available target names.
|
||||
|
||||
## Generating YAML parsers for new radios
|
||||
|
||||
When new radios are integrated, it might be necessary to generate new YAML parser structures
|
||||
if that new radio does not fit 100% to an existing one in terms of storage.
|
||||
|
||||
In this case, a number of preparatory steps shall be taken:
|
||||
- `radio/src/storage/yaml/CMakeLists.txt`: add the proper file name depending on radio
|
||||
and naming conventions (use downcase target name as used in other scripts).
|
||||
- `radio/src/storage/yaml/yaml_datastructs.cpp`: add the proper generated file depending
|
||||
on the radio.
|
||||
|
||||
New radios shall not add conversions routines from the old storage format. This means
|
||||
that the `STORAGE_CONVERSIONS` variable should be set appriately to `221` (YAML format)
|
||||
in `radio/src/storage/conversions/CMakeLists.txt`.
|
||||
|
||||
This will effect that the conversion routines will be excluded from compilation.
|
||||
|
||||
|
||||
## A word of caution on changing `RadioData` and `ModelData` structures
|
||||
|
||||
The YAML parser generator does not account for padding bits added automatically by the
|
||||
compiler. This means that great care should be taken to understand how these padding bits
|
||||
are added when using bit fields.
|
||||
|
||||
For example:
|
||||
```c++
|
||||
NOBACKUP(uint8_t countryCode:2);
|
||||
NOBACKUP(int8_t pwrOnSpeed:3);
|
||||
NOBACKUP(int8_t pwrOffSpeed:3);
|
||||
```
|
||||
|
||||
These fields define a bit field which fills the supporting `8-bit` type completely. If however
|
||||
another field would be introduced, it could add padding bits *implicitely*.
|
||||
|
||||
```c++
|
||||
NOBACKUP(uint8_t countryCode:2);
|
||||
NOBACKUP(int8_t pwrOnSpeed:3);
|
||||
NOBACKUP(int8_t pwrOffSpeed:3);
|
||||
// New field
|
||||
NOBACKUP(int8_t myField:2);
|
||||
```
|
||||
|
||||
The new field `myField` here will introduce a new bit field spread on `8 bits` with only `2`
|
||||
defined explicitly. The compiler however might introduce `6 padding bits` at the end,
|
||||
depending on what is defined *after*. If the next attributes are also bit field members, the
|
||||
padding bits will be added after the next bit field, and so on.
|
||||
|
||||
If however a normal attribute is defined next, the padding bits will be added directly after
|
||||
`myField`, and the parser generator will **not** account for them, so that a shift in the bit
|
||||
address will be introduced and the complete structure will be broken.
|
||||
|
||||
```c++
|
||||
NOBACKUP(uint8_t countryCode:2);
|
||||
NOBACKUP(int8_t pwrOnSpeed:3);
|
||||
NOBACKUP(int8_t pwrOffSpeed:3);
|
||||
// New field
|
||||
NOBACKUP(int8_t myField:2);
|
||||
// Normal type
|
||||
NOBACKUP(int8_t fullField);
|
||||
```
|
||||
|
||||
In this case, silent padding bits shall be explicitly defined to avoid the issue:
|
||||
```c++
|
||||
NOBACKUP(uint8_t countryCode:2);
|
||||
NOBACKUP(int8_t pwrOnSpeed:3);
|
||||
NOBACKUP(int8_t pwrOffSpeed:3);
|
||||
// New field
|
||||
NOBACKUP(int8_t myField:2);
|
||||
NOBACKUP(int8_t paddingBits:6 SKIP);
|
||||
// Normal type
|
||||
NOBACKUP(int8_t fullField);
|
||||
```
|
||||
|
||||
Here we use a special attribute `SKIP` to instruct the parser generator to ignore this field
|
||||
but still account for it in the bit count.
|
||||
|
||||
## Custom fields
|
||||
|
||||
When a special behavior shall be implemented for certain attributes, custom functions
|
||||
can be used to implement that behavior.
|
||||
|
||||
This allows for covering the following use cases:
|
||||
- custom input/output formats
|
||||
- backward compatibility
|
||||
|
||||
### Custom input/output formats
|
||||
|
||||
Attributes that require a specific input or output format can use the `CUST()` macro.
|
||||
This macro takes 2 parameters as follows:
|
||||
- `read` function: used for reading YAML payload.
|
||||
- `write` function: used for writing YAML payload.
|
||||
|
||||
Example:
|
||||
```
|
||||
NOBACKUP(int8_t beepVolume:4 CUST(r_5pos,w_5pos));
|
||||
```
|
||||
|
||||
Here the parser generator will assume that `r_5pos` and `w_5pos` are 2 existing functions,
|
||||
with the following prototype:
|
||||
```c++
|
||||
bool w_5pos(const YamlNode* node, uint32_t val, yaml_writer_func wf, void* opaque);
|
||||
uint32_t r_5pos(const YamlNode* node, const char* val, uint8_t val_len);
|
||||
```
|
||||
|
||||
### Full Custom Attributes
|
||||
|
||||
Fully Custom Attributes are used when there is no direct relationship between a YAML tag
|
||||
and a member of the data structure. It allows mainly for implementing complex logic
|
||||
and backward compatibility. Backward compatibility is reached when using such an attribute
|
||||
for which only the `read` function is defined, but no `write` function.
|
||||
|
||||
Here is an example of such backward compatibility attributes:
|
||||
```
|
||||
CUST_ATTR(auxSerialMode, r_serialMode, nullptr);
|
||||
CUST_ATTR(aux2SerialMode, r_serialMode, nullptr);
|
||||
NOBACKUP(uint16_t serialPort ARRAY(STORAGE_SERIAL_PORTS,struct_serialConfig,nullptr));
|
||||
```
|
||||
|
||||
Both `auxSerialMode` and `aux2SerialMode` are defined here right in front of the new
|
||||
`serialPort` array to simplify offset calculations in the function setting the values.
|
||||
|
||||
The same macro can also be used to implement `read`/`write` logic, as is used for
|
||||
module configuration:
|
||||
```
|
||||
CUST_ATTR(subType,r_modSubtype,w_modSubtype);
|
||||
...
|
||||
uint8_t subType:4 SKIP;
|
||||
```
|
||||
|
||||
Here the real `subType` attribute is marked `SKIP` so that it is ignored by the parser
|
||||
generator, and a full custom attribute is used to set this attribute instead
|
||||
(`CUST_ATTR(subType,r_modSubtype,w_modSubtype)`). This is done this way to allow for different
|
||||
`enum` types depending on the module type.
|
||||
|
||||
The custom functions `r_modSubtype` and `w_modSubtype` select the proper lookup table
|
||||
depending on another attribute (`type`) in `ModuleData`, which is not possible otherwise.
|
||||
|
||||
It allows also at the same time to map the MPM protocol numbers to the real ones (those
|
||||
as defined in the MPM documentation), whereby EdgeTX use internally different protocol numbers
|
||||
for historical reasons (special treatment of FrSky protocols).
|
||||
|
||||
### Union members
|
||||
|
||||
EdgeTX uses `union` extensively. This means however that the parser needs to know which
|
||||
member to select / output when writing YAML.
|
||||
|
||||
This is done by implementing a `selector`, for example as seen in the TelemetrySensor
|
||||
structure:
|
||||
|
||||
```c++
|
||||
union {
|
||||
NOBACKUP(PACK(struct {
|
||||
uint16_t ratio;
|
||||
int16_t offset;
|
||||
}) custom);
|
||||
NOBACKUP(PACK(struct {
|
||||
uint8_t source;
|
||||
uint8_t index;
|
||||
uint16_t spare SKIP;
|
||||
}) cell);
|
||||
NOBACKUP(PACK(struct {
|
||||
int8_t sources[4];
|
||||
}) calc);
|
||||
NOBACKUP(PACK(struct {
|
||||
uint8_t source;
|
||||
uint8_t spare[3] SKIP;
|
||||
}) consumption);
|
||||
NOBACKUP(PACK(struct {
|
||||
uint8_t gps;
|
||||
uint8_t alt;
|
||||
uint16_t spare SKIP;
|
||||
}) dist);
|
||||
uint32_t param;
|
||||
} NAME(cfg) FUNC(select_sensor_cfg);
|
||||
```
|
||||
|
||||
The `TelemetrySensor` defines a anonymous `union`, which the `NAME(cfg)` macro gives a tag
|
||||
name, while `FUNC(select_sensor_cfg)` defines the selector function.
|
||||
|
||||
The `selector` function is then implemented as follows in `yaml_datastruct_funcs.cpp`:
|
||||
```c++
|
||||
uint8_t select_sensor_cfg(void* user, uint8_t* data, uint32_t bitoffs)
|
||||
{
|
||||
data += bitoffs >> 3UL;
|
||||
data -= offsetof(TelemetrySensor, param);
|
||||
const TelemetrySensor* sensor = (const TelemetrySensor*)data;
|
||||
|
||||
if (sensor->unit < UNIT_FIRST_VIRTUAL) {
|
||||
if (sensor->type == TELEM_TYPE_CALCULATED) {
|
||||
switch(sensor->formula) {
|
||||
case TELEM_FORMULA_CELL: return 1; // cell
|
||||
case TELEM_FORMULA_DIST: return 4; // dist
|
||||
case TELEM_FORMULA_CONSUMPTION: return 3; // consumption
|
||||
case TELEM_FORMULA_TOTALIZE: return 3; // consumption
|
||||
default: return 2; // calc
|
||||
}
|
||||
} else {
|
||||
return 0; // custom
|
||||
}
|
||||
}
|
||||
|
||||
return 5;
|
||||
}
|
||||
```
|
||||
|
||||
The function starts with computing the structure index to obtain a pointer and basically
|
||||
returns an index which corresponds to which union member shall be used (as defined,
|
||||
counting from `0`).
|
||||
|
||||
### Forced `enum` type
|
||||
|
||||
The parser generator uses member types as defined to determine what to generate.
|
||||
However, some structure members are in fact `enum` types, whereby declared as `uint8_t` or
|
||||
similar.
|
||||
|
||||
So that these values can be output / read as their string representation, it is necessary
|
||||
to specify the `enum` type that shall be used as follows:
|
||||
```c++
|
||||
uint8_t func ENUM(LogicalSwitchesFunctions);
|
||||
```
|
||||
|
||||
This will also force the parser generator to process that `enum` type in case it is not used
|
||||
explictly (by type definition) somewhere else.
|
||||
|
||||
This can also be used to force the generation of the lookup table even if the values are not
|
||||
used directly:
|
||||
```c++
|
||||
uint16_t srcRaw:10 ENUM(MixSources) CUST(r_mixSrcRaw,w_mixSrcRaw);
|
||||
```
|
||||
|
||||
This will force the `enum` type to be considered for generation, whereby this specific
|
||||
attribute will used custom functions.
|
||||
|
||||
In this particular case the function can then make use of this lookup table as they see fit.
|
||||
|
||||
For example with (reading):
|
||||
```
|
||||
yaml_parse_enum(enum_MixSources, val, val_len);
|
||||
```
|
||||
|
||||
Or (writing):
|
||||
```
|
||||
str = yaml_output_enum(val, enum_MixSources);
|
||||
```
|
||||
Reference in New Issue
Block a user