UNPKG

@betaflight/api

Version:

A high-level API to read data from betaflight flight controllers

268 lines 11.8 kB
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