diff --git a/.eslintrc.json b/.eslintrc.json index 14812b676..439f4b436 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,5 +1,6 @@ { - "extends": ["eslint:recommended", "plugin:react/all"], + "extends": ["eslint:recommended", "plugin:react/all", "plugin:jest/recommended"], + "ignorePatterns": ["service-worker.js"], "plugins": [ "react" ], @@ -32,9 +33,17 @@ "object-curly-newline": ["error", { "ObjectExpression": { "multiline": true, "minProperties": 2 }, "ObjectPattern": { "multiline": true, "minProperties": 2 }, - "ImportDeclaration": { "multiline": true, "minProperties": 1 }, + "ImportDeclaration": { "multiline": true, "minProperties": 2 }, "ExportDeclaration": { "multiline": true, "minProperties": 1 } - }] + }], + "comma-dangle": ["error", { + "arrays": "always-multiline", + "objects": "always-multiline", + "imports": "always-multiline", + "exports": "always-multiline", + "functions": "never" + }], + "object-property-newline": "error" }, "settings": { "react": { @@ -59,7 +68,8 @@ "Promise": false, "FileReader": false, "Blob": false, - "localStorage": false + "localStorage": false, + "__dirname": false }, "parser": "babel-eslint" } diff --git a/.travis.yml b/.travis.yml index d6b81871f..f7d492db4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,5 @@ language: node_js +script: + - yarn coverage node_js: - node diff --git a/README.md b/README.md index 7d16a5097..aad4d0596 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ ESC Configurator -[![Build Status](https://travis-ci.org/stylesuxx/esc-configurator.svg?branch=develop)](https://travis-ci.org/stylesuxx/esc-configurator) [![Crowdin](https://badges.crowdin.net/esc-configuratorcom/localized.svg)](https://crowdin.com/project/esc-configuratorcom) [![Netlify Status](https://api.netlify.com/api/v1/badges/d297f59c-7496-49fb-9803-1cf2876aaad4/deploy-status)](https://app.netlify.com/sites/sad-goodall-6b6045/deploys) [![Discord](https://img.shields.io/discord/822952715944460368.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/QvSS5dk23C) +[![Build Status](https://travis-ci.org/stylesuxx/esc-configurator.svg?branch=develop)](https://travis-ci.org/stylesuxx/esc-configurator) [![codecov](https://codecov.io/gh/stylesuxx/esc-configurator/branch/develop/graph/badge.svg?token=WLZXIOEDP7)](https://codecov.io/gh/stylesuxx/esc-configurator) + [![Crowdin](https://badges.crowdin.net/esc-configuratorcom/localized.svg)](https://crowdin.com/project/esc-configuratorcom) [![Netlify Status](https://api.netlify.com/api/v1/badges/d297f59c-7496-49fb-9803-1cf2876aaad4/deploy-status)](https://app.netlify.com/sites/sad-goodall-6b6045/deploys) [![Discord](https://img.shields.io/discord/822952715944460368.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/QvSS5dk23C) # ESC Configurator - PWA (Progressive Web App) A progressive web-app to flash your BLHELI_S capable ESC's directly from the web using the [Web Serial API](https://wicg.github.io/serial/) or the [Web USB API](https://wicg.github.io/webusb/) as a fallback for Chrome on Android. The Web USB solution will work on a majority of android devices, but not all. Unfortunately it is not possible to detect if it will work, you will simply have to try. Also see this [SO thread](https://stackoverflow.com/questions/66771484/webusb-can-not-claim-device-on-some-android-devices) and this [Chromium issue](https://bugs.chromium.org/p/chromium/issues/detail?id=1099521). diff --git a/package.json b/package.json index a05b4a581..0dd62bb49 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,17 @@ { "name": "esc-configurator", - "version": "0.9.1", + "version": "0.10.0", "private": true, "dependencies": { "@palmabit/react-cookie-law": "^0.6.2", "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", + "compare-versions": "^3.6.0", + "date-fns": "^2.19.0", "dateformat": "^4.5.1", "i18next": "^19.9.0", + "rc-slider": "^9.7.2", "react": "^17.0.1", "react-dom": "^17.0.1", "react-gtm-module": "^2.0.11", @@ -39,7 +42,8 @@ "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", - "coverage": "react-scripts test --coverage --watchAll=false", + "test-once": "yarn test --coverage --watchAll=false", + "coverage": "yarn test-once && codecov", "eject": "react-scripts eject", "lint": "eslint ./src --ext .jsx,.js" }, @@ -65,12 +69,19 @@ "@typescript-eslint/eslint-plugin": "^4.0.0", "@typescript-eslint/parser": "^4.0.0", "babel-eslint": "^10.0.0", + "codecov": "^3.8.1", "eslint": "^7.5.0", "eslint-config-react-app": "^6.0.0", "eslint-plugin-flowtype": "^5.2.0", "eslint-plugin-import": "^2.22.0", + "eslint-plugin-jest": "^24.3.2", "eslint-plugin-jsx-a11y": "^6.3.1", "eslint-plugin-react": "^7.20.3", - "eslint-plugin-react-hooks": "^4.0.8" - } + "eslint-plugin-react-hooks": "^4.0.8", + "pre-commit": "^1.2.2" + }, + "pre-commit": [ + "lint", + "test-once" + ] } diff --git a/src/Containers/App/images/CF_settings_black.svg b/src/Components/App/images/CF_settings_black.svg similarity index 100% rename from src/Containers/App/images/CF_settings_black.svg rename to src/Components/App/images/CF_settings_black.svg diff --git a/src/Containers/App/images/CF_settings_white.svg b/src/Components/App/images/CF_settings_white.svg similarity index 100% rename from src/Containers/App/images/CF_settings_white.svg rename to src/Components/App/images/CF_settings_white.svg diff --git a/src/Containers/App/images/icon_128.png b/src/Components/App/images/icon_128.png similarity index 100% rename from src/Containers/App/images/icon_128.png rename to src/Components/App/images/icon_128.png diff --git a/src/Containers/App/images/loading-bars.svg b/src/Components/App/images/loading-bars.svg similarity index 100% rename from src/Containers/App/images/loading-bars.svg rename to src/Components/App/images/loading-bars.svg diff --git a/src/Containers/App/images/logo.svg b/src/Components/App/images/logo.svg similarity index 100% rename from src/Containers/App/images/logo.svg rename to src/Components/App/images/logo.svg diff --git a/src/Components/App/index.jsx b/src/Components/App/index.jsx new file mode 100644 index 000000000..bb20bbead --- /dev/null +++ b/src/Components/App/index.jsx @@ -0,0 +1,253 @@ +import { ToastContainer } from 'react-toastify'; +import { useTranslation } from 'react-i18next'; +import React, { useRef } from 'react'; +import PropTypes from 'prop-types'; + +import 'react-toastify/dist/ReactToastify.min.css'; + + +import PortPicker from '../PortPicker'; +import Log from '../Log'; +import Statusbar from '../Statusbar'; +import CookieConsent from '../CookieConsent'; +import MainContent from '../MainContent'; +import AppSettings from '../AppSettings'; + +import { useInterval } from '../../utils/helpers/React'; + +import changelogEntries from '../../changelog.json'; +import './style.scss'; + +function App({ + actions, + appSettings, + configs, + connected, + escs, + flashTargets, + fourWay, + hasPort, + hasSerial, + language, + languages, + onAllMotorSpeed, + onChangePort, + onCancelFirmwareSelection, + onClose, + onConnect, + onCookieAccept, + onDisconnect, + onFlashUrl, + onIndividualSettingsUpdate, + onLanguageSelection, + onLocalSubmit, + onOpenSettings, + onSetBaudRate, + onReadEscs, + onResetDefaultls, + onSaveLog, + onSelectFirmwareForAll, + onSetPort, + onSettingsUpdate, + onSingleFlash, + onSingleMotorSpeed, + onUpdate, + onWriteSetup, + open, + packetErrors, + portNames, + progress, + serial, + serialLog, + settings, + showSettings, + version, +}) { + const { t } = useTranslation('common'); + const statusbarRef = useRef(); + + /* istanbul ignore next */ + useInterval(async() => { + if(open && !actions.isReading && !fourWay) { + if(serial.getBatteryState) { + const batteryState = await serial.getBatteryState(); + statusbarRef.current.updateBatteryState(batteryState); + } + } else { + statusbarRef.current.updateBatteryState(null); + } + }, 1000); + + /* istanbul ignore next */ + useInterval(async() => { + if(serial.getUtilization) { + const utilization = await serial.getUtilization(); + statusbarRef.current.updateUtilization(utilization); + } + }, 1000); + + const languageElements = languages.map((item) => ( + + )); + + return ( +
+
+
+
+ + +
+ + +
+ + + + +
+ + + + {showSettings && + } + + +
+ ); +} + +App.defaultProps = { + serial: { + getBatteryState: null, + getUtilization: null, + }, +}; + +App.propTypes = { + actions: PropTypes.shape({ isReading: PropTypes.bool.isRequired }).isRequired, + appSettings: PropTypes.shape({}).isRequired, + configs: PropTypes.shape({}).isRequired, + connected: PropTypes.number.isRequired, + escs: PropTypes.arrayOf(PropTypes.shape({})).isRequired, + flashTargets: PropTypes.arrayOf(PropTypes.number).isRequired, + fourWay: PropTypes.bool.isRequired, + hasPort: PropTypes.bool.isRequired, + hasSerial: PropTypes.bool.isRequired, + language: PropTypes.string.isRequired, + languages: PropTypes.arrayOf(PropTypes.shape({})).isRequired, + onAllMotorSpeed: PropTypes.func.isRequired, + onCancelFirmwareSelection: PropTypes.func.isRequired, + onChangePort: PropTypes.func.isRequired, + onClose: PropTypes.func.isRequired, + onConnect: PropTypes.func.isRequired, + onCookieAccept: PropTypes.func.isRequired, + onDisconnect: PropTypes.func.isRequired, + onFlashUrl: PropTypes.func.isRequired, + onIndividualSettingsUpdate: PropTypes.func.isRequired, + onLanguageSelection: PropTypes.func.isRequired, + onLocalSubmit: PropTypes.func.isRequired, + onOpenSettings: PropTypes.func.isRequired, + onReadEscs: PropTypes.func.isRequired, + onResetDefaultls: PropTypes.func.isRequired, + onSaveLog: PropTypes.func.isRequired, + onSelectFirmwareForAll: PropTypes.func.isRequired, + onSetBaudRate: PropTypes.func.isRequired, + onSetPort: PropTypes.func.isRequired, + onSettingsUpdate: PropTypes.func.isRequired, + onSingleFlash: PropTypes.func.isRequired, + onSingleMotorSpeed: PropTypes.func.isRequired, + onUpdate: PropTypes.func.isRequired, + onWriteSetup: PropTypes.func.isRequired, + open: PropTypes.bool.isRequired, + packetErrors: PropTypes.number.isRequired, + portNames: PropTypes.arrayOf(PropTypes.string).isRequired, + progress: PropTypes.arrayOf(PropTypes.number).isRequired, + serial: PropTypes.shape({ + getBatteryState:PropTypes.func, + getUtilization:PropTypes.func, + }), + serialLog: PropTypes.arrayOf(PropTypes.any).isRequired, + settings: PropTypes.shape({}).isRequired, + showSettings: PropTypes.bool.isRequired, + version: PropTypes.string.isRequired, +}; + +export default App; diff --git a/src/Containers/App/style.scss b/src/Components/App/style.scss similarity index 100% rename from src/Containers/App/style.scss rename to src/Components/App/style.scss diff --git a/src/Components/AppSettings/__tests__/index.test.jsx b/src/Components/AppSettings/__tests__/index.test.jsx new file mode 100644 index 000000000..d60281b98 --- /dev/null +++ b/src/Components/AppSettings/__tests__/index.test.jsx @@ -0,0 +1,79 @@ +import React from 'react'; +import { + render, screen, +} from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import AppSettings from '../'; + +jest.mock('react-i18next', () => ({ useTranslation: () => ({ t: (key) => key }) })); + +test('loads and displays AppSettings', () => { + const settings = { + testSetting: { + type: "boolean", + value: true, + }, + testSettingFalse: { + type: "boolean", + value: false, + }, + }; + + const onClose = jest.fn(); + const onUpdate = jest.fn(); + + render( + + ); + + expect(screen.getByText(/settingsHeader/i)).toBeInTheDocument(); + expect(screen.getByText(/close/i)).toBeInTheDocument(); +}); + +test('processes clicks on checkbox', () => { + const settings = { + testSetting: { + type: "boolean", + value: true, + }, + }; + + const onClose = jest.fn(); + const onUpdate = jest.fn(); + + render( + + ); + + expect(screen.getByText(/settingsHeader/i)).toBeInTheDocument(); + + userEvent.click(screen.getByRole(/checkbox/i)); + expect(onUpdate).toHaveBeenCalled(); +}); + +test('processes clicks on close', () => { + const settings = {}; + + const onClose = jest.fn(); + const onUpdate = jest.fn(); + + render( + + ); + + userEvent.click(screen.getByText(/close/i)); + expect(onClose).toHaveBeenCalled(); +}); diff --git a/src/Components/AppSettings/index.jsx b/src/Components/AppSettings/index.jsx index 45aa1433b..3a5f703a7 100644 --- a/src/Components/AppSettings/index.jsx +++ b/src/Components/AppSettings/index.jsx @@ -1,11 +1,9 @@ +import { useTranslation } from 'react-i18next'; import PropTypes from 'prop-types'; import React from 'react'; import Checkbox from '../Input/Checkbox'; -import { - useTranslation, -} from 'react-i18next'; import './style.scss'; function AppSettings({ @@ -46,7 +44,7 @@ function AppSettings({ value={setting.value ? 1 : 0} /> ); - } break; + } } }); @@ -62,6 +60,7 @@ function AppSettings({
{t('closeText')}
diff --git a/src/Components/Buttonbar/__tests__/index.test.jsx b/src/Components/Buttonbar/__tests__/index.test.jsx new file mode 100644 index 000000000..96d4dd2f7 --- /dev/null +++ b/src/Components/Buttonbar/__tests__/index.test.jsx @@ -0,0 +1,134 @@ +import React from 'react'; +import { + render, screen, +} from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import Buttonbar from '../'; + +jest.mock('react-i18next', () => ({ useTranslation: () => ({ t: (key) => key }) })); + +test('loads and displays Buttonbar', () => { + const onWriteSetup = jest.fn(); + const onReadSetup = jest.fn(); + const onResetDefaults = jest.fn(); + const onSaveLog = jest.fn(); + const onSeletFirmwareForAll = jest.fn(); + + render( + + ); + + expect(screen.queryAllByText(/resetDefaults/i).length).toEqual(2); + expect(screen.getByText(/escButtonRead/i)).toBeInTheDocument(); + expect(screen.getByText(/escButtonWrite/i)).toBeInTheDocument(); + expect(screen.getByText(/escButtonFlashAll/i)).toBeInTheDocument(); + expect(screen.getByText(/escButtonSaveLog/i)).toBeInTheDocument(); +}); + +test('trigger onSaveLog', () => { + const onWriteSetup = jest.fn(); + const onReadSetup = jest.fn(); + const onResetDefaults = jest.fn(); + const onSaveLog = jest.fn(); + const onSeletFirmwareForAll = jest.fn(); + + render( + + ); + + userEvent.click(screen.getByText(/escButtonSaveLog/i)); + expect(onSaveLog).toHaveBeenCalled(); +}); + +test('does not trigger handlers when inactive', () => { + const onWriteSetup = jest.fn(); + const onReadSetup = jest.fn(); + const onResetDefaults = jest.fn(); + const onSaveLog = jest.fn(); + const onSelectFirmwareForAll = jest.fn(); + + render( + + ); + + userEvent.click(screen.queryAllByText(/resetDefaults/i)[1]); + expect(onResetDefaults).not.toHaveBeenCalled(); + + userEvent.click(screen.getByText(/escButtonRead/i)); + expect(onReadSetup).not.toHaveBeenCalled(); + + userEvent.click(screen.getByText(/escButtonWrite/i)); + expect(onWriteSetup).not.toHaveBeenCalled(); + + userEvent.click(screen.getByText(/escButtonFlashAll/i)); + expect(onSelectFirmwareForAll).not.toHaveBeenCalled(); +}); + +test('does trigger handlers when enabled', () => { + const onWriteSetup = jest.fn(); + const onReadSetup = jest.fn(); + const onResetDefaults = jest.fn(); + const onSaveLog = jest.fn(); + const onSelectFirmwareForAll = jest.fn(); + + render( + + ); + + userEvent.click(screen.queryAllByText(/resetDefaults/i)[1]); + expect(onResetDefaults).toHaveBeenCalled(); + + userEvent.click(screen.getByText(/escButtonRead/i)); + expect(onReadSetup).toHaveBeenCalled(); + + userEvent.click(screen.getByText(/escButtonWrite/i)); + expect(onWriteSetup).toHaveBeenCalled(); + + userEvent.click(screen.getByText(/escButtonFlashAll/i)); + expect(onSelectFirmwareForAll).toHaveBeenCalled(); +}); diff --git a/src/Components/Buttonbar/index.jsx b/src/Components/Buttonbar/index.jsx index d7a5d6eb9..46a215283 100644 --- a/src/Components/Buttonbar/index.jsx +++ b/src/Components/Buttonbar/index.jsx @@ -1,8 +1,6 @@ +import { useTranslation } from 'react-i18next'; import PropTypes from 'prop-types'; import React from 'react'; -import { - useTranslation, -} from 'react-i18next'; import './style.scss'; diff --git a/src/Components/Changelog/__tests__/index.test.jsx b/src/Components/Changelog/__tests__/index.test.jsx new file mode 100644 index 000000000..ea11a0d45 --- /dev/null +++ b/src/Components/Changelog/__tests__/index.test.jsx @@ -0,0 +1,56 @@ +import React from 'react'; +import { + render, screen, +} from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import Changelog from '../'; + +jest.mock('react-i18next', () => ({ useTranslation: () => ({ t: (key) => key }) })); + +test('loads and displays Changelog', () => { + const entries = [ + { + title: 'title', + items: [ + 'item', + ], + }, + ]; + + render( + + ); + + expect(screen.getByText(/defaultChangelogHead/i)).toBeInTheDocument(); + expect(screen.getByText(/defaultChangelogTitle/i)).toBeInTheDocument(); + expect(screen.queryByText(/changelogClose/i)).not.toBeInTheDocument(); +}); + + +test('expands and close Changelog', () => { + const entries = [ + { + title: 'title', + items: [ + 'item', + ], + }, + ]; + + render( + + ); + + userEvent.click(screen.getByText(/defaultChangelogTitle/i)); + expect(screen.queryByText(/defaultChangelogTitle/i)).not.toBeInTheDocument(); + expect(screen.getByText(/changelogClose/i)).toBeInTheDocument(); + + userEvent.click(screen.getByText(/changelogClose/i)); + expect(screen.getByText(/defaultChangelogTitle/i)).toBeInTheDocument(); + expect(screen.queryByText(/changelogClose/i)).not.toBeInTheDocument(); +}); diff --git a/src/Components/Changelog/index.jsx b/src/Components/Changelog/index.jsx index 11cd0cdb3..755b5e790 100644 --- a/src/Components/Changelog/index.jsx +++ b/src/Components/Changelog/index.jsx @@ -1,11 +1,9 @@ +import { useTranslation } from 'react-i18next'; import PropTypes from 'prop-types'; import React, { useState, useCallback, } from 'react'; -import { - useTranslation, -} from 'react-i18next'; import './style.scss'; diff --git a/src/Components/CookieConsent/index.jsx b/src/Components/CookieConsent/index.jsx index f0b4b8bad..a5dfb9494 100644 --- a/src/Components/CookieConsent/index.jsx +++ b/src/Components/CookieConsent/index.jsx @@ -1,10 +1,7 @@ -import PropTypes from 'prop-types';import { - CookieBanner -} from '@palmabit/react-cookie-law'; +import { CookieBanner } from '@palmabit/react-cookie-law'; +import { useTranslation } from 'react-i18next'; +import PropTypes from 'prop-types'; import React from 'react'; -import { - useTranslation, -} from 'react-i18next'; function CookieConsent({ onCookieAccept }) { const { t } = useTranslation('common'); @@ -40,8 +37,8 @@ function CookieConsent({ onCookieAccept }) { policy: { display: 'inline-block', lineHeight: '30px', - fontSize: '14px' - } + fontSize: '14px', + }, }} /> ); diff --git a/src/Components/ErrorBoundary/index.jsx b/src/Components/ErrorBoundary/index.jsx new file mode 100644 index 000000000..0ae15eef9 --- /dev/null +++ b/src/Components/ErrorBoundary/index.jsx @@ -0,0 +1,43 @@ +import PropTypes from 'prop-types'; +import React from 'react'; + +class ErrorBoundary extends React.Component { + static getDerivedStateFromError(error) { + // Update state so the next render will show the fallback UI. + return { hasError: true }; + } + + static propTypes = { children: PropTypes.element.isRequired }; + + constructor(props) { + super(props); + this.state = { hasError: false }; + } + + shouldComponentUpdate() { + return true; + } + + componentDidCatch(error, errorInfo) { + // You can also log the error to an error reporting service + //logErrorToMyService(error, errorInfo); + console.log(error); + } + + render() { + const { hasError } = this.state; + const { children } = this.props; + if (hasError) { + // You can render any custom fallback UI + return ( +
+ Something went wrong. +
+ ); + } + + return children; + } +} + +export default ErrorBoundary; diff --git a/src/Components/FirmwareSelector/__tests__/index.test.jsx b/src/Components/FirmwareSelector/__tests__/index.test.jsx new file mode 100644 index 000000000..65671c375 --- /dev/null +++ b/src/Components/FirmwareSelector/__tests__/index.test.jsx @@ -0,0 +1,213 @@ +import React from 'react'; +import { + render, screen, fireEvent, +} from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import sources from '../../../sources'; + +import FirmwareSelector from '../'; + +jest.mock('react-i18next', () => ({ useTranslation: () => ({ t: (key) => key }) })); + +test('loads and displays FirmwareSelector', () => { + const configs = { + versions: {}, + escs: {}, + pwm: {}, + platforms: {}, + }; + + const onSubmit = jest.fn(); + const onLocalSubmit = jest.fn(); + const onCancel = jest.fn(); + + render( + + ); + + expect(screen.getByText(/forceFlashText/i)).toBeInTheDocument(); + expect(screen.getByText(/forceFlashHint/i)).toBeInTheDocument(); + expect(screen.getByText(/migrateFlashText/i)).toBeInTheDocument(); + expect(screen.getByText(/migrateFlashHint/i)).toBeInTheDocument(); + expect(screen.getByText(/forceFlashText/i)).toBeInTheDocument(); + + expect(screen.getByText("escButtonSelect")).toBeInTheDocument(); + expect(screen.getByText(/escButtonSelectLocally/i)).toBeInTheDocument(); + expect(screen.getByText(/buttonCancel/i)).toBeInTheDocument(); + + expect(screen.getByText(/selectFirmware/i)).toBeInTheDocument(); + expect(screen.getByText(/selectTarget/i)).toBeInTheDocument(); +}); + +test('loads and displays FirmwareSelector with Bluejay config', async() => { + const configs = { + versions: {}, + escs: {}, + pwm: {}, + platforms: {}, + }; + + for(let i = 0; i < sources.length; i += 1) { + const source = sources[i]; + const name = source.getName(); + + configs.versions[name] = await source.getVersions(); + configs.escs[name] = await source.getEscs(); + configs.platforms[name] = source.getPlatform(); + configs.pwm[name] = source.getPwm(); + } + + const onSubmit = jest.fn(); + const onLocalSubmit = jest.fn(); + const onCancel = jest.fn(); + + const { container } = render( + + ); + + expect(screen.getByText(/forceFlashText/i)).toBeInTheDocument(); + expect(screen.getByText(/forceFlashHint/i)).toBeInTheDocument(); + expect(screen.getByText(/migrateFlashText/i)).toBeInTheDocument(); + expect(screen.getByText(/migrateFlashHint/i)).toBeInTheDocument(); + expect(screen.getByText(/forceFlashText/i)).toBeInTheDocument(); + + expect(screen.getByText("escButtonSelect")).toBeInTheDocument(); + expect(screen.getByText(/escButtonSelectLocally/i)).toBeInTheDocument(); + expect(screen.getByText(/buttonCancel/i)).toBeInTheDocument(); + + expect(screen.getByText(/selectFirmware/i)).toBeInTheDocument(); + expect(screen.getByText(/selectTarget/i)).toBeInTheDocument(); + + fireEvent.change(screen.getByRole(/combobox/i, { name: 'Firmware' }), { + target: { + value: 'Blheli', + name: 'Firmware', + }, + }); + + fireEvent.change(screen.getByRole(/combobox/i, { name: 'ESC' }), { + target: { + value: '#S_H_50#', + name: 'ESC', + }, + }); + + fireEvent.change(screen.getByRole(/combobox/i, { name: 'Version' }), { + target: { + value: '16.7 [Official]', + name: 'Version', + }, + }); + + const checkboxes = screen.getAllByRole(/checkbox/i); + for(let i = 0; i < checkboxes.length; i += 1) { + userEvent.click(checkboxes[i]); + } + + fireEvent.change(screen.getByRole(/combobox/i, { name: 'Firmware' }), { + target: { + value: 'Bluejay', + name: 'Firmware', + }, + }); + + fireEvent.change(screen.getByRole(/combobox/i, { name: 'ESC' }), { + target: { + value: '#S_H_50#', + name: 'ESC', + }, + }); + + fireEvent.change(screen.getByRole(/combobox/i, { name: 'Version' }), { + target: { + value: 'https://github.com/mathiasvr/bluejay/releases/download/v0.10/{0}_v0.10.hex', + name: 'Version', + }, + }); + + fireEvent.change(screen.getByRole(/combobox/i, { name: 'PWM Frequency' }), { + target: { + value: '96', + name: 'PWM Frequency', + }, + }); + + userEvent.click(screen.getByText('escButtonSelect')); + expect(onSubmit).toHaveBeenCalled(); + + userEvent.click(screen.getByText('escButtonSelectLocally')); + fireEvent.change(container.querySelector('input[type=file]')); + expect(onLocalSubmit).toHaveBeenCalled(); + + userEvent.click(screen.getByText('buttonCancel')); + expect(onCancel).toHaveBeenCalled(); +}); + +test('loads and displays FirmwareSelector with AM32 config', async() => { + const configs = { + versions: {}, + escs: {}, + pwm: {}, + platforms: {}, + }; + + for(let i = 0; i < sources.length; i += 1) { + const source = sources[i]; + const name = source.getName(); + + configs.versions[name] = await source.getVersions(); + configs.escs[name] = await source.getEscs(); + configs.platforms[name] = source.getPlatform(); + configs.pwm[name] = source.getPwm(); + } + + const onSubmit = jest.fn(); + const onLocalSubmit = jest.fn(); + const onCancel = jest.fn(); + + render( + + ); + + expect(screen.getByText(/forceFlashText/i)).toBeInTheDocument(); + expect(screen.getByText(/forceFlashHint/i)).toBeInTheDocument(); + expect(screen.getByText(/migrateFlashText/i)).toBeInTheDocument(); + expect(screen.getByText(/migrateFlashHint/i)).toBeInTheDocument(); + expect(screen.getByText(/forceFlashText/i)).toBeInTheDocument(); + + expect(screen.getByText("escButtonSelect")).toBeInTheDocument(); + expect(screen.getByText(/escButtonSelectLocally/i)).toBeInTheDocument(); + expect(screen.getByText(/buttonCancel/i)).toBeInTheDocument(); + + expect(screen.getByText(/selectFirmware/i)).toBeInTheDocument(); + expect(screen.getByText(/selectTarget/i)).toBeInTheDocument(); + + fireEvent.change(screen.getByRole(/combobox/i, { name: 'Version' }), { + target: { + value: 'https://github.com/AlkaMotors/AM32-MultiRotor-ESC-firmware/releases/download/v1.65/{0}_1.65.hex', + name: 'Version', + }, + }); + + userEvent.click(screen.getByText('escButtonSelect')); + expect(onSubmit).toHaveBeenCalled(); +}); diff --git a/src/Components/FirmwareSelector/index.jsx b/src/Components/FirmwareSelector/index.jsx index fff54de6b..a57022824 100644 --- a/src/Components/FirmwareSelector/index.jsx +++ b/src/Components/FirmwareSelector/index.jsx @@ -1,10 +1,8 @@ +import { useTranslation } from 'react-i18next'; import PropTypes from 'prop-types'; import React, { useState, useEffect, useRef, } from 'react'; -import { - useTranslation, -} from 'react-i18next'; import { isValidLayout, @@ -13,14 +11,11 @@ import { import LabeledSelect from '../Input/LabeledSelect'; -import { - BLHELI_TYPES, - BLHELI_MODES, -} from '../../sources/Blheli/eeprom'; +import { EEPROM as BLHELI_EEPROM } from '../../sources/Blheli'; +import { EEPROM as BLUEJAY_EEPROM } from '../../sources/Bluejay'; -import { - EEPROM as BLUEJAY_EEPROM -} from '../../sources/Bluejay'; +const BLHELI_TYPES = BLHELI_EEPROM.TYPES; +const BLHELI_MODES = BLHELI_EEPROM.MODES; const BLUEJAY_TYPES = BLUEJAY_EEPROM.TYPES; import { @@ -147,7 +142,7 @@ function FirmwareSelector({ versionOptions.push({ key: current.key, value: url, - name: current.name + name: current.name, }); } @@ -215,9 +210,12 @@ function FirmwareSelector({ onLocalSubmit(e, force, migrate); } + /* + // TODO: Not yet implemented - this might only be needed for ATMEL function updateMode(e) { setMode(e.target.value); } + */ function updateVersion(e) { const newSelection = Object.assign({}, selection, { url: e.target.value }); @@ -248,7 +246,7 @@ function FirmwareSelector({ const formattedUrl = format( selection.url, `${name}${pwmSuffix}`, - mode, + mode ); onSubmit(formattedUrl, force, migrate); @@ -302,13 +300,13 @@ function FirmwareSelector({
- Select Target + {t('selectTarget')}
+ {/* {type === BLHELI_TYPES.SILABS || type === BLHELI_TYPES.ATMEL && } + */} 0 && ({ + useTranslation: () => ({ + t: (key) => key, + i18n: { + exists: (name) => { + if(name === 'hints:STARTUP_BEEP') { + return false; + } + + return true; + }, + }, + }), +})); + +test('loads and displays unsupported CustomSettings', () => { + const availableSettings = { + LAYOUT_REVISION: 203, + MAIN_REVISION: 1, + NAME: 'FW name', + SUB_REVISION: 100, + }; + + const escs = [ + { + meta: { available: true }, + settings: { MODE: 1 }, + }, + ]; + + const onFlash = jest.fn(); + const onSettingsUpdate = jest.fn(); + + render( + + ); + + expect(screen.getByText(/common:versionUnsupported/i)).toBeInTheDocument(); +}); + +test('loads and displays CustomSettings', () => { + const availableSettings = { + LAYOUT_REVISION: 203, + MAIN_REVISION: 1, + NAME: 'FW name', + SUB_REVISION: 100, + }; + + const escs = [ + { + meta: { available: true }, + settings: { MODE: 0 }, + make: 'make 1234', + settingsDescriptions: { + base: [ + { + name: 'MOTOR_DIRECTION', + type: 'enum', + label: 'escMotorDirection', + options: [ + { + value: '1', + label: 'Normal', + }, + { + value: '2', + label: 'Reversed', + }, + { + value: '3', + label: 'Bidirectional', + }, + { + value: '4', + label: 'Bidirectional Reversed', + }, + ], + }, + { + name: '_PPM_MIN_THROTTLE', + type: 'number', + min: 1000, + max: 1500, + step: 4, + label: 'escPPMMinThrottle', + offset: 1000, + factor: 4, + suffix: ' μs', + }, + { + name: 'STARTUP_BEEP', + type: 'bool', + label: 'escStartupBeep', + }, + { + name: 'IVALID', + type: 'IVALID', + label: 'invalid', + }, + { + name: '_PPM_CENTER_THROTTLE', + type: 'number', + min: 1000, + max: 2020, + step: 4, + label: 'escPPMCenterThrottle', + offset: 1000, + factor: 4, + suffix: ' μs', + visibleIf: (settings) => [3, 4].includes(settings.MOTOR_DIRECTION), + }, + ], + }, + individualSettings: { + MAIN_REVISION: 0, + SUB_REVISION: 201, + NAME: 'Bluejay (Beta)', + }, + }, + ]; + + const onFlash = jest.fn(); + const onSettingsUpdate = jest.fn(); + + render( + + ); + + expect(screen.getByText(/commonParameters/i)).toBeInTheDocument(); + + expect(screen.getByText(/escMotorDirection/i)).toBeInTheDocument(); + expect(screen.getByText(/escPPMMinThrottle/i)).toBeInTheDocument(); + expect(screen.getByText(/escStartupBeep/i)).toBeInTheDocument(); + expect(screen.queryByText(/invalid/i)).not.toBeInTheDocument(); +}); + +test('handles setting adjustments', () => { + const availableSettings = { + LAYOUT_REVISION: 203, + MAIN_REVISION: 1, + NAME: 'FW name', + SUB_REVISION: 100, + STARTUP_BEEP: 0, + MOTOR_DIRECTION: 1, + }; + + const esc = { + meta: { available: true }, + settings: { + MODE: 0, + STARTUP_BEEP: 0, + }, + make: 'make 1234', + settingsDescriptions: { + overrides: { + '0.201': [ + { + name: 'MOTOR_DIRECTION', + type: 'enum', + label: 'escMotorDirection', + options: [ + { + value: '1', + label: 'Normal', + }, + { + value: '2', + label: 'Reversed', + }, + { + value: '3', + label: 'Bidirectional', + }, + { + value: '4', + label: 'Bidirectional Reversed', + }, + ], + }, + ], + }, + base: [ + { + name: 'MOTOR_DIRECTION', + type: 'enum', + label: 'escMotorDirection', + options: [ + { + value: '1', + label: 'Normal', + }, + { + value: '2', + label: 'Reversed', + }, + { + value: '3', + label: 'Bidirectional', + }, + { + value: '4', + label: 'Bidirectional Reversed', + }, + ], + }, + { + name: '_PPM_MIN_THROTTLE', + type: 'number', + min: 1000, + max: 1500, + step: 4, + label: 'escPPMMinThrottle', + offset: 1000, + factor: 4, + suffix: ' μs', + }, + { + name: 'STARTUP_BEEP', + type: 'bool', + label: 'escStartupBeep', + }, + { + name: 'IVALID', + type: 'IVALID', + label: 'invalid', + }, + { + name: '_PPM_CENTER_THROTTLE', + type: 'number', + min: 1000, + max: 2020, + step: 4, + label: 'escPPMCenterThrottle', + offset: 1000, + factor: 4, + suffix: ' μs', + visibleIf: (settings) => [3, 4].includes(settings.MOTOR_DIRECTION), + }, + ], + }, + individualSettings: { + MAIN_REVISION: 0, + SUB_REVISION: 201, + NAME: 'Bluejay (Beta)', + }, + }; + + const escs = []; + + for(let i = 0; i < 4; i += 1) { + const current = Object.assign({}, esc); + escs.push(current); + } + + const onFlash = jest.fn(); + const onSettingsUpdate = jest.fn(); + + render( + + ); + + userEvent.click(screen.getByRole(/checkbox/i)); + expect(onSettingsUpdate).toHaveBeenCalled(); + + fireEvent.change(screen.getByRole(/combobox/i), { + taget: { + value: 3, + name: 'MOTOR_DIRECTION', + }, + }); +}); + +test('handles setting adjustments with direct input', () => { + const availableSettings = { + LAYOUT_REVISION: 203, + MAIN_REVISION: 1, + NAME: 'FW name', + SUB_REVISION: 100, + }; + + const escs = [ + { + meta: { available: true }, + settings: { MODE: 0 }, + make: 'make 1234', + settingsDescriptions: { + base: [ + { + name: 'MOTOR_DIRECTION', + type: 'enum', + label: 'escMotorDirection', + options: [ + { + value: '1', + label: 'Normal', + }, + { + value: '2', + label: 'Reversed', + }, + { + value: '3', + label: 'Bidirectional', + }, + { + value: '4', + label: 'Bidirectional Reversed', + }, + ], + }, + { + name: '_PPM_MIN_THROTTLE', + type: 'number', + min: 1000, + max: 1500, + step: 4, + label: 'escPPMMinThrottle', + offset: 1000, + factor: 4, + suffix: ' μs', + }, + { + name: 'STARTUP_BEEP', + type: 'bool', + label: 'escStartupBeep', + }, + { + name: 'IVALID', + type: 'IVALID', + label: 'invalid', + }, + { + name: '_PPM_CENTER_THROTTLE', + type: 'number', + min: 1000, + max: 2020, + step: 4, + label: 'escPPMCenterThrottle', + offset: 1000, + factor: 4, + suffix: ' μs', + visibleIf: (settings) => [3, 4].includes(settings.MOTOR_DIRECTION), + }, + ], + }, + individualSettings: { + MAIN_REVISION: 0, + SUB_REVISION: 201, + NAME: 'Bluejay (Beta)', + }, + }, + ]; + + const onFlash = jest.fn(); + const onSettingsUpdate = jest.fn(); + + render( + + ); + + userEvent.click(screen.getByRole(/checkbox/i)); + expect(onSettingsUpdate).toHaveBeenCalled(); + + fireEvent.change(screen.getByRole(/spinbutton/i), { + target: { + value: 1250, + name: '_PPM_MIN_THROTTLE', + }, + }); + fireEvent.blur(screen.getByRole(/spinbutton/i)); +}); + +test('shows error if not all MULTI', () => { + const availableSettings = { + LAYOUT_REVISION: 203, + MAIN_REVISION: 1, + NAME: 'FW name', + SUB_REVISION: 100, + }; + + const escs = [ + { + meta: { available: true }, + settings: { MODE: 1 }, + make: 'make 1234', + settingsDescriptions: { + base: [ + { + name: 'MOTOR_DIRECTION', + type: 'enum', + label: 'escMotorDirection', + options: [ + { + value: '1', + label: 'Normal', + }, + { + value: '2', + label: 'Reversed', + }, + { + value: '3', + label: 'Bidirectional', + }, + { + value: '4', + label: 'Bidirectional Reversed', + }, + ], + }, + { + name: '_PPM_MIN_THROTTLE', + type: 'number', + min: 1000, + max: 1500, + step: 4, + label: 'escPPMMinThrottle', + offset: 1000, + factor: 4, + suffix: ' μs', + }, + { + name: 'STARTUP_BEEP', + type: 'bool', + label: 'escStartupBeep', + }, + { + name: 'IVALID', + type: 'IVALID', + label: 'invalid', + }, + { + name: '_PPM_CENTER_THROTTLE', + type: 'number', + min: 1000, + max: 2020, + step: 4, + label: 'escPPMCenterThrottle', + offset: 1000, + factor: 4, + suffix: ' μs', + visibleIf: (settings) => [3, 4].includes(settings.MOTOR_DIRECTION), + }, + ], + }, + individualSettings: { + MAIN_REVISION: 0, + SUB_REVISION: 201, + NAME: 'Bluejay (Beta)', + }, + }, + ]; + + const onFlash = jest.fn(); + const onSettingsUpdate = jest.fn(); + + render( + + ); + + expect(screen.getByText(/multiOnly/i)).toBeInTheDocument(); +}); + +test('handles out of sync settings', () => { + const availableSettings = { + LAYOUT_REVISION: 201, + MAIN_REVISION: 0, + NAME: 'FW name', + SUB_REVISION: 100, + }; + + const esc = { + meta: { available: true }, + settings: { + MODE: 0, + MOTOR_DIRECTION: 0, + }, + make: 'make 1234', + settingsDescriptions: { + base: [ + { + name: 'MOTOR_DIRECTION', + type: 'enum', + label: 'escMotorDirection', + options: [ + { + value: '1', + label: 'Normal', + }, + { + value: '2', + label: 'Reversed', + }, + { + value: '3', + label: 'Bidirectional', + }, + { + value: '4', + label: 'Bidirectional Reversed', + }, + ], + }, + { + name: '_PPM_MIN_THROTTLE', + type: 'number', + min: 1000, + max: 1500, + step: 4, + label: 'escPPMMinThrottle', + offset: 1000, + factor: 4, + suffix: ' μs', + }, + { + name: 'STARTUP_BEEP', + type: 'bool', + label: 'escStartupBeep', + }, + { + name: 'IVALID', + type: 'IVALID', + label: 'invalid', + }, + { + name: '_PPM_CENTER_THROTTLE', + type: 'number', + min: 1000, + max: 2020, + step: 4, + label: 'escPPMCenterThrottle', + offset: 1000, + factor: 4, + suffix: ' μs', + visibleIf: (settings) => [3, 4].includes(settings.MOTOR_DIRECTION), + }, + ], + }, + individualSettings: { + MAIN_REVISION: 0, + SUB_REVISION: 201, + NAME: 'Bluejay (Beta)', + }, + }; + + const escs = []; + + for(let i = 0; i < 4; i += 1) { + const current = JSON.parse(JSON.stringify(esc)); + if(i === 3) { + current.settings.MOTOR_DIRECTION = 1; + } + escs.push(current); + } + + const onFlash = jest.fn(); + const onSettingsUpdate = jest.fn(); + + render( + + ); + + userEvent.click(screen.getByRole(/checkbox/i)); + expect(onSettingsUpdate).toHaveBeenCalled(); + + fireEvent.change(screen.getByRole(/combobox/i), { + taget: { + value: 3, + name: 'MOTOR_DIRECTION', + }, + }); +}); + +test('handles setting overrides', () => { + const availableSettings = { + LAYOUT_REVISION: 201, + MAIN_REVISION: 0, + NAME: 'FW name', + SUB_REVISION: 100, + STARTUP_BEEP: 1, + MOTOR_DIRECTION: 1, + }; + + const esc = { + meta: { available: true }, + settings: { + MODE: 0, + MOTOR_DIRECTION: 0, + STARTUP_BEEP: 1, + }, + make: 'make 1234', + settingsDescriptions: { + overrides: { + '0.100': [ + { + name: 'MOTOR_DIRECTION', + type: 'enum', + label: 'escMotorDirectionOverride', + options: [ + { + value: '1', + label: 'Normal', + }, + { + value: '2', + label: 'Reversed', + }, + { + value: '3', + label: 'Bidirectional', + }, + { + value: '4', + label: 'Bidirectional Reversed', + }, + ], + }, + ], + }, + base: [ + { + name: 'MOTOR_DIRECTION', + type: 'enum', + label: 'escMotorDirection', + options: [ + { + value: '1', + label: 'Normal', + }, + { + value: '2', + label: 'Reversed', + }, + { + value: '3', + label: 'Bidirectional', + }, + { + value: '4', + label: 'Bidirectional Reversed', + }, + ], + }, + { + name: '_PPM_MIN_THROTTLE', + type: 'number', + min: 1000, + max: 1500, + step: 4, + label: 'escPPMMinThrottle', + offset: 1000, + factor: 4, + suffix: ' μs', + }, + { + name: 'STARTUP_BEEP', + type: 'bool', + label: 'escStartupBeep', + }, + { + name: 'IVALID', + type: 'IVALID', + label: 'invalid', + }, + { + name: '_PPM_CENTER_THROTTLE', + type: 'number', + min: 1000, + max: 2020, + step: 4, + label: 'escPPMCenterThrottle', + offset: 1000, + factor: 4, + suffix: ' μs', + visibleIf: (settings) => [3, 4].includes(settings.MOTOR_DIRECTION), + }, + ], + }, + individualSettings: { + MAIN_REVISION: 0, + SUB_REVISION: 201, + NAME: 'Bluejay (Beta)', + }, + }; + + const escs = []; + + for(let i = 0; i < 4; i += 1) { + const current = JSON.parse(JSON.stringify(esc)); + if(i === 3) { + current.settings.MOTOR_DIRECTION = 1; + } + escs.push(current); + } + + const onFlash = jest.fn(); + const onSettingsUpdate = jest.fn(); + + render( + + ); + + expect(screen.getByText(/escMotorDirectionOverride/i)).toBeInTheDocument(); + + userEvent.click(screen.getByRole(/checkbox/i)); + expect(onSettingsUpdate).toHaveBeenCalled(); + + fireEvent.change(screen.getByRole(/combobox/i), { + taget: { + value: 3, + name: 'MOTOR_DIRECTION', + }, + }); +}); diff --git a/src/Components/Flash/CommonSettings/index.jsx b/src/Components/Flash/CommonSettings/index.jsx index 99fe8c203..8042d5a7b 100644 --- a/src/Components/Flash/CommonSettings/index.jsx +++ b/src/Components/Flash/CommonSettings/index.jsx @@ -1,8 +1,6 @@ +import { useTranslation } from 'react-i18next'; import PropTypes from 'prop-types'; import React from 'react'; -import { - useTranslation, -} from 'react-i18next'; import { getMasterSettings, @@ -80,17 +78,16 @@ function CommonSettings({ version: unsupported, name: availableSettings.NAME, layout: availableSettings.LAYOUT_REVISION, - }) + }), }} /> ); } - if (!allMulti) { return (

- Only MULTI mode currently supported + {t('multiOnly')}

); } @@ -105,7 +102,7 @@ function CommonSettings({ return null; } - // Check all settings against + // Check all settings against each other let inSync = true; for(let i = 0; i < allSettings.length; i += 1) { const current = allSettings[i]; @@ -117,7 +114,10 @@ function CommonSettings({ let setting = description; if (overrides) { - setting = overrides.find((override) => override.name === description.name); + const settingOverride = overrides.find((override) => override.name === description.name); + if(settingOverride) { + setting = settingOverride; + } } const value = availableSettings[setting.name]; const hint = i18n.exists(`hints:${setting.name}`) ? t(`hints:${setting.name}`) : null; @@ -216,7 +216,10 @@ function CommonSettings({ ); } -CommonSettings.defaultProps = { disabled: false }; +CommonSettings.defaultProps = { + directInput: false, + disabled: false, +}; CommonSettings.propTypes = { availableSettings: PropTypes.shape({ @@ -225,9 +228,12 @@ CommonSettings.propTypes = { NAME: PropTypes.string.isRequired, SUB_REVISION: PropTypes.number.isRequired, }).isRequired, - directInput: PropTypes.bool.isRequired, + directInput: PropTypes.bool, disabled: PropTypes.bool, - escs: PropTypes.arrayOf(PropTypes.shape()).isRequired, + escs: PropTypes.arrayOf(PropTypes.shape({ + meta: PropTypes.shape({ available: PropTypes.bool.isRequired }).isRequired, + settings: PropTypes.shape({ MODE: PropTypes.number.isRequired }).isRequired, + })).isRequired, onSettingsUpdate: PropTypes.func.isRequired, }; diff --git a/src/Components/Flash/Escs/Esc/__tests__/index.test.jsx b/src/Components/Flash/Escs/Esc/__tests__/index.test.jsx new file mode 100644 index 000000000..4319d913b --- /dev/null +++ b/src/Components/Flash/Escs/Esc/__tests__/index.test.jsx @@ -0,0 +1,347 @@ +import React from 'react'; +import { + render, screen, fireEvent, +} from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import Esc from '../'; + +jest.mock('react-i18next', () => ({ useTranslation: () => ({ t: (key) => key }) })); + +test('loads and displays Esc', () => { + const esc = { individualSettings: {} }; + + const onFlash = jest.fn(); + const onSettingsUpdate = jest.fn(); + + render( + + ); + + expect(screen.getByText(/Unsupported\/Unrecognized/i)).toBeInTheDocument(); + expect(screen.getByText(/escButtonFlash/i)).toBeInTheDocument(); + expect(screen.getByText(/ESC 1/i)).toBeInTheDocument(); +}); + +test('displays name, version and bootloader', () => { + const esc = { + bootloaderRevision: 'bl 23', + displayName: 'displayName 1234', + individualSettings: { + MAIN_REVISION: 1, + SUB_REVISION: 200, + NAME: 'FW Name', + }, + }; + + const onFlash = jest.fn(); + const onSettingsUpdate = jest.fn(); + + render( + + ); + + expect(screen.getByText(/displayName 1234/i)).toBeInTheDocument(); +}); + +test('handles empty name', () => { + const esc = { + bootloaderRevision: 'bl 23', + displayName: 'displayName 1234', + individualSettings: { + MAIN_REVISION: 1, + SUB_REVISION: 200, + NAME: '', + }, + }; + + const onFlash = jest.fn(); + const onSettingsUpdate = jest.fn(); + + render( + + ); + + expect(screen.getByText(/displayName 1234/i)).toBeInTheDocument(); +}); + +test('does not trigger onFlash when disabled', () => { + const esc = { + bootloaderRevision: 'bl 23', + individualSettings: { + MAIN_REVISION: 1, + SUB_REVISION: 200, + NAME: 'FW Name', + }, + }; + + const onFlash = jest.fn(); + const onSettingsUpdate = jest.fn(); + + render( + + ); + + userEvent.click(screen.getByText(/escButtonFlash/i)); + expect(onFlash).not.toHaveBeenCalled(); +}); + +test('does trigger onFlash when enabled', () => { + const esc = { + bootloaderRevision: 'bl 23', + individualSettings: { + MAIN_REVISION: 1, + SUB_REVISION: 200, + NAME: 'FW Name', + }, + }; + + const onFlash = jest.fn(); + const onSettingsUpdate = jest.fn(); + + render( + + ); + + userEvent.click(screen.getByText(/escButtonFlash/i)); + expect(onFlash).toHaveBeenCalled(); +}); + +test('shows and handles settings when available', () => { + const esc = { + bootloaderRevision: 'bl 23', + individualSettings: { + MAIN_REVISION: 1, + SUB_REVISION: 200, + NAME: 'FW Name', + MOTOR_DIRECTION: 1, + _PPM_MIN_THROTTLE: 125, + STARTUP_BEEP: 0, + }, + individualSettingsDescriptions: { + base: [ + { + name: 'MOTOR_DIRECTION', + type: 'enum', + label: 'escMotorDirection', + options: [ + { + value: '1', + label: 'Normal', + }, + { + value: '2', + label: 'Reversed', + }, + { + value: '3', + label: 'Bidirectional', + }, + { + value: '4', + label: 'Bidirectional Reversed', + }, + ], + }, + { + name: '_PPM_MIN_THROTTLE', + type: 'number', + min: 1000, + max: 1500, + step: 4, + label: 'escPPMMinThrottle', + offset: 1000, + factor: 4, + suffix: ' μs', + }, + { + name: 'STARTUP_BEEP', + type: 'bool', + label: 'escStartupBeep', + }, + { + name: 'IVALID', + type: 'IVALID', + label: 'invalid', + }, + { + name: '_PPM_CENTER_THROTTLE', + type: 'number', + min: 1000, + max: 2020, + step: 4, + label: 'escPPMCenterThrottle', + offset: 1000, + factor: 4, + suffix: ' μs', + visibleIf: (settings) => [3, 4].includes(settings.MOTOR_DIRECTION), + }, + ], + }, + }; + + const onFlash = jest.fn(); + const onSettingsUpdate = jest.fn(); + + render( + + ); + + expect(screen.getByText(/escMotorDirection/i)).toBeInTheDocument(); + expect(screen.getByText(/escPPMMinThrottle/i)).toBeInTheDocument(); + expect(screen.getByText(/escStartupBeep/i)).toBeInTheDocument(); + expect(screen.queryByText(/invalid/i)).not.toBeInTheDocument(); + + userEvent.click(screen.getByRole(/checkbox/i)); + + // Change select + fireEvent.change(screen.getByRole(/combobox/i), { + taget: { + value: 3, + name: 'MOTOR_DIRECTION', + }, + }); +}); + +test('shows and handles settings with direct input', () => { + const esc = { + bootloaderRevision: 'bl 23', + individualSettings: { + MAIN_REVISION: 1, + SUB_REVISION: 200, + NAME: 'FW Name', + MOTOR_DIRECTION: 1, + _PPM_MIN_THROTTLE: 125, + STARTUP_BEEP: 1, + }, + individualSettingsDescriptions: { + base: [ + { + name: 'MOTOR_DIRECTION', + type: 'enum', + label: 'escMotorDirection', + options: [ + { + value: '1', + label: 'Normal', + }, + { + value: '2', + label: 'Reversed', + }, + { + value: '3', + label: 'Bidirectional', + }, + { + value: '4', + label: 'Bidirectional Reversed', + }, + ], + }, + { + name: '_PPM_MIN_THROTTLE', + type: 'number', + min: 1000, + max: 1500, + step: 4, + label: 'escPPMMinThrottle', + offset: 1000, + factor: 4, + suffix: ' μs', + }, + { + name: 'STARTUP_BEEP', + type: 'bool', + label: 'escStartupBeep', + }, + { + name: 'IVALID', + type: 'IVALID', + label: 'invalid', + }, + { + name: '_PPM_CENTER_THROTTLE', + type: 'number', + min: 1000, + max: 2020, + step: 4, + label: 'escPPMCenterThrottle', + offset: 1000, + factor: 4, + suffix: ' μs', + visibleIf: (settings) => [3, 4].includes(settings.MOTOR_DIRECTION), + }, + ], + }, + }; + + const onFlash = jest.fn(); + const onSettingsUpdate = jest.fn(); + + render( + + ); + + expect(screen.getByText(/escMotorDirection/i)).toBeInTheDocument(); + expect(screen.getByText(/escPPMMinThrottle/i)).toBeInTheDocument(); + expect(screen.getByText(/escStartupBeep/i)).toBeInTheDocument(); + expect(screen.queryByText(/invalid/i)).not.toBeInTheDocument(); + + userEvent.click(screen.getByRole(/checkbox/i)); + + fireEvent.change(screen.getByRole(/spinbutton/i), { + target: { + value: 1250, + name: '_PPM_MIN_THROTTLE', + }, + }); + fireEvent.blur(screen.getByRole(/spinbutton/i)); +}); diff --git a/src/Components/Flash/Escs/Esc/index.jsx b/src/Components/Flash/Escs/Esc/index.jsx index 0d4f8f3d1..809292522 100644 --- a/src/Components/Flash/Escs/Esc/index.jsx +++ b/src/Components/Flash/Escs/Esc/index.jsx @@ -1,8 +1,6 @@ +import { useTranslation } from 'react-i18next'; import PropTypes from 'prop-types'; import React from 'react'; -import { - useTranslation, -} from 'react-i18next'; import Checkbox from '../../../Input/Checkbox'; import Select from '../../../Input/Select'; @@ -26,30 +24,8 @@ function Esc({ const currentSettings = settings; const settingsDescriptions = esc.individualSettingsDescriptions; - let revision = 'Unsupported/Unrecognized'; - if(settings.MAIN_REVISION !== undefined && settings.SUB_REVISION !== undefined) { - revision = `${settings.MAIN_REVISION}.${settings.SUB_REVISION}`; - } - - let make = ''; - if (esc.make) { - make = `${esc.make}, `; - } - - let name = ''; - if(settings.NAME) { - name = settings ? (settings.NAME).trim() : ''; - if (name.length > 0) { - name = `, ${name}`; - } - } - - let bootloader = ''; - if (esc.bootloaderRevision !== null) { - bootloader = ` (bootloader revision ${esc.bootloaderRevision})`; - } - - const title = `ESC ${(index + 1)}: ${make} ${revision}${name}${bootloader}`; + const name = esc.displayName ? esc.displayName : 'Unsupported/Unrecognized'; + const title = `ESC ${(index + 1)}: ${name}`; function flashFirmware() { onFlash(index); diff --git a/src/Components/Flash/Escs/__tests__/index.test.jsx b/src/Components/Flash/Escs/__tests__/index.test.jsx new file mode 100644 index 000000000..e9c8d7b47 --- /dev/null +++ b/src/Components/Flash/Escs/__tests__/index.test.jsx @@ -0,0 +1,63 @@ +import React from 'react'; +import { + render, screen, +} from '@testing-library/react'; + +import Escs from '../'; + +jest.mock('react-i18next', () => ({ useTranslation: () => ({ t: (key) => key }) })); + +test('loads and displays without Escs', () => { + const onFlash = jest.fn(); + const onSettingsUpdate = jest.fn(); + + render( + + ); + + expect(screen.queryByText(/ESC 1/i)).not.toBeInTheDocument(); +}); + +test('loads and displays with Escs', () => { + const esc = { + bootloaderRevision: 'bl 23', + make: 'make 1234', + individualSettings: { + MAIN_REVISION: 1, + SUB_REVISION: 200, + NAME: 'FW Name', + }, + }; + const escs = []; + const flashProgress = []; + + for(let i = 0; i < 4; i += 1) { + const current = Object.assign({}, esc); + current.index = i; + escs.push(current); + flashProgress.push(0); + } + + const onFlash = jest.fn(); + const onSettingsUpdate = jest.fn(); + + render( + + ); + + expect(screen.getByText(/ESC 1/i)).toBeInTheDocument(); + expect(screen.getByText(/ESC 2/i)).toBeInTheDocument(); + expect(screen.getByText(/ESC 3/i)).toBeInTheDocument(); + expect(screen.getByText(/ESC 4/i)).toBeInTheDocument(); +}); diff --git a/src/Components/Flash/Escs/index.jsx b/src/Components/Flash/Escs/index.jsx index 3865b4807..1654c06d4 100644 --- a/src/Components/Flash/Escs/index.jsx +++ b/src/Components/Flash/Escs/index.jsx @@ -31,13 +31,17 @@ function Escs({ ); } -Escs.defaultProps = { escs: [] }; +Escs.defaultProps = { + directInput: false, + escs: [], + flashProgress: [], +}; Escs.propTypes = { canFlash: PropTypes.bool.isRequired, - directInput: PropTypes.bool.isRequired, + directInput: PropTypes.bool, escs: PropTypes.arrayOf(PropTypes.shape()), - flashProgress: PropTypes.arrayOf(PropTypes.number).isRequired, + flashProgress: PropTypes.arrayOf(PropTypes.number), onFlash: PropTypes.func.isRequired, onSettingsUpdate: PropTypes.func.isRequired, }; diff --git a/src/Components/Flash/__tests__/index.test.jsx b/src/Components/Flash/__tests__/index.test.jsx new file mode 100644 index 000000000..e5034d9b1 --- /dev/null +++ b/src/Components/Flash/__tests__/index.test.jsx @@ -0,0 +1,123 @@ +import React from 'react'; +import { + render, + screen, +} from '@testing-library/react'; + +import Flash from '../'; + +jest.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (key) => key, + i18n: { + exists: (name) => { + if(name === 'hints:STARTUP_BEEP') { + return false; + } + + return true; + }, + }, + }), +})); + +test('loads and displays unsupported CustomSettings', () => { + const availableSettings = { + LAYOUT_REVISION: 203, + MAIN_REVISION: 1, + NAME: 'FW name', + SUB_REVISION: 100, + }; + + const escs = [ + { + index: 0, + meta: { available: true }, + settings: { MODE: 0 }, + make: 'make 1234', + settingsDescriptions: { + base: [ + { + name: 'MOTOR_DIRECTION', + type: 'enum', + label: 'escMotorDirection', + options: [ + { + value: '1', + label: 'Normal', + }, + { + value: '2', + label: 'Reversed', + }, + { + value: '3', + label: 'Bidirectional', + }, + { + value: '4', + label: 'Bidirectional Reversed', + }, + ], + }, + { + name: '_PPM_MIN_THROTTLE', + type: 'number', + min: 1000, + max: 1500, + step: 4, + label: 'escPPMMinThrottle', + offset: 1000, + factor: 4, + suffix: ' μs', + }, + { + name: 'STARTUP_BEEP', + type: 'bool', + label: 'escStartupBeep', + }, + { + name: 'IVALID', + type: 'IVALID', + label: 'invalid', + }, + { + name: '_PPM_CENTER_THROTTLE', + type: 'number', + min: 1000, + max: 2020, + step: 4, + label: 'escPPMCenterThrottle', + offset: 1000, + factor: 4, + suffix: ' μs', + visibleIf: (settings) => [3, 4].includes(settings.MOTOR_DIRECTION), + }, + ], + }, + individualSettings: { + MAIN_REVISION: 0, + SUB_REVISION: 201, + NAME: 'Bluejay (Beta)', + }, + }, + ]; + + const onFlash = jest.fn(); + const onSettingsUpdate = jest.fn(); + + render( + + ); + + expect(screen.getByText(/notePropsOff/i)).toBeInTheDocument(); + expect(screen.getByText(/noteConnectPower/i)).toBeInTheDocument(); + expect(screen.getByText(/commonParameters/i)).toBeInTheDocument(); + expect(screen.getByText(/commonParameters/i)).toBeInTheDocument(); +}); diff --git a/src/Components/Flash/index.jsx b/src/Components/Flash/index.jsx index cb2a71244..998bbc9b8 100644 --- a/src/Components/Flash/index.jsx +++ b/src/Components/Flash/index.jsx @@ -1,15 +1,12 @@ +import { useTranslation } from 'react-i18next'; import PropTypes from 'prop-types'; import React from 'react'; -import { - useTranslation, -} from 'react-i18next'; -import Escs from './Escs'; import CommonSettings from './CommonSettings'; +import Escs from './Escs'; import './style.scss'; - /** * @param {Object} {escs} Parameters * @@ -65,10 +62,15 @@ function Flash({ ); } +Flash.defaultProps = { + canFlash: false, + directInput: false, +}; + Flash.propTypes = { availableSettings: PropTypes.shape().isRequired, - canFlash: PropTypes.bool.isRequired, - directInput: PropTypes.bool.isRequired, + canFlash: PropTypes.bool, + directInput: PropTypes.bool, escs: PropTypes.arrayOf(PropTypes.shape()).isRequired, flashProgress: PropTypes.arrayOf(PropTypes.number).isRequired, onFlash: PropTypes.func.isRequired, diff --git a/src/Components/Home/index.jsx b/src/Components/Home/index.jsx index bb62ef237..c0b19b2c0 100644 --- a/src/Components/Home/index.jsx +++ b/src/Components/Home/index.jsx @@ -1,11 +1,9 @@ +import { useTranslation } from 'react-i18next'; import React from 'react'; + import bluejay from './images/bluejay_logo.png'; import './style.scss'; -import { - useTranslation, -} from 'react-i18next'; - function Home() { const { t } = useTranslation('common'); @@ -18,7 +16,7 @@ function Home() { {t('homeDisclaimerHeader')} -
+
diff --git a/src/Components/Input/Info/index.jsx b/src/Components/Input/Info/index.jsx index a1bb4574f..1c178f5d6 100644 --- a/src/Components/Input/Info/index.jsx +++ b/src/Components/Input/Info/index.jsx @@ -1,6 +1,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import ReactTooltip from 'react-tooltip'; +import ErrorBoundary from '../../ErrorBoundary'; import './style.scss'; @@ -24,13 +25,15 @@ function Info({ ? - - {hint} - + + + {hint} + +
} ); diff --git a/src/Components/Input/LabeledSelect/index.jsx b/src/Components/Input/LabeledSelect/index.jsx index 4bced9e3b..158066b20 100644 --- a/src/Components/Input/LabeledSelect/index.jsx +++ b/src/Components/Input/LabeledSelect/index.jsx @@ -22,6 +22,7 @@ function LabeledSelect({
-
- -
- - -
- - - - -
- - - - {showSettings && - } - - -
+ ); } } diff --git a/src/changelog.json b/src/changelog.json index 5029b951f..e3cea52f5 100644 --- a/src/changelog.json +++ b/src/changelog.json @@ -1,4 +1,22 @@ [ + { + "title": "0.10.0", + "items": [ + "Show battery charge if in Motor tab", + "Motor control via cursors", + "Added simplified Chinese", + "Improved translations", + "Set language to browser language if possible", + "Store logs in local storage", + "Sources generate display names", + "Show PWM frequency with Bluejay", + "Display AM32 bootloader version", + "Added coverage and lots of tests", + "Improved rendering of some components", + "Refactored to pure functions", + "Fixed issue where flashing through EMU would lead to firmware loss" + ] + }, { "title": "0.9.1", "items": [ diff --git a/src/index.jsx b/src/index.jsx index 88931c123..5501b18fc 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -1,13 +1,11 @@ -import React, { - Suspense, -} from 'react'; +/* istanbul ignore file */ + +import React, { Suspense } from 'react'; import ReactDOM from 'react-dom'; import App from './Containers/App'; import * as serviceWorkerRegistration from './serviceWorkerRegistration'; import reportWebVitals from './reportWebVitals'; -import { - I18nextProvider, -} from 'react-i18next'; +import { I18nextProvider } from 'react-i18next'; import i18next from 'i18next'; import CommonEn from './translations/en/common.json'; @@ -20,6 +18,11 @@ import HintsDe from './translations/de/hints.json'; import SettingsDe from './translations/de/settings.json'; import LogDe from './translations/de/log.json'; +import CommonChCN from './translations/zh-CN/common.json'; +import HintsChCN from './translations/zh-CN/hints.json'; +import SettingsChCN from './translations/zh-CN/settings.json'; +import LogChCN from './translations/zh-CN/log.json'; + i18next.init({ interpolation: { excapeValue: false }, lng: 'en', @@ -36,6 +39,12 @@ i18next.init({ log: LogDe, settings: SettingsDe, }, + 'zh-CN': { + common: CommonChCN, + hints: HintsChCN, + log: LogChCN, + settings: SettingsChCN, + }, }, }); @@ -47,7 +56,7 @@ ReactDOM.render( , - document.getElementById('root'), + document.getElementById('root') ); /* diff --git a/src/reportWebVitals.js b/src/reportWebVitals.js index e5d4378c4..fe8e52505 100644 --- a/src/reportWebVitals.js +++ b/src/reportWebVitals.js @@ -1,7 +1,9 @@ +/* istanbul ignore file */ + const reportWebVitals = (onPerfEntry) => { if (onPerfEntry && onPerfEntry instanceof Function) { import('web-vitals').then(({ - getCLS, getFID, getFCP, getLCP, getTTFB, + getCLS, getFID, getFCP, getLCP, getTTFB, }) => { getCLS(onPerfEntry); getFID(onPerfEntry); diff --git a/src/service-worker.js b/src/service-worker.js index 7ec18974b..5cc760764 100644 --- a/src/service-worker.js +++ b/src/service-worker.js @@ -1,3 +1,4 @@ +/* istanbul ignore file */ /* eslint-disable no-restricted-globals */ /* @@ -9,21 +10,14 @@ * service worker, and the Workbox build step will be skipped. */ +import { clientsClaim } from 'workbox-core'; +import { ExpirationPlugin } from 'workbox-expiration'; import { - clientsClaim, -} from 'workbox-core'; -import { - ExpirationPlugin, -} from 'workbox-expiration'; -import { - createHandlerBoundToURL, precacheAndRoute, + createHandlerBoundToURL, + precacheAndRoute, } from 'workbox-precaching'; -import { - registerRoute, -} from 'workbox-routing'; -import { - StaleWhileRevalidate, -} from 'workbox-strategies'; +import { registerRoute } from 'workbox-routing'; +import { StaleWhileRevalidate } from 'workbox-strategies'; clientsClaim(); diff --git a/src/serviceWorkerRegistration.js b/src/serviceWorkerRegistration.js index de6e15819..7124873b0 100644 --- a/src/serviceWorkerRegistration.js +++ b/src/serviceWorkerRegistration.js @@ -1,6 +1,6 @@ -import { - toast -} from 'react-toastify'; +/* istanbul ignore file */ + +import { toast } from 'react-toastify'; /* * This optional code is used to register a service worker. @@ -27,7 +27,7 @@ const isLocalhost = Boolean(window.location.hostname === 'localhost' || window. location. hostname. - match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/),); + match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/)); /** * @param {Object} config Service worker cnfiguration @@ -69,7 +69,7 @@ export function register(config) { */ navigator.serviceWorker.ready.then(() => { console.log('This web app is being served cache-first by a service ' + - 'worker. To learn more, visit https://cra.link/PWA',); + 'worker. To learn more, visit https://cra.link/PWA'); }); } else { // Is not localhost. Just register service worker @@ -112,12 +112,12 @@ function registerValidSW(swUrl, config) { * content until all client tabs are closed. */ console.log('New content is available and will be used when all ' + - 'tabs for this page are closed. See https://cra.link/PWA.',); + 'tabs for this page are closed. See https://cra.link/PWA.'); registration.waiting.postMessage({ type: 'SKIP_WAITING' }); toast.info('Update available! To update the app, refresh this tab.', { toastId: 'appUpdateAvailable', - autoClose: false + autoClose: false, }); // Execute callback @@ -182,7 +182,7 @@ function checkValidServiceWorker(swUrl, config) { }). catch(() => { console.log('No internet connection found.' + - 'App is running in offline mode.',); + 'App is running in offline mode.'); }); } diff --git a/src/settings.json b/src/settings.json index 24bda154a..9a20d3958 100644 --- a/src/settings.json +++ b/src/settings.json @@ -1,4 +1,4 @@ { - "version": "0.9.1", + "version": "0.10.0", "corsProxy": "https://bubblesort.me/?" } diff --git a/src/sources/AM32/__tests__/index.test.js b/src/sources/AM32/__tests__/index.test.js new file mode 100644 index 000000000..ae0ca3ee4 --- /dev/null +++ b/src/sources/AM32/__tests__/index.test.js @@ -0,0 +1,83 @@ +import { + EEPROM, + buildDisplayName, +} from '../'; + +test('visibleIf VARIABLE_PWM_FREQUENCY 0', () => { + const keys = Object.keys(EEPROM.SETTINGS_DESCRIPTIONS); + const settings = { VARIABLE_PWM_FREQUENCY: 0 }; + + const visibleIf = []; + for(let i = 0; i < keys.length; i += 1) { + const base = EEPROM.SETTINGS_DESCRIPTIONS[i].base; + for(let j = 0; j < base.length; j += 1) { + const current = base[j]; + if(current.visibleIf) { + visibleIf.push(current.visibleIf); + } + } + + for(let i = 0; i < visibleIf.length; i += 1) { + expect(visibleIf[i](settings)).toBeTruthy(); + } + } +}); + +test('build display Name', () => { + const flash = { + settings: { + MAIN_REVISION: 1, + SUB_REVISION: 100, + }, + bootloader: { + valid: true, + pin: 'PB2', + version: 8, + }, + }; + + const name = buildDisplayName(flash, 'MAKE'); + expect(name).toEqual('MAKE - AM32, 1.100, Bootloader v8 (PB2)'); +}); + +test('build display Name with missing revisions', () => { + const flash = { + settings: {}, + bootloader: { + valid: true, + pin: 'PB2', + version: 8, + }, + }; + + const name = buildDisplayName(flash, 'MAKE'); + expect(name).toEqual('MAKE - AM32, Unsupported/Unrecognized, Bootloader v8 (PB2)'); +}); + +test('invalid bootloader', () => { + const flash = { + settings: {}, + bootloader: { + valid: false, + pin: 'PB2', + version: 8, + }, + }; + + const name = buildDisplayName(flash, 'MAKE'); + expect(name).toEqual('MAKE - AM32, Unsupported/Unrecognized, Bootloader unknown'); +}); + +test('no firmware flashed', () => { + const flash = { + settings: {}, + bootloader: { + valid: true, + pin: 'PB2', + version: 8, + }, + }; + + const name = buildDisplayName(flash, 'NOT READY'); + expect(name).toEqual('NOT READY - AM32, FLASH FIRMWARE, Bootloader v8 (PB2)'); +}); diff --git a/src/sources/AM32/eeprom.js b/src/sources/AM32/eeprom.js index ec0d5d2c0..f0a28e82b 100644 --- a/src/sources/AM32/eeprom.js +++ b/src/sources/AM32/eeprom.js @@ -1,4 +1,8 @@ const AM32_TYPES = { ARM: 'Arm' }; +const BOOT_LOADER_PINS = { + PA2: 0x02, + PB4: 0x14, +}; const AM32_RESET_DELAY_MS = 1000; @@ -6,6 +10,12 @@ const AM32_PAGE_SIZE = 0x0400; const AM32_EEPROM_OFFSET = 0x7c00; const AM32_LAYOUT_SIZE = 0x30; +const AM32_FLASH_VERSION_OFFSET = 0x10C0; +const AM32_FLASH_VERSION_SIZE = 14; + +const AM32_BOOT_LOADER_VERSION_OFFSET = 0x00C0; +const AM32_BOOT_LOADER_VERSION_SIZE = 1; + const AM32_LAYOUT = { BOOT_BYTE: { offset: 0x00, @@ -200,65 +210,149 @@ const AM32_SETTINGS_LAYOUT_0 = [ const AM32_SETTINGS_LAYOUT_1 = [ { - name: 'SINUSOIDAL_STARTUP', type: 'bool', label: 'escSinusoidalStartup' + name: 'SINUSOIDAL_STARTUP', + type: 'bool', + label: 'escSinusoidalStartup', }, { - name: 'COMPLEMENTARY_PWM', type: 'bool', label: 'escComplementaryPwm' + name: 'COMPLEMENTARY_PWM', + type: 'bool', + label: 'escComplementaryPwm', }, { - name: 'VARIABLE_PWM_FREQUENCY', type: 'bool', label: 'escVariablePwmFrequency' + name: 'VARIABLE_PWM_FREQUENCY', + type: 'bool', + label: 'escVariablePwmFrequency', }, { - name: 'STUCK_ROTOR_PROTECTION', type: 'bool', label: 'escStuckRotorProtection' + name: 'STUCK_ROTOR_PROTECTION', + type: 'bool', + label: 'escStuckRotorProtection', }, { - name: 'STALL_PROTECTION', type: 'bool', label: 'escStallProtection' + name: 'STALL_PROTECTION', + type: 'bool', + label: 'escStallProtection', }, { - name: 'TIMING_ADVANCE', type: 'number', min: 0, max: 22.5, step: 7.5, label: 'escTimingAdvance', displayFactor: 7.5 + name: 'TIMING_ADVANCE', + type: 'number', + label: 'escTimingAdvance', + min: 0, + max: 22.5, + step: 7.5, + displayFactor: 7.5, }, { - name: 'MOTOR_KV', type: 'number', min: 20, max: 10220, step: 40, label: 'escMotorKv', displayFactor: 40, displayOffset: 20 + name: 'MOTOR_KV', + type: 'number', + label: 'escMotorKv', + min: 20, + max: 10220, + step: 40, + displayFactor: 40, + displayOffset: 20, }, { - name: 'MOTOR_POLES', type: 'number', min: 2, max: 36, step: 1, label: 'escMotorPoles' + name: 'MOTOR_POLES', + type: 'number', + label: 'escMotorPoles', + min: 2, + max: 36, + step: 1, }, { - name: 'STARTUP_POWER', type: 'number', min: 50, max: 150, step: 1, label: 'escStartupPower' + name: 'STARTUP_POWER', + type: 'number', + label: 'escStartupPower', + min: 50, + max: 150, + step: 1, }, { - name: 'PWM_FREQUENCY', type: 'number', min: 24, max: 48, step: 1, label: 'escPwmFrequency', - visibleIf: (settings) => settings.VARIABLE_PWM_FREQUENCY === 0 + name: 'PWM_FREQUENCY', + type: 'number', + label: 'escPwmFrequency', + min: 24, + max: 48, + step: 1, + visibleIf: (settings) => settings.VARIABLE_PWM_FREQUENCY === 0, }, { - name: 'BRAKE_ON_STOP', type: 'bool', label: 'escBrakeOnStop' + name: 'BRAKE_ON_STOP', + type: 'bool', + label: 'escBrakeOnStop', }, { - name: 'BEEP_VOLUME', type: 'number', min: 0, max: 11, step: 1, label: 'escBeepVolume' + name: 'BEEP_VOLUME', + type: 'number', + label: 'escBeepVolume', + min: 0, + max: 11, + step: 1, }, { - name: 'INTERVAL_TELEMETRY', type: 'bool', label: 'escIntervalTelemetry' + name: 'INTERVAL_TELEMETRY', + type: 'bool', + label: 'escIntervalTelemetry', }, { - name: 'SERVO_LOW_THRESHOLD', type: 'number', min: 750, max: 1250, step: 1, label: 'escServoLowThreshold', displayFactor: 2, displayOffset: 750 + name: 'SERVO_LOW_THRESHOLD', + type: 'number', + label: 'escServoLowThreshold', + min: 750, + max: 1250, + step: 1, + displayFactor: 2, + displayOffset: 750, }, { - name: 'SERVO_HIGH_THRESHOLD',type: 'number', min: 1750, max: 2250, step: 1, label: 'escServoHighThreshold', displayFactor: 2, displayOffset: 1750 + name: 'SERVO_HIGH_THRESHOLD', + type: 'number', + label: 'escServoHighThreshold', + min: 1750, + max: 2250, + step: 1, + displayFactor: 2, + displayOffset: 1750, }, { - name: 'SERVO_NEUTRAL', type: 'number', min: 1374, max: 1630, step: 1, label: 'escServoNeutral' , displayFactor: 1, displayOffset: 1374 + name: 'SERVO_NEUTRAL', + type: 'number', + label: 'escServoNeutral', + min: 1374, + max: 1630, + step: 1, + displayFactor: 1, + displayOffset: 1374, }, { - name: 'SERVO_DEAD_BAND', type: 'number', min: 0, max: 100, step: 1, label: 'escServoDeadBand' + name: 'SERVO_DEAD_BAND', + type: 'number', + label: 'escServoDeadBand', + min: 0, + max: 100, + step: 1, }, { - name: 'LOW_VOLTAGE_CUTOFF', type: 'bool', label: 'escLowVoltageCutoff' + name: 'LOW_VOLTAGE_CUTOFF', + type: 'bool', + label: 'escLowVoltageCutoff', }, { - name: 'LOW_VOLTAGE_THRESHOLD', type: 'number', min: 250, max: 350, step: 1, label: 'escLowVoltageThreshold' ,displayFactor: 1, displayOffset: 250 + name: 'LOW_VOLTAGE_THRESHOLD', + type: 'number', + label: 'escLowVoltageThreshold', + min: 250, + max: 350, + step: 1, + displayFactor: 1, + displayOffset: 250, }, { - name: 'RC_CAR_REVERSING', type: 'bool', label: 'escRcCarReversing' + name: 'RC_CAR_REVERSING', + type: 'bool', + label: 'escRcCarReversing', }, ]; @@ -269,16 +363,20 @@ const AM32_SETTINGS_DESCRIPTIONS = { const AM32_INDIVIDUAL_SETTINGS = [ { - name: 'MOTOR_DIRECTION', type: 'bool', label: 'escDirectionReversed', + name: 'MOTOR_DIRECTION', + type: 'bool', + label: 'escDirectionReversed', }, { - name: 'BIDIRECTIONAL_MODE', type: 'bool', label: 'escBidirectionalMode', + name: 'BIDIRECTIONAL_MODE', + type: 'bool', + label: 'escBidirectionalMode', }, ]; const AM32_INDIVIDUAL_SETTINGS_DESCRIPTIONS = { '0': { base: AM32_INDIVIDUAL_SETTINGS }, - '1': { base: AM32_INDIVIDUAL_SETTINGS } + '1': { base: AM32_INDIVIDUAL_SETTINGS }, }; const AM32_DEFAULTS = { @@ -320,7 +418,7 @@ const AM32_DEFAULTS = { LOW_VOLTAGE_CUTOFF: 0, LOW_VOLTAGE_THRESHOLD: 50, RC_CAR_REVERSING: 0, - } + }, }; const EEPROM = { @@ -331,20 +429,14 @@ const EEPROM = { LAYOUT_SIZE: AM32_LAYOUT_SIZE, NAMES: [''], PAGE_SIZE: AM32_PAGE_SIZE, + RESET_DELAY: AM32_RESET_DELAY_MS, SETTINGS_DESCRIPTIONS: AM32_SETTINGS_DESCRIPTIONS, TYPES: AM32_TYPES, -}; - -export { - AM32_TYPES, - AM32_LAYOUT, - AM32_DEFAULTS, - AM32_PAGE_SIZE, - AM32_LAYOUT_SIZE, - AM32_EEPROM_OFFSET, - AM32_RESET_DELAY_MS, - AM32_SETTINGS_DESCRIPTIONS, - AM32_INDIVIDUAL_SETTINGS_DESCRIPTIONS, + VERSION_OFFSET: AM32_FLASH_VERSION_OFFSET, + VERSION_SIZE: AM32_FLASH_VERSION_SIZE, + BOOT_LOADER_OFFSET: AM32_BOOT_LOADER_VERSION_OFFSET, + BOOT_LOADER_SIZE: AM32_BOOT_LOADER_VERSION_SIZE, + BOOT_LOADER_PINS, }; export default EEPROM; diff --git a/src/sources/AM32/index.js b/src/sources/AM32/index.js index 0fd2102ab..a474efbe2 100644 --- a/src/sources/AM32/index.js +++ b/src/sources/AM32/index.js @@ -1,6 +1,4 @@ -import Source, { - PLATFORMS, -} from '../Source'; +import Source, { PLATFORMS } from '../Source'; import EEPROM from './eeprom'; @@ -10,6 +8,22 @@ import ESCS_LOCAL from './escs.json'; const VERSIONS_REMOTE = 'https://raw.githubusercontent.com/stylesuxx/esc-configurator/master/src/sources/AM32/versions.json'; const ESCS_REMOTE = 'https://raw.githubusercontent.com/stylesuxx/esc-configurator/master/src/sources/AM32/escs.json'; +function buildDisplayName(flash, make) { + const settings = flash.settings; + let revision = 'Unsupported/Unrecognized'; + if(settings.MAIN_REVISION !== undefined && settings.SUB_REVISION !== undefined) { + revision = `${settings.MAIN_REVISION}.${settings.SUB_REVISION}`; + } + + if(make === 'NOT READY') { + revision = 'FLASH FIRMWARE'; + } + + const bootloader = flash.bootloader.valid ? `, Bootloader v${flash.bootloader.version} (${flash.bootloader.pin})` : ', Bootloader unknown'; + + return `${make} - AM32, ${revision}${bootloader}`; +} + const pwmOptions = []; const am32Config = new Source( 'AM32', @@ -22,6 +36,9 @@ const am32Config = new Source( pwmOptions ); - +export { + buildDisplayName, + EEPROM, +}; export default am32Config; diff --git a/src/sources/Blheli/__tests__/index.test.js b/src/sources/Blheli/__tests__/index.test.js new file mode 100644 index 000000000..101063d46 --- /dev/null +++ b/src/sources/Blheli/__tests__/index.test.js @@ -0,0 +1,66 @@ +import { + EEPROM, + buildDisplayName, +} from '../'; + +test('general settings visibleIf GOVERNOR_MODE 3', () => { + const keys = Object.keys(EEPROM.SETTINGS_DESCRIPTIONS); + const settings = { GOVERNOR_MODE: 3 }; + + const visibleIf = []; + for(let i = 0; i < keys.length; i += 1) { + const base = EEPROM.SETTINGS_DESCRIPTIONS[keys[i]].MULTI.base; + for(let j = 0; j < base.length; j += 1) { + const current = base[j]; + if(current.visibleIf) { + visibleIf.push(current.visibleIf); + } + } + + for(let i = 0; i < visibleIf.length; i += 1) { + expect(visibleIf[i](settings)).toBeTruthy(); + } + } +}); + +test('individual settings visibleIf GOVERNOR_MODE 3, MOTOR_DIRECTION 3', () => { + const keys = Object.keys(EEPROM.INDIVIDUAL_SETTINGS_DESCRIPTIONS); + const settings = { + GOVERNOR_MODE: 3, + MOTOR_DIRECTION: 3, + }; + + const visibleIf = []; + for(let i = 0; i < keys.length; i += 1) { + const base = EEPROM.INDIVIDUAL_SETTINGS_DESCRIPTIONS[keys[i]].base; + for(let j = 0; j < base.length; j += 1) { + const current = base[j]; + if(current.visibleIf) { + visibleIf.push(current.visibleIf); + } + } + + for(let i = 0; i < visibleIf.length; i += 1) { + expect(visibleIf[i](settings)).toBeTruthy(); + } + } +}); + +test('build display Name', () => { + const flash = { + settings: { + MAIN_REVISION: 1, + SUB_REVISION: 100, + }, + }; + + const name = buildDisplayName(flash, 'MAKE'); + expect(name).toEqual('MAKE - BlHeli_S, 1.100'); +}); + +test('build display Name with missing revisions', () => { + const flash = { settings: {} }; + + const name = buildDisplayName(flash, 'MAKE'); + expect(name).toEqual('MAKE - BlHeli_S, Unsupported/Unrecognized'); +}); diff --git a/src/sources/Blheli/eeprom.js b/src/sources/Blheli/eeprom.js index a5a745367..ccb8c8974 100644 --- a/src/sources/Blheli/eeprom.js +++ b/src/sources/Blheli/eeprom.js @@ -27,6 +27,7 @@ const BLHELI_SILABS = { ADDRESS_SPACE_SIZE: 0x1C00, }; +/* const BLHELI_ATMEL = { BLB_SIZE: 0x0200, SK_SIZE: 0x0400, @@ -36,1166 +37,1649 @@ const BLHELI_ATMEL = { SK_ADDRESS_16: 0x3C00, EEPROM_SIZE: 0x0200, }; +*/ const BLHELI_LAYOUT = { MAIN_REVISION: { - offset: 0x00, size: 1, + offset: 0x00, + size: 1, }, SUB_REVISION: { - offset: 0x01, size: 1, + offset: 0x01, + size: 1, }, LAYOUT_REVISION: { - offset: 0x02, size: 1, + offset: 0x02, + size: 1, }, P_GAIN: { - offset: 0x03, size: 1, + offset: 0x03, + size: 1, }, I_GAIN: { - offset: 0x04, size: 1, + offset: 0x04, + size: 1, }, GOVERNOR_MODE: { - offset: 0x05, size: 1, + offset: 0x05, + size: 1, }, LOW_VOLTAGE_LIMIT: { - offset: 0x06, size: 1, + offset: 0x06, + size: 1, }, MOTOR_GAIN: { - offset: 0x07, size: 1, + offset: 0x07, + size: 1, }, MOTOR_IDLE: { - offset: 0x08, size: 1, + offset: 0x08, + size: 1, }, STARTUP_POWER: { - offset: 0x09, size: 1, + offset: 0x09, + size: 1, }, PWM_FREQUENCY: { - offset: 0x0A, size: 1, + offset: 0x0A, + size: 1, }, MOTOR_DIRECTION: { - offset: 0x0B, size: 1, + offset: 0x0B, + size: 1, }, INPUT_PWM_POLARITY: { - offset: 0x0C, size: 1, + offset: 0x0C, + size: 1, }, MODE: { - offset: 0x0D, size: 2, + offset: 0x0D, + size: 2, }, PROGRAMMING_BY_TX: { - offset: 0x0F, size: 1, + offset: 0x0F, + size: 1, }, REARM_AT_START: { - offset: 0x10, size: 1, + offset: 0x10, + size: 1, }, GOVERNOR_SETUP_TARGET: { - offset: 0x11, size: 1, + offset: 0x11, + size: 1, }, STARTUP_RPM: { - offset: 0x12, size: 1, + offset: 0x12, + size: 1, }, STARTUP_ACCELERATION: { - offset: 0x13, size: 1, + offset: 0x13, + size: 1, }, VOLT_COMP: { - offset: 0x14, size: 1, + offset: 0x14, + size: 1, }, COMMUTATION_TIMING: { - offset: 0x15, size: 1, + offset: 0x15, + size: 1, }, DAMPING_FORCE: { - offset: 0x16, size: 1, + offset: 0x16, + size: 1, }, GOVERNOR_RANGE: { - offset: 0x17, size: 1, + offset: 0x17, + size: 1, }, STARTUP_METHOD: { - offset: 0x18, size: 1, + offset: 0x18, + size: 1, }, PPM_MIN_THROTTLE: { - offset: 0x19, size: 1, + offset: 0x19, + size: 1, }, PPM_MAX_THROTTLE: { - offset: 0x1A, size: 1, + offset: 0x1A, + size: 1, }, BEEP_STRENGTH: { - offset: 0x1B, size: 1, + offset: 0x1B, + size: 1, }, BEACON_STRENGTH: { - offset: 0x1C, size: 1, + offset: 0x1C, + size: 1, }, BEACON_DELAY: { - offset: 0x1D, size: 1, + offset: 0x1D, + size: 1, }, THROTTLE_RATE: { - offset: 0x1E, size: 1, + offset: 0x1E, + size: 1, }, DEMAG_COMPENSATION: { - offset: 0x1F, size: 1, + offset: 0x1F, + size: 1, }, BEC_VOLTAGE: { - offset: 0x20, size: 1, + offset: 0x20, + size: 1, }, PPM_CENTER_THROTTLE: { - offset: 0x21, size: 1, + offset: 0x21, + size: 1, }, SPOOLUP_TIME: { - offset: 0x22, size: 1, + offset: 0x22, + size: 1, }, TEMPERATURE_PROTECTION: { - offset: 0x23, size: 1, + offset: 0x23, + size: 1, }, LOW_RPM_POWER_PROTECTION: { - offset: 0x24, size: 1, + offset: 0x24, + size: 1, }, PWM_INPUT: { - offset: 0x25, size: 1, + offset: 0x25, + size: 1, }, PWM_DITHER: { - offset: 0x26, size: 1, + offset: 0x26, + size: 1, }, BRAKE_ON_STOP: { - offset: 0x27, size: 1, + offset: 0x27, + size: 1, }, LED_CONTROL: { - offset: 0x28, size: 1, + offset: 0x28, + size: 1, }, LAYOUT: { - offset: 0x40, size: 16, + offset: 0x40, + size: 16, }, MCU: { - offset: 0x50, size: 16, + offset: 0x50, + size: 16, }, NAME: { - offset: 0x60, size: 16, + offset: 0x60, + size: 16, }, }; // layout 33, 16.3, 16.4, 16.5 const BLHELI_S_SETTINGS_LAYOUT_33 = [ { - name: 'PROGRAMMING_BY_TX', type: 'bool', label: 'escProgrammingByTX' + name: 'PROGRAMMING_BY_TX', + type: 'bool', + label: 'escProgrammingByTX', }, { - name: 'STARTUP_POWER', type: 'enum', options: [ + name: 'STARTUP_POWER', + type: 'enum', + options: [ { - value: '1', label: '0.031' + value: '1', + label: '0.031', }, { - value: '2', label: '0.047' + value: '2', + label: '0.047', }, { - value: '3', label: '0.063' + value: '3', + label: '0.063', }, { - value: '4', label: '0.094' + value: '4', + label: '0.094', }, { - value: '5', label: '0.125' + value: '5', + label: '0.125', }, { - value: '6', label: '0.188' + value: '6', + label: '0.188', }, { - value: '7', label: '0.25' + value: '7', + label: '0.25', }, { - value: '8', label: '0.38' + value: '8', + label: '0.38', }, { - value: '9', label: '0.50' + value: '9', + label: '0.50', }, { - value: '10', label: '0.75' + value: '10', + label: '0.75', }, { - value: '11', label: '1.00' + value: '11', + label: '1.00', }, { - value: '12', label: '1.25' + value: '12', + label: '1.25', }, { - value: '13', label: '1.50' - } + value: '13', + label: '1.50', + }, ], - label: 'escStartupPower' + label: 'escStartupPower', }, { - name: 'TEMPERATURE_PROTECTION', type: 'enum', label: 'escTemperatureProtection', + name: 'TEMPERATURE_PROTECTION', + type: 'enum', + label: 'escTemperatureProtection', options: [ { - value: '0', label: 'Disabled' + value: '0', + label: 'Disabled', }, { - value: '1', label: '80C' + value: '1', + label: '80C', }, { - value: '2', label: '90 C' + value: '2', + label: '90 C', }, { - value: '3', label: '100 C' + value: '3', + label: '100 C', }, { - value: '4', label: '110 C' + value: '4', + label: '110 C', }, { - value: '5', label: '120 C' + value: '5', + label: '120 C', }, { - value: '6', label: '130 C' + value: '6', + label: '130 C', }, { - value: '7', label: '140 C' - } - ] + value: '7', + label: '140 C', + }, + ], }, { - name: 'LOW_RPM_POWER_PROTECTION', type: 'bool', label: 'escLowRPMPowerProtection' + name: 'LOW_RPM_POWER_PROTECTION', + type: 'bool', + label: 'escLowRPMPowerProtection', }, { - name: 'BRAKE_ON_STOP', type: 'bool', label: 'escBrakeOnStop' + name: 'BRAKE_ON_STOP', + type: 'bool', + label: 'escBrakeOnStop', }, { - name: 'DEMAG_COMPENSATION', type: 'enum', label: 'escDemagCompensation', + name: 'DEMAG_COMPENSATION', + type: 'enum', + label: 'escDemagCompensation', options: [ { - value: '1', label: 'Off' + value: '1', + label: 'Off', }, { - value: '2', label: 'Low' + value: '2', + label: 'Low', }, { - value: '3', label: 'High' - } - ] + value: '3', + label: 'High', + }, + ], }, { - name: 'COMMUTATION_TIMING', type: 'enum', label: 'escMotorTiming', + name: 'COMMUTATION_TIMING', + type: 'enum', + label: 'escMotorTiming', options: [ { - value: '1', label: 'Low' + value: '1', + label: 'Low', }, { - value: '2', label: 'MediumLow' + value: '2', + label: 'MediumLow', }, { - value: '3', label: 'Medium' + value: '3', + label: 'Medium', }, { - value: '4', label: 'MediumHigh' + value: '4', + label: 'MediumHigh', }, { - value: '5', label: 'High' - } - ] + value: '5', + label: 'High', + }, + ], }, { - name: 'BEEP_STRENGTH', type: 'number', min: 1, max: 255, step: 1, label: 'escBeepStrength' + name: 'BEEP_STRENGTH', + type: 'number', + min: 1, + max: 255, + step: 1, + label: 'escBeepStrength', }, { - name: 'BEACON_STRENGTH', type: 'number', min: 1, max: 255, step: 1, label: 'escBeaconStrength' + name: 'BEACON_STRENGTH', + type: 'number', + min: 1, + max: 255, + step: 1, + label: 'escBeaconStrength', }, { - name: 'BEACON_DELAY', type: 'enum', label: 'escBeaconDelay', + name: 'BEACON_DELAY', + type: 'enum', + label: 'escBeaconDelay', options: [ { - value: '1', label: '1 minute' + value: '1', + label: '1 minute', }, { - value: '2', label: '2 minutes' + value: '2', + label: '2 minutes', }, { - value: '3', label: '5 minutes' + value: '3', + label: '5 minutes', }, { - value: '4', label: '10 minutes' + value: '4', + label: '10 minutes', }, { - value: '5', label: 'Infinite' - } - ] - } + value: '5', + label: 'Infinite', + }, + ], + }, ]; // layout 32, 16.0, 16.1, 16.2 const BLHELI_S_SETTINGS_LAYOUT_32 = [ { - name: 'PROGRAMMING_BY_TX', type: 'bool', label: 'escProgrammingByTX' + name: 'PROGRAMMING_BY_TX', + type: 'bool', + label: 'escProgrammingByTX', }, { - name: 'STARTUP_POWER', type: 'enum', options: [ + name: 'STARTUP_POWER', + type: 'enum', + options: [ { - value: '1', label: '0.031' + value: '1', + label: '0.031', }, { - value: '2', label: '0.047' + value: '2', + label: '0.047', }, { - value: '3', label: '0.063' + value: '3', + label: '0.063', }, { - value: '4', label: '0.094' + value: '4', + label: '0.094', }, { - value: '5', label: '0.125' + value: '5', + label: '0.125', }, { - value: '6', label: '0.188' + value: '6', + label: '0.188', }, { - value: '7', label: '0.25' + value: '7', + label: '0.25', }, { - value: '8', label: '0.38' + value: '8', + label: '0.38', }, { - value: '9', label: '0.50' + value: '9', + label: '0.50', }, { - value: '10', label: '0.75' + value: '10', + label: '0.75', }, { - value: '11', label: '1.00' + value: '11', + label: '1.00', }, { - value: '12', label: '1.25' + value: '12', + label: '1.25', }, { - value: '13', label: '1.50' - } + value: '13', + label: '1.50', + }, ], - label: 'escStartupPower' + label: 'escStartupPower', }, { - name: 'TEMPERATURE_PROTECTION', type: 'bool', label: 'escTemperatureProtection' + name: 'TEMPERATURE_PROTECTION', + type: 'bool', + label: 'escTemperatureProtection', }, { - name: 'LOW_RPM_POWER_PROTECTION', type: 'bool', label: 'escLowRPMPowerProtection' + name: 'LOW_RPM_POWER_PROTECTION', + type: 'bool', + label: 'escLowRPMPowerProtection', }, { - name: 'BRAKE_ON_STOP', type: 'bool', label: 'escBrakeOnStop' + name: 'BRAKE_ON_STOP', + type: 'bool', + label: 'escBrakeOnStop', }, { - name: 'DEMAG_COMPENSATION', type: 'enum', label: 'escDemagCompensation', + name: 'DEMAG_COMPENSATION', + type: 'enum', + label: 'escDemagCompensation', options: [ { - value: '1', label: 'Off' + value: '1', + label: 'Off', }, { - value: '2', label: 'Low' + value: '2', + label: 'Low', }, { - value: '3', label: 'High' - } - ] + value: '3', + label: 'High', + }, + ], }, { - name: 'COMMUTATION_TIMING', type: 'enum', label: 'escMotorTiming', + name: 'COMMUTATION_TIMING', + type: 'enum', + label: 'escMotorTiming', options: [ { - value: '1', label: 'Low' + value: '1', + label: 'Low', }, { - value: '2', label: 'MediumLow' + value: '2', + label: 'MediumLow', }, { - value: '3', label: 'Medium' + value: '3', + label: 'Medium', }, { - value: '4', label: 'MediumHigh' + value: '4', + label: 'MediumHigh', }, { - value: '5', label: 'High' - } - ] + value: '5', + label: 'High', + }, + ], }, { - name: 'BEEP_STRENGTH', type: 'number', min: 1, max: 255, step: 1, label: 'escBeepStrength' + name: 'BEEP_STRENGTH', + type: 'number', + min: 1, + max: 255, + step: 1, + label: 'escBeepStrength', }, { - name: 'BEACON_STRENGTH', type: 'number', min: 1, max: 255, step: 1, label: 'escBeaconStrength' + name: 'BEACON_STRENGTH', + type: 'number', + min: 1, + max: 255, + step: 1, + label: 'escBeaconStrength', }, { - name: 'BEACON_DELAY', type: 'enum', label: 'escBeaconDelay', + name: 'BEACON_DELAY', + type: 'enum', + label: 'escBeaconDelay', options: [ { - value: '1', label: '1 minute' + value: '1', + label: '1 minute', }, { - value: '2', label: '2 minutes' + value: '2', + label: '2 minutes', }, { - value: '3', label: '5 minutes' + value: '3', + label: '5 minutes', }, { - value: '4', label: '10 minutes' + value: '4', + label: '10 minutes', }, { - value: '5', label: 'Infinite' - } - ] - } + value: '5', + label: 'Infinite', + }, + ], + }, ]; // layout 21, 14.5, 14.6, 14.7 const BLHELI_MULTI_SETTINGS_LAYOUT_21 = [ { - name: 'PROGRAMMING_BY_TX', type: 'bool', label: 'escProgrammingByTX' + name: 'PROGRAMMING_BY_TX', + type: 'bool', + label: 'escProgrammingByTX', }, { - name: 'GOVERNOR_MODE', type: 'enum', label: 'escClosedLoopMode', + name: 'GOVERNOR_MODE', + type: 'enum', + label: 'escClosedLoopMode', options: [ { - value: '1', label: 'HiRange' + value: '1', + label: 'HiRange', }, { - value: '2', label: 'MidRange' + value: '2', + label: 'MidRange', }, { - value: '3', label: 'LoRange' + value: '3', + label: 'LoRange', }, { - value: '4', label: 'Off' - } - ] + value: '4', + label: 'Off', + }, + ], }, { - name: 'P_GAIN', type: 'enum', options: [ + name: 'P_GAIN', + type: 'enum', + options: [ { - value: '1', label: '0.13' + value: '1', + label: '0.13', }, { - value: '2', label: '0.17' + value: '2', + label: '0.17', }, { - value: '3', label: '0.25' + value: '3', + label: '0.25', }, { - value: '4', label: '0.38' + value: '4', + label: '0.38', }, { - value: '5', label: '0.50' + value: '5', + label: '0.50', }, { - value: '6', label: '0.75' + value: '6', + label: '0.75', }, { - value: '7', label: '1.00' + value: '7', + label: '1.00', }, { - value: '8', label: '1.50' + value: '8', + label: '1.50', }, { - value: '9', label: '2.00' + value: '9', + label: '2.00', }, { - value: '10', label: '3.00' + value: '10', + label: '3.00', }, { - value: '11', label: '4.00' + value: '11', + label: '4.00', }, { - value: '12', label: '6.00' + value: '12', + label: '6.00', }, { - value: '13', label: '8.00' - } + value: '13', + label: '8.00', + }, ], visibleIf: (settings) => settings.GOVERNOR_MODE !== 4, - label: 'escClosedLoopPGain' + label: 'escClosedLoopPGain', }, { - name: 'I_GAIN', type: 'enum', options: [ + name: 'I_GAIN', + type: 'enum', + options: [ { - value: '1', label: '0.13' + value: '1', + label: '0.13', }, { - value: '2', label: '0.17' + value: '2', + label: '0.17', }, { - value: '3', label: '0.25' + value: '3', + label: '0.25', }, { - value: '4', label: '0.38' + value: '4', + label: '0.38', }, { - value: '5', label: '0.50' + value: '5', + label: '0.50', }, { - value: '6', label: '0.75' + value: '6', + label: '0.75', }, { - value: '7', label: '1.00' + value: '7', + label: '1.00', }, { - value: '8', label: '1.50' + value: '8', + label: '1.50', }, { - value: '9', label: '2.00' + value: '9', + label: '2.00', }, { - value: '10', label: '3.00' + value: '10', + label: '3.00', }, { - value: '11', label: '4.00' + value: '11', + label: '4.00', }, { - value: '12', label: '6.00' + value: '12', + label: '6.00', }, { - value: '13', label: '8.00' - } + value: '13', + label: '8.00', + }, ], visibleIf: (settings) => settings.GOVERNOR_MODE !== 4, - label: 'escClosedLoopIGain' + label: 'escClosedLoopIGain', }, { - name: 'MOTOR_GAIN', type: 'enum', options: [ + name: 'MOTOR_GAIN', + type: 'enum', + options: [ { - value: '1', label: '0.75' + value: '1', + label: '0.75', }, { - value: '2', label: '0.88' + value: '2', + label: '0.88', }, { - value: '3', label: '1.00' + value: '3', + label: '1.00', }, { - value: '4', label: '1.12' + value: '4', + label: '1.12', }, { - value: '5', label: '1.25' - } + value: '5', + label: '1.25', + }, ], - label: 'escMotorGain' + label: 'escMotorGain', }, { - name: 'STARTUP_POWER', type: 'enum', options: [ + name: 'STARTUP_POWER', + type: 'enum', + options: [ { - value: '1', label: '0.031' + value: '1', + label: '0.031', }, { - value: '2', label: '0.047' + value: '2', + label: '0.047', }, { - value: '3', label: '0.063' + value: '3', + label: '0.063', }, { - value: '4', label: '0.094' + value: '4', + label: '0.094', }, { - value: '5', label: '0.125' + value: '5', + label: '0.125', }, { - value: '6', label: '0.188' + value: '6', + label: '0.188', }, { - value: '7', label: '0.25' + value: '7', + label: '0.25', }, { - value: '8', label: '0.38' + value: '8', + label: '0.38', }, { - value: '9', label: '0.50' + value: '9', + label: '0.50', }, { - value: '10', label: '0.75' + value: '10', + label: '0.75', }, { - value: '11', label: '1.00' + value: '11', + label: '1.00', }, { - value: '12', label: '1.25' + value: '12', + label: '1.25', }, { - value: '13', label: '1.50' - } + value: '13', + label: '1.50', + }, ], - label: 'escStartupPower' + label: 'escStartupPower', }, { - name: 'TEMPERATURE_PROTECTION', type: 'bool', label: 'escTemperatureProtection' + name: 'TEMPERATURE_PROTECTION', + type: 'bool', + label: 'escTemperatureProtection', }, { - name: 'PWM_DITHER', type: 'enum', label: 'escPWMOutputDither', + name: 'PWM_DITHER', + type: 'enum', + label: 'escPWMOutputDither', options: [ { - value: '1', label: 'Off' + value: '1', + label: 'Off', }, { - value: '2', label: '3' + value: '2', + label: '3', }, { - value: '3', label: '7' + value: '3', + label: '7', }, { - value: '4', label: '15' + value: '4', + label: '15', }, { - value: '5', label: '31' - } - ] + value: '5', + label: '31', + }, + ], }, { - name: 'LOW_RPM_POWER_PROTECTION', type: 'bool', label: 'escLowRPMPowerProtection' + name: 'LOW_RPM_POWER_PROTECTION', + type: 'bool', + label: 'escLowRPMPowerProtection', }, { - name: 'BRAKE_ON_STOP', type: 'bool', label: 'escBrakeOnStop' + name: 'BRAKE_ON_STOP', + type: 'bool', + label: 'escBrakeOnStop', }, { - name: 'DEMAG_COMPENSATION', type: 'enum', label: 'escDemagCompensation', + name: 'DEMAG_COMPENSATION', + type: 'enum', + label: 'escDemagCompensation', options: [ { - value: '1', label: 'Off' + value: '1', + label: 'Off', }, { - value: '2', label: 'Low' + value: '2', + label: 'Low', }, { - value: '3', label: 'High' - } - ] + value: '3', + label: 'High', + }, + ], }, { - name: 'PWM_FREQUENCY', type: 'enum', label: 'escPWMFrequencyDamped', + name: 'PWM_FREQUENCY', + type: 'enum', + label: 'escPWMFrequencyDamped', options: [ { - value: '1', label: 'Off' + value: '1', + label: 'Off', }, { - value: '2', label: 'Low' + value: '2', + label: 'Low', }, { - value: '3', label: 'DampedLight' - } - ] + value: '3', + label: 'DampedLight', + }, + ], }, { - name: 'PWM_INPUT', type: 'bool', label: 'escEnablePWMInput' + name: 'PWM_INPUT', + type: 'bool', + label: 'escEnablePWMInput', }, { - name: 'COMMUTATION_TIMING', type: 'enum', label: 'escMotorTiming', + name: 'COMMUTATION_TIMING', + type: 'enum', + label: 'escMotorTiming', options: [ { - value: '1', label: 'Low' + value: '1', + label: 'Low', }, { - value: '2', label: 'MediumLow' + value: '2', + label: 'MediumLow', }, { - value: '3', label: 'Medium' + value: '3', + label: 'Medium', }, { - value: '4', label: 'MediumHigh' + value: '4', + label: 'MediumHigh', }, { - value: '5', label: 'High' - } - ] + value: '5', + label: 'High', + }, + ], }, { - name: 'INPUT_PWM_POLARITY', type: 'enum', label: 'escInputPolarity', + name: 'INPUT_PWM_POLARITY', + type: 'enum', + label: 'escInputPolarity', options: [ { - value: '1', label: 'Positive' + value: '1', + label: 'Positive', }, { - value: '2', label: 'Negative' - } - ] + value: '2', + label: 'Negative', + }, + ], }, { - name: 'BEEP_STRENGTH', type: 'number', min: 1, max: 255, step: 1, label: 'escBeepStrength' + name: 'BEEP_STRENGTH', + type: 'number', + min: 1, + max: 255, + step: 1, + label: 'escBeepStrength', }, { - name: 'BEACON_STRENGTH', type: 'number', min: 1, max: 255, step: 1, label: 'escBeaconStrength' + name: 'BEACON_STRENGTH', + type: 'number', + min: 1, + max: 255, + step: 1, + label: 'escBeaconStrength', }, { - name: 'BEACON_DELAY', type: 'enum', label: 'escBeaconDelay', + name: 'BEACON_DELAY', + type: 'enum', + label: 'escBeaconDelay', options: [ { - value: '1', label: '1 minute' + value: '1', + label: '1 minute', }, { - value: '2', label: '2 minutes' + value: '2', + label: '2 minutes', }, { - value: '3', label: '5 minutes' + value: '3', + label: '5 minutes', }, { - value: '4', label: '10 minutes' + value: '4', + label: '10 minutes', }, { - value: '5', label: 'Infinite' - } - ] - } + value: '5', + label: 'Infinite', + }, + ], + }, ]; // layout 20, 14.0, 14.1, 14.2, 14.3, 14.4 const BLHELI_MULTI_SETTINGS_LAYOUT_20 = [ { - name: 'PROGRAMMING_BY_TX', type: 'bool', label: 'escProgrammingByTX' + name: 'PROGRAMMING_BY_TX', + type: 'bool', + label: 'escProgrammingByTX', }, { - name: 'GOVERNOR_MODE', type: 'enum', label: 'escClosedLoopMode', + name: 'GOVERNOR_MODE', + type: 'enum', + label: 'escClosedLoopMode', options: [ { - value: '1', label: 'HiRange' + value: '1', + label: 'HiRange', }, { - value: '2', label: 'MidRange' + value: '2', + label: 'MidRange', }, { - value: '3', label: 'LoRange' + value: '3', + label: 'LoRange', }, { - value: '4', label: 'Off' - } - ] + value: '4', + label: 'Off', + }, + ], }, { - name: 'P_GAIN', type: 'enum', options: [ + name: 'P_GAIN', + type: 'enum', + options: [ { - value: '1', label: '0.13' + value: '1', + label: '0.13', }, { - value: '2', label: '0.17' + value: '2', + label: '0.17', }, { - value: '3', label: '0.25' + value: '3', + label: '0.25', }, { - value: '4', label: '0.38' + value: '4', + label: '0.38', }, { - value: '5', label: '0.50' + value: '5', + label: '0.50', }, { - value: '6', label: '0.75' + value: '6', + label: '0.75', }, { - value: '7', label: '1.00' + value: '7', + label: '1.00', }, { - value: '8', label: '1.50' + value: '8', + label: '1.50', }, { - value: '9', label: '2.00' + value: '9', + label: '2.00', }, { - value: '10', label: '3.00' + value: '10', + label: '3.00', }, { - value: '11', label: '4.00' + value: '11', + label: '4.00', }, { - value: '12', label: '6.00' + value: '12', + label: '6.00', }, { - value: '13', label: '8.00' - } + value: '13', + label: '8.00', + }, ], visibleIf: (settings) => settings.GOVERNOR_MODE !== 4, - label: 'escClosedLoopPGain' + label: 'escClosedLoopPGain', }, { - name: 'I_GAIN', type: 'enum', options: [ + name: 'I_GAIN', + type: 'enum', + options: [ { - value: '1', label: '0.13' + value: '1', + label: '0.13', }, { - value: '2', label: '0.17' + value: '2', + label: '0.17', }, { - value: '3', label: '0.25' + value: '3', + label: '0.25', }, { - value: '4', label: '0.38' + value: '4', + label: '0.38', }, { - value: '5', label: '0.50' + value: '5', + label: '0.50', }, { - value: '6', label: '0.75' + value: '6', + label: '0.75', }, { - value: '7', label: '1.00' + value: '7', + label: '1.00', }, { - value: '8', label: '1.50' + value: '8', + label: '1.50', }, { - value: '9', label: '2.00' + value: '9', + label: '2.00', }, { - value: '10', label: '3.00' + value: '10', + label: '3.00', }, { - value: '11', label: '4.00' + value: '11', + label: '4.00', }, { - value: '12', label: '6.00' + value: '12', + label: '6.00', }, { - value: '13', label: '8.00' - } + value: '13', + label: '8.00', + }, ], visibleIf: (settings) => settings.GOVERNOR_MODE !== 4, - label: 'escClosedLoopIGain' + label: 'escClosedLoopIGain', }, { - name: 'MOTOR_GAIN', type: 'enum', options: [ + name: 'MOTOR_GAIN', + type: 'enum', + options: [ { - value: '1', label: '0.75' + value: '1', + label: '0.75', }, { - value: '2', label: '0.88' + value: '2', + label: '0.88', }, { - value: '3', label: '1.00' + value: '3', + label: '1.00', }, { - value: '4', label: '1.12' + value: '4', + label: '1.12', }, { - value: '5', label: '1.25' - } + value: '5', + label: '1.25', + }, ], - label: 'escMotorGain' + label: 'escMotorGain', }, { - name: 'STARTUP_POWER', type: 'enum', options: [ + name: 'STARTUP_POWER', + type: 'enum', + options: [ { - value: '1', label: '0.031' + value: '1', + label: '0.031', }, { - value: '2', label: '0.047' + value: '2', + label: '0.047', }, { - value: '3', label: '0.063' + value: '3', + label: '0.063', }, { - value: '4', label: '0.094' + value: '4', + label: '0.094', }, { - value: '5', label: '0.125' + value: '5', + label: '0.125', }, { - value: '6', label: '0.188' + value: '6', + label: '0.188', }, { - value: '7', label: '0.25' + value: '7', + label: '0.25', }, { - value: '8', label: '0.38' + value: '8', + label: '0.38', }, { - value: '9', label: '0.50' + value: '9', + label: '0.50', }, { - value: '10', label: '0.75' + value: '10', + label: '0.75', }, { - value: '11', label: '1.00' + value: '11', + label: '1.00', }, { - value: '12', label: '1.25' + value: '12', + label: '1.25', }, { - value: '13', label: '1.50' - } + value: '13', + label: '1.50', + }, ], - label: 'escStartupPower' + label: 'escStartupPower', }, { - name: 'TEMPERATURE_PROTECTION', type: 'bool', label: 'escTemperatureProtection' + name: 'TEMPERATURE_PROTECTION', + type: 'bool', + label: 'escTemperatureProtection', }, { - name: 'PWM_DITHER', type: 'enum', label: 'escPWMOutputDither', + name: 'PWM_DITHER', + type: 'enum', + label: 'escPWMOutputDither', options: [ { - value: '1', label: 'Off' + value: '1', + label: 'Off', }, { - value: '2', label: '7' + value: '2', + label: '7', }, { - value: '3', label: '15' + value: '3', + label: '15', }, { - value: '4', label: '31' + value: '4', + label: '31', }, { - value: '5', label: '63' - } - ] + value: '5', + label: '63', + }, + ], }, { - name: 'LOW_RPM_POWER_PROTECTION', type: 'bool', label: 'escLowRPMPowerProtection' + name: 'LOW_RPM_POWER_PROTECTION', + type: 'bool', + label: 'escLowRPMPowerProtection', }, { - name: 'DEMAG_COMPENSATION', type: 'enum', label: 'escDemagCompensation', + name: 'DEMAG_COMPENSATION', + type: 'enum', + label: 'escDemagCompensation', options: [ { - value: '1', label: 'Off' + value: '1', + label: 'Off', }, { - value: '2', label: 'Low' + value: '2', + label: 'Low', }, { - value: '3', label: 'High' - } - ] + value: '3', + label: 'High', + }, + ], }, { - name: 'PWM_FREQUENCY', type: 'enum', label: 'escPWMFrequencyDamped', + name: 'PWM_FREQUENCY', + type: 'enum', + label: 'escPWMFrequencyDamped', options: [ { - value: '1', label: 'Off' + value: '1', + label: 'Off', }, { - value: '2', label: 'Low' + value: '2', + label: 'Low', }, { - value: '3', label: 'DampedLight' - } - ] + value: '3', + label: 'DampedLight', + }, + ], }, { - name: 'PWM_INPUT', type: 'bool', label: 'escEnablePWMInput' + name: 'PWM_INPUT', + type: 'bool', + label: 'escEnablePWMInput', }, { - name: 'COMMUTATION_TIMING', type: 'enum', label: 'escMotorTiming', + name: 'COMMUTATION_TIMING', + type: 'enum', + label: 'escMotorTiming', options: [ { - value: '1', label: 'Low' + value: '1', + label: 'Low', }, { - value: '2', label: 'MediumLow' + value: '2', + label: 'MediumLow', }, { - value: '3', label: 'Medium' + value: '3', + label: 'Medium', }, { - value: '4', label: 'MediumHigh' + value: '4', + label: 'MediumHigh', }, { - value: '5', label: 'High' - } - ] + value: '5', + label: 'High', + }, + ], }, { - name: 'INPUT_PWM_POLARITY', type: 'enum', label: 'escInputPolarity', + name: 'INPUT_PWM_POLARITY', + type: 'enum', + label: 'escInputPolarity', options: [ { - value: '1', label: 'Positive' + value: '1', + label: 'Positive', }, { - value: '2', label: 'Negative' - } - ] + value: '2', + label: 'Negative', + }, + ], }, { - name: 'BEEP_STRENGTH', type: 'number', min: 1, max: 255, step: 1, label: 'escBeepStrength' + name: 'BEEP_STRENGTH', + type: 'number', + min: 1, + max: 255, + step: 1, + label: 'escBeepStrength', }, { - name: 'BEACON_STRENGTH', type: 'number', min: 1, max: 255, step: 1, label: 'escBeaconStrength' + name: 'BEACON_STRENGTH', + type: 'number', + min: 1, + max: 255, + step: 1, + label: 'escBeaconStrength', }, { - name: 'BEACON_DELAY', type: 'enum', label: 'escBeaconDelay', + name: 'BEACON_DELAY', + type: 'enum', + label: 'escBeaconDelay', options: [ { - value: '1', label: '1 minute' + value: '1', + label: '1 minute', }, { - value: '2', label: '2 minutes' + value: '2', + label: '2 minutes', }, { - value: '3', label: '5 minutes' + value: '3', + label: '5 minutes', }, { - value: '4', label: '10 minutes' + value: '4', + label: '10 minutes', }, { - value: '5', label: 'Infinite' - } - ] - } + value: '5', + label: 'Infinite', + }, + ], + }, ]; // layout 19, 13.2 const BLHELI_MULTI_SETTINGS_LAYOUT_19 = [ { - name: 'PROGRAMMING_BY_TX', type: 'bool', label: 'escProgrammingByTX' + name: 'PROGRAMMING_BY_TX', + type: 'bool', + label: 'escProgrammingByTX', }, { - name: 'GOVERNOR_MODE', type: 'enum', label: 'escClosedLoopMode', + name: 'GOVERNOR_MODE', + type: 'enum', + label: 'escClosedLoopMode', options: [ { - value: '1', label: 'HiRange' + value: '1', + label: 'HiRange', }, { - value: '2', label: 'MidRange' + value: '2', + label: 'MidRange', }, { - value: '3', label: 'LoRange' + value: '3', + label: 'LoRange', }, { - value: '4', label: 'Off' - } - ] + value: '4', + label: 'Off', + }, + ], }, { - name: 'P_GAIN', type: 'enum', options: [ + name: 'P_GAIN', + type: 'enum', + options: [ { - value: '1', label: '0.13' + value: '1', + label: '0.13', }, { - value: '2', label: '0.17' + value: '2', + label: '0.17', }, { - value: '3', label: '0.25' + value: '3', + label: '0.25', }, { - value: '4', label: '0.38' + value: '4', + label: '0.38', }, { - value: '5', label: '0.50' + value: '5', + label: '0.50', }, { - value: '6', label: '0.75' + value: '6', + label: '0.75', }, { - value: '7', label: '1.00' + value: '7', + label: '1.00', }, { - value: '8', label: '1.50' + value: '8', + label: '1.50', }, { - value: '9', label: '2.00' + value: '9', + label: '2.00', }, { - value: '10', label: '3.00' + value: '10', + label: '3.00', }, { - value: '11', label: '4.00' + value: '11', + label: '4.00', }, { - value: '12', label: '6.00' + value: '12', + label: '6.00', }, { - value: '13', label: '8.00' - } + value: '13', + label: '8.00', + }, ], visibleIf: (settings) => settings.GOVERNOR_MODE !== 4, - label: 'escClosedLoopPGain' + label: 'escClosedLoopPGain', }, { - name: 'I_GAIN', type: 'enum', options: [ + name: 'I_GAIN', + type: 'enum', + options: [ { - value: '1', label: '0.13' + value: '1', + label: '0.13', }, { - value: '2', label: '0.17' + value: '2', + label: '0.17', }, { - value: '3', label: '0.25' + value: '3', + label: '0.25', }, { - value: '4', label: '0.38' + value: '4', + label: '0.38', }, { - value: '5', label: '0.50' + value: '5', + label: '0.50', }, { - value: '6', label: '0.75' + value: '6', + label: '0.75', }, { - value: '7', label: '1.00' + value: '7', + label: '1.00', }, { - value: '8', label: '1.50' + value: '8', + label: '1.50', }, { - value: '9', label: '2.00' + value: '9', + label: '2.00', }, { - value: '10', label: '3.00' + value: '10', + label: '3.00', }, { - value: '11', label: '4.00' + value: '11', + label: '4.00', }, { - value: '12', label: '6.00' + value: '12', + label: '6.00', }, { - value: '13', label: '8.00' - } + value: '13', + label: '8.00', + }, ], visibleIf: (settings) => settings.GOVERNOR_MODE !== 4, - label: 'escClosedLoopIGain' + label: 'escClosedLoopIGain', }, { - name: 'LOW_VOLTAGE_LIMIT', type: 'enum', options: [ + name: 'LOW_VOLTAGE_LIMIT', + type: 'enum', + options: [ { - value: '1', label: 'Off' + value: '1', + label: 'Off', }, { - value: '2', label: '3.0V/c' + value: '2', + label: '3.0V/c', }, { - value: '3', label: '3.1V/c' + value: '3', + label: '3.1V/c', }, { - value: '4', label: '3.2V/c' + value: '4', + label: '3.2V/c', }, { - value: '5', label: '3.3V/c' + value: '5', + label: '3.3V/c', }, { - value: '6', label: '3.4V/c' - } + value: '6', + label: '3.4V/c', + }, ], - label: 'escLowVoltageLimit' + label: 'escLowVoltageLimit', }, { - name: 'MOTOR_GAIN', type: 'enum', options: [ + name: 'MOTOR_GAIN', + type: 'enum', + options: [ { - value: '1', label: '0.75' + value: '1', + label: '0.75', }, { - value: '2', label: '0.88' + value: '2', + label: '0.88', }, { - value: '3', label: '1.00' + value: '3', + label: '1.00', }, { - value: '4', label: '1.12' + value: '4', + label: '1.12', }, { - value: '5', label: '1.25' - } + value: '5', + label: '1.25', + }, ], - label: 'escMotorGain' + label: 'escMotorGain', }, { - name: 'STARTUP_POWER', type: 'enum', options: [ + name: 'STARTUP_POWER', + type: 'enum', + options: [ { - value: '1', label: '0.031' + value: '1', + label: '0.031', }, { - value: '2', label: '0.047' + value: '2', + label: '0.047', }, { - value: '3', label: '0.063' + value: '3', + label: '0.063', }, { - value: '4', label: '0.094' + value: '4', + label: '0.094', }, { - value: '5', label: '0.125' + value: '5', + label: '0.125', }, { - value: '6', label: '0.188' + value: '6', + label: '0.188', }, { - value: '7', label: '0.25' + value: '7', + label: '0.25', }, { - value: '8', label: '0.38' + value: '8', + label: '0.38', }, { - value: '9', label: '0.50' + value: '9', + label: '0.50', }, { - value: '10', label: '0.75' + value: '10', + label: '0.75', }, { - value: '11', label: '1.00' + value: '11', + label: '1.00', }, { - value: '12', label: '1.25' + value: '12', + label: '1.25', }, { - value: '13', label: '1.50' - } + value: '13', + label: '1.50', + }, ], - label: 'escStartupPower' + label: 'escStartupPower', }, { - name: 'TEMPERATURE_PROTECTION', type: 'bool', label: 'escTemperatureProtection' + name: 'TEMPERATURE_PROTECTION', + type: 'bool', + label: 'escTemperatureProtection', }, { - name: 'DEMAG_COMPENSATION', type: 'enum', label: 'escDemagCompensation', + name: 'DEMAG_COMPENSATION', + type: 'enum', + label: 'escDemagCompensation', options: [ { - value: '1', label: 'Off' + value: '1', + label: 'Off', }, { - value: '2', label: 'Low' + value: '2', + label: 'Low', }, { - value: '3', label: 'High' - } - ] + value: '3', + label: 'High', + }, + ], }, { - name: 'PWM_FREQUENCY', type: 'enum', label: 'escPWMFrequencyDamped', + name: 'PWM_FREQUENCY', + type: 'enum', + label: 'escPWMFrequencyDamped', options: [ { - value: '1', label: 'Off' + value: '1', + label: 'Off', }, { - value: '2', label: 'Low' + value: '2', + label: 'Low', }, { - value: '3', label: 'DampedLight' - } - ] + value: '3', + label: 'DampedLight', + }, + ], }, { - name: 'COMMUTATION_TIMING', type: 'enum', label: 'escMotorTiming', + name: 'COMMUTATION_TIMING', + type: 'enum', + label: 'escMotorTiming', options: [ { - value: '1', label: 'Low' + value: '1', + label: 'Low', }, { - value: '2', label: 'MediumLow' + value: '2', + label: 'MediumLow', }, { - value: '3', label: 'Medium' + value: '3', + label: 'Medium', }, { - value: '4', label: 'MediumHigh' + value: '4', + label: 'MediumHigh', }, { - value: '5', label: 'High' - } - ] + value: '5', + label: 'High', + }, + ], }, { - name: 'BEEP_STRENGTH', type: 'number', min: 1, max: 255, step: 1, label: 'escBeepStrength' + name: 'BEEP_STRENGTH', + type: 'number', + min: 1, + max: 255, + step: 1, + label: 'escBeepStrength', }, { - name: 'BEACON_STRENGTH', type: 'number', min: 1, max: 255, step: 1, label: 'escBeaconStrength' + name: 'BEACON_STRENGTH', + type: 'number', + min: 1, + max: 255, + step: 1, + label: 'escBeaconStrength', }, { - name: 'BEACON_DELAY', type: 'enum', label: 'escBeaconDelay', + name: 'BEACON_DELAY', + type: 'enum', + label: 'escBeaconDelay', options: [ { - value: '1', label: '1 minute' + value: '1', + label: '1 minute', }, { - value: '2', label: '2 minutes' + value: '2', + label: '2 minutes', }, { - value: '3', label: '5 minutes' + value: '3', + label: '5 minutes', }, { - value: '4', label: '10 minutes' + value: '4', + label: '10 minutes', }, { - value: '5', label: 'Infinite' - } - ] - } + value: '5', + label: 'Infinite', + }, + ], + }, ]; - const BLHELI_SETTINGS_DESCRIPTIONS = { // BLHeli_S '33': { MULTI: { base: BLHELI_S_SETTINGS_LAYOUT_33 }, // There is no MAIN nor MULTI mode in BLHeli_S, added for completeness MAIN: { base: [] }, - TAIL: { base: [] } + TAIL: { base: [] }, }, '32': { MULTI: { base: BLHELI_S_SETTINGS_LAYOUT_32 }, // There is no MAIN nor MULTI mode in BLHeli_S, added for completeness MAIN: { base: [] }, - TAIL: { base: [] } + TAIL: { base: [] }, }, // BLHeli @@ -1205,28 +1689,35 @@ const BLHELI_SETTINGS_DESCRIPTIONS = { overrides: { '14.5': [ { - name: 'PWM_DITHER', type: 'enum', label: 'escPWMOutputDither', + name: 'PWM_DITHER', + type: 'enum', + label: 'escPWMOutputDither', options: [ { - value: '1', label: 'Off' + value: '1', + label: 'Off', }, { - value: '2', label: '7' + value: '2', + label: '7', }, { - value: '3', label: '15' + value: '3', + label: '15', }, { - value: '4', label: '31' + value: '4', + label: '31', }, { - value: '5', label: '63' - } - ] - } + value: '5', + label: '63', + }, + ], + }, ], - } + }, }, MAIN: { base: [] }, - TAIL: { base: [] } + TAIL: { base: [] }, }, '20': { @@ -1235,97 +1726,156 @@ const BLHELI_SETTINGS_DESCRIPTIONS = { overrides: { '14.0': [ { - name: 'PWM_DITHER', type: 'enum', label: 'escPWMOutputDither', + name: 'PWM_DITHER', + type: 'enum', + label: 'escPWMOutputDither', options: [ { - value: '1', label: '1' + value: '1', + label: '1', }, { - value: '2', label: '3' + value: '2', + label: '3', }, { - value: '3', label: '7' + value: '3', + label: '7', }, { - value: '4', label: '15' + value: '4', + label: '15', }, { - value: '5', label: '31' - } - ] - } - ] - } + value: '5', + label: '31', + }, + ], + }, + ], + }, }, MAIN: { base: [] }, - TAIL: { base: [] } + TAIL: { base: [] }, }, '19': { MULTI: { base: BLHELI_MULTI_SETTINGS_LAYOUT_19 }, MAIN: { base: [] }, - TAIL: { base: [] } - } + TAIL: { base: [] }, + }, }; - // @todo add validation for min/max throttle const BLHELI_S_INDIVIDUAL_SETTINGS = [ { - name: 'MOTOR_DIRECTION', type: 'enum', label: 'escMotorDirection', + name: 'MOTOR_DIRECTION', + type: 'enum', + label: 'escMotorDirection', options: [ { - value: '1', label: 'Normal' + value: '1', + label: 'Normal', }, { - value: '2', label: 'Reversed' + value: '2', + label: 'Reversed', }, { - value: '3', label: 'Bidirectional' + value: '3', + label: 'Bidirectional', }, { - value: '4', label: 'Bidirectional Reversed' - } - ] - }, - { - name: 'PPM_MIN_THROTTLE', type: 'number', min: 1000, max: 1500, step: 4, label: 'escPPMMinThrottle', - offset: 1000, factor: 4, suffix: ' μs' + value: '4', + label: 'Bidirectional Reversed', + }, + ], }, { - name: 'PPM_MAX_THROTTLE', type: 'number', min: 1504, max: 2020, step: 4, label: 'escPPMMaxThrottle', - offset: 1000, factor: 4, suffix: ' μs' + name: 'PPM_MIN_THROTTLE', + type: 'number', + min: 1000, + max: 1500, + step: 4, + label: 'escPPMMinThrottle', + offset: 1000, + factor: 4, + suffix: ' μs', + }, + { + name: 'PPM_MAX_THROTTLE', + type: 'number', + min: 1504, + max: 2020, + step: 4, + label: 'escPPMMaxThrottle', + offset: 1000, + factor: 4, + suffix: ' μs', + }, + { + name: 'PPM_CENTER_THROTTLE', + type: 'number', + min: 1000, + max: 2020, + step: 4, + label: 'escPPMCenterThrottle', + offset: 1000, + factor: 4, + suffix: ' μs', + visibleIf: (settings) => [ 3, 4 ].includes(settings.MOTOR_DIRECTION), }, - { - name: 'PPM_CENTER_THROTTLE', type: 'number', min: 1000, max: 2020, step: 4, label: 'escPPMCenterThrottle', - offset: 1000, factor: 4, suffix: ' μs', - visibleIf: (settings) => [ 3, 4 ].includes(settings.MOTOR_DIRECTION) - } ]; const BLHELI_INDIVIDUAL_SETTINGS = [ { - name: 'MOTOR_DIRECTION', type: 'enum', label: 'escMotorDirection', + name: 'MOTOR_DIRECTION', + type: 'enum', + label: 'escMotorDirection', options: [ { - value: '1', label: 'Normal' + value: '1', + label: 'Normal', }, { - value: '2', label: 'Reversed' + value: '2', + label: 'Reversed', }, { - value: '3', label: 'Bidirectional' - } - ] - }, - { - name: 'PPM_MIN_THROTTLE', type: 'number', min: 1000, max: 1500, step: 4, label: 'escPPMMinThrottle', - offset: 1000, factor: 4, suffix: ' μs' + value: '3', + label: 'Bidirectional', + }, + ], }, { - name: 'PPM_MAX_THROTTLE', type: 'number', min: 1504, max: 2020, step: 4, label: 'escPPMMaxThrottle', - offset: 1000, factor: 4, suffix: ' μs' + name: 'PPM_MIN_THROTTLE', + type: 'number', + min: 1000, + max: 1500, + step: 4, + label: 'escPPMMinThrottle', + offset: 1000, + factor: 4, + suffix: ' μs', + }, + { + name: 'PPM_MAX_THROTTLE', + type: 'number', + min: 1504, + max: 2020, + step: 4, + label: 'escPPMMaxThrottle', + offset: 1000, + factor: 4, + suffix: ' μs', + }, + { + name: 'PPM_CENTER_THROTTLE', + type: 'number', + min: 1000, + max: 2020, + step: 4, + label: 'escPPMCenterThrottle', + offset: 1000, + factor: 4, + suffix: ' μs', + visibleIf: (settings) => settings.MOTOR_DIRECTION === 3, }, - { - name: 'PPM_CENTER_THROTTLE', type: 'number', min: 1000, max: 2020, step: 4, label: 'escPPMCenterThrottle', - offset: 1000, factor: 4, suffix: ' μs', - visibleIf: (settings) => settings.MOTOR_DIRECTION === 3 - } ]; const BLHELI_INDIVIDUAL_SETTINGS_DESCRIPTIONS = { @@ -1336,7 +1886,7 @@ const BLHELI_INDIVIDUAL_SETTINGS_DESCRIPTIONS = { // BLHeli '21': { base: BLHELI_INDIVIDUAL_SETTINGS }, '20': { base: BLHELI_INDIVIDUAL_SETTINGS }, - '19': { base: BLHELI_INDIVIDUAL_SETTINGS } + '19': { base: BLHELI_INDIVIDUAL_SETTINGS }, }; var BLHELI_S_DEFAULTS = { @@ -1355,7 +1905,7 @@ var BLHELI_S_DEFAULTS = { TEMPERATURE_PROTECTION: 7, LOW_RPM_POWER_PROTECTION: 1, BRAKE_ON_STOP: 0, - LED_CONTROL: 0 + LED_CONTROL: 0, }, '32': { STARTUP_POWER: 9, @@ -1372,8 +1922,8 @@ var BLHELI_S_DEFAULTS = { TEMPERATURE_PROTECTION: 1, LOW_RPM_POWER_PROTECTION: 1, BRAKE_ON_STOP: 0, - LED_CONTROL: 0 - } + LED_CONTROL: 0, + }, }; const EEPROM = { @@ -1382,24 +1932,12 @@ const EEPROM = { INDIVIDUAL_SETTINGS_DESCRIPTIONS: BLHELI_INDIVIDUAL_SETTINGS_DESCRIPTIONS, LAYOUT: BLHELI_LAYOUT, LAYOUT_SIZE: BLHELI_LAYOUT_SIZE, + MODES: BLHELI_MODES, NAMES: [''], PAGE_SIZE: BLHELI_SILABS_PAGE_SIZE, SETTINGS_DESCRIPTIONS: BLHELI_SETTINGS_DESCRIPTIONS, + SILABS: BLHELI_SILABS, TYPES: BLHELI_TYPES, }; -export { - BLHELI_TYPES, - BLHELI_MODES, - BLHELI_ATMEL, - BLHELI_LAYOUT, - BLHELI_SILABS, - BLHELI_S_DEFAULTS, - BLHELI_LAYOUT_SIZE, - BLHELI_SILABS_PAGE_SIZE, - BLHELI_SILABS_EEPROM_OFFSET, - BLHELI_SETTINGS_DESCRIPTIONS, - BLHELI_INDIVIDUAL_SETTINGS_DESCRIPTIONS, -}; - export default EEPROM; diff --git a/src/sources/Blheli/index.js b/src/sources/Blheli/index.js index 9b8ea6240..16136a232 100644 --- a/src/sources/Blheli/index.js +++ b/src/sources/Blheli/index.js @@ -1,8 +1,5 @@ -import Source, { - PLATFORMS, -} from '../Source'; - import EEPROM from './eeprom'; +import Source, { PLATFORMS } from '../Source'; import VERSIONS_LOCAL from './versions.json'; import ESCS_LOCAL from './escs.json'; @@ -10,6 +7,16 @@ import ESCS_LOCAL from './escs.json'; const VERSIONS_REMOTE = 'https://raw.githubusercontent.com/blheli-configurator/blheli-configurator/master/js/blheli_versions.json'; const ESCS_REMOTE = 'https://raw.githubusercontent.com/blheli-configurator/blheli-configurator/master/js/blheli_escs.json'; +function buildDisplayName(flash, make) { + const settings = flash.settings; + let revision = 'Unsupported/Unrecognized'; + if(settings.MAIN_REVISION !== undefined && settings.SUB_REVISION !== undefined) { + revision = `${settings.MAIN_REVISION}.${settings.SUB_REVISION}`; + } + + return `${make} - BlHeli_S, ${revision}`; +} + const pwmOptions = []; const blheliConfig = new Source( 'Blheli', @@ -22,4 +29,9 @@ const blheliConfig = new Source( pwmOptions ); +export { + buildDisplayName, + EEPROM, +}; + export default blheliConfig; diff --git a/src/sources/BlheliM/index.js b/src/sources/BlheliM/index.js index bd4bbe926..15ad4b2dc 100644 --- a/src/sources/BlheliM/index.js +++ b/src/sources/BlheliM/index.js @@ -1,3 +1,5 @@ +/* +// Not implemented yet - need colaboration from dev import Source from '../Source'; import BLHELI_M_VERSIONS_LOCAL from './versions.json'; @@ -23,3 +25,4 @@ export { BLHELI_M_ESCS_LOCAL, blheliMConfig, }; +*/ diff --git a/src/sources/Bluejay/__tests__/index.test.js b/src/sources/Bluejay/__tests__/index.test.js new file mode 100644 index 000000000..6ce339a0b --- /dev/null +++ b/src/sources/Bluejay/__tests__/index.test.js @@ -0,0 +1,82 @@ +import { + EEPROM, + buildDisplayName, +} from '../'; + +test('visibleIf MOTOR_DIRECTION 3', () => { + const keys = Object.keys(EEPROM.SETTINGS_DESCRIPTIONS); + const settings = { MOTOR_DIRECTION: 3 }; + + const visibleIf = []; + for(let i = 0; i < keys.length; i += 1) { + const base = EEPROM.SETTINGS_DESCRIPTIONS[keys[i]].MULTI.base; + for(let j = 0; j < base.length; j += 1) { + const current = base[j]; + if(current.visibleIf) { + visibleIf.push(current.visibleIf); + } + } + + for(let i = 0; i < visibleIf.length; i += 1) { + expect(visibleIf[i](settings)).toBeTruthy(); + } + } +}); + +test('individual visibleIf MOTOR_DIRECTION 3', () => { + const keys = Object.keys(EEPROM.INDIVIDUAL_SETTINGS_DESCRIPTIONS); + const settings = { MOTOR_DIRECTION: 3 }; + + const visibleIf = []; + for(let i = 0; i < keys.length; i += 1) { + const base = EEPROM.INDIVIDUAL_SETTINGS_DESCRIPTIONS[keys[i]].base; + for(let j = 0; j < base.length; j += 1) { + const current = base[j]; + if(current.visibleIf) { + visibleIf.push(current.visibleIf); + } + } + + for(let i = 0; i < visibleIf.length; i += 1) { + expect(visibleIf[i](settings)).toBeTruthy(); + } + } +}); + +test('build display Name', () => { + const flash = { + settings: { + MAIN_REVISION: 1, + SUB_REVISION: 100, + __PWM_FREQUENCY: 24, + NAME: 'Bluejay', + }, + }; + + const name = buildDisplayName(flash, 'MAKE'); + expect(name).toEqual('MAKE - Bluejay, 1.100, 24kHz'); +}); + +test('build display Name with missing revisions', () => { + const flash = { + settings: { + __PWM_FREQUENCY: 24, + NAME: 'Bluejay', + }, + }; + + const name = buildDisplayName(flash, 'MAKE'); + expect(name).toEqual('MAKE - Bluejay, Unsupported/Unrecognized, 24kHz'); +}); + +test('build display Name without PWM', () => { + const flash = { + settings: { + __PWM_FREQUENCY: 0xFF, + NAME: 'Bluejay', + }, + }; + + const name = buildDisplayName(flash, 'MAKE'); + expect(name).toEqual('MAKE - Bluejay, Unsupported/Unrecognized'); +}); diff --git a/src/sources/Bluejay/eeprom.js b/src/sources/Bluejay/eeprom.js index 2d4f719ff..ad6017837 100644 --- a/src/sources/Bluejay/eeprom.js +++ b/src/sources/Bluejay/eeprom.js @@ -42,28 +42,36 @@ const SETTINGS_DESCRIPTIONS = { label: 'escTemperatureProtection', options: [ { - value: '0', label: 'Disabled', + value: '0', + label: 'Disabled', }, { - value: '1', label: '80 C', + value: '1', + label: '80 C', }, { - value: '2', label: '90 C', + value: '2', + label: '90 C', }, { - value: '3', label: '100 C', + value: '3', + label: '100 C', }, { - value: '4', label: '110 C', + value: '4', + label: '110 C', }, { - value: '5', label: '120 C', + value: '5', + label: '120 C', }, { - value: '6', label: '130 C', + value: '6', + label: '130 C', }, { - value: '7', label: '140 C', + value: '7', + label: '140 C', }, ], }, @@ -73,19 +81,24 @@ const SETTINGS_DESCRIPTIONS = { label: 'escMotorTiming', options: [ { - value: '1', label: '0° (Low)', + value: '1', + label: '0° (Low)', }, { - value: '2', label: '7.5° (MediumLow)', + value: '2', + label: '7.5° (MediumLow)', }, { - value: '3', label: '15° (Medium)', + value: '3', + label: '15° (Medium)', }, { - value: '4', label: '22.5° (MediumHigh)', + value: '4', + label: '22.5° (MediumHigh)', }, { - value: '5',label: '30° (High)', + value: '5', + label: '30° (High)', }, ], }, @@ -95,13 +108,16 @@ const SETTINGS_DESCRIPTIONS = { label: 'escDemagCompensation', options: [ { - value: '1', label: 'Off', + value: '1', + label: 'Off', }, { - value: '2', label: 'Low', + value: '2', + label: 'Low', }, { - value: '3', label: 'High', + value: '3', + label: 'High', }, ], }, @@ -110,46 +126,60 @@ const SETTINGS_DESCRIPTIONS = { type: 'enum', options: [ { - value: '1', label: '1x (More protection)', + value: '1', + label: '1x (More protection)', }, { - value: '2', label: '2x', + value: '2', + label: '2x', }, { - value: '3', label: '3x', + value: '3', + label: '3x', }, { - value: '4', label: '4x', + value: '4', + label: '4x', }, { - value: '5', label: '5x', + value: '5', + label: '5x', }, { - value: '6', label: '6x', + value: '6', + label: '6x', }, { - value: '7', label: '7x', + value: '7', + label: '7x', }, { - value: '8', label: '8x', + value: '8', + label: '8x', }, { - value: '9', label: '9x', + value: '9', + label: '9x', }, { - value: '10', label: '10x', + value: '10', + label: '10x', }, { - value: '11', label: '11x', + value: '11', + label: '11x', }, { - value: '12', label: '12x', + value: '12', + label: '12x', }, { - value: '13', label: '13x (Less protection)', + value: '13', + label: '13x (Less protection)', }, { - value: '0', label: 'Off', + value: '0', + label: 'Off', }, ], label: '_RPM Power Protection (Rampup)', @@ -176,19 +206,24 @@ const SETTINGS_DESCRIPTIONS = { label: 'escBeaconDelay', options: [ { - value: '1', label: '1 minute', + value: '1', + label: '1 minute', }, { - value: '2', label: '2 minutes', + value: '2', + label: '2 minutes', }, { - value: '3', label: '5 minutes', + value: '3', + label: '5 minutes', }, { - value: '4', label: '10 minutes', + value: '4', + label: '10 minutes', }, { - value: '5', label: 'Infinite', + value: '5', + label: 'Infinite', }, ], }, @@ -221,7 +256,8 @@ const SETTINGS_DESCRIPTIONS = { type: 'enum', options: [ { - value: '1', label: '0.5% (0.031)', + value: '1', + label: '0.5% (0.031)', }, /* @@ -232,25 +268,32 @@ const SETTINGS_DESCRIPTIONS = { *{ value: '6', label: '3.5% (0.188)' }, */ { - value: '7', label: '5% (0.25)', + value: '7', + label: '5% (0.25)', }, { - value: '8', label: '7% (0.38)', + value: '8', + label: '7% (0.38)', }, { - value: '9', label: '10% (0.50)', + value: '9', + label: '10% (0.50)', }, { - value: '10', label: '15% (0.75)', + value: '10', + label: '15% (0.75)', }, { - value: '11', label: '20% (1.00)', + value: '11', + label: '20% (1.00)', }, { - value: '12', label: '24% (1.25)', + value: '12', + label: '24% (1.25)', }, { - value: '13', label: '29% (1.50)', + value: '13', + label: '29% (1.50)', }, ], label: '_Rampup Start Power', @@ -788,20 +831,25 @@ const LAYOUT = { size: 1, }, BRAKE_ON_STOP: { - offset: 0x27, size: 1, + offset: 0x27, + size: 1, }, LED_CONTROL: { - offset: 0x28, size: 1, + offset: 0x28, + size: 1, }, LAYOUT: { - offset: 0x40, size: 16, + offset: 0x40, + size: 16, }, MCU: { - offset: 0x50, size: 16, + offset: 0x50, + size: 16, }, NAME: { - offset: 0x60, size: 16, + offset: 0x60, + size: 16, }, }; @@ -812,16 +860,20 @@ const INDIVIDUAL_SETTINGS = [ label: 'escMotorDirection', options: [ { - value: '1', label: 'Normal', + value: '1', + label: 'Normal', }, { - value: '2', label: 'Reversed', + value: '2', + label: 'Reversed', }, { - value: '3', label: 'Bidirectional', + value: '3', + label: 'Bidirectional', }, { - value: '4', label: 'Bidirectional Reversed', + value: '4', + label: 'Bidirectional Reversed', }, ], }, @@ -829,19 +881,25 @@ const INDIVIDUAL_SETTINGS = [ const BLHELI_S_INDIVIDUAL_SETTINGS_BACKWARD = [ { - name: 'MOTOR_DIRECTION', type: 'enum', label: 'escMotorDirection', + name: 'MOTOR_DIRECTION', + type: 'enum', + label: 'escMotorDirection', options: [ { - value: '1', label: 'Normal', + value: '1', + label: 'Normal', }, { - value: '2', label: 'Reversed', + value: '2', + label: 'Reversed', }, { - value: '3', label: 'Bidirectional', + value: '3', + label: 'Bidirectional', }, { - value: '4', label: 'Bidirectional Reversed', + value: '4', + label: 'Bidirectional Reversed', }, ], }, @@ -939,7 +997,7 @@ var DEFAULTS = { TEMPERATURE_PROTECTION: 7, _LOW_RPM_POWER_PROTECTION: 1, // Backward BRAKE_ON_STOP: 0, - LED_CONTROL: 0 + LED_CONTROL: 0, }, }; diff --git a/src/sources/Bluejay/index.js b/src/sources/Bluejay/index.js index 185c2b342..4eca501d4 100644 --- a/src/sources/Bluejay/index.js +++ b/src/sources/Bluejay/index.js @@ -1,6 +1,4 @@ -import Source, { - PLATFORMS, -} from '../Source'; +import Source, { PLATFORMS } from '../Source.js'; import EEPROM from './eeprom'; @@ -10,6 +8,22 @@ import ESCS_LOCAL from './escs.json'; const VERSIONS_REMOTE = 'https://raw.githubusercontent.com/mathiasvr/bluejay-configurator/bluejay/js/bluejay_versions.json'; const ESCS_REMOTE = 'https://raw.githubusercontent.com/mathiasvr/bluejay-configurator/bluejay/js/bluejay_escs.json'; +function buildDisplayName(flash, make) { + const settings = flash.settings; + let revision = 'Unsupported/Unrecognized'; + if(settings.MAIN_REVISION !== undefined && settings.SUB_REVISION !== undefined) { + revision = `${settings.MAIN_REVISION}.${settings.SUB_REVISION}`; + } + + let pwm = ''; + if(settings.__PWM_FREQUENCY && settings.__PWM_FREQUENCY !== 0xFF) { + pwm = `, ${settings.__PWM_FREQUENCY}kHz`; + } + const name = `${settings.NAME.trim()}`; + + return `${make} - ${name}, ${revision}${pwm}`; +} + const pwmOptions = [24, 48, 96]; const bluejayConfig = new Source( 'Bluejay', @@ -23,6 +37,7 @@ const bluejayConfig = new Source( ); export { + buildDisplayName, EEPROM, }; diff --git a/src/sources/Bluejay/versions.json b/src/sources/Bluejay/versions.json index 9168b2764..9f255c28e 100644 --- a/src/sources/Bluejay/versions.json +++ b/src/sources/Bluejay/versions.json @@ -1,9 +1,9 @@ { "EFM8":[ { - "name":"0.11-test", - "url":"https://github.com/mathiasvr/bluejay/releases/download/v0.11-test/{0}_v0.11-test.hex", - "key":"0.11-test", + "name":"0.11", + "url":"https://github.com/mathiasvr/bluejay/releases/download/v0.11/{0}_v0.11.hex", + "key":"0.11", "configurator":"1.3.2" }, { diff --git a/src/sources/Source.js b/src/sources/Source.js index dd852f365..381bc3018 100644 --- a/src/sources/Source.js +++ b/src/sources/Source.js @@ -1,13 +1,6 @@ -import { - BLHELI_TYPES, -} from './Blheli/eeprom'; - +import BLHELI_EEPROM from './Blheli/eeprom'; import BLUEJAY_EEPROM from './Bluejay/eeprom'; -const BLUEJAY_TYPES = BLUEJAY_EEPROM.TYPES; - -import { - AM32_TYPES, -} from './AM32/eeprom'; +import AM32_EEPROM from './AM32/eeprom'; class Source { constructor(name, platform, versions, escs, eeprom, localVersions, localEscs, pwm) { @@ -87,12 +80,12 @@ const PLATFORMS = { }; const SILABS_TYPES = [ - BLHELI_TYPES.BLHELI_S_SILABS, - BLUEJAY_TYPES.EFM8, + BLHELI_EEPROM.TYPES.BLHELI_S_SILABS, + BLUEJAY_EEPROM.TYPES.EFM8, ]; const ARM_TYPES = [ - AM32_TYPES.ARM, + AM32_EEPROM.TYPES.ARM, ]; export { diff --git a/src/sources/__tests__/Source.test.js b/src/sources/__tests__/Source.test.js index 115fb3063..70b1a7cd2 100644 --- a/src/sources/__tests__/Source.test.js +++ b/src/sources/__tests__/Source.test.js @@ -1,17 +1,26 @@ -import { - Source -} from '../Source'; - -import { - blheliSource, -} from '../index'; +import Source from '../Source'; +import { blheliSource } from '../index'; test('Source without parameters', () => { - try { - const source = new Source(); - } catch(e) { - expect(e).not.toBeNull(); - } + expect(() => new Source()).toThrow(); +}); + +test('Source with invalid URL', async() => { + const invalidSource = new Source('invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'localVersions', 'localESCs', 'invalid'); + + const versions = await invalidSource.getVersions(); + expect(versions).toBe('localVersions'); + + const escs = await invalidSource.getEscs(); + expect(escs).toBe('localESCs'); +}); + +test('offline', async() => { + jest.spyOn(window.navigator, 'onLine', 'get').mockReturnValue(false); + const invalidSource = new Source('invalid', 'invalid', 'https://google.com', 'invalid', 'invalid', 'localVersions', 'localESCs', 'invalid'); + + const versions = await invalidSource.getVersions(); + expect(versions).toBe('localVersions'); }); test('blheliSource get versions', async() => { @@ -29,12 +38,12 @@ test('blheliSource get platform', async() => { expect(platform).toBe(0); }); -test('blheliSource get platform', async() => { +test('blheliSource get name', async() => { const name = await blheliSource.getName(); expect(name).toBe('Blheli'); }); -test('blheliSource get platform', async() => { +test('blheliSource get pwm', async() => { const pwm = await blheliSource.getPwm(); expect(pwm.length).toBe(0); }); diff --git a/src/translations/de/common.json b/src/translations/de/common.json index c4eb5b859..a2417f3f9 100644 --- a/src/translations/de/common.json +++ b/src/translations/de/common.json @@ -44,7 +44,7 @@ "homeContributionHeader": "Beitragen", "homeContributionText": "Wenn du helfen möchtest den ESC Konfigurator besser zu machen, hast du mehrere Möglichkeiten:
", "homeDisclaimerHeader": "Disclaimer", - "homeDisclamierText": "Diese App unterstützt ESCs, die BLHeli für Atmel, BLHeli für SiLabs und BLHeli_S verwenden.
BLHeli FC passthrough ist die einzige derzeit unterstützte Schnittstelle.

Falls Probleme auftreten sollten, klicke auf Debug-Protokoll speichern und sende einen Fehlerericht über GitHub.

Der Quellcode kann von Github heruntergeladen werden

Der aktuellste CP210x Treiber kann von hier heruntergeladen werden
Der aktuellste STM USB VCP Treiber kann von hier heruntergeladen werden
", + "homeDisclaimerText": "Diese App unterstützt ESCs, die BLHeli für Atmel, BLHeli für SiLabs und BLHeli_S verwenden.
BLHeli FC passthrough ist die einzige derzeit unterstützte Schnittstelle.

Falls Probleme auftreten sollten, klicke auf Debug-Protokoll speichern und sende einen Fehlerericht über GitHub.

Der Quellcode kann von Github heruntergeladen werden

Der aktuellste CP210x Treiber kann von hier heruntergeladen werden
Der aktuellste STM USB VCP Treiber kann von hier heruntergeladen werden
", "homeWelcome": "Willkommen im ESC - Konfigurator, eine Anwendung um Updates und Konfiguration deiner ESC's zu erleichtern.", "betaWarning": "Dieses App befindet sich im BETA Stadium.
Möglicherweise funktioniert noch nicht alles wie erwartet - falls du irgendwelche Fehler findest, melde sie bitte.", "escButtonSelectLocally": "Lokale Datei flashen", @@ -53,7 +53,7 @@ "forceFlashText": "Falsches MCU Layout ignorieren?", "forceFlashHint": "(Ungeeignete Firmware könnte deinen ESC beschädigen. Auf eigene Verantwortung fortfahren.)", "migrateFlashText": "Einstellungen wenn möglich übernehmen?", - "migrateFlashHint": "(Dies wird die Standardeinstellungen der Firmware wiederherstellen)", + "migrateFlashHint": "(Dies wird alle Einstellungen auf die Firmware-Standardwerte zurücksetzen)", "escDirectionReversed": "Richtung umgekehrt", "escBidirectionalMode": "Bidirektionaler Modus", "escSinusoidalStartup": "Sinusförmiges Starten", @@ -61,9 +61,9 @@ "escVariablePwmFrequency": "Variable PWM Frequenz", "escStuckRotorProtection": "Blockierschutz für Rotor", "escStallProtection": "Blockierschutz", - "escTimingAdvance": "Motor Timing (Grad)", - "escPwmFrequency": "PWM-Frequenz (kHz)", - "escMotorKv": "Motor KV", + "escTimingAdvance": "Motor Timing [Grad]", + "escPwmFrequency": "PWM-Frequenz [kHz]", + "escMotorKv": "Motor [Kv]", "escMotorPoles": "Motor Pole", "escBeepVolume": "Beacon Lautstärke", "escIntervalTelemetry": "30ms Telemetrie Ausgabe", @@ -74,9 +74,9 @@ "escLowVoltageCutoff": "Abschaltung bei niedriger Spannung", "escLowVoltageThreshold": "Wert für Abschaltung bei niedriger Spannung", "escRcCarReversing": "Motorendrehrichtung umkehren wie bei RC Autos", - "bluejayText": "

Bluejay ist eine auf BLHELI_S basierende Firmware die bi-direktionales DSHOT unterstützt - eine gute Wahl falls du RPM Filter verwenden möchtest. Dieses Projekt beschäftigt sich damit BLHELI_S zu vereinfachen und zu verbessern.

Als Bonus bekommst du noch einen Startmelodie Editor dazu.

", - "blheli32ToAM32": "

Die aufstrebende Firmware für ARM-basierte ESCs. Obwohl es relativ neu in der Szene ist, hat es viel Interesse, sowohl bei Anwendern als auch bei Herstellern erregt. AM32 ESC's werden in Kürze von verschiedenen Herstellern erhältlich sein.

AM32 kann auf BLHELI_32 ESC'sgeflashed werden. Aber du musst zuerst den AM32 Bootloader über STM32 Cube Programmierer und ST Link V2 flashen. Der erforderliche Bootloader befindet sich im AM32 Bootloader Repository.

", - "blhelisText": "

BLHELI_S benötigt wahrscheinlich keine Einführung - die beliebte ESC-Firmware für fast jeden EFM8-basierten ESC im Quadrocopter-Hobby.

Erprobt und getestet, unterstützt es alle analogen und digitalen Protokolle die in Betaflight existieren

", + "bluejayText": "

Bluejay ist eine auf BLHeli_S basierende Firmware die bi-direktionales DShot unterstützt - eine gute Wahl falls du RPM Filter verwenden möchtest. Dieses Projekt beschäftigt sich damit BLHeli_S zu vereinfachen und zu verbessern.

Als Bonus bekommst du noch einen Startmelodie Editor dazu.

", + "blheli32ToAM32": "

Die aufstrebende Firmware für ARM-basierte ESCs. Obwohl diese relativ neu in der Szene ist, hat sie viel Interesse, sowohl bei Anwendern als auch bei Herstellern erregt. AM32 ESCs werden in Kürze von diversen Herstellern erhältlich sein.

AM32 kann auf BLHELI_32 ESCsgeflashed werden. Aber du musst zuerst den AM32 Bootloader über STM32 Cube Programmierer und ST Link V2 flashen. Der erforderliche Bootloader befindet sich im AM32 Bootloader Repository.

", + "blhelisText": "

BLHeli_S benötigt wahrscheinlich keine Einführung - die beliebte ESC-Firmware für fast jeden EFM8-basierten ESC im Quadrocopter-Hobby.

Erprobt und getestet, unterstützt es alle analogen und digitalen Protokolle die in Betaflight existieren

", "whatsNextHeader": "Was kommt als nächstes?", "whatsNextText": "

Wenn du mitverfolgen willst welche Funktionen in Zukunft entstehen, kannst du dies im github-Repositorybeobachten. Gerne kannst du auch eine Feature-Anfrage erstellen, wenn du eine Idee zu einem Feature hast, welches du gerne implementiert hättest.

", "openPortSelection": "Portauswahl öffnen", @@ -86,5 +86,19 @@ "masterSpeed": "Geschwindigkeit aller Motoren", "motorNr": "Motor {{index}}", "homeDiscordHeader": "Komm auf unseren Discord Server!", - "homeDiscordText": "Wenn du Fragen hast oder rasche Hilfe brauchst, schau bei uns am Discord Server vorbei:" + "homeDiscordText": "Wenn du Fragen hast oder rasche Hilfe brauchst, schau bei uns am Discord Server vorbei:", + "homeChinaHeader": "Für unsere chinesischen Besucher", + "homeChinaText": "Sag deinen Freunden hinter der großen chinesischen Firewall, dass sie uns über einen lokalen Spiegel direkt in China erreichen können.", + "melodyEditor": "Melodie Editor", + "homeAttributionHeader": "Beteiligung", + "homeAttributionText": "Dieses Projekt wurde stark vom BLHeli Configuratorinspiriert. Der Großteil der Benutzeroberfläche wurde von Grund auf neu geschrieben, aber viele der mit dem Flashen und Firmware-Handling zusammenhängenden Low-Level-Funktionen wurden wiederverwendet - also großen Dank an alle, die am ursprünglichen BLHeli Configurator beteiligt waren.", + "multiOnly": "Nur der MULTI-Modus wird derzeit unterstützt", + "selectFirmware": "Firmware auswählen", + "selectEsc": "ESC auswählen", + "selectMode": "Modus auswählen", + "selectVersion": "Version auswählen", + "selectPwmFrequency": "PWM Frequenz auswählen", + "selectTarget": "Target wählen", + "battery": "Akku:", + "settings": "Einstellungen" } diff --git a/src/translations/de/hints.json b/src/translations/de/hints.json index 171546272..31332376c 100644 --- a/src/translations/de/hints.json +++ b/src/translations/de/hints.json @@ -16,10 +16,10 @@ "COMPLEMENTARY_PWM": "Aktiviert das aktive Bremsen durch die Verwendung der Low-Side-Schalter im Current-Decay-Betrieb anstatt der MOSFET Body-Dioden.", "VARIABLE_PWM_FREQUENCY": "Erhöht die PWM-Frequenz proportional zu Motordrehzahl von 24-48kHz, um Störungen zu vermeiden.", "STUCK_ROTOR_PROTECTION": "Trennt den Strom vom Motor, stoppt und versucht einen Neustart nach 10 gescheiterten Versuchen.", - "STALL_PROTECTION": "Erhöht Schub automatisch wenn unter einem Drehzahl Schwellwert und versuch das Stoppen vom Motor zu verhindert. Für Multicopter nicht empfohlen.", + "STALL_PROTECTION": "Erhöht den Schub automatisch wenn dieser unter einen Drehzahl Schwellwert fällt und versucht das Stoppen des Motors zu verhindern. Für Multicopter nicht empfohlen.", "TIMING_ADVANCE": "Motorkommutations Timing. Höhere Werte führen in der Regel zu weniger de-syncs und bringen mehr Leistung auf Kosten der Effizienz.", "MOTOR_KV": "Der vom Hersteller angegebene KV-Wert des Motors. Dieser Wert wird verwendet um das Limit für den Schutz bei niedriger Drehzahl zu bestimmen.", - "MOTOR_POLES": "Pol Anzahl des Motors. Dieser Wert wird verwendet, um die sinusförmige Startgeschwindigkeit anzupassen.", + "MOTOR_POLES": "Polzahl des Motors. Dieser Wert wird verwendet, um die sinusförmige Startgeschwindigkeit anzupassen.", "STARTUP_POWER": "Steuert die dem Motor während des Starts verliehene Ausgangsleistung und den minimalen Schub.", "PWM_FREQUENCY": "Wenn variables PWM deaktiviert ist, wird die Schaltfrequenz (PWM) manuell auf den gewählten Bereich gesetzt.", "BEEP_VOLUME": "Die Lautstärke für die Piep-Töne. Zu hohe Werte können den Motor beschädigen.", diff --git a/src/translations/de/log.json b/src/translations/de/log.json index f59e71d1d..0650f72fd 100644 --- a/src/translations/de/log.json +++ b/src/translations/de/log.json @@ -15,7 +15,7 @@ "readEscs": "Lese {{connected}} ESCs", "readEscsSuccess": "ESCs gelesen", "readEscsFailed": "ESCs lesen fehlgeschlagen", - "readEsc": "ESC {{index}} gelesen", + "readEsc": "ESC {{index}}: {{name}}", "flashingEsc": "ESC {{index}} wird geflasht", "flashingEscFailed": "ESC {{index}} konnte nicht geflasht werden - prüfe den Dateityp", "readEscFailed": "ESC {{index}} konnte nicht gelesen werden", @@ -27,5 +27,7 @@ "escSettingsLayoutMismatch": "Layout stimmt nicht überein, Layout ignorieren nicht aktiv - abgebrochen", "escSettingsMcuMismatch": "MCU stimmt nicht überein, ignorieren nicht aktiv - abgebrochen", "escFlashedInTime": "ESC {{index}} - in {{seconds}}s geflasht", - "passthroughNotSupported": "BLHELI passthrough nicht unterstützt" + "passthroughNotSupported": "BLHELI passthrough nicht unterstützt", + "firmwareMismatch": "Firmware stimmt nicht überein! Flash: {{flash}} vs. EEPROM: {{eeprom}}", + "bootloaderMismatch": "Bootloader Versionen stimmen nicht überein! Flash: {{flash}} vs. EEPROM: {{eeprom}}" } diff --git a/src/translations/en/common.json b/src/translations/en/common.json index 46bcc40c4..cf57db51b 100644 --- a/src/translations/en/common.json +++ b/src/translations/en/common.json @@ -19,7 +19,7 @@ "escBeaconDelay": "Beacon Delay", "escBeepStrength": "Beep Strength", "escBeaconStrength": "Beacon Strength", - "escStartupBeep": "Staartup Beep", + "escStartupBeep": "Startup Beep", "escDithering": "Dithering", "escMotorDirection": "Motor Direction", "escPPMMinThrottle": "PPM Min Throttle", @@ -29,7 +29,6 @@ "_RPM Power Protection (Rampup)": "RPM Power Protection (Rampup)", "_Minimum Startup Power (Boost)": "Minimum Startup Power (Boost)", "_Maximum Startup Power (Protection)": "Maximum Startup Power (Protection)", - "_Maximum Startup Power (Protection)": "Maximum Startup Power (Protection)", "escButtonFlash": "Flash Firmware", "defaultChangelogHead": "Changelog", "escButtonRead": "Read Setup", @@ -41,11 +40,11 @@ "defaultChangelogTitle": "Changelog", "changelogClose": "Close", "homeExperimental": "This is an experimental web app to configure ESC firmware online.", - "homeVersionInfo": "You will always find the latest stable version here. Currently the following firmwares are supported:", + "homeVersionInfo": "You will always find the latest stable version here. Currently the following firmware are supported:", "homeContributionHeader": "Contributing", - "homeContributionText": "If you would like to help make Bluejay Configurator even better you can help in many ways, including:
", + "homeContributionText": "If you would like to help make ESC Configurator even better you can help in many ways, including:
", "homeDisclaimerHeader": "Disclaimer", - "homeDisclamierText": "The web application supports ESCs running BLHeli for Atmel, BLHeli for SiLabs and BLHeli_S.
BLHeli FC passthrough is the only interface currently supported.

Should you run into any problems, make sure to use the Save Debug Log button and submit a new issue via GitHub.

Application source code can be downloaded from here

Latest CP210x Drivers can be downloaded from here
Latest STM USB VCP Drivers can be downloaded from here
", + "homeDisclaimerText": "The web application supports ESCs running BLHeli for Atmel, BLHeli for SiLabs and BLHeli_S.
BLHeli FC passthrough is the only interface currently supported.

Should you run into any problems, make sure to use the Save Debug Log button and submit a new issue via GitHub.

Application source code can be downloaded from here

Latest CP210x Drivers can be downloaded from here
Latest STM USB VCP Drivers can be downloaded from here
", "homeWelcome": "Welcome to ESC - Configurator, a utility designed to simplify updating and configuring of your ESCs.", "betaWarning": "This tool is considered BETA.
Things might not work as expected yet - if you find any bugs please report them.", "escButtonSelectLocally": "Flash Local Firmware", @@ -54,7 +53,7 @@ "forceFlashText": "Ignore inappropriate MCU and Layout?", "forceFlashHint": "(Flashing inappropriate firmware may damage your ESC, do so at your own risk)", "migrateFlashText": "Migrate settings if possible?", - "migrateFlashHint": "(This will set the default settings from firmware)", + "migrateFlashHint": "(This will reset all settings to the firmware defaults)", "escDirectionReversed": "Direction Reversed", "escBidirectionalMode": "Bidirectional Mode", "escSinusoidalStartup": "Sinusoidal Startup", @@ -62,9 +61,9 @@ "escVariablePwmFrequency": "Variable PWM Frequency", "escStuckRotorProtection": "Stuck Rotor Protection", "escStallProtection": "Stall Protection", - "escTimingAdvance": "Timing Advance (degrees)", - "escPwmFrequency": "PWM Frequency (kHz)", - "escMotorKv": "Motor KV", + "escTimingAdvance": "Timing Advance [degrees]", + "escPwmFrequency": "PWM Frequency [kHz]", + "escMotorKv": "Motor [Kv]", "escMotorPoles": "Motor Poles", "escBeepVolume": "Beeper Volume", "escIntervalTelemetry": "30ms Telemetry Output", @@ -74,10 +73,10 @@ "escServoDeadBand": "Servo Neutral Dead Band", "escLowVoltageCutoff": "Low Voltage Cut Off", "escLowVoltageThreshold": "Low Voltage Cut-Off Threshold", - "escRcCarReversing": "Rc Car Style Reversing", - "bluejayText": "

Bluejay is BLHELI_S based firmware capable of bi-directional DSHOT - so a great choice if you want to run RPM filtering on your rig. This project also aims to clean up and simplify the original BLHELI_S source code.

A startup sound editor is also part of the deal.

", - "blheli32ToAM32": "

The up and coming firmware for ARM based ESC's. Although being relatively new on the scene it has a lot of interest, both from users and manufacturers. AM32 ESC's will soon be available from different manufacturers.

AM32 can be flashed on BLHELI_32 ESC's. But, you will have to first flash the AM32 Bootloader via STM32 Cube Programmer and ST Link V2 programming adapter. The required bootloader can be found in the AM32 bootloader repository.

", - "blhelisText": "

BLHELI_S probably does not need an introduction - the wildly popular ESC firmware used on almost every EFM8 based ESC in the quadrocopter hobby.

Tried and tested, supports every analog and digital protocol out there.

", + "escRcCarReversing": "RC Car Style Reversing", + "bluejayText": "

Bluejay is BLHeli_S based firmware capable of bi-directional DShot - so a great choice if you want to run RPM filtering on your rig. This project also aims to clean up and simplify the original BLHeli_S source code.

A startup sound editor is also part of the deal.

", + "blheli32ToAM32": "

The up and coming firmware for ARM based ESCs. Although being relatively new on the scene it has a lot of interest, both from users and manufacturers. AM32 ESCs will soon be available from different manufacturers.

AM32 can be flashed on BLHeli_32 ESCs. But, you will have to first flash the AM32 bootloader via STM32 Cube Programmer and ST Link V2 programming adapter. The required bootloader can be found in the AM32 bootloader repository.

", + "blhelisText": "

BLHeli_S probably does not need an introduction - the wildly popular ESC firmware used on almost every EFM8 based ESC in the quadcopter hobby.

Tried and tested, supports every analog and digital protocol out there.

", "whatsNextHeader": "What's next?", "whatsNextText": "

If you want to see which features are upcoming, drop by in the github repository. Also feel free to add a feature request if you have an idea that you want to see implemented.

", "openPortSelection": "Open Port Selection", @@ -88,9 +87,18 @@ "motorNr": "Motor {{index}}", "homeDiscordHeader": "Join us on Discord!", "homeDiscordText": "If you have any questions or need a quick helping hand, join us on our Discord server:", - "homeChinaHeader": "For our Chinese Visiotors", + "homeChinaHeader": "For our Chinese visitors", "homeChinaText": "Tell your friends behind the great firewall of China, that they can reach us via a local mirror directly in China.", "melodyEditor": "Melody Editor", "homeAttributionHeader": "Attribution", - "homeAttributionText": "This project was heavily inspired by the Blheli Configurator. Most of the UI has been re-written from scratch but a lot of the low level stuff related to flashing and firmware handling have been re-used - so a big shout out to everyone involved in the original Blheli Configurator." + "homeAttributionText": "This project was heavily inspired by the BLHeli Configurator. Most of the UI has been re-written from scratch but a lot of the low level stuff related to flashing and firmware handling have been re-used - so a big shout out to everyone involved in the original BLHeli Configurator.", + "multiOnly": "Only MULTI mode currently supported", + "selectFirmware": "Select Firmware", + "selectEsc": "Select ESC", + "selectMode": "Select Mode", + "selectVersion": "Select Version", + "selectPwmFrequency": "Select PWM Frequency", + "selectTarget": "Select Target", + "battery": "Battery:", + "settings": "Settings" } diff --git a/src/translations/en/log.json b/src/translations/en/log.json index 25ffa17c0..98d216e0d 100644 --- a/src/translations/en/log.json +++ b/src/translations/en/log.json @@ -15,7 +15,7 @@ "readEscs": "Trying to read {{connected}} ESC's", "readEscsSuccess": "Done reading ESC's", "readEscsFailed": "Failed reading ESC's", - "readEsc": "Read ESC {{index}}", + "readEsc": "Read ESC {{index}}: {{name}}", "flashingEsc": "Flashing ESC {{index}}", "flashingEscFailed": "Failed flashing ESC {{index}} - check file type", "readEscFailed": "Failed reading ESC {{index}}", @@ -27,5 +27,7 @@ "escSettingsLayoutMismatch": "Layout mismatch, override not enabled - aborted", "escSettingsMcuMismatch": "MCU mismatch, override not enabled - aborted", "escFlashedInTime": "Flashed ESC {{index}} - {{seconds}}s", - "passthroughNotSupported": "BLHELI passthrough not supported" + "passthroughNotSupported": "BLHELI passthrough not supported", + "firmwareMismatch": "Firmware mismatch! Flash: {{flash}} vs. EEPROM: {{eeprom}}", + "bootloaderMismatch": "Bootloader mismatch! Flash: {{flash}} vs. EEPROM: {{eeprom}}" } diff --git a/src/translations/zh-CN/common.json b/src/translations/zh-CN/common.json new file mode 100644 index 000000000..6cfedae64 --- /dev/null +++ b/src/translations/zh-CN/common.json @@ -0,0 +1,104 @@ +{ + "port": "端口", + "baudRate": "波特率", + "connect": "连接", + "disconnect": "断开连接", + "serialPermission": "选择串行端口", + "showLog": "显示日志", + "hideLog": "隐藏日志", + "notePropsOff": "注意: 在使用本页面前,请确保您已经 取下 所有螺旋桨。", + "noteConnectPower": "注意: 请将电源连接至电调。", + "commonParameters": "公用参数", + "escProgrammingByTX": "通过 TX 编程", + "escLowRPMPowerProtection": "低转速功率保护", + "escBrakeOnStop": "停止时刹车", + "escStartupPower": "启动功率", + "escTemperatureProtection": "温度保护", + "escDemagCompensation": "退磁补偿", + "escMotorTiming": "电机进角", + "escBeaconDelay": "信标延迟", + "escBeepStrength": "蜂鸣强度", + "escBeaconStrength": "信标强度", + "escStartupBeep": "启动蜂鸣音", + "escDithering": "抖动", + "escMotorDirection": "电机方向", + "escPPMMinThrottle": "PPM 最小油门", + "escPPMMaxThrottle": "PPM 最大油门", + "statusbarPortUtilization": "端口利用率:", + "statusbarPacketError": "数据包错误:", + "_RPM Power Protection (Rampup)": "RPM 功率保护 (爬升功率)", + "_Minimum Startup Power (Boost)": "最小启动功率 (升压)", + "_Maximum Startup Power (Protection)": "最大启动功率 (保护)", + "escButtonFlash": "烧录固件", + "defaultChangelogHead": "更新日志", + "escButtonRead": "读取配置", + "escButtonFlashAll": "烧录全部", + "escButtonSaveLog": "保存调试日志", + "escButtonWrite": "写入配置", + "buttonCancel": "取消", + "escButtonSelect": "烧录", + "defaultChangelogTitle": "更新日志", + "changelogClose": "关闭", + "homeExperimental": "这是一个实验性的 web 应用程序,可以在线配置 ESC 固件。", + "homeVersionInfo": "您可以在这里找到最新的稳定版本。目前支持以下固件:", + "homeContributionHeader": "贡献", + "homeContributionText": "如果您想要帮助 ESC 配置程序变得更好,您可以通过多种方式提供帮助。包括:
", + "homeDisclaimerHeader": "免责声明", + "homeDisclaimerText": "Web 应用程序支持运行 BLHeli for Atmel、BLHeli for SiLabs 和 BLHeli_S 固件的电调.
BLHeli 飞控透穿 是目前唯一支持的接口。

如果您遇到任何问题,请务必使用 保存调试日志 按钮并通过 GitHub 提交新问题。

应用程序源代码可以从 这里 下载。

最新的 CP210x 驱动程序 可从 这里下载。
最新的 STM USB VCP 驱动程序 可从 这里 下载。
", + "homeWelcome": "欢迎使用 ESC - 配置程序,为简化固件升级、配置电调而生的工具。", + "betaWarning": " 此工具目前还在BETA测试阶段。
部分功能可能无法正常工作 - 如果您发现任何bug,请 反馈 ", + "escButtonSelectLocally": "烧写本地固件", + "cookieText": "此站点或此站点使用的第三方工具使用操作所需的 cookie 和 cookie 策略中所概述的有用性。 接受即表示同意使用 cookie。", + "resetDefaults": "恢复默认设置", + "forceFlashText": "忽略不匹配的 MCU 和布局?", + "forceFlashHint": "(闪烁不匹配的固件可能会损坏您的 ESC,请自行承担风险)", + "migrateFlashText": "可能时迁移设置?", + "migrateFlashHint": "(这会将所有设置重置为固件默认值)", + "escDirectionReversed": "方向已反转", + "escBidirectionalMode": "双向模式", + "escSinusoidalStartup": "正弦式启动", + "escComplementaryPwm": "互补 PWM", + "escVariablePwmFrequency": "可变 PWM 频率", + "escStuckRotorProtection": "转子堵转保护", + "escStallProtection": "失速保护", + "escTimingAdvance": "电机进角 [degrees]", + "escPwmFrequency": "PWM 频率 [kHz]", + "escMotorKv": "电机 [Kv]", + "escMotorPoles": "电机极数", + "escBeepVolume": "蜂鸣音量", + "escIntervalTelemetry": "30ms 遥测输出", + "escServoLowThreshold": "伺服输入低位阈值", + "escServoHighThreshold": "伺服输入高位阈值", + "escServoNeutral": "伺服输入中心点", + "escServoDeadBand": "伺服输入中心点死区", + "escLowVoltageCutoff": "低电压截止", + "escLowVoltageThreshold": "低电压截止阈值", + "escRcCarReversing": "RC 遥控车风格的反转换向", + "bluejayText": "

Bluejay 是一个基于 BLHeli_S 固件能够使用双向DShot的新固件 - 如果你想要在你的飞机上运行 RPM 滤波器,那么这是一个很好的选择。该项目还旨在清理和简化最初的 BLHeli_S 源代码。

启动音乐编辑器也是它的一个新功能。

", + "blheli32ToAM32": "

基于 ARM 的 ESC 的固件。虽然目前其较新,但用户和制造商都对此很感兴趣。预装 AM32 固件的 ESC 将很快可以从不同的制造商那里买到。

可以在 BLHeli_32 ESC 上刷入 AM32 固件。 但是,您必须先通过 STM32 Cube 程序 和 STLink\nV2 编程适配器刷入 AM32 引导程序。所需要的引导程序可以在 AM32 bootloader 仓库中找到。

", + "blhelisText": "

BLHeli_S 可能不需要介绍 - 在几乎每个基于 EFM8 的四轴飞行器上使用的最常见的 ESC 固件。

已尝试并测试,支持每个模拟和数字协议。

", + "whatsNextHeader": "接下来是什么?", + "whatsNextText": "

如果你想要看到哪些功能即将到来,请顺便查看一下我们的 github 仓库。 如果您有一个想法想要继承进来,请随时添加功能请求。

", + "openPortSelection": "打开端口选择", + "versionUnsupported": "{{name}} 版本 '{{version}}' (布局 {{layout}}) 尚未支持。

请打开一个问题来让我们知道。", + "motorControl": "电机控制", + "enableMotorControl": "启用电机控制", + "masterSpeed": "主速度", + "motorNr": "电机 {{index}}", + "homeDiscordHeader": "加入我们的 Discord !", + "homeDiscordText": "如果你有任何问题或需要快速的帮助,加入我们的 Discord 服务器:", + "homeChinaHeader": "对于我们的中国用户", + "homeChinaText": "告诉你的朋友们,在GFW下,他们可以在中国通过直接访问 内地的镜像站 来使用我们的应用程序。", + "melodyEditor": "旋律编辑器", + "homeAttributionHeader": "致谢", + "homeAttributionText": "此项目受 BLHeli Configurator 的强烈启发。 大多数用户界面都是从零开始重写的,但大量与刷新和固件处理有关的低级物品已被重新使用 - 所以对所有参与原来的 BLHeli Configurator 的人致以最高敬意。", + "multiOnly": "目前只支持 MULTI 模式", + "selectFirmware": "选择固件", + "selectEsc": "选择 ESC", + "selectMode": "选择模式", + "selectVersion": "选择版本", + "selectPwmFrequency": "选择 PWM 频率", + "selectTarget": "选择目标", + "battery": "电池:", + "settings": "设置" +} diff --git a/src/translations/zh-CN/hints.json b/src/translations/zh-CN/hints.json new file mode 100644 index 000000000..f4e1f4f4c --- /dev/null +++ b/src/translations/zh-CN/hints.json @@ -0,0 +1,34 @@ +{ + "STARTUP_BEEP": "当 ESC 上电时启动蜂鸣旋律。", + "BRAKE_ON_STOP": "当电机停转时快速刹停。", + "BEACON_DELAY": "在多长时间无有效输入后,信标开始响起。", + "TEMPERATURE_PROTECTION": "当温度高于多少时 ESC 停止工作。", + "BEEP_STRENGTH": "上电时电调音乐的音量。此数值设置过高会伤害电机。", + "BEACON_STRENGTH": "信标的音量。将此值设置得过高可能会损害电机。", + "STARTUP_POWER_MIN": "启动电机时施加的最小功率。如果电机无法在低油门输入下启动,请提高此值。", + "DITHERING": "将有效 PWM 分辨率提升为2000步。通常建议保持开启,尤其是在 PWM 频率高于 24kHz 时。", + "STARTUP_POWER_MAX": "限制启动电机或反转方向时的功率。", + "RPM_POWER_SLOPE": "根据电机的转速快慢来限制功率的增加量。较低的值可以避免功率尖峰,但同时也会降低加速速度和最大可达速度。", + "MOTOR_DIRECTION": "电机的旋转方向可以是普通或反向。在双向模式下,中央油门是零。", + "COMMUTATION_TIMING": "电机换相超前时间。较高的进角不易发生电机失步,并以效率为代价,提供更高的功率。", + "DEMAG_COMPENSATION": "应在多大程度上切断电源,以防换相后较长的绕组退磁时间导致电机失速。", + "SINUSOIDAL_STARTUP": "在全油门段的前10%内,电机将会在开环换相形式下被正弦驱动。", + "COMPLEMENTARY_PWM": "通过开启低侧场效应管(而不是使用 MOSFET 的体二极管)来衰减电流以完成主动制动。", + "VARIABLE_PWM_FREQUENCY": "根据电机 RPM ,按比例来提升PWM频率,从而避免油门跳动。", + "STUCK_ROTOR_PROTECTION": "在10次启动尝试失败后切断电源并停止尝试重启。", + "STALL_PROTECTION": "当 RPM 低于阈值时自动提高油门以避免电机堵转,不推荐多轴飞行器使用", + "TIMING_ADVANCE": "电机换相超前时间。较高的进角不易发生电机失步,并以效率为代价,提供更高的功率。", + "MOTOR_KV": "制造商标明的 KV 参数。此值用于计算低 RPM 功率保护的 RPM 上限值。", + "MOTOR_POLES": "电机的极数。此值用于调整正弦启动的速度。", + "STARTUP_POWER": "控制电机启动和最小油门水平时的驱动电机的初始功率。", + "PWM_FREQUENCY": "当可变 PWM 被禁用时,可以使用它在可选范围内手动设置开关频率 (PWM)。", + "BEEP_VOLUME": "电机产生蜂鸣音的功率水平。将此值设置得过高可能会损害电机。", + "INTERVAL_TELEMETRY": "输出间隔 30ms 的遥测数据。", + "SERVO_LOW_THRESHOLD": "低于此点的任何信号都被视为零油门。", + "SERVO_HIGH_THRESHOLD": "高于此点的任何信号都被视为最大油门。", + "SERVO_NEUTRAL": "对于双向模式,这是零油门位置。", + "SERVO_DEAD_BAND": "施加在伺服输入中位的两侧,这个范围内的任何信号都将被视为零。", + "LOW_VOLTAGE_CUTOFF": "启用后,当电压降到低电压阈值以下时,将会切断电源。", + "LOW_VOLTAGE_THRESHOLD": "切断电源时每节电池的电压等级。电压单位为 *10伏。例如,要在在 3.3V 切断,请输入 330。", + "RC_CAR_REVERSING": "仅适用于地面车辆。将会覆盖用户设置,并将 ESC 设置为双向模式。" +} diff --git a/src/translations/zh-CN/log.json b/src/translations/zh-CN/log.json new file mode 100644 index 000000000..edc5f52e3 --- /dev/null +++ b/src/translations/zh-CN/log.json @@ -0,0 +1,33 @@ +{ + "closedPort": "关闭端口", + "pluggedIn": "已插入", + "unplugged": "已断开", + "portSelected": "端口已选择", + "portChanged": "端口已更改", + "portOpened": "打开串行端口", + "mspApiVersion": "已识别 MultiWii API 版本: {{version}}", + "mspBuildInfo": "运行已发布的固件: {{info}}", + "mspBoardInfo": "主板: {{identifier}}, 版本: {{version}}", + "mspUid": "已收到唯一设备ID - 0x{{id}}", + "mspFcInfo": "飞行控制器信息,标识符: {{id}} 版本: {{version}}", + "portUsed": "端口已被另一个应用程序使用 - 请尝试重新连接", + "fourWayFailed": "无法启用 4 路接口 - 请重新连接飞行控制器", + "readEscs": "正在尝试读取 {{connected}} 个 ESC", + "readEscsSuccess": "ESC 读取完成", + "readEscsFailed": "ESC 读取失败", + "readEsc": "Read ESC {{index}}: {{name}}", + "flashingEsc": "正在刷入 ESC {{index}}", + "flashingEscFailed": "刷写 ESC {{index}} 失败 - 请检查文件类型", + "readEscFailed": "读取 ESC {{index}} 失败", + "getFileFailed": "无法打开要烧录的文件", + "layoutNotSupported": "布局版本 {{revision}} 尚不支持", + "escSettingsNoChange": "无更改 - 不更新 ESC {{index}}", + "escUpdateSuccess": "正在更新 ESC {{index}} - 完成", + "escUpdateFailed": "正在更新 ESC {{index}} - 失败", + "escSettingsLayoutMismatch": "布局不匹配,覆盖未启用 - 中止", + "escSettingsMcuMismatch": "MCU 不匹配,覆盖未启用 - 中止", + "escFlashedInTime": "已刷入电调 {{index}} - {{seconds}}s", + "passthroughNotSupported": "不支持 BLHELI 透传协议", + "firmwareMismatch": "固件不匹配!刷入: {{flash}} vs. EEEPROM: {{eeprom}}", + "bootloaderMismatch": "引导程序不匹配!刷入: {{flash}} vs. EEEPROM: {{eeprom}}" +} diff --git a/src/translations/zh-CN/settings.json b/src/translations/zh-CN/settings.json new file mode 100644 index 000000000..2c8d51d70 --- /dev/null +++ b/src/translations/zh-CN/settings.json @@ -0,0 +1,8 @@ +{ + "settingsHeader": "设置", + "closeText": "关闭", + "directInput": "直接输入", + "directInputHint": "使用直接输入数值以代替滑块。", + "printLogs": "打印日志", + "printLogsHint": "如果启用,则会将日志打印到控制台。" +} diff --git a/src/translations/zh/common.json b/src/translations/zh-TW/common.json similarity index 58% rename from src/translations/zh/common.json rename to src/translations/zh-TW/common.json index 40c9ff677..cfa81313c 100644 --- a/src/translations/zh/common.json +++ b/src/translations/zh-TW/common.json @@ -19,7 +19,7 @@ "escBeaconDelay": "Beacon Delay", "escBeepStrength": "Beep Strength", "escBeaconStrength": "Beacon Strength", - "escStartupBeep": "Staartup Beep", + "escStartupBeep": "Startup Beep", "escDithering": "Dithering", "escMotorDirection": "Motor Direction", "escPPMMinThrottle": "PPM Min Throttle", @@ -40,11 +40,11 @@ "defaultChangelogTitle": "Changelog", "changelogClose": "Close", "homeExperimental": "This is an experimental web app to configure ESC firmware online.", - "homeVersionInfo": "You will always find the latest stable version here. Currently the following firmwares are supported:", + "homeVersionInfo": "You will always find the latest stable version here. Currently the following firmware are supported:", "homeContributionHeader": "Contributing", - "homeContributionText": "If you would like to help make Bluejay Configurator even better you can help in many ways, including:
", + "homeContributionText": "If you would like to help make ESC Configurator even better you can help in many ways, including:
", "homeDisclaimerHeader": "Disclaimer", - "homeDisclamierText": "The web application supports ESCs running BLHeli for Atmel, BLHeli for SiLabs and BLHeli_S.
BLHeli FC passthrough is the only interface currently supported.

Should you run into any problems, make sure to use the Save Debug Log button and submit a new issue via GitHub.

Application source code can be downloaded from here

Latest CP210x Drivers can be downloaded from here
Latest STM USB VCP Drivers can be downloaded from here
", + "homeDisclaimerText": "The web application supports ESCs running BLHeli for Atmel, BLHeli for SiLabs and BLHeli_S.
BLHeli FC passthrough is the only interface currently supported.

Should you run into any problems, make sure to use the Save Debug Log button and submit a new issue via GitHub.

Application source code can be downloaded from here

Latest CP210x Drivers can be downloaded from here
Latest STM USB VCP Drivers can be downloaded from here
", "homeWelcome": "Welcome to ESC - Configurator, a utility designed to simplify updating and configuring of your ESCs.", "betaWarning": "This tool is considered BETA.
Things might not work as expected yet - if you find any bugs please report them.", "escButtonSelectLocally": "Flash Local Firmware", @@ -53,7 +53,7 @@ "forceFlashText": "Ignore inappropriate MCU and Layout?", "forceFlashHint": "(Flashing inappropriate firmware may damage your ESC, do so at your own risk)", "migrateFlashText": "Migrate settings if possible?", - "migrateFlashHint": "(This will set the default settings from firmware)", + "migrateFlashHint": "(This will reset all settings to the firmware defaults)", "escDirectionReversed": "Direction Reversed", "escBidirectionalMode": "Bidirectional Mode", "escSinusoidalStartup": "Sinusoidal Startup", @@ -61,9 +61,9 @@ "escVariablePwmFrequency": "Variable PWM Frequency", "escStuckRotorProtection": "Stuck Rotor Protection", "escStallProtection": "Stall Protection", - "escTimingAdvance": "Timing Advance (degrees)", - "escPwmFrequency": "PWM Frequency (kHz)", - "escMotorKv": "Motor KV", + "escTimingAdvance": "Timing Advance [degrees]", + "escPwmFrequency": "PWM Frequency [kHz]", + "escMotorKv": "Motor [Kv]", "escMotorPoles": "Motor Poles", "escBeepVolume": "Beeper Volume", "escIntervalTelemetry": "30ms Telemetry Output", @@ -73,10 +73,10 @@ "escServoDeadBand": "Servo Neutral Dead Band", "escLowVoltageCutoff": "Low Voltage Cut Off", "escLowVoltageThreshold": "Low Voltage Cut-Off Threshold", - "escRcCarReversing": "Rc Car Style Reversing", - "bluejayText": "

Bluejay is BLHELI_S based firmware capable of bi-directional DSHOT - so a great choice if you want to run RPM filtering on your rig. This project also aims to clean up and simplify the original BLHELI_S source code.

A startup sound editor is also part of the deal.

", - "blheli32ToAM32": "

The up and coming firmware for ARM based ESC's. Although being relatively new on the scene it has a lot of interest, both from users and manufacturers. AM32 ESC's will soon be available from different manufacturers.

AM32 can be flashed on BLHELI_32 ESC's. But, you will have to first flash the AM32 Bootloader via STM32 Cube Programmer and ST Link V2 programming adapter. The required bootloader can be found in the AM32 bootloader repository.

", - "blhelisText": "

BLHELI_S probably does not need an introduction - the wildly popular ESC firmware used on almost every EFM8 based ESC in the quadrocopter hobby.

Tried and tested, supports every analog and digital protocol out there.

", + "escRcCarReversing": "RC Car Style Reversing", + "bluejayText": "

Bluejay is BLHeli_S based firmware capable of bi-directional DShot - so a great choice if you want to run RPM filtering on your rig. This project also aims to clean up and simplify the original BLHeli_S source code.

A startup sound editor is also part of the deal.

", + "blheli32ToAM32": "

The up and coming firmware for ARM based ESCs. Although being relatively new on the scene it has a lot of interest, both from users and manufacturers. AM32 ESCs will soon be available from different manufacturers.

AM32 can be flashed on BLHeli_32 ESCs. But, you will have to first flash the AM32 bootloader via STM32 Cube Programmer and ST Link V2 programming adapter. The required bootloader can be found in the AM32 bootloader repository.

", + "blhelisText": "

BLHeli_S probably does not need an introduction - the wildly popular ESC firmware used on almost every EFM8 based ESC in the quadcopter hobby.

Tried and tested, supports every analog and digital protocol out there.

", "whatsNextHeader": "What's next?", "whatsNextText": "

If you want to see which features are upcoming, drop by in the github repository. Also feel free to add a feature request if you have an idea that you want to see implemented.

", "openPortSelection": "Open Port Selection", @@ -86,5 +86,19 @@ "masterSpeed": "Master Speed", "motorNr": "Motor {{index}}", "homeDiscordHeader": "Join us on Discord!", - "homeDiscordText": "If you have any questions or need a quick helping hand, join us on our Discord server:" + "homeDiscordText": "If you have any questions or need a quick helping hand, join us on our Discord server:", + "homeChinaHeader": "For our Chinese visitors", + "homeChinaText": "Tell your friends behind the great firewall of China, that they can reach us via a local mirror directly in China.", + "melodyEditor": "Melody Editor", + "homeAttributionHeader": "Attribution", + "homeAttributionText": "This project was heavily inspired by the BLHeli Configurator. Most of the UI has been re-written from scratch but a lot of the low level stuff related to flashing and firmware handling have been re-used - so a big shout out to everyone involved in the original BLHeli Configurator.", + "multiOnly": "Only MULTI mode currently supported", + "selectFirmware": "Select Firmware", + "selectEsc": "Select ESC", + "selectMode": "Select Mode", + "selectVersion": "Select Version", + "selectPwmFrequency": "Select PWM Frequency", + "selectTarget": "Select Target", + "battery": "Battery:", + "settings": "Settings" } diff --git a/src/translations/zh/log.json b/src/translations/zh-TW/log.json similarity index 84% rename from src/translations/zh/log.json rename to src/translations/zh-TW/log.json index 25ffa17c0..98d216e0d 100644 --- a/src/translations/zh/log.json +++ b/src/translations/zh-TW/log.json @@ -15,7 +15,7 @@ "readEscs": "Trying to read {{connected}} ESC's", "readEscsSuccess": "Done reading ESC's", "readEscsFailed": "Failed reading ESC's", - "readEsc": "Read ESC {{index}}", + "readEsc": "Read ESC {{index}}: {{name}}", "flashingEsc": "Flashing ESC {{index}}", "flashingEscFailed": "Failed flashing ESC {{index}} - check file type", "readEscFailed": "Failed reading ESC {{index}}", @@ -27,5 +27,7 @@ "escSettingsLayoutMismatch": "Layout mismatch, override not enabled - aborted", "escSettingsMcuMismatch": "MCU mismatch, override not enabled - aborted", "escFlashedInTime": "Flashed ESC {{index}} - {{seconds}}s", - "passthroughNotSupported": "BLHELI passthrough not supported" + "passthroughNotSupported": "BLHELI passthrough not supported", + "firmwareMismatch": "Firmware mismatch! Flash: {{flash}} vs. EEPROM: {{eeprom}}", + "bootloaderMismatch": "Bootloader mismatch! Flash: {{flash}} vs. EEPROM: {{eeprom}}" } diff --git a/src/translations/zh/settings.json b/src/translations/zh-TW/settings.json similarity index 100% rename from src/translations/zh/settings.json rename to src/translations/zh-TW/settings.json diff --git a/src/translations/zh/hints.json b/src/translations/zh/hints.json deleted file mode 100644 index 0b704e489..000000000 --- a/src/translations/zh/hints.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "STARTUP_BEEP": "Enable beep melody when the ESC is powered up.", - "BRAKE_ON_STOP": "Brake when motors are stopped.", - "BEACON_DELAY": "After how much time of inactivity the beacon should start to go off.", - "TEMPERATURE_PROTECTION": "At which temperature the ESC will shut down.", - "BEEP_STRENGTH": "The volume of the startup beeps. Setting this too high might damage the motors.", - "BEACON_STRENGTH": "The volume of the beacon. Setting this too high might damage the motors.", - "STARTUP_POWER_MIN": "The least amount of power applied when starting up the motors. Increase if motors are not able to start up with low throttle input.", - "DITHERING": "Increases the effective pwm resolution 2000 steps. It is generally recommended to leave this on especially with pwm frequencies above 24kHz.", - "STARTUP_POWER_MAX": "Limits power when starting motors or reversing direction.", - "RPM_POWER_SLOPE": "Limits how much power can be increased according to how fast the motor is spinning. Lower values will avoid power spikes but can also decrease acceleration and maximum attainable speed.", - "MOTOR_DIRECTION": "The motor rotation direction can be normal or reversed. In bidirectional mode, center throttle is zero.", - "COMMUTATION_TIMING": "Motor commutation advance timing. Higher timing are less prone to desyncs and can provide more power at the cost of efficiency.", - "DEMAG_COMPENSATION": "The extent to which power should be cut to protect against motor stalls caused by long winding demagnetization time after commutation.", - "SINUSOIDAL_STARTUP": "During the first 10 percent of throttle the motor is stepped in sinusoidal open loop commutation.", - "COMPLEMENTARY_PWM": "Enables active braking by using low side switches for current decay instead of MOSFET body diodes.", - "VARIABLE_PWM_FREQUENCY": "Increases PWM frequency proportionally to motor rpm from 24-48khz to avoid thottle disturbances.", - "STUCK_ROTOR_PROTECTION": "Cuts power to the motor and stops trying to restart after 10 failed start attempts.", - "STALL_PROTECTION": "Increases throttle automatically below a rpm threshold to try and avoid stalling the motor, not reccomended for multirotors.", - "TIMING_ADVANCE": "Motor commutation advance timing. Higher timing are less prone to desyncs and can provide more power at the cost of efficiency.", - "MOTOR_KV": "The KV rating of the motor stated by the manufacturer. The value is used to set RPM limit for low rpm throttle protection.", - "MOTOR_POLES": "Pole count of the motor. This value is used to adjust sinusoidal startup speed.", - "STARTUP_POWER": "Controls the initial power given to the motor during startup and minimum throttle level.", - "PWM_FREQUENCY": "When variable PWM is disabled, this manually sets the switching frequency (PWM) to the chosen range.", - "BEEP_VOLUME": "The level of power for the audible beeps made by the motor. Settings too high might damage the motors.", - "INTERVAL_TELEMETRY": "Outputs telemetry data on a 30ms interval.", - "SERVO_LOW_THRESHOLD": "Any signal below this point is considered zero throttle.", - "SERVO_HIGH_THRESHOLD": "Any signal above this point is considered maximum throttle.", - "SERVO_NEUTRAL": "For bi-directional modes this is the zero throttle position in microseconds.", - "SERVO_DEAD_BAND": "Applied to either side of Servo Neutral, anything in this range is considered zero throttle.", - "LOW_VOLTAGE_CUTOFF": "When enabled will cut power to the motor when the voltage drops below the low voltage threshold.", - "LOW_VOLTAGE_THRESHOLD": "Voltage level per cell where power is cut. Units in volts * 10. To cut off at 3.3v enter 330 for example.", - "RC_CAR_REVERSING": "For ground vehicles only. Overrides user settings and places ESC into bi-directional mode with double tap to reverse type control." -} diff --git a/src/utils/Blheli.js b/src/utils/Blheli.js deleted file mode 100644 index a267200ee..000000000 --- a/src/utils/Blheli.js +++ /dev/null @@ -1,84 +0,0 @@ -import { - BLHELI_MODES, -} from '../sources/Blheli/eeprom'; - -class Blheli { - modeToString(mode) { - for (const property in BLHELI_MODES) { - if (Object.prototype.hasOwnProperty.call( - BLHELI_MODES, - property - )) { - if (BLHELI_MODES[property] === mode) { - return property; - } - } - } - } - - settingsObject(settingsUint8Array, layout) { - const object = {}; - - for (const prop in layout) { - if (Object.prototype.hasOwnProperty.call( - layout, - prop - )) { - const setting = layout[prop]; - - if (setting.size === 1) { - object[prop] = settingsUint8Array[setting.offset]; - } else if (setting.size === 2) { - object[prop] = settingsUint8Array[setting.offset] << 8 | - settingsUint8Array[setting.offset + 1]; - } else if (setting.size > 2) { - object[prop] = String.fromCharCode.apply( - undefined, - settingsUint8Array.subarray(setting.offset). - subarray( - 0, - setting.size - ) - ).trim(); - } else { - throw new Error('Logic error'); - } - } - } - - return object; - } - - settingsArray(settingsObject, layout, layoutSize) { - const array = new Uint8Array(layoutSize).fill(0xff); - - for (const prop in layout) { - if (Object.prototype.hasOwnProperty.call( - layout, - prop - )) { - const setting = layout[prop]; - - if (setting.size === 1) { - array[setting.offset] = settingsObject[prop]; - } else if (setting.size === 2) { - array[setting.offset] = settingsObject[prop] >> 8 & 0xff; - array[setting.offset + 1] = settingsObject[prop] & 0xff; - } else if (setting.size > 2) { - const { length } = settingsObject[prop]; - for (let i = 0; i < setting.size; i += 1) { - array[setting.offset + i] = i < length ? - settingsObject[prop].charCodeAt(i) : - ' '.charCodeAt(0); - } - } else { - throw new Error('Logic error'); - } - } - } - - return array; - } -} - -export default Blheli; diff --git a/src/utils/FourWay.js b/src/utils/FourWay.js index 719ee3320..1b2dedbeb 100644 --- a/src/utils/FourWay.js +++ b/src/utils/FourWay.js @@ -1,29 +1,26 @@ -import Blheli from './Blheli'; +import Convert from './helpers/Convert'; +import Flash from './helpers/Flash'; import { - BLHELI_SILABS, -} from '../sources/Blheli/eeprom'; + buildDisplayName as blheliBuildDisplayName, + EEPROM as BLHELI_EEPROM, +} from '../sources/Blheli'; import { - AM32_RESET_DELAY_MS, -} from '../sources/AM32/eeprom'; + buildDisplayName as am32BuildDisplayName, + EEPROM as AM32_EEPROM, +} from '../sources/AM32'; -import blheliSource from '../sources/Blheli'; -import bluejaySource from '../sources/Bluejay'; -import am32Source from '../sources/AM32'; +import { + buildDisplayName as bluejayBuildDisplayName, + EEPROM as BLUEJAY_EEPROM, +} from '../sources/Bluejay'; // TODO: We might use the ones from the source here... import BLHELI_ESCS from '../sources/Blheli/escs.json'; import BLUEJAY_ESCS from '../sources/Bluejay/escs.json'; import AM32_ESCS from '../sources/AM32/escs.json'; -import { - fillImage, - parseHex, - buf2ascii, - ascii2buf, -} from './helpers/Flash'; - import { canMigrate, getIndividualSettings, @@ -44,14 +41,7 @@ import { MODES, SILABS_MODES, } from './FourWayConstants'; - -import { - NotEnoughDataError, -} from './helpers/QueueProcessor'; - -const BLHELI_EEPROM = blheliSource.getEeprom(); -const BLUEJAY_EEPROM = bluejaySource.getEeprom(); -const AM32_EEPROM = am32Source.getEeprom(); +import { NotEnoughDataError } from './helpers/QueueProcessor'; class FourWay { constructor(serial) { @@ -210,9 +200,11 @@ class FourWay { const message = self.createMessage(command, params, address); // Debug print all messages except the keep alive messages + /* if (command !== COMMANDS.cmd_InterfaceTestAlive) { console.debug('sending', this.commandToString(command), address.toString(0x10)); } + */ const processMessage = async(resolve, reject) => { /** @@ -230,7 +222,7 @@ class FourWay { return resolve(msg); } } catch(e) { - console.debug('Command failed:', e.message); + console.debug(`Command ${this.commandToString(command)} failed: ${e.message}`); return reject(e); } @@ -256,9 +248,8 @@ class FourWay { flash.meta = {}; try { - const blheli = new Blheli(); const interfaceMode = flash.params[3]; - + flash.meta.input = flash.params[2]; flash.meta.signature = flash.params[1] << 8 | flash.params[0]; flash.meta.interfaceMode = interfaceMode; flash.meta.available = true; @@ -273,7 +264,7 @@ class FourWay { if (isSiLabs) { layoutSize = BLHELI_EEPROM.LAYOUT_SIZE; - settingsArray = (await this.read(BLHELI_SILABS.EEPROM_OFFSET, layoutSize)).params; + settingsArray = (await this.read(BLHELI_EEPROM.SILABS.EEPROM_OFFSET, layoutSize)).params; } else if (isArm) { layoutSize = AM32_EEPROM.LAYOUT_SIZE; layout = AM32_EEPROM.LAYOUT; @@ -288,10 +279,7 @@ class FourWay { flash.isAtmel = isAtmel; flash.settingsArray = settingsArray; - flash.settings = blheli.settingsObject( - settingsArray, - layout - ); + flash.settings = Convert.arrayToSettingsObject(settingsArray, layout); /** * Baased on the name we can decide if the initially guessed layout @@ -310,11 +298,7 @@ class FourWay { if(newLayout) { layout = newLayout; - - flash.settings = blheli.settingsObject( - settingsArray, - layout - ); + flash.settings = Convert.arrayToSettingsObject(settingsArray, layout); } const layoutRevision = flash.settings.LAYOUT_REVISION.toString(); @@ -342,7 +326,7 @@ class FourWay { flash.individualSettingsDescriptions = individualSettingsDescriptions[layoutRevision]; if (interfaceMode !== MODES.ARMBLB) { - const mode = blheli.modeToString(flash.settings.MODE); + const mode = Convert.modeToString(flash.settings.MODE); try { const descriptions = settingsDescriptions[layoutRevision][mode]; flash.settingsDescriptions = descriptions; @@ -352,23 +336,63 @@ class FourWay { } const layoutName = (flash.settings.LAYOUT || '').trim(); - let bootloaderRevision = null; let make = null; + let displayName = 'UNKNOWN'; if (isSiLabs) { const blheliLayouts = BLHELI_ESCS.layouts[BLHELI_EEPROM.TYPES.SILABS]; const blheliSLayouts = BLHELI_ESCS.layouts[BLHELI_EEPROM.TYPES.BLHELI_S_SILABS]; const bluejayLayouts = BLUEJAY_ESCS.layouts[BLUEJAY_EEPROM.TYPES.EFM8]; - if (layoutName in blheliLayouts) { + if (BLUEJAY_EEPROM.NAMES.includes(name) && layoutName in bluejayLayouts) { + make = bluejayLayouts[layoutName].name; + displayName = bluejayBuildDisplayName(flash, make); + } + else if (layoutName in blheliLayouts) { make = blheliLayouts[layoutName].name; } else if (layoutName in blheliSLayouts) { make = blheliSLayouts[layoutName].name; - } else if (layoutName in bluejayLayouts) { - make = bluejayLayouts[layoutName].name; + displayName = blheliBuildDisplayName(flash, make); } } else if (isArm) { - bootloaderRevision = flash.settings.BOOT_LOADER_REVISION; - flash.settings.LAYOUT = flash.settings.NAME; + /* Read version information direct from EEPROM so we can later + * compare to the settings object. This allows us to verify, that + * everything went well after flashing. + */ + const [mainRevision, subRevision] = (await this.read(AM32_EEPROM.VERSION_OFFSET, AM32_EEPROM.VERSION_SIZE)).params; + + if( + flash.settings.MAIN_REVISION !== mainRevision || + flash.settings.SUB_REVISION !== subRevision + ) { + const flashFirmware = `${flash.settings.MAIN_REVISION}.${flash.settings.SUB_REVISION}`; + const eepromFirmware = `${mainRevision}.${subRevision}`; + this.addLogMessage('firmwareMismatch', { + flash: flashFirmware, + eeprom: eepromFirmware, + }); + } + + flash.bootloader = {}; + if(flash.meta.input) { + flash.bootloader.input = flash.meta.input; + flash.bootloader.valid = false; + } + + /* Bootloader input pins are limited. If something different is set, + * then the user probably has an old fw flashed. + */ + for(let [key, value] of Object.entries(AM32_EEPROM.BOOT_LOADER_PINS)) { + if(value === flash.bootloader.input) { + flash.bootloader.valid = true; + flash.bootloader.pin = key; + flash.bootloader.version = flash.settings.BOOT_LOADER_REVISION; + } + } + + flash.settings.MAIN_REVISION = mainRevision; + flash.settings.SUB_REVISION = subRevision; + + displayName = am32BuildDisplayName(flash, flash.settings.NAME); } else { const blheliAtmelLayouts = BLHELI_ESCS.layouts[BLHELI_EEPROM.TYPES.ATMEL]; if (layoutName in blheliAtmelLayouts) { @@ -377,7 +401,7 @@ class FourWay { } flash.defaultSettings = defaultSettings[layoutRevision]; - flash.bootloaderRevision = bootloaderRevision; + flash.displayName = displayName; flash.layoutSize = layoutSize; flash.layout = layout; flash.make = make; @@ -411,8 +435,7 @@ class FourWay { const flash = await this.sendMessagePromised(COMMANDS.cmd_DeviceInitFlash, [target]); if (flash) { - const blheli = new Blheli(); - const newSettingsArray = blheli.settingsArray(settings, esc.layout, esc.layoutSize); + const newSettingsArray = Convert.objectToSettingsArray(settings, esc.layout, esc.layoutSize); if(newSettingsArray.length !== esc.settingsArray.length) { throw new Error('byteLength of buffers do not match'); } @@ -476,7 +499,7 @@ class FourWay { switch(interfaceMode) { case MODES.SiLC2: { - return BLHELI_SILABS.FLASH_SIZE; + return BLHELI_EEPROM.SILABS.FLASH_SIZE; } case MODES.SiLBLB: { @@ -590,7 +613,7 @@ class FourWay { this.totalBytes = BLHELI_EEPROM.PAGE_SIZE * 14 * 2; this.bytesWritten = 0; - const message = await this.read(BLHELI_SILABS.EEPROM_OFFSET, BLHELI_EEPROM.LAYOUT_SIZE); + const message = await this.read(BLHELI_EEPROM.SILABS.EEPROM_OFFSET, BLHELI_EEPROM.LAYOUT_SIZE); // checkESCAndMCU const escSettingArrayTmp = message.params; @@ -604,7 +627,7 @@ class FourWay { BLHELI_EEPROM.LAYOUT.LAYOUT.offset + BLHELI_EEPROM.LAYOUT.LAYOUT.size); if (!compare(target_layout, fw_layout)) { - var target_layout_str = buf2ascii(target_layout).trim(); + var target_layout_str = Convert.bufferToAscii(target_layout).trim(); if (target_layout_str.length === 0) { target_layout_str = 'EMPTY'; } @@ -622,7 +645,7 @@ class FourWay { BLHELI_EEPROM.LAYOUT.MCU.offset, BLHELI_EEPROM.LAYOUT.MCU.offset + BLHELI_EEPROM.LAYOUT.MCU.size); if (!compare(target_mcu, fw_mcu)) { - var target_mcu_str = buf2ascii(target_mcu).trim(); + var target_mcu_str = Convert.bufferToAscii(target_mcu).trim(); if (target_mcu_str.length === 0) { target_mcu_str = 'EMPTY'; } @@ -678,7 +701,7 @@ class FourWay { const eepromInfo = new Uint8Array(17).fill(0x00); eepromInfo.set([originalSettings[1], originalSettings[2]], 1); - eepromInfo.set(ascii2buf('FLASH FAIL '), 5); + eepromInfo.set(Convert.asciiToBuffer('FLASH FAIL '), 5); await this.write(AM32_EEPROM.EEPROM_OFFSET, eepromInfo); @@ -687,7 +710,7 @@ class FourWay { originalSettings[0] = 0x01; originalSettings.fill(0x00, 3, 5); - originalSettings.set(ascii2buf('NOT READY '), 5); + originalSettings.set(Convert.asciiToBuffer('NOT READY '), 5); await this.write(AM32_EEPROM.EEPROM_OFFSET, originalSettings); }; @@ -708,7 +731,7 @@ class FourWay { // Reset after flashing to update name and settings await this.reset(target); - await delay(AM32_RESET_DELAY_MS); + await delay(AM32_EEPROM.RESET_DELAY); } break; default: throw new Error(`Flashing with ${interfaceMode} is not yet implemented`); @@ -731,9 +754,9 @@ class FourWay { if(esc.isArm) { try { - const parsed = parseHex(hex); + const parsed = Flash.parseHex(hex); const endAddress = parsed.data[parsed.data.length - 1].address + parsed.data[parsed.data.length - 1].bytes; - const flash = fillImage(parsed, endAddress - flashOffset, flashOffset); + const flash = Flash.fillImage(parsed, endAddress - flashOffset, flashOffset); //TODO: Also check for the firmware name // But we first need to get this moved to a fixed location @@ -754,12 +777,12 @@ class FourWay { } } else if(!esc.isAtmel) { try { - const parsed = parseHex(hex); - const flash = fillImage(parsed, flashSize, flashOffset); + const parsed = Flash.parseHex(hex); + const flash = Flash.fillImage(parsed, flashSize, flashOffset); // Check pseudo-eeprom page for BLHELI signature - const mcu = buf2ascii( - flash.subarray(BLHELI_SILABS.EEPROM_OFFSET) + const mcu = Convert.bufferToAscii( + flash.subarray(BLHELI_EEPROM.SILABS.EEPROM_OFFSET) .subarray(BLHELI_EEPROM.LAYOUT.MCU.offset) .subarray(0, BLHELI_EEPROM.LAYOUT.MCU.size)); @@ -828,7 +851,7 @@ class FourWay { } async writeEEpromSafeguard(settings) { - settings.set(ascii2buf('**FLASH*FAILED**'), BLHELI_EEPROM.LAYOUT.NAME.offset); + settings.set(Convert.asciiToBuffer('**FLASH*FAILED**'), BLHELI_EEPROM.LAYOUT.NAME.offset); const response = await this.write(BLHELI_EEPROM.EEPROM_OFFSET, settings); const verifySafeguard = async (resolve, reject) => { @@ -907,11 +930,12 @@ class FourWay { return this.sendMessagePromised(COMMANDS.cmd_DevicePageErase, [page]); } - read(address, bytes) { + read(address, bytes, retries = 10) { return this.sendMessagePromised( COMMANDS.cmd_DeviceRead, [bytes === 256 ? 0 : bytes], - address + address, + retries ); } @@ -936,9 +960,7 @@ class FourWay { } exit() { - if (this.interval) { - clearInterval(this.interval); - } + clearInterval(this.interval); return this.sendMessagePromised(COMMANDS.cmd_InterfaceExit); } diff --git a/src/utils/Msp.js b/src/utils/Msp.js index b9b63c919..6585ee26c 100644 --- a/src/utils/Msp.js +++ b/src/utils/Msp.js @@ -1,6 +1,6 @@ -import { - NotEnoughDataError, -} from './helpers/QueueProcessor'; +import compareVersions from 'compare-versions'; + +import { NotEnoughDataError } from './helpers/QueueProcessor'; const MSP = { MSP_API_VERSION: 1, @@ -9,6 +9,8 @@ const MSP = { MSP_BOARD_INFO: 4, MSP_BUILD_INFO: 5, + MSP_BATTERY_STATE: 130, + MSP_SET_MOTOR: 214, MSP_SET_PASSTHROUGH: 245, @@ -41,6 +43,8 @@ class Msp { const speedBufferOut = new ArrayBuffer(16); this.speedBufView = new Uint8Array(speedBufferOut); + + this.version = null; } setLogCallback(logCallback) { @@ -289,6 +293,10 @@ class Msp { return this.send(MSP.MSP_MOTOR); } + getBatteryState() { + return this.send(MSP.MSP_BATTERY_STATE); + } + set4WayIf() { return this.send(MSP.MSP_SET_PASSTHROUGH); } @@ -388,6 +396,8 @@ class Msp { config.apiVersion = `${data.getUint8(offset++)}.`; config.apiVersion += `${data.getUint8(offset++)}.0`; + this.version = config.apiVersion; + return config; } @@ -451,6 +461,23 @@ class Msp { return config; } + case MSP.MSP_BATTERY_STATE: { + const battery = { + cellCount: data.getUint8(0), + capacity: data.getUint16(1, 1), // mAh + voltage: data.getUint8(3) / 10.0, // V + drawn: data.getUint16(4, 1), // mAh + amps: data.getUint16(6, 1) / 100, // A + state: data.getUint8(8), + }; + + if(compareVersions.compare(this.version, '1.41.0', '>=')) { + battery.voltage = data.getUint16(9, 1) / 100.0; // V + } + + return battery; + } + case MSP.MSP_SET_3D: { console.debug('3D settings saved'); } break; diff --git a/src/utils/Serial.js b/src/utils/Serial.js index 64e0e2e73..2924cc571 100644 --- a/src/utils/Serial.js +++ b/src/utils/Serial.js @@ -1,9 +1,6 @@ import Msp from './Msp'; import FourWay from './FourWay'; - -import { - QueueProcessor, -} from './helpers/QueueProcessor'; +import { QueueProcessor } from './helpers/QueueProcessor'; /** * Abstraction layer for all serial communication @@ -33,7 +30,6 @@ class Serial { this.logCallback = null; this.packetErrorsCallback = null; - this.utilizationCallback = null; this.qp = new QueueProcessor(); @@ -43,16 +39,26 @@ class Serial { this.receivedTotal = 0; } - /** - * Send a buffer via serial and process response with the response handler - */ - async executeCommand(buffer, responseHandler) { - const sendHandler = async function() { - await this.writeBuffer(buffer); - }.bind(this); - - return this.qp.addCommand(sendHandler, responseHandler); - } + /* MSP commands */ + enable4WayInterface = () => this.msp.set4WayIf(); + getApiVersion = () => this.msp.getApiVersion(); + getBatteryState = () => this.msp.getBatteryState(); + getBoardInfo = () => this.msp.getBoardInfo(); + getBuildInfo = () => this.msp.getBuildInfo(); + getFcVariant = () => this.msp.getFcVariant(); + getFcVersion = () => this.msp.getFcVersion(); + getMotorData = () => this.msp.getMotorData(); + getUid = () => this.msp.getUid(); + spinAllMotors = (speed) => this.msp.spinAllMotors(speed); + spinMotor = (index, speed) => this.msp.spinMotor(index, speed); + + /* 4 Way interface commands */ + exitFourWayInterface = () => this.fourWay.exit(); + getFourWayInterfaceInfo = (esc) => this.fourWay.getInfo(esc); + resetFourWayInterface = (esc) => this.fourWay.reset(esc); + startFourWayInterface = () => this.fourWay.start(); + writeHex = (index, esc, hex, force, migrate, cbProgress) => this.fourWay.writeHex(index, esc, hex, force, migrate, cbProgress); + writeSettings = (index, esc, settings) => this.fourWay.writeSettings(index, esc, settings); setLogCallback(logCallback) { this.logCallback = logCallback; @@ -61,10 +67,6 @@ class Serial { this.msp.setLogCallback(logCallback); } - setUtilizationCallback(utilizationCallback) { - this.utilizationCallback = utilizationCallback; - } - setPacketErrorsCallback(packetErrorsCallback) { this.packetErrorsCallback = packetErrorsCallback; @@ -72,83 +74,15 @@ class Serial { this.msp.setPacketErrorsCallback(packetErrorsCallback); } - async getApiVersion() { - return this.msp.getApiVersion(); - } - - async getFcVariant() { - return this.msp.getFcVariant(); - } - - async getFcVersion() { - return this.msp.getFcVersion(); - } - - async getBuildInfo() { - return this.msp.getBuildInfo(); - } - - async getBoardInfo() { - return this.msp.getBoardInfo(); - } - - async getMotorData() { - return this.msp.getMotorData(); - } - - async getUid() { - return this.msp.getUid(); - } - - async enable4WayInterface() { - return this.msp.set4WayIf(); - } - - async spinMotor(index, speed) { - return this.msp.spinMotor(index, speed); - } - - async spinAllMotors(speed) { - return this.msp.spinAllMotors(speed); - } - - async fourWayWriteSettings(index, esc, settings) { - return this.fourWay.writeSettings(index, esc, settings); - } - - async fourWayWriteHex(index, esc, hex, force, migrate, cbProgress) { - return this.fourWay.writeHex(index, esc, hex, force, migrate, cbProgress); - } - - async fourWayStart() { - this.fourWay.start(); - } - - async fourWayExit() { - return this.fourWay.exit(); - } - - async fourWayReset(esc) { - return this.fourWay.reset(esc); - } - - async fourWayTestAlive() { - return this.fourWay.testAlive(); - } - - async fourWayReadEEprom(address, bytes) { - return this.fourWay.readEEprom( - address, - bytes - ); - } - - async fourWayInitFlash(esc) { - return this.fourWay.initFlash(esc); - } + /** + * Send a buffer via serial and process response with the response handler + */ + async executeCommand(buffer, responseHandler) { + const sendHandler = async function() { + await this.writeBuffer(buffer); + }.bind(this); - async fourWayGetInfo(esc) { - return this.fourWay.getInfo(esc); + return this.qp.addCommand(sendHandler, responseHandler); } async writeBuffer(buffer) { @@ -189,7 +123,7 @@ class Serial { }; } - async open(baudRate) { + async open(baudRate = 115200) { this.baudRate = baudRate; await this.port.open({ baudRate }); @@ -208,18 +142,18 @@ class Serial { this.startReader(); } - disconnect() { + async disconnect() { this.running = false; this.reader = null; this.writer = null; - } - - async close() { - this.running = false; if(this.fourWay) { await this.fourWay.exit(); } + } + + async close() { + this.running = false; if(this.reader) { this.reader.cancel(); @@ -235,6 +169,8 @@ class Serial { } catch(e) { // we tried... } + + this.disconnect(); } } diff --git a/src/utils/helpers/Convert.js b/src/utils/helpers/Convert.js new file mode 100644 index 000000000..65d7029e5 --- /dev/null +++ b/src/utils/helpers/Convert.js @@ -0,0 +1,74 @@ +import { EEPROM } from '../../sources/Blheli'; + +class Convert { + static modeToString(mode) { + for (const [key, value] of Object.entries(EEPROM.MODES)) { + if (value === mode) { + return key; + } + } + } + + static arrayToSettingsObject(settingsUint8Array, layout) { + const object = {}; + + for (const [prop, setting] of Object.entries(layout)) { + if (setting.size === 1) { + object[prop] = settingsUint8Array[setting.offset]; + } else if (setting.size === 2) { + object[prop] = settingsUint8Array[setting.offset] << 8 | + settingsUint8Array[setting.offset + 1]; + } else if (setting.size > 2) { + object[prop] = String.fromCharCode.apply( + undefined, + settingsUint8Array.subarray(setting.offset). + subarray(0, setting.size) + ).trim(); + } else { + throw new Error('Logic error'); + } + } + + return object; + } + + static objectToSettingsArray(settingsObject, layout, layoutSize) { + const array = new Uint8Array(layoutSize).fill(0xff); + + for (const [prop, setting] of Object.entries(layout)) { + if (setting.size === 1) { + array[setting.offset] = settingsObject[prop]; + } else if (setting.size === 2) { + array[setting.offset] = settingsObject[prop] >> 8 & 0xff; + array[setting.offset + 1] = settingsObject[prop] & 0xff; + } else if (setting.size > 2) { + const { length } = settingsObject[prop]; + for (let i = 0; i < setting.size; i += 1) { + array[setting.offset + i] = i < length ? + settingsObject[prop].charCodeAt(i) : + ' '.charCodeAt(0); + } + } else { + throw new Error('Logic error'); + } + } + + return array; + } + + static bufferToAscii(buffer) { + return String.fromCharCode.apply(null, buffer); + } + + static asciiToBuffer(ascii) { + const buffer = new Uint8Array(ascii.length); + + for (var i = 0; i < ascii.length; i += 1) { + buffer[i] = ascii.charCodeAt(i); + } + + return buffer; + } +} + +export default Convert; diff --git a/src/utils/helpers/Flash.js b/src/utils/helpers/Flash.js index f4603055e..74546121c 100644 --- a/src/utils/helpers/Flash.js +++ b/src/utils/helpers/Flash.js @@ -1,143 +1,126 @@ -// Pad data to fixed size -function fillImage(data, size, flashOffset) { - var image = new Uint8Array(size).fill(0xFF); - - //data.data.forEach((block) => { - for(let i = 0; i < data.data.length; i += 1) { - const block = data.data[i]; - const address = block.address - flashOffset; - - // Check preconditions - if (address >= image.byteLength) { - return null; +class Flash { + // Pad data to fixed size + static fillImage(data, size, flashOffset) { + var image = new Uint8Array(size).fill(0xFF); + + //data.data.forEach((block) => { + for(let i = 0; i < data.data.length; i += 1) { + const block = data.data[i]; + const address = block.address - flashOffset; + + // Check preconditions + if (address >= image.byteLength) { + return null; + } + + // block.data may be too large, select maximum allowed size + var clampedLength = Math.min(block.bytes, image.byteLength - address); + image.set(block.data.slice(0, clampedLength), address); } - // block.data may be too large, select maximum allowed size - var clampedLength = Math.min(block.bytes, image.byteLength - address); - image.set(block.data.slice(0, clampedLength), address); + return image; } - return image; -} - -function parseHex(string) { - string = string.split("\n"); - - // check if there is an empty line in the end of hex file, if there is, remove it - if (string[string.length - 1] === "") { - string.pop(); - } + static parseHex(string) { + string = string.split("\n"); - var result = { - data: [], - endOfFile: false, - bytes: 0, - startLinearAddress: 0, - }; - - var extendedLinearAddress = 0; - var nextAddress = 0; - - for (var i = 0; i < string.length; i += 1) { - // each byte is represnted by two chars - var byteCount = parseInt(string[i].substr(1, 2), 16); - var address = parseInt(string[i].substr(3, 4), 16); - var recordType = parseInt(string[i].substr(7, 2), 16); - var content = string[i].substr(9, byteCount * 2); // still in string format - var checksum = parseInt(string[i].substr(9 + byteCount * 2, 2), 16); // (this is a 2's complement value) - - switch (recordType) { - // data record - case 0x00: { - if (address !== nextAddress || nextAddress === 0) { - result.data.push({ - 'address': extendedLinearAddress + address, - 'bytes': 0, - 'data': [], - }); - } - - // store address for next comparison - nextAddress = address + byteCount; - - // process data - var crc = byteCount + parseInt(string[i].substr(3, 2), 16) + parseInt(string[i].substr(5, 2), 16) + recordType; - for (var needle = 0; needle < byteCount * 2; needle += 2) { // * 2 because of 2 hex chars per 1 byte - var num = parseInt(content.substr(needle, 2), 16); // get one byte in hex and convert it to decimal - var string_block = result.data.length - 1; - - result.data[string_block].data.push(num); - result.data[string_block].bytes += 1; - - - crc += num; - result.bytes += 1; - } - - // change crc to 2's complement - crc = (~crc + 1) & 0xFF; - - // Return in case of fail - if (crc !== checksum) { - return null; - } - } break; - - // end of file record - case 0x01: { - result.endOfFile = true; - } break; - - // extended segment address record - case 0x02: { - if (parseInt(content, 16) !== 0) { // ignore if segment is 0 - console.debug('extended segment address record found - NOT IMPLEMENTED!'); - } - } break; - - // start segment address record - case 0x03: { - if (parseInt(content, 16) !== 0) { // ignore if segment is 0 - console.debug('start segment address record found - NOT IMPLEMENTED!'); - } - } break; - - // extended linear address record - case 0x04: { - extendedLinearAddress = (parseInt(content.substr(0, 2), 16) << 24) | parseInt(content.substr(2, 2), 16) << 16; - } break; - - // start linear address record - case 0x05: { - result.startLinearAddress = parseInt(content, 16); - } break; + // check if there is an empty line in the end of hex file, if there is, remove it + if (string[string.length - 1] === "") { + string.pop(); } - } - - if (result.endOfFile) { - return result; - } - return null; -} - -function buf2ascii(buffer) { - return String.fromCharCode.apply(null, buffer); -} + var result = { + data: [], + endOfFile: false, + bytes: 0, + startLinearAddress: 0, + }; + + var extendedLinearAddress = 0; + var nextAddress = 0; + + for (var i = 0; i < string.length; i += 1) { + // each byte is represnted by two chars + var byteCount = parseInt(string[i].substr(1, 2), 16); + var address = parseInt(string[i].substr(3, 4), 16); + var recordType = parseInt(string[i].substr(7, 2), 16); + var content = string[i].substr(9, byteCount * 2); // still in string format + var checksum = parseInt(string[i].substr(9 + byteCount * 2, 2), 16); // (this is a 2's complement value) + + switch (recordType) { + // data record + case 0x00: { + if (address !== nextAddress || nextAddress === 0) { + result.data.push({ + 'address': extendedLinearAddress + address, + 'bytes': 0, + 'data': [], + }); + } + + // store address for next comparison + nextAddress = address + byteCount; + + // process data + var crc = byteCount + parseInt(string[i].substr(3, 2), 16) + parseInt(string[i].substr(5, 2), 16) + recordType; + for (var needle = 0; needle < byteCount * 2; needle += 2) { // * 2 because of 2 hex chars per 1 byte + var num = parseInt(content.substr(needle, 2), 16); // get one byte in hex and convert it to decimal + var string_block = result.data.length - 1; + + result.data[string_block].data.push(num); + result.data[string_block].bytes += 1; + + + crc += num; + result.bytes += 1; + } + + // change crc to 2's complement + crc = (~crc + 1) & 0xFF; + + // Return in case of fail + if (crc !== checksum) { + return null; + } + } break; + + // end of file record + case 0x01: { + result.endOfFile = true; + } break; + + // extended segment address record + case 0x02: { + if (parseInt(content, 16) !== 0) { // ignore if segment is 0 + console.debug('extended segment address record found - NOT IMPLEMENTED!'); + } + } break; + + // start segment address record + case 0x03: { + if (parseInt(content, 16) !== 0) { // ignore if segment is 0 + console.debug('start segment address record found - NOT IMPLEMENTED!'); + } + } break; + + // extended linear address record + case 0x04: { + extendedLinearAddress = (parseInt(content.substr(0, 2), 16) << 24) | parseInt(content.substr(2, 2), 16) << 16; + } break; + + // start linear address record + case 0x05: { + result.startLinearAddress = parseInt(content, 16); + } break; + } + } -function ascii2buf(ascii) { - const buffer = new Uint8Array(ascii.length); + if (result.endOfFile) { + return result; + } - for (var i = 0; i < ascii.length; i += 1) { - buffer[i] = ascii.charCodeAt(i); + return null; } - - return buffer; } -export { - fillImage, - parseHex, - buf2ascii, - ascii2buf, -}; +export default Flash; diff --git a/src/utils/helpers/General.js b/src/utils/helpers/General.js index 2818a221f..1c8289ebc 100644 --- a/src/utils/helpers/General.js +++ b/src/utils/helpers/General.js @@ -1,21 +1,16 @@ -import { - EEPROM as BLUEJAY_EEPROM -} from '../../sources/Bluejay'; - -const BLUEJAY_TYPES = BLUEJAY_EEPROM.TYPES; +import { EEPROM as BLHELI_EEPROM } from '../../sources/Blheli'; +import BLHELI_ESCS from '../../sources/Blheli/escs.json'; +import { EEPROM as BLUEJAY_EEPROM } from '../../sources/Bluejay'; import BLUEJAY_ESCS from '../../sources/Bluejay/escs.json'; -import { - BLHELI_TYPES, -} from '../../sources/Blheli/eeprom'; -import BLHELI_ESCS from '../../sources/Blheli/escs.json'; - -import { - AM32_TYPES, -} from '../../sources/AM32/eeprom'; +import { EEPROM as AM32_EEPROM } from '../../sources/AM32'; import AM32_ESCS from '../../sources/AM32/escs.json'; +const BLHELI_TYPES = BLHELI_EEPROM.TYPES; +const BLUEJAY_TYPES = BLUEJAY_EEPROM.TYPES; +const AM32_TYPES = AM32_EEPROM.TYPES; + function compare(a, b) { if (a.byteLength !== b.byteLength) { return false; diff --git a/src/utils/helpers/React.js b/src/utils/helpers/React.js new file mode 100644 index 000000000..3a753e3d0 --- /dev/null +++ b/src/utils/helpers/React.js @@ -0,0 +1,31 @@ +import { + useEffect, + useRef, +} from 'react'; + +function useInterval(callback, delay) { + const savedCallback = useRef(); + + // Remember the latest callback. + useEffect(() => { + savedCallback.current = callback; + }, [callback]); + + // Set up the interval. + useEffect(() => { + function tick() { + savedCallback.current(); + } + + if (delay !== null) { + let id = setInterval(tick, delay); + return () => clearInterval(id); + } else { + savedCallback.current(); + } + }, [delay]); +} + +export { + useInterval, +}; diff --git a/src/utils/helpers/Settings.js b/src/utils/helpers/Settings.js index 6867961e8..e79f76efd 100644 --- a/src/utils/helpers/Settings.js +++ b/src/utils/helpers/Settings.js @@ -1,6 +1,4 @@ -import { - BLHELI_MODES, -} from '../../sources/Blheli/eeprom'; +import { EEPROM as BLHELI_EEPROM } from '../../sources/Blheli'; const getMasterSettings = (escs) => { const master = getMaster(escs); @@ -49,7 +47,7 @@ const getMaster = (escs) => escs.find((esc) => esc.meta.available); const getAllSettings = (escs) => escs.map((esc) => esc.settings); -const isMulti = (escs) => escs.every((esc) => !esc.settings.MODE || esc.settings.MODE === BLHELI_MODES.MULTI); +const isMulti = (escs) => escs.every((esc) => !esc.settings.MODE || esc.settings.MODE === BLHELI_EEPROM.MODES.MULTI); function canMigrate(settingName, from, to, toSettingsDescriptions, toIndividualSettingsDescriptions) { if (from.MODE === to.MODE) { @@ -62,7 +60,7 @@ function canMigrate(settingName, from, to, toSettingsDescriptions, toIndividualS } const fromLayout = toSettingsDescriptions[from.LAYOUT_REVISION]; - const toLayout = toSettingsDescriptions[from.LAYOUT_REVISION]; + const toLayout = toSettingsDescriptions[to.LAYOUT_REVISION]; let fromCommons = null; let toCommons = null; diff --git a/src/utils/helpers/__tests__/Convert.test.js b/src/utils/helpers/__tests__/Convert.test.js new file mode 100644 index 000000000..474d75282 --- /dev/null +++ b/src/utils/helpers/__tests__/Convert.test.js @@ -0,0 +1,153 @@ +import Convert from '../Convert'; +import { EEPROM } from '../../../sources/Bluejay'; + +const settingsArray = new Uint8Array(Object.values({ + "0":0, + "1":11, + "2":201, + "3":255, + "4":51, + "5":1, + "6":1, + "7":25, + "8":255, + "9":9, + "10":96, + "11":1, + "12":255, + "13":85, + "14":170, + "15":255, + "16":255, + "17":255, + "18":255, + "19":255, + "20":255, + "21":4, + "22":255, + "23":255, + "24":255, + "25":255, + "26":255, + "27":40, + "28":80, + "29":4, + "30":255, + "31":2, + "32":255, + "33":255, + "34":255, + "35":7, + "36":255, + "37":255, + "38":255, + "39":0, + "40":0, + "41":255, + "42":255, + "43":255, + "44":255, + "45":255, + "46":255, + "47":255, + "48":255, + "49":255, + "50":255, + "51":255, + "52":255, + "53":255, + "54":255, + "55":255, + "56":255, + "57":255, + "58":255, + "59":255, + "60":255, + "61":255, + "62":255, + "63":255, + "64":35, + "65":83, + "66":95, + "67":72, + "68":95, + "69":53, + "70":48, + "71":35, + "72":32, + "73":32, + "74":32, + "75":32, + "76":32, + "77":32, + "78":32, + "79":32, + "80":35, + "81":66, + "82":76, + "83":72, + "84":69, + "85":76, + "86":73, + "87":36, + "88":69, + "89":70, + "90":77, + "91":56, + "92":66, + "93":50, + "94":49, + "95":35, + "96":66, + "97":108, + "98":117, + "99":101, + "100":106, + "101":97, + "102":121, + "103":32, + "104":40, + "105":66, + "106":69, + "107":84, + "108":65, + "109":41, + "110":32, + "111":32, +})); + +test('modeToString', () => { + expect(Convert.modeToString(0x55AA)).toEqual('MULTI'); +}); + +test('settingsUint8Array', () => { + const layout = JSON.parse(JSON.stringify(EEPROM.LAYOUT)); + + const settingsObject = Convert.arrayToSettingsObject(settingsArray, layout); + const keys = Object.keys(settingsObject); + expect(keys.length).toEqual(43); + + layout.MAIN_REVISION.size = 0; + expect(() => Convert.arrayToSettingsObject(settingsArray, layout)).toThrow(); +}); + +test('settingsArray', () => { + const layout = JSON.parse(JSON.stringify(EEPROM.LAYOUT)); + + const settingsObject = Convert.arrayToSettingsObject(settingsArray, layout); + const settingsArrayResult = Convert.objectToSettingsArray(settingsObject, layout, EEPROM.LAYOUT_SIZE); + expect(settingsArrayResult.length).toEqual(112); + + layout.MAIN_REVISION.size = 0; + expect(() => Convert.objectToSettingsArray(settingsObject, layout, EEPROM.LAYOUT_SIZE)).toThrow(); +}); + +test('bufferToAscii', () => { + const ascii = Convert.bufferToAscii([0x0054, 0x0045, 0x0053, 0x0054]); + expect(ascii).toEqual('TEST'); +}); + +test('asciiToBuffer', () => { + const buffer = Convert.asciiToBuffer('TEST'); + expect(buffer).toEqual(new Uint8Array([0x0054, 0x0045, 0x0053, 0x0054])); +}); diff --git a/src/utils/helpers/__tests__/Flash.test.js b/src/utils/helpers/__tests__/Flash.test.js index 3c3d6eccd..9ee851a83 100644 --- a/src/utils/helpers/__tests__/Flash.test.js +++ b/src/utils/helpers/__tests__/Flash.test.js @@ -1,27 +1,11 @@ import fs from 'fs'; -import path from 'path'; -import { - buf2ascii, - ascii2buf, - parseHex, - fillImage, -} from '../Flash'; - -test('buffer to ACSII', () => { - const ascii = buf2ascii([0x0054, 0x0045, 0x0053, 0x0054]); - expect(ascii).toEqual('TEST'); -}); - -test('ASCII to buffer', () => { - const buffer = ascii2buf('TEST'); - expect(buffer).toEqual(new Uint8Array([0x0054, 0x0045, 0x0053, 0x0054])); -}); +import Flash from '../Flash'; test('should parse a valid hex file', () => { const hexContent = fs.readFileSync(`${__dirname}/valid.hex`); const hexString = hexContent.toString(); - const result = parseHex(hexString); + const result = Flash.parseHex(hexString); expect(result).not.toBeNull(); }); @@ -29,7 +13,7 @@ test('should parse a valid hex file', () => { test('should not parse an invalid hex file', () => { const hexContent = fs.readFileSync(`${__dirname}/invalid.hex`); const hexString = hexContent.toString(); - const result = parseHex(hexString); + const result = Flash.parseHex(hexString); expect(result).toBeNull(); }); @@ -37,7 +21,7 @@ test('should not parse an invalid hex file', () => { test('should not parse a hex file with broken checksum', () => { const hexContent = fs.readFileSync(`${__dirname}/broken_checksum.hex`); const hexString = hexContent.toString(); - const result = parseHex(hexString); + const result = Flash.parseHex(hexString); expect(result).toBeNull(); }); @@ -45,10 +29,10 @@ test('should not parse a hex file with broken checksum', () => { test('should fill an Image to a given size', () => { const hexContent = fs.readFileSync(`${__dirname}/valid.hex`); const hexString = hexContent.toString(); - const parsed = parseHex(hexString); + const parsed = Flash.parseHex(hexString); const endAddress = parsed.data[parsed.data.length - 1].address + parsed.data[parsed.data.length - 1].bytes; const flashOffset = 0; - const result = fillImage(parsed, endAddress - flashOffset, flashOffset); + const result = Flash.fillImage(parsed, endAddress - flashOffset, flashOffset); expect(result.length).toEqual(endAddress); }); @@ -56,10 +40,10 @@ test('should fill an Image to a given size', () => { test('should fail filling an Image with address higher than length', () => { const hexContent = fs.readFileSync(`${__dirname}/valid.hex`); const hexString = hexContent.toString(); - const parsed = parseHex(hexString); + const parsed = Flash.parseHex(hexString); const endAddress = parsed.data[parsed.data.length - 1].address + parsed.data[parsed.data.length - 1].bytes; const flashOffset = 0; - const result = fillImage(parsed, endAddress - flashOffset - 1000, flashOffset); + const result = Flash.fillImage(parsed, endAddress - flashOffset - 1000, flashOffset); expect(result).toBeNull(); }); diff --git a/src/utils/helpers/__tests__/General.test.js b/src/utils/helpers/__tests__/General.test.js index de33bcc45..43d96e019 100644 --- a/src/utils/helpers/__tests__/General.test.js +++ b/src/utils/helpers/__tests__/General.test.js @@ -1,18 +1,12 @@ import fs from 'fs'; -import path from 'path'; -import { - parseHex, - fillImage -} from '../Flash'; +import Flash from '../Flash'; import { retry, delay, compare, - isValidFlash, - isValidLayout, - getPossibleTypes, + isValidFlash, isValidLayout, getPossibleTypes, } from '../General'; test('compare same buffers', () => { @@ -45,10 +39,10 @@ test('delay', async() => { test('valid Flash', () => { const hexContent = fs.readFileSync(`${__dirname}/valid.hex`); const hexString = hexContent.toString(); - const parsed = parseHex(hexString); + const parsed = Flash.parseHex(hexString); const endAddress = parsed.data[parsed.data.length - 1].address + parsed.data[parsed.data.length - 1].bytes; const flashOffset = 0; - const flash = fillImage(parsed, endAddress - flashOffset, flashOffset); + const flash = Flash.fillImage(parsed, endAddress - flashOffset, flashOffset); expect(isValidFlash('#BLHELI$EFM8B21#', flash)).toBeTruthy(); }); @@ -62,11 +56,7 @@ test('retry failing each time', async() => { reject(new Error('Fail')); } - try { - await retry(test, 5, 10); - } catch(e) { - expect(); - } + await expect(retry(test, 5, 10)).rejects.toThrow(); }); test('retry failing each time without delay', async() => { @@ -74,15 +64,11 @@ test('retry failing each time without delay', async() => { reject(new Error('Fail')); } - try { - await retry(test, 5); - } catch(e) { - expect(); - } + await expect(retry(test, 5)).rejects.toThrow(); }); test('retry succeeds at first try', async() => { - function test(resolve, reject) { + function test(resolve) { resolve(true); } diff --git a/src/utils/helpers/__tests__/QueueProcessor.test.js b/src/utils/helpers/__tests__/QueueProcessor.test.js index 0f37a227f..f69e76fbd 100644 --- a/src/utils/helpers/__tests__/QueueProcessor.test.js +++ b/src/utils/helpers/__tests__/QueueProcessor.test.js @@ -1,6 +1,3 @@ -import fs from 'fs'; -import path from 'path'; - import { NotEnoughDataError, TimeoutError, @@ -17,11 +14,7 @@ test('command times out', async() => { qp.addData(new Uint8Array([1, 2, 3])); }; - try { - const result = await qp.addCommand(transmit, receive); - } catch(e) { - expect(e).not.toBeNull(); - } + await expect(qp.addCommand(transmit, receive)).rejects.toThrow(TimeoutError); }); test('command times out while processing', async() => { @@ -39,11 +32,7 @@ test('command times out while processing', async() => { }, 2000); }; - try { - const result = await qp.addCommand(transmit, command); - } catch(e) { - expect(e).not.toBeNull(); - } + await expect(qp.addCommand(transmit, command)).rejects.toThrow(TimeoutError); }); test('command fails', async() => { @@ -58,16 +47,12 @@ test('command fails', async() => { }, 200); }; - try { - const result = await qp.addCommand(transmit, command); - } catch(e) { - expect(e).not.toBeNull(); - } + await expect(qp.addCommand(transmit, command)).rejects.toThrow(Error); }); test('command resolves', async() => { const qp = new QueueProcessor(); - const command = (buffer, resolve, reject) => { + const command = (buffer, resolve) => { resolve(true); }; @@ -96,7 +81,7 @@ test('command no receive handler', async() => { test('command has Data instantly', async() => { const qp = new QueueProcessor(); - const command = (buffer, resolve, reject) => { + const command = (buffer, resolve) => { resolve(true); }; diff --git a/src/utils/helpers/__tests__/React.test.jsx b/src/utils/helpers/__tests__/React.test.jsx new file mode 100644 index 000000000..a940177f6 --- /dev/null +++ b/src/utils/helpers/__tests__/React.test.jsx @@ -0,0 +1,57 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import { render } from '@testing-library/react'; +import { useInterval } from '../React'; + +function TestComponent({ + callback, + timeout, +}) { + useInterval(() => { + callback(); + }, timeout); + + return ( +
+ Test +
+ ); +} +TestComponent.propTypes = { + callback: PropTypes.func.isRequired, + timeout: PropTypes.number.isRequired, +}; + +test('useInterval with proper delay', async () => { + const callback = jest.fn(); + + render( + + ); + + await new Promise((r) => { + setTimeout(r, 500); + }); + + expect(callback).toHaveBeenCalled(); +}); + +test('useInterval without delay', async () => { + const callback = jest.fn(); + + render( + + ); + + await new Promise((r) => { + setTimeout(r, 500); + }); + + expect(callback).toHaveBeenCalled(); +}); diff --git a/src/utils/helpers/__tests__/Settings.test.js b/src/utils/helpers/__tests__/Settings.test.js index 5bb2440ed..991593895 100644 --- a/src/utils/helpers/__tests__/Settings.test.js +++ b/src/utils/helpers/__tests__/Settings.test.js @@ -1,6 +1,3 @@ -import fs from 'fs'; -import path from 'path'; - import escs from './escs.json'; import bluejaySource from '../../../sources/Bluejay'; @@ -92,3 +89,125 @@ test('can migrate from different platforms', () => { expect(result).not.toBeTruthy(); }); + +test('no multi valid', () => { + const settingsDescriptions = { + 1: { + base: [ + { name: 'MOTOR_DIRECTION' }, + ], + }, + 2: { + base: [ + { name: 'MOTOR_DIRECTION' }, + ], + }, + }; + + const individualSettingsDescriptions = { + 1: {}, + 2: {}, + }; + + const from = { + MODE: 1, + LAYOUT_REVISION: 1, + }; + + const to = { + MODE: 1, + LAYOUT_REVISION: 2, + }; + + const result = canMigrate('MOTOR_DIRECTION', from, to, settingsDescriptions, individualSettingsDescriptions); + expect(result).toBeTruthy(); +}); + +test('no multi invalid', () => { + const settingsDescriptions = { + 1: { + base: [ + { name: 'MOTOR_DIRECTION' }, + ], + }, + 2: {}, + }; + + const individualSettingsDescriptions = { + 1: {}, + 2: {}, + }; + + const from = { + MODE: 1, + LAYOUT_REVISION: 1, + }; + + const to = { + MODE: 1, + LAYOUT_REVISION: 2, + }; + + const result = canMigrate('MOTOR_DIRECTION', from, to, settingsDescriptions, individualSettingsDescriptions); + expect(result).toBeFalsy(); +}); + +test('individual settings valid', () => { + const settingsDescriptions = { + 1: { base: [] }, + 2: { base: [] }, + }; + + const individualSettingsDescriptions = { + 1: { + base: [ + { name: 'MOTOR_DIRECTION' }, + ], + }, + 2: { + base: [ + { name: 'MOTOR_DIRECTION' }, + ], + }, + }; + const from = { + MODE: 1, + LAYOUT_REVISION: 1, + }; + + const to = { + MODE: 1, + LAYOUT_REVISION: 2, + }; + + const result = canMigrate('MOTOR_DIRECTION', from, to, settingsDescriptions, individualSettingsDescriptions); + expect(result).toBeTruthy(); +}); + +test('individual settings invalid', () => { + const settingsDescriptions = { + 1: { base: [] }, + 2: { base: [] }, + }; + + const individualSettingsDescriptions = { + 1: { + base: [ + { name: 'MOTOR_DIRECTION' }, + ], + }, + 2: { base: [] }, + }; + const from = { + MODE: 1, + LAYOUT_REVISION: 1, + }; + + const to = { + MODE: 1, + LAYOUT_REVISION: 2, + }; + + const result = canMigrate('MOTOR_DIRECTION', from, to, settingsDescriptions, individualSettingsDescriptions); + expect(result).toBeFalsy(); +}); diff --git a/yarn.lock b/yarn.lock index 08ce64a0d..5df237f5d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1091,6 +1091,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.10.1", "@babel/runtime@^7.11.1": + version "7.13.10" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.10.tgz#47d42a57b6095f4468da440388fdbad8bebf0d7d" + integrity sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4": version "7.12.18" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.18.tgz#af137bd7e7d9705a412b3caaf991fe6aaa97831b" @@ -1656,6 +1663,11 @@ dependencies: "@babel/runtime" "^7.12.5" +"@tootallnate/once@1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" + integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== + "@types/anymatch@*": version "1.3.1" resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a" @@ -2221,6 +2233,18 @@ adjust-sourcemap-loader@3.0.0: loader-utils "^2.0.0" regex-parser "^2.2.11" +agent-base@5: + version "5.1.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c" + integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g== + +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + aggregate-error@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" @@ -2343,6 +2367,11 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +argv@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/argv/-/argv-0.0.2.tgz#ecbd16f8949b157183711b1bda334f37840185ab" + integrity sha1-7L0W+JSbFXGDcRsb2jNPN4QBhas= + aria-query@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b" @@ -3266,6 +3295,11 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" +classnames@2.x, classnames@^2.2.1, classnames@^2.2.5, classnames@^2.2.6: + version "2.3.1" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e" + integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA== + clean-css@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.3.tgz#507b5de7d97b48ee53d84adb0160ff6216380f78" @@ -3315,6 +3349,17 @@ coa@^2.0.2: chalk "^2.4.1" q "^1.1.2" +codecov@^3.8.1: + version "3.8.1" + resolved "https://registry.yarnpkg.com/codecov/-/codecov-3.8.1.tgz#06fe026b75525ed1ce864d4a34f1010c52c51546" + integrity sha512-Qm7ltx1pzLPsliZY81jyaQ80dcNR4/JpcX0IHCIWrHBXgseySqbdbYfkdiXd7o/xmzQpGRVCKGYeTrHUpn6Dcw== + dependencies: + argv "0.0.2" + ignore-walk "3.0.3" + js-yaml "3.14.0" + teeny-request "6.0.1" + urlgrey "0.4.4" + collect-v8-coverage@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" @@ -3400,6 +3445,11 @@ commondir@^1.0.1: resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= +compare-versions@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62" + integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA== + component-emitter@^1.2.1: version "1.3.0" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" @@ -3437,7 +3487,7 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@^1.5.0: +concat-stream@^1.4.7, concat-stream@^1.5.0: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -3628,6 +3678,15 @@ cross-spawn@7.0.3, cross-spawn@^7.0.0, cross-spawn@^7.0.2: shebang-command "^2.0.0" which "^2.0.1" +cross-spawn@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" + integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk= + dependencies: + lru-cache "^4.0.1" + shebang-command "^1.2.0" + which "^1.2.9" + cross-spawn@^6.0.0: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -3920,6 +3979,11 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" +date-fns@^2.19.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.19.0.tgz#65193348635a28d5d916c43ec7ce6fbd145059e1" + integrity sha512-X3bf2iTPgCAQp9wvjOQytnf5vO5rESYRXlPIVcgSbtT5OTScPcsf9eZU+B/YIkKAtYr5WeCii58BgATrNitlWg== + dateformat@^4.5.1: version "4.5.1" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-4.5.1.tgz#c20e7a9ca77d147906b6dc2261a8be0a5bd2173c" @@ -3932,6 +3996,13 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: dependencies: ms "2.0.0" +debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== + dependencies: + ms "2.1.2" + debug@^3.1.1, debug@^3.2.6: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" @@ -3939,13 +4010,6 @@ debug@^3.1.1, debug@^3.2.6: dependencies: ms "^2.1.1" -debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" - integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== - dependencies: - ms "2.1.2" - decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -4147,6 +4211,11 @@ dom-accessibility-api@^0.5.4: resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz#b06d059cdd4a4ad9a79275f9d414a5c126241166" integrity sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ== +dom-align@^1.7.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/dom-align/-/dom-align-1.12.0.tgz#56fb7156df0b91099830364d2d48f88963f5a29c" + integrity sha512-YkoezQuhp3SLFGdOlr5xkqZ640iXrnHAwVYcDg8ZKRUtO7mSzSC2BA5V0VuyAwPSJA4CLIc6EDDJh4bEsD2+zA== + dom-converter@^0.2: version "0.2.0" resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" @@ -4525,6 +4594,13 @@ eslint-plugin-jest@^24.1.0: dependencies: "@typescript-eslint/experimental-utils" "^4.0.1" +eslint-plugin-jest@^24.3.2: + version "24.3.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-24.3.2.tgz#30a8b2dea6278d0da1d6fb9d6cd530aaf58050a1" + integrity sha512-cicWDr+RvTAOKS3Q/k03+Z3odt3VCiWamNUHWd6QWbVQWcYJyYgUTu8x0mx9GfeDEimawU5kQC+nQ3MFxIM6bw== + dependencies: + "@typescript-eslint/experimental-utils" "^4.0.1" + eslint-plugin-jsx-a11y@^6.3.1: version "6.4.1" resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.4.1.tgz#a2d84caa49756942f42f1ffab9002436391718fd" @@ -5666,6 +5742,15 @@ http-parser-js@>=0.5.1: resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.3.tgz#01d2709c79d41698bb01d4decc5e9da4e4a033d9" integrity sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg== +http-proxy-agent@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" + integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== + dependencies: + "@tootallnate/once" "1" + agent-base "6" + debug "4" + http-proxy-middleware@0.19.1: version "0.19.1" resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz#183c7dc4aa1479150306498c210cdaf96080a43a" @@ -5699,6 +5784,14 @@ https-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= +https-proxy-agent@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz#702b71fb5520a132a66de1f67541d9e62154d82b" + integrity sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg== + dependencies: + agent-base "5" + debug "4" + human-signals@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" @@ -5742,6 +5835,13 @@ iferr@^0.1.5: resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= +ignore-walk@3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37" + integrity sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw== + dependencies: + minimatch "^3.0.4" + ignore@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" @@ -6717,6 +6817,14 @@ jest@26.6.0: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== +js-yaml@3.14.0: + version "3.14.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" + integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + js-yaml@^3.13.1: version "3.14.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" @@ -7069,6 +7177,14 @@ lower-case@^2.0.2: dependencies: tslib "^2.0.3" +lru-cache@^4.0.1: + version "4.1.5" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" + integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== + dependencies: + pseudomap "^1.0.2" + yallist "^2.1.2" + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -7477,6 +7593,11 @@ no-case@^3.0.4: lower-case "^2.0.2" tslib "^2.0.3" +node-fetch@^2.2.0: + version "2.6.1" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" + integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== + node-forge@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" @@ -7801,6 +7922,11 @@ os-browserify@^0.3.0: resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= +os-shim@^0.1.2: + version "0.1.3" + resolved "https://registry.yarnpkg.com/os-shim/-/os-shim-0.1.3.tgz#6b62c3791cf7909ea35ed46e17658bb417cb3917" + integrity sha1-a2LDeRz3kJ6jXtRuF2WLtBfLORc= + p-each-series@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.2.0.tgz#105ab0357ce72b202a8a8b94933672657b5e2a9a" @@ -8802,6 +8928,15 @@ postcss@^8.1.0: nanoid "^3.1.20" source-map "^0.6.1" +pre-commit@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/pre-commit/-/pre-commit-1.2.2.tgz#dbcee0ee9de7235e57f79c56d7ce94641a69eec6" + integrity sha1-287g7p3nI15X95xW186UZBpp7sY= + dependencies: + cross-spawn "^5.0.1" + spawn-sync "^1.0.15" + which "1.2.x" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -8897,6 +9032,11 @@ prr@~1.0.1: resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= +pseudomap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" + integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= + psl@^1.1.28: version "1.8.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" @@ -9039,6 +9179,65 @@ raw-body@2.4.0: iconv-lite "0.4.24" unpipe "1.0.0" +rc-align@^4.0.0: + version "4.0.9" + resolved "https://registry.yarnpkg.com/rc-align/-/rc-align-4.0.9.tgz#46d8801c4a139ff6a65ad1674e8efceac98f85f2" + integrity sha512-myAM2R4qoB6LqBul0leaqY8gFaiECDJ3MtQDmzDo9xM9NRT/04TvWOYd2YHU9zvGzqk9QXF6S9/MifzSKDZeMw== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "2.x" + dom-align "^1.7.0" + rc-util "^5.3.0" + resize-observer-polyfill "^1.5.1" + +rc-motion@^2.0.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/rc-motion/-/rc-motion-2.4.1.tgz#323f47c8635e6b2bc0cba2dfad25fc415b58e1dc" + integrity sha512-TWLvymfMu8SngPx5MDH8dQ0D2RYbluNTfam4hY/dNNx9RQ3WtGuZ/GXHi2ymLMzH+UNd6EEFYkOuR5JTTtm8Xg== + dependencies: + "@babel/runtime" "^7.11.1" + classnames "^2.2.1" + rc-util "^5.2.1" + +rc-slider@^9.7.2: + version "9.7.2" + resolved "https://registry.yarnpkg.com/rc-slider/-/rc-slider-9.7.2.tgz#282f571f7582752ebaa33964e441184f4e79ad74" + integrity sha512-mVaLRpDo6otasBs6yVnG02ykI3K6hIrLTNfT5eyaqduFv95UODI9PDS6fWuVVehVpdS4ENgOSwsTjrPVun+k9g== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "^2.2.5" + rc-tooltip "^5.0.1" + rc-util "^5.0.0" + shallowequal "^1.1.0" + +rc-tooltip@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/rc-tooltip/-/rc-tooltip-5.1.0.tgz#abb453c463c31a705aa01d268279f4ae6ae3b15f" + integrity sha512-pFqD1JZwNIpbdcefB7k5xREoHAWM/k3yQwYF0iminbmDXERgq4rvBfUwIvlCqqZSM7HDr9hYeYr6ZsVNaKtvCQ== + dependencies: + "@babel/runtime" "^7.11.2" + rc-trigger "^5.0.0" + +rc-trigger@^5.0.0: + version "5.2.3" + resolved "https://registry.yarnpkg.com/rc-trigger/-/rc-trigger-5.2.3.tgz#8c55046ab432d7b52d51c69afb57ebb5bbe37e17" + integrity sha512-6Fokao07HUbqKIDkDRFEM0AGZvsvK0Fbp8A/KFgl1ngaqfO1nY037cISCG1Jm5fxImVsXp9awdkP7Vu5cxjjog== + dependencies: + "@babel/runtime" "^7.11.2" + classnames "^2.2.6" + rc-align "^4.0.0" + rc-motion "^2.0.0" + rc-util "^5.5.0" + +rc-util@^5.0.0, rc-util@^5.2.1, rc-util@^5.3.0, rc-util@^5.5.0: + version "5.9.8" + resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.9.8.tgz#dfcacc1f7b7c45fa18ab786e2b530dd0509073f1" + integrity sha512-typLSHYGf5irvGLYQshs0Ra3aze086h0FhzsAkyirMunYZ7b3Te8gKa5PVaanoHaZa9sS6qx98BxgysoRP+6Tw== + dependencies: + "@babel/runtime" "^7.12.5" + react-is "^16.12.0" + shallowequal "^1.1.0" + react-app-polyfill@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/react-app-polyfill/-/react-app-polyfill-2.0.0.tgz#a0bea50f078b8a082970a9d853dc34b6dcc6a3cf" @@ -9125,7 +9324,7 @@ react-input-range@^1.3.0: autobind-decorator "^1.3.4" prop-types "^15.5.8" -react-is@^16.7.0, react-is@^16.8.1: +react-is@^16.12.0, react-is@^16.7.0, react-is@^16.8.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -9490,6 +9689,11 @@ requires-port@^1.0.0: resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= +resize-observer-polyfill@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" + integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg== + resolve-cwd@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" @@ -9918,6 +10122,11 @@ sha.js@^2.4.0, sha.js@^2.4.8: inherits "^2.0.1" safe-buffer "^5.0.1" +shallowequal@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" + integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== + shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -10114,6 +10323,14 @@ sourcemap-codec@^1.4.4: resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== +spawn-sync@^1.0.15: + version "1.0.15" + resolved "https://registry.yarnpkg.com/spawn-sync/-/spawn-sync-1.0.15.tgz#b00799557eb7fb0c8376c29d44e8a1ea67e57476" + integrity sha1-sAeZVX63+wyDdsKdROih6mfldHY= + dependencies: + concat-stream "^1.4.7" + os-shim "^0.1.2" + spdx-correct@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" @@ -10255,6 +10472,13 @@ stream-each@^1.1.0: end-of-stream "^1.1.0" stream-shift "^1.0.0" +stream-events@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/stream-events/-/stream-events-1.0.5.tgz#bbc898ec4df33a4902d892333d47da9bf1c406d5" + integrity sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg== + dependencies: + stubs "^3.0.0" + stream-http@^2.7.2: version "2.8.3" resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" @@ -10420,6 +10644,11 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +stubs@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/stubs/-/stubs-3.0.0.tgz#e8d2ba1fa9c90570303c030b6900f7d5f89abe5b" + integrity sha1-6NK6H6nJBXAwPAMLaQD31fiavls= + style-loader@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.3.0.tgz#828b4a3b3b7e7aa5847ce7bae9e874512114249e" @@ -10522,6 +10751,17 @@ tar@^6.0.2: mkdirp "^1.0.3" yallist "^4.0.0" +teeny-request@6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/teeny-request/-/teeny-request-6.0.1.tgz#9b1f512cef152945827ba7e34f62523a4ce2c5b0" + integrity sha512-TAK0c9a00ELOqLrZ49cFxvPVogMUFaWY8dUsQc/0CuQPGF+BOxOQzXfE413BAk2kLomwNplvdtMpeaeGWmoc2g== + dependencies: + http-proxy-agent "^4.0.0" + https-proxy-agent "^4.0.0" + node-fetch "^2.2.0" + stream-events "^1.0.5" + uuid "^3.3.2" + temp-dir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" @@ -10977,6 +11217,11 @@ url@^0.11.0: punycode "1.3.2" querystring "0.2.0" +urlgrey@0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/urlgrey/-/urlgrey-0.4.4.tgz#892fe95960805e85519f1cd4389f2cb4cbb7652f" + integrity sha1-iS/pWWCAXoVRnxzUOJ8stMu3ZS8= + use@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" @@ -11310,6 +11555,13 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= +which@1.2.x: + version "1.2.14" + resolved "https://registry.yarnpkg.com/which/-/which-1.2.14.tgz#9a87c4378f03e827cecaf1acdf56c736c01c14e5" + integrity sha1-mofEN48D6CfOyvGs31bHNsAcFOU= + dependencies: + isexe "^2.0.0" + which@^1.2.9, which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" @@ -11561,6 +11813,11 @@ y18n@^4.0.0: resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4" integrity sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ== +yallist@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" + integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= + yallist@^3.0.2: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"