@betaflight/api
Version:
A high-level API to read data from betaflight flight controllers
268 lines • 11.8 kB
JavaScript
import semver from "semver";
import { WriteBuffer, apiVersion, execute } from "@betaflight/msp";
import { bitCheck, mergeDeep, times } from "../utils";
import { osdFields, OSD_VIDEO_VALUE_TO_TYPE, OSD_UNIT_VALUE_TO_TYPE, osdTimerSources, osdStatisticFields, OSD_PRECISION_VALUE_TO_TYPE, osdWarnings, OSD_VALUE_VISIBLE, osdAlarms, } from "./constants";
import codes from "../codes";
import { OSDVideoTypes, OSDAlarms, OSDTimerSources, OSDWarnings, OSDFields, OSDUnitTypes, OSDStatisticFields, OSDPrecisionTypes, } from "./types";
export { OSDVideoTypes, OSDAlarms, osdAlarms, OSDTimerSources, osdTimerSources, OSDWarnings, osdWarnings, OSDFields, osdFields, OSDUnitTypes, OSDStatisticFields, OSDPrecisionTypes, };
const isVisible = (positionData, profile) => positionData !== -1 && (positionData & (OSD_VALUE_VISIBLE << profile)) !== 0;
const unpackPosition = (positionData) => ({
x: positionData & 0x001f,
y: (positionData >> 5) & 0x001f,
});
const unpackLegacyPosition = (positionData) => positionData === -1 ? { x: 0, y: 0 } : { x: positionData, y: 0 };
const packLegacyPosition = (position, visible) => {
if (visible) {
return position.x === -1 ? 0 : position.x;
}
return -1;
};
const inWriteOrder = (values, sortOrder, subsitutions) => sortOrder.map((orderedKey, i) => { var _a;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return (_a = values.find(({ key }) => key === orderedKey)) !== null && _a !== void 0 ? _a : subsitutions[i]; });
export const readOSDConfig = async (port) => {
var _a, _b;
const api = apiVersion(port);
const data = await execute(port, { code: codes.MSP_OSD_CONFIG });
const expectedDisplayItems = osdFields(api);
const flagsData = data.readU8();
const hasOSD = flagsData !== 0;
const flag0Active = bitCheck(flagsData, 0);
const videoSystem = hasOSD
? (_a = OSD_VIDEO_VALUE_TO_TYPE[data.readU8()]) !== null && _a !== void 0 ? _a : OSDVideoTypes.AUTO
: OSDVideoTypes.AUTO;
const unitMode = hasOSD && semver.gte(api, "1.21.0") && flag0Active
? (_b = OSD_UNIT_VALUE_TO_TYPE[data.readU8()]) !== null && _b !== void 0 ? _b : OSDUnitTypes.IMPERIAL
: OSDUnitTypes.IMPERIAL;
const alarms = hasOSD && semver.gte(api, "1.21.0") && flag0Active
? [
{ key: OSDAlarms.RSSI, value: data.readU8() },
{ key: OSDAlarms.CAP, value: data.readU16() },
]
: [];
let displayItemsCount = expectedDisplayItems.length;
if (hasOSD && semver.gte(api, "1.36.0") && flag0Active) {
// This value was obsoleted by the introduction of configurable timers, and has been reused to encode the number of display elements sent in this command
data.readU8();
const tmp = data.readU8();
if (semver.gte(api, "1.37.0")) {
displayItemsCount = tmp;
}
}
else {
alarms.push({ key: OSDAlarms.TIME, value: data.readU16() });
}
if (hasOSD && semver.gte(api, "1.36.0") && flag0Active) {
alarms.push({ key: OSDAlarms.ALT, value: data.readU16() });
}
const haveMax7456Video = bitCheck(flagsData, 4) || (flagsData === 1 && semver.lt(api, "1.34.0"));
const flags = {
hasOSD,
haveMax7456Video,
isMax7456Detected: bitCheck(flagsData, 5) || (haveMax7456Video && semver.lt(api, "1.43.0")),
haveOsdFeature: bitCheck(flagsData, 0) || (flagsData === 1 && semver.lt(api, "1.34.0")),
isOsdSlave: bitCheck(flagsData, 1) && semver.gte(api, "1.34.0"),
};
// Read display element positions, the parsing is done later because we need the number of profiles
const itemPositions = semver.gte(api, "1.21.0")
? times(() => data.readU16(), displayItemsCount)
: times(() => data.read16(), displayItemsCount);
const expectedStaticFields = osdStatisticFields(api);
const staticItems = semver.gte(api, "1.36.0")
? times((i) => {
var _a;
return ({
key: (_a = expectedStaticFields[i]) !== null && _a !== void 0 ? _a : OSDStatisticFields.UNKNOWN,
enabled: data.readU8() === 1,
});
}, data.readU8())
: [];
// Parse configurable timers
const timersCount = data.readU8();
const timerSources = osdTimerSources(api);
const timers = semver.gte(api, "1.36.0")
? times((i) => {
var _a, _b;
const timerData = data.readU16();
return {
key: i,
src: (_a = timerSources[timerData & 0x0f]) !== null && _a !== void 0 ? _a : OSDTimerSources.UNKNOWN,
precision: (_b = OSD_PRECISION_VALUE_TO_TYPE[(timerData >> 4) & 0x0f]) !== null && _b !== void 0 ? _b : OSDPrecisionTypes.SECOND,
time: (timerData >> 8) & 0xff,
};
}, timersCount)
: [];
// Parse warning
const expectedWarnings = osdWarnings(api);
let warningCount = expectedWarnings.length;
let warningFlags = data.readU16();
if (semver.gte(api, "1.41.0")) {
warningCount = data.readU8();
// the flags were replaced with a 32bit version
warningFlags = data.readU32();
}
const warnings = semver.gte(api, "1.36.0")
? times((i) => {
var _a;
return ({
key: (_a = expectedWarnings[i]) !== null && _a !== void 0 ? _a : OSDWarnings.UNKNOWN,
enabled: (warningFlags & (1 << i)) !== 0,
});
}, warningCount)
: [];
const osdProfiles = semver.gte(api, "1.41.0")
? {
count: data.readU8(),
selected: data.readU8() - 1,
}
: {
count: 1,
selected: 0,
};
const parameters = {
overlayRadioMode: semver.gte(api, "1.41.0") ? data.readU8() : 0,
cameraFrameWidth: semver.gte(api, "1.43.0") ? data.readU8() : 24,
cameraFrameHeight: semver.gte(api, "1.43.0") ? data.readU8() : 11,
};
const displayItems = itemPositions.map((positionData, i) => {
var _a;
return ({
key: (_a = expectedDisplayItems[i]) !== null && _a !== void 0 ? _a : OSDFields.UNKNOWN,
position: semver.gte(api, "1.21.0")
? unpackPosition(positionData)
: unpackLegacyPosition(positionData),
visibilityProfiles: times((profileIndex) => isVisible(positionData, profileIndex), osdProfiles.count),
});
});
return {
flags,
statisticItems: staticItems,
displayItems,
alarms,
warnings,
timers,
videoSystem,
osdProfiles,
parameters,
unitMode,
};
};
export const writeOSDDisplayItem = async (port, { key, visibilityProfiles, position }) => {
var _a;
const data = new WriteBuffer();
const api = apiVersion(port);
const itemOrder = osdFields(api);
const index = itemOrder.indexOf(key);
if (index === -1) {
throw new Error(`OSDFields.${OSDFields[key]} does not exist on device`);
}
const packedPosition = semver.gte(api, "1.21.0")
? visibilityProfiles.reduce((packedVisible, visibilityProfile, i) => packedVisible | (visibilityProfile ? OSD_VALUE_VISIBLE << i : 0), 0) |
((position.y & 0x001f) << 5) |
position.x
: packLegacyPosition(position, (_a = visibilityProfiles[0]) !== null && _a !== void 0 ? _a : false);
data.push8(index);
data.push16(packedPosition);
await execute(port, {
code: codes.MSP_SET_OSD_CONFIG,
data,
});
};
const writeOSDOtherData = async (port, { flags, videoSystem, unitMode, alarms, warnings, osdProfiles, parameters, }) => {
var _a, _b, _c, _d, _e, _f, _g, _h;
const api = apiVersion(port);
const data = new WriteBuffer();
data.push(-1, videoSystem);
if (flags.hasOSD && semver.gte(api, "1.21.0")) {
data.push8(unitMode);
// watch out, order matters! match the firmware
data.push8((_b = (_a = alarms[0]) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : 0);
data.push16((_d = (_c = alarms[1]) === null || _c === void 0 ? void 0 : _c.value) !== null && _d !== void 0 ? _d : 0);
if (semver.lt(api, "1.36.0")) {
data.push16((_f = (_e = alarms[2]) === null || _e === void 0 ? void 0 : _e.value) !== null && _f !== void 0 ? _f : 0);
}
else {
// This value is unused by the firmware with configurable timers
data.push16(0);
}
data.push16((_h = (_g = alarms[3]) === null || _g === void 0 ? void 0 : _g.value) !== null && _h !== void 0 ? _h : 0);
if (semver.gte(api, "1.37.0")) {
const warningFlags = warnings.reduce((acc, warning, i) => (warning.enabled ? acc | (1 << i) : acc), 0);
data.push16(warningFlags);
if (semver.gte(api, "1.41.0")) {
data.push32(warningFlags);
data.push8(osdProfiles.selected + 1);
data.push8(parameters.overlayRadioMode);
}
if (semver.gte(api, "1.43.0")) {
data.push8(parameters.cameraFrameWidth);
data.push8(parameters.cameraFrameHeight);
}
}
}
await execute(port, { code: codes.MSP_SET_OSD_CONFIG, data });
};
export const writeOSDAlarm = async (port, alarm) => {
const osdConfig = await readOSDConfig(port);
await writeOSDOtherData(port, {
...osdConfig,
alarms: inWriteOrder([alarm], osdAlarms(), osdConfig.alarms),
});
};
export const writeOSDWarning = async (port, warning) => {
const api = apiVersion(port);
const osdConfig = await readOSDConfig(port);
await writeOSDOtherData(port, {
...osdConfig,
warnings: inWriteOrder([warning], osdWarnings(api), osdConfig.warnings),
});
};
export const writeOSDSelectedProfile = async (port, selectedIndex) => {
const osdConfig = await readOSDConfig(port);
await writeOSDOtherData(port, {
...osdConfig,
osdProfiles: { ...osdConfig.osdProfiles, selected: selectedIndex },
});
};
export const writeOSDVideoSystem = async (port, videoSystem) => {
const osdConfig = await readOSDConfig(port);
await writeOSDOtherData(port, { ...osdConfig, videoSystem });
};
export const writeOSDUnitMode = async (port, unitMode) => {
const osdConfig = await readOSDConfig(port);
await writeOSDOtherData(port, { ...osdConfig, unitMode });
};
export const writePartialOSDParameters = async (port, parameters) => {
const osdConfig = await readOSDConfig(port);
await writeOSDOtherData(port, {
...osdConfig,
parameters: mergeDeep(osdConfig.parameters, parameters),
});
};
export const writeOSDStatisticItem = async (port, { key, enabled }) => {
const data = new WriteBuffer();
const staticItemsOrder = osdStatisticFields(apiVersion(port));
const index = staticItemsOrder.indexOf(key);
if (index === -1) {
throw new Error(`OSDStaticFields.${OSDStatisticFields[key]} does not exist on device`);
}
data.push8(index);
data.push16(Number(enabled));
data.push8(0);
await execute(port, { code: codes.MSP_SET_OSD_CONFIG, data });
};
export const writeOSDTimer = async (port, timer) => {
const data = new WriteBuffer();
data.push(-2, timer.key);
data.push16((timer.src & 0x0f) |
((timer.precision & 0x0f) << 4) |
((timer.time & 0xff) << 8));
await execute(port, { code: codes.MSP_SET_OSD_CONFIG, data });
};
export const writeOSDChar = async (port, charIndex, charBytes) => {
const buffer = new WriteBuffer();
buffer.push8(charIndex);
buffer.push(...charBytes);
await execute(port, { code: codes.MSP_OSD_CHAR_WRITE, data: buffer });
};
//# sourceMappingURL=index.js.map