зеркало из https://github.com/microsoft/jacdac-ts.git
Eslint (#164)
* linting and prettiyin * configure eslint * removing lint staged * linted bus * udpated spec * linnting * more linting * disble tslint * linting * more linting * more linting * more linting * configure prettier * remove duplicated settings files * linting * more linting * linting * lint * more linting * add lint in readme * fix build * non-quiet * linting * address codeql warnings
This commit is contained in:
Родитель
dd0298a3b3
Коммит
3a6b9176e3
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
},
|
||||
"ecmaVersion": 12,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"react",
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"react/display-name": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off"
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ on:
|
|||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
|
@ -22,11 +22,6 @@ jobs:
|
|||
# a pull request then we can checkout the head.
|
||||
fetch-depth: 2
|
||||
|
||||
# If this run was triggered by a pull request event, then checkout
|
||||
# the head of the pull request instead of the merge commit.
|
||||
- run: git checkout HEAD^2
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
|
|
|
@ -2,7 +2,6 @@ node_modules
|
|||
.nyc_output
|
||||
.DS_Store
|
||||
*.log
|
||||
.vscode
|
||||
.idea
|
||||
compiled
|
||||
.awcache
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"arrowParens": "avoid",
|
||||
"semi": false,
|
||||
"tabWidth": 4
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"tslint.enable": false,
|
||||
"eslint.alwaysShowStatus": true,
|
||||
"eslint.format.enable": false,
|
||||
"eslint.debug": true,
|
||||
"eslint.lintTask.enable": true,
|
||||
}
|
|
@ -73,6 +73,14 @@ We use [Mocha](https://mochajs.org/) to run the unit test suite from ``/tests``.
|
|||
yarn test
|
||||
```
|
||||
|
||||
## Linting
|
||||
|
||||
Run the following command to detect linting issues
|
||||
|
||||
```
|
||||
yarn lint
|
||||
```
|
||||
|
||||
### Docs build
|
||||
|
||||
Launch the gatsbdy develop mode and navigate to http://localhost:8000 . This build does not require to load dist as the library is compiled directly into the web site.
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"arrowParens": "avoid",
|
||||
"semi": false
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -16,7 +16,11 @@
|
|||
"@microsoft/applicationinsights-web-basic": "^2.5.11",
|
||||
"@octokit/core": "^3.2.5",
|
||||
"@react-three/drei": "^3.8.6",
|
||||
"@typescript-eslint/eslint-plugin": "^4.15.2",
|
||||
"@typescript-eslint/parser": "^4.15.2",
|
||||
"compare-versions": "^3.6.0",
|
||||
"eslint": "^7.20.0",
|
||||
"eslint-plugin-react": "^7.22.0",
|
||||
"gatsby": "^2.32.3",
|
||||
"gatsby-image": "^2.11.0",
|
||||
"gatsby-plugin-manifest": "^2.12.0",
|
||||
|
@ -50,6 +54,7 @@
|
|||
"material-ui-dropzone": "^3.5.0",
|
||||
"notistack": "^1.0.3",
|
||||
"octokit-plugin-create-pull-request": "^3.9.3",
|
||||
"prettier": "2.2.1",
|
||||
"prism-react-renderer": "^1.2.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^17.0.1",
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React, { useMemo } from 'react';
|
||||
import { Menu, MenuItem, Typography } from '@material-ui/core';
|
||||
import { SRV_CTRL } from '../../../src/jdom/constants';
|
||||
import { isInfrastructure, serviceSpecificationFromClassIdentifier, serviceSpecifications } from '../../../src/jdom/spec';
|
||||
// tslint:disable-next-line: match-default-export-name no-submodule-imports
|
||||
import AddIcon from '@material-ui/icons/Add';
|
||||
|
|
|
@ -3,7 +3,7 @@ import useDbValue from "./useDbValue";
|
|||
import useEffectAsync from "./useEffectAsync";
|
||||
import Alert from "./ui/Alert"
|
||||
import { AccordionActions, AccordionSummary, AccordionDetails, Accordion, Typography, TextField, Box } from '@material-ui/core';
|
||||
import { Button, Link } from "gatsby-theme-material-ui";
|
||||
import { Button } from "gatsby-theme-material-ui";
|
||||
// tslint:disable-next-line: match-default-export-name no-submodule-imports
|
||||
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
|
||||
// tslint:disable-next-line: match-default-export-name no-submodule-imports
|
||||
|
|
|
@ -23,6 +23,7 @@ export interface AppProps {
|
|||
setSearchQuery: (s: string) => void,
|
||||
toolsMenu: boolean,
|
||||
setToolsMenu: (visible: boolean) => void,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
setError: (error: any) => void,
|
||||
widgetMode: boolean,
|
||||
showDeviceHostsDialog: boolean,
|
||||
|
@ -32,21 +33,22 @@ export interface AppProps {
|
|||
|
||||
const AppContext = createContext<AppProps>({
|
||||
drawerType: DrawerType.None,
|
||||
setDrawerType: (type) => { },
|
||||
setDrawerType: () => { },
|
||||
searchQuery: undefined,
|
||||
setSearchQuery: (s) => { },
|
||||
setSearchQuery: () => { },
|
||||
toolsMenu: false,
|
||||
setToolsMenu: (v) => { },
|
||||
setError: (error: any) => { },
|
||||
setToolsMenu: () => { },
|
||||
setError: () => { },
|
||||
widgetMode: false,
|
||||
showDeviceHostsDialog: false,
|
||||
toggleShowDeviceHostsDialog: () => { },
|
||||
showRenameDeviceDialog: (device) => { }
|
||||
showRenameDeviceDialog: () => { }
|
||||
});
|
||||
AppContext.displayName = "app";
|
||||
|
||||
export default AppContext;
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
export const AppProvider = ({ children }) => {
|
||||
const { bus } = useContext<JacdacContextProps>(JacdacContext)
|
||||
const [type, setType] = useState(DrawerType.None)
|
||||
|
@ -90,7 +92,7 @@ export const AppProvider = ({ children }) => {
|
|||
useEffect(() => bus.subscribe(CONNECTION_STATE, cs => {
|
||||
switch (cs) {
|
||||
case BusState.Connected:
|
||||
if (!!bus.transport)
|
||||
if (bus.transport)
|
||||
enqueueSnackbar("connected...", {
|
||||
variant: "info"
|
||||
})
|
||||
|
|
|
@ -15,7 +15,6 @@ import PacketRecorder from "./PacketRecorder";
|
|||
import DrawerSearchInput from "./DrawerSearchInput";
|
||||
import DrawerSearchResults from "./DrawerSearchResults";
|
||||
import DrawerToolsButtonGroup from "./DrawerToolsButtonGroup";
|
||||
import PacketStats from "./PacketStats";
|
||||
|
||||
const useStyles = makeStyles((theme) => createStyles({
|
||||
drawer: {
|
||||
|
|
|
@ -87,9 +87,8 @@ export default function CmdButton(props: {
|
|||
}
|
||||
}
|
||||
finally {
|
||||
if (!mounted())
|
||||
return;
|
||||
setWorking(false)
|
||||
if (mounted())
|
||||
setWorking(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -139,8 +139,8 @@ export class Db extends JDEventSource {
|
|||
try {
|
||||
const transaction = this.db.transaction([table], "readwrite");
|
||||
const blobs = transaction.objectStore(table)
|
||||
const request = data !== undefined ? blobs.put(data, id) : blobs.delete(id);;
|
||||
request.onsuccess = (event) => {
|
||||
const request = data !== undefined ? blobs.put(data, id) : blobs.delete(id);
|
||||
request.onsuccess = () => {
|
||||
this.emit(CHANGE)
|
||||
resolve()
|
||||
}
|
||||
|
@ -191,6 +191,7 @@ DbContext.displayName = "db";
|
|||
|
||||
export default DbContext;
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
export const DbProvider = ({ children }) => {
|
||||
const [db, setDb] = useState<Db>(undefined)
|
||||
const [error, setError] = useState(undefined)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect, useState, Fragment } from 'react';
|
||||
import React from 'react';
|
||||
// tslint:disable-next-line: no-submodule-imports
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
// tslint:disable-next-line: no-submodule-imports
|
||||
|
@ -11,12 +11,12 @@ import ServiceButton from './ServiceButton';
|
|||
import useChange from '../jacdac/useChange';
|
||||
import { navigate } from "gatsby";
|
||||
import { JDService } from '../../../src/jdom/service';
|
||||
import { CardActions, createStyles, Theme } from '@material-ui/core';
|
||||
import { CardActions, createStyles } from '@material-ui/core';
|
||||
import DeviceCardHeader from './DeviceCardHeader';
|
||||
import { useRegisterStringValue } from '../jacdac/useRegisterValue';
|
||||
import { DeviceLostAlert } from './alert/DeviceLostAlert';
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => createStyles({
|
||||
const useStyles = makeStyles(() => createStyles({
|
||||
root: {
|
||||
},
|
||||
bullet: {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Typography, useTheme } from "@material-ui/core";
|
||||
import { Typography } from "@material-ui/core";
|
||||
import React from "react"
|
||||
import { JDDevice } from "../../../src/jdom/device";
|
||||
import useDeviceName from "./useDeviceName";
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useMemo, useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
// tslint:disable-next-line: no-submodule-imports
|
||||
import Tabs from '@material-ui/core/Tabs';
|
||||
// tslint:disable-next-line: no-submodule-imports
|
||||
|
@ -34,7 +34,7 @@ export default function DeviceSpecificationSource(props: {
|
|||
const [tab, setTab] = useState(0);
|
||||
const spec = deviceSpecification
|
||||
|
||||
const handleTabChange = (event: React.ChangeEvent<{}>, newValue: number) => {
|
||||
const handleTabChange = (event: React.ChangeEvent<unknown>, newValue: number) => {
|
||||
setTab(newValue);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,13 +1,5 @@
|
|||
import { Link } from "gatsby-theme-material-ui";
|
||||
import React from "react";
|
||||
// tslint:disable-next-line: no-submodule-imports
|
||||
import TreeView from '@material-ui/lab/TreeView';
|
||||
// tslint:disable-next-line: no-submodule-imports match-default-export-name
|
||||
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
|
||||
// tslint:disable-next-line: no-submodule-imports match-default-export-name
|
||||
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
|
||||
// tslint:disable-next-line: no-submodule-imports
|
||||
import TreeItem from '@material-ui/lab/TreeItem';
|
||||
import Alert from "./ui/Alert"
|
||||
|
||||
import { useDrawerSearchResults } from "./useDrawerSearchResults";
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { Badge } from "@material-ui/core";
|
||||
import React, { useContext, useEffect, useState } from "react";
|
||||
import React, { useContext } from "react";
|
||||
import AppContext, { DrawerType } from "./AppContext";
|
||||
// tslint:disable-next-line: no-submodule-imports match-default-export-name
|
||||
import HistoryIcon from '@material-ui/icons/History';
|
||||
|
@ -9,12 +8,9 @@ import MenuIcon from '@material-ui/icons/Menu';
|
|||
import AccountTreeIcon from '@material-ui/icons/AccountTree';
|
||||
import IconButtonWithTooltip from "./ui/IconButtonWithTooltip";
|
||||
import ConnectButton from "../jacdac/ConnectButton";
|
||||
import JacdacContext, { JacdacContextProps } from "../jacdac/Context";
|
||||
import { DEVICE_CHANGE } from "../../../src/jdom/constants";
|
||||
import WebUSBSupported from "./WebUSBSupported";
|
||||
|
||||
export default function DrawerToolsButtonGroup(props: { className?: string, showToc?: boolean, showCurrent?: boolean, showConnect?: boolean }) {
|
||||
const { bus } = useContext<JacdacContextProps>(JacdacContext)
|
||||
const { className, showToc, showCurrent, showConnect } = props;
|
||||
const { drawerType, setDrawerType } = useContext(AppContext)
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import React from "react";
|
||||
import { JDEvent } from "../../../src/jdom/event";
|
||||
import { Typography, Badge } from "@material-ui/core";
|
||||
import KindIcon from "./KindIcon";
|
||||
|
|
|
@ -134,10 +134,10 @@ export default class FieldDataSet extends JDEventSource {
|
|||
this.emit(CHANGE);
|
||||
}
|
||||
|
||||
toCSV(sep: string = ",", options?: { units?: boolean }) {
|
||||
toCSV(sep = ",", options?: { units?: boolean }) {
|
||||
const allheaders = ["time", ...this.headers].join(sep)
|
||||
const start = this.startTimestamp
|
||||
let csv: string[] = [allheaders]
|
||||
const csv: string[] = [allheaders]
|
||||
if (options?.units)
|
||||
csv.push(["ms", ...this.units].join(sep))
|
||||
this.rows.forEach(row => csv.push(
|
||||
|
|
|
@ -7,7 +7,6 @@ import { DEVICE_CHANGE, DEVICE_FIRMWARE_INFO, FIRMWARE_BLOBS_CHANGE } from "../.
|
|||
import useEventRaised from "../jacdac/useEventRaised";
|
||||
import { computeUpdates } from "../../../src/jdom/flashing";
|
||||
import IconButtonWithTooltip from "./ui/IconButtonWithTooltip";
|
||||
import useDevices from "./hooks/useDevices";
|
||||
|
||||
export default function FlashButton(props: { className?: string }) {
|
||||
const { bus } = useContext<JacdacContextProps>(JacdacContext)
|
||||
|
|
|
@ -7,7 +7,6 @@ import AppContext from "./AppContext";
|
|||
import { GITHUB_API_KEY } from "./github";
|
||||
import useDbValue from "./useDbValue";
|
||||
import { useSnackbar } from "notistack";
|
||||
import Alert from "./ui/Alert";
|
||||
import GitHubIcon from '@material-ui/icons/GitHub';
|
||||
import ApiKeyAccordion from "./ApiKeyAccordion";
|
||||
|
||||
|
@ -21,7 +20,7 @@ export default function GithubPullRequestButton(props: {
|
|||
}) {
|
||||
const { commit, files, label, title, body, head } = props;
|
||||
const { value: token } = useDbValue(GITHUB_API_KEY, "")
|
||||
const [response, setResponse] = useState(undefined);
|
||||
const [, setResponse] = useState(undefined);
|
||||
const [busy, setBusy] = useState(false)
|
||||
const { setError: setAppError } = useContext(AppContext)
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { } from 'react';
|
||||
// tslint:disable-next-line: no-submodule-imports
|
||||
import { Box, Card, CardActions, CardContent, CardHeader, CircularProgress, Typography } from '@material-ui/core';
|
||||
import { Box, CardHeader, Typography } from '@material-ui/core';
|
||||
import { useLatestRelease, useRepository } from './github';
|
||||
import GitHubIcon from '@material-ui/icons/GitHub';
|
||||
import { Link } from 'gatsby-theme-material-ui';
|
||||
|
@ -12,7 +12,7 @@ export default function GithubRepositoryCardHeader(props: {
|
|||
}) {
|
||||
const { slug, showRelease } = props;
|
||||
const { response: repo, loading: repoLoading } = useRepository(slug);
|
||||
const { response: release, loading: releaseLoading } = useLatestRelease(showRelease && slug);
|
||||
const { response: release } = useLatestRelease(showRelease && slug);
|
||||
const iframe = inIFrame();
|
||||
const target = iframe ? "_blank" : ""
|
||||
|
||||
|
|
|
@ -84,7 +84,7 @@ function DeviceTreeItem(props: { device: JDDevice } & StyledTreeViewItemProps &
|
|||
}
|
||||
|
||||
function ServiceTreeItem(props: { service: JDService } & StyledTreeViewItemProps & JDomTreeViewProps) {
|
||||
const { service, checked, setChecked, checkboxes, dashboard, registerFilter, eventFilter, ...other } = props;
|
||||
const { service, checked, setChecked, checkboxes, registerFilter, eventFilter, ...other } = props;
|
||||
const specification = service.specification;
|
||||
const showSpecificationAction = false;
|
||||
const id = service.id
|
||||
|
@ -226,12 +226,12 @@ export default function JDomTreeView(props: JDomTreeViewProps) {
|
|||
const { bus } = useContext<JacdacContextProps>(JacdacContext)
|
||||
const devices = useChange(bus, () => bus.devices().filter(dev => !deviceFilter || deviceFilter(dev)))
|
||||
|
||||
const handleToggle = (event: React.ChangeEvent<{}>, nodeIds: string[]) => {
|
||||
const handleToggle = (event: React.ChangeEvent<unknown>, nodeIds: string[]) => {
|
||||
setExpanded(nodeIds);
|
||||
if (onToggle) onToggle(nodeIds)
|
||||
};
|
||||
|
||||
const handleSelect = (event: React.ChangeEvent<{}>, nodeIds: string[]) => {
|
||||
const handleSelect = (event: React.ChangeEvent<unknown>, nodeIds: string[]) => {
|
||||
setSelected(nodeIds);
|
||||
if (onSelect) onSelect(nodeIds)
|
||||
};
|
||||
|
|
|
@ -47,16 +47,17 @@ const PacketsContext = createContext<PacketsProps>({
|
|||
tracing: false,
|
||||
toggleTracing: () => { },
|
||||
paused: false,
|
||||
setPaused: (p) => { },
|
||||
setPaused: () => { },
|
||||
progress: undefined,
|
||||
timeRange: undefined,
|
||||
toggleTimeRange: () => { },
|
||||
setTimeRange: (range) => { }
|
||||
setTimeRange: () => { }
|
||||
});
|
||||
PacketsContext.displayName = "packets";
|
||||
|
||||
export default PacketsContext;
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
export const PacketsProvider = ({ children }) => {
|
||||
const { bus, disconnectAsync } = useContext<JacdacContextProps>(JacdacContext)
|
||||
const { value: filter, setValue: _setFilter } = useDbValue("packetfilter", "repeated-announce:false")
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React, { Fragment } from "react";
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
const Page = ({ props, children }) => {
|
||||
return <Fragment {...props}>{children}</Fragment>
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { createContext, useContext, useEffect, useMemo, useRef } from "react";
|
||||
import React, { createContext, useContext, useEffect, useRef } from "react";
|
||||
import { JSONTryParse, SMap } from "../../../src/jdom/utils";
|
||||
import { BrowserFileStorage, HostedFileStorage, IFileStorage } from '../../../src/embed/filestorage'
|
||||
import { IThemeMessage } from "../../../src/embed/protocol";
|
||||
|
@ -64,11 +64,15 @@ const ServiceManagerContext = createContext<ServiceManagerContextProps>({
|
|||
});
|
||||
ServiceManagerContext.displayName = "Services";
|
||||
|
||||
export default ServiceManagerContext;
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
export const ServiceManagerProvider = ({ children }) => {
|
||||
const { toggleDarkMode } = useContext(DarkModeContext)
|
||||
const { bus } = useContext<JacdacContextProps>(JacdacContext)
|
||||
const props = useRef<ServiceManagerContextProps>(createProps())
|
||||
const propsRef = useRef<ServiceManagerContextProps>(createProps())
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const handleMessage = (ev: MessageEvent<any>) => {
|
||||
const msg = ev.data;
|
||||
if (msg?.source !== 'jacdac')
|
||||
|
@ -91,14 +95,14 @@ export const ServiceManagerProvider = ({ children }) => {
|
|||
return () => { };
|
||||
}, [])
|
||||
|
||||
return <ServiceManagerContext.Provider value={props.current}>
|
||||
return <ServiceManagerContext.Provider value={propsRef.current}>
|
||||
{children}
|
||||
</ServiceManagerContext.Provider>
|
||||
|
||||
function createProps(): ServiceManagerContextProps {
|
||||
const isHosted = inIFrame();
|
||||
let fileStorage: IFileStorage = new BrowserFileStorage()
|
||||
let deviceNames = new LocalStorageDeviceNameSettings(
|
||||
const deviceNames = new LocalStorageDeviceNameSettings(
|
||||
bus,
|
||||
new LocalStorageSettings("jacdac_device_names")
|
||||
);
|
||||
|
@ -120,5 +124,3 @@ export const ServiceManagerProvider = ({ children }) => {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ServiceManagerContext;
|
|
@ -15,7 +15,7 @@ export default function ServiceRegisters(props: {
|
|||
hideMissingValues?: boolean,
|
||||
showTrends?: boolean
|
||||
}) {
|
||||
const { service, registerIdentifiers, filter, showRegisterName, hideMissingValues, showTrends, expanded } = props;
|
||||
const { service, registerIdentifiers, filter, showRegisterName, hideMissingValues, showTrends } = props;
|
||||
const specification = useChange(service, spec => spec.specification);
|
||||
const registers = useMemo(() => {
|
||||
const packets = specification?.packets;
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
import React, { useState } from 'react';
|
||||
// tslint:disable-next-line: no-submodule-imports
|
||||
import Tabs from '@material-ui/core/Tabs';
|
||||
// tslint:disable-next-line: no-submodule-imports
|
||||
import Tab from '@material-ui/core/Tab';
|
||||
import { Tabs, Tab } from '@material-ui/core';
|
||||
import { serviceSpecificationFromClassIdentifier } from '../../../src/jdom/spec';
|
||||
import { Paper, createStyles, makeStyles, Theme } from '@material-ui/core';
|
||||
import TabPanel, { a11yProps } from './ui/TabPanel';
|
||||
|
@ -38,7 +35,7 @@ export default function ServiceSpecificationSource(props: {
|
|||
const convs = converters();
|
||||
const showDTDL = spec?.camelName !== "system"
|
||||
|
||||
const handleTabChange = (event: React.ChangeEvent<{}>, newValue: number) => {
|
||||
const handleTabChange = (event: React.ChangeEvent<unknown>, newValue: number) => {
|
||||
setTab(newValue);
|
||||
};
|
||||
|
||||
|
@ -61,7 +58,7 @@ export default function ServiceSpecificationSource(props: {
|
|||
{showSpecification && <TabPanel key="spec" value={tab} index={index++}>
|
||||
<ServiceSpecification service={spec} />
|
||||
</TabPanel>}
|
||||
{["sts", "ts", "c", "json"].map((lang, i) =>
|
||||
{["sts", "ts", "c", "json"].map((lang) =>
|
||||
<TabPanel key={`conv${lang}`} value={tab} index={index++}>
|
||||
<Snippet value={() => convs[lang](spec)} mode={lang} />
|
||||
</TabPanel>)}
|
||||
|
|
|
@ -4,7 +4,7 @@ import { Link } from 'gatsby-theme-material-ui';
|
|||
import useGridBreakpoints from './useGridBreakpoints';
|
||||
import JacdacContext, { JacdacContextProps } from "../jacdac/Context";
|
||||
import useChange from '../jacdac/useChange';
|
||||
import { Grid, Card, CardHeader, CardActions, Button, createStyles, makeStyles, Paper, Step, StepContent, StepLabel, Stepper, Theme, Typography } from '@material-ui/core';
|
||||
import { Grid, Card, CardActions, Button, createStyles, makeStyles, Paper, Step, StepContent, StepLabel, Stepper, Theme, Typography } from '@material-ui/core';
|
||||
// tslint:disable-next-line: no-submodule-imports
|
||||
import Alert from "./ui/Alert";
|
||||
import DeviceCardHeader from "./DeviceCardHeader"
|
||||
|
@ -101,8 +101,8 @@ export default function ServiceTest(props: { serviceSpec: jdspec.ServiceSpec })
|
|||
<StepLabel>Select a service to test</StepLabel>
|
||||
<StepContent>
|
||||
{!!services.length && <Grid container spacing={2}>
|
||||
{services.map(service => <Grid item {...gridBreakpoints}>
|
||||
<Card key={service.id}>
|
||||
{services.map(service => <Grid item key={service.id} {...gridBreakpoints}>
|
||||
<Card >
|
||||
<DeviceCardHeader device={service.device} />
|
||||
<CardActions>
|
||||
<Button variant="contained" color="primary" onClick={handleSelect(service)}>Select</Button>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useContext, useMemo } from "react"
|
||||
import { makeStyles, createStyles, Theme, List, ListItem, Typography, useTheme, Box, useMediaQuery } from '@material-ui/core';
|
||||
import { makeStyles, createStyles, Theme, List, ListItem, Typography, useTheme, Box } from '@material-ui/core';
|
||||
import { Link } from 'gatsby-theme-material-ui';
|
||||
// tslint:disable-next-line: no-submodule-imports
|
||||
import ListItemText from '@material-ui/core/ListItemText';
|
||||
|
@ -71,7 +71,6 @@ export default function Toc(props: { pagePath: string }) {
|
|||
const { setDrawerType } = useContext(AppContext)
|
||||
const theme = useTheme();
|
||||
const classes = useStyles();
|
||||
const mobile = useMediaQuery(theme.breakpoints.down("lg"));
|
||||
const data = useStaticQuery(graphql`
|
||||
query {
|
||||
site {
|
||||
|
@ -127,7 +126,7 @@ export default function Toc(props: { pagePath: string }) {
|
|||
|
||||
const tree = useMemo(() => {
|
||||
// convert pages into tree
|
||||
let toc: TocNode[] = [{
|
||||
const toc: TocNode[] = [{
|
||||
name: "Home",
|
||||
path: "/",
|
||||
order: 0
|
||||
|
@ -189,7 +188,7 @@ export default function Toc(props: { pagePath: string }) {
|
|||
return tree;
|
||||
}, []);
|
||||
|
||||
const TocListItem = (props: { entry: TocNode, level: number }) => {
|
||||
function TocListItem(props: { entry: TocNode, level: number }) {
|
||||
const { entry, level } = props;
|
||||
const { path, children, name } = entry;
|
||||
const selected = pagePath === path;
|
||||
|
@ -211,6 +210,6 @@ export default function Toc(props: { pagePath: string }) {
|
|||
}
|
||||
|
||||
return <List dense className={classes.root}>
|
||||
{tree.map(entry => <TocListItem key={'tocitem' + entry.path} entry={entry} level={0} />)}
|
||||
{tree.map((entry, i) => <TocListItem key={i} entry={entry} level={0} />)}
|
||||
</List>
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ function UnitTrendChart(props: {
|
|||
const mint = Math.min.apply(null, times);
|
||||
let minv = unit == "/" ? 0 : Math.min.apply(null, indexes.map(i => dataSet.mins[i]));
|
||||
let maxv = unit == "/" ? 1 : Math.max.apply(null, indexes.map(i => dataSet.maxs[i]));
|
||||
let opposite = unit != "/" && Math.sign(minv) != Math.sign(maxv)
|
||||
const opposite = unit != "/" && Math.sign(minv) != Math.sign(maxv)
|
||||
if (isNaN(minv) && isNaN(maxv)) {
|
||||
minv = 0
|
||||
maxv = 1
|
||||
|
|
|
@ -126,7 +126,7 @@ export default function WebDiagnostics() {
|
|||
setV(v + 1);
|
||||
}
|
||||
|
||||
const handleChange = (panel: string) => (event: React.ChangeEvent<{}>, isExpanded: boolean) => {
|
||||
const handleChange = (panel: string) => (event: React.ChangeEvent<unknown>, isExpanded: boolean) => {
|
||||
setExpanded(isExpanded ? panel : false);
|
||||
};
|
||||
|
||||
|
|
|
@ -30,8 +30,8 @@ function NoSsrConnectAlert(props: { serviceClass?: number }) {
|
|||
connectionState === BusState.Disconnected)
|
||||
return <Box displayPrint="none">
|
||||
<Alert severity="info" closeable={true}>
|
||||
{!spec && <span>Don't forget to connect!</span>}
|
||||
{spec && <span>Don't forget to connect some {spec.name} devices!</span>}
|
||||
{!spec && <span>Did you connect your device?</span>}
|
||||
{spec && <span>Did you connect a {spec.name} device?</span>}
|
||||
<ConnectButton className={classes.button} full={true} transparent={true} />
|
||||
</Alert>
|
||||
</Box>
|
||||
|
|
|
@ -105,18 +105,18 @@ const serviceViews: {
|
|||
},
|
||||
[SRV_CHARACTER_SCREEN]: {
|
||||
component: DashboardCharacterScreen,
|
||||
weight: (srv) => 3
|
||||
weight: () => 3
|
||||
},
|
||||
[SRV_RAIN_GAUGE]: {
|
||||
component: DashboardRainGauge,
|
||||
},
|
||||
[SRV_LED_MATRIX]: {
|
||||
component: DashboardLEDMatrix,
|
||||
weight: (srv) => 3
|
||||
weight: () => 3
|
||||
},
|
||||
[SRV_ARCADE_GAMEPAD]: {
|
||||
component: DashboardArcadeGamepad,
|
||||
weight: (srv) => 3
|
||||
weight: () => 3
|
||||
},
|
||||
[SRV_WIND_DIRECTION]: {
|
||||
component: DashboardWindDirection,
|
||||
|
@ -160,7 +160,7 @@ const serviceViews: {
|
|||
},
|
||||
[SRV_SOUND_PLAYER]: {
|
||||
component: DashboardSoundPlayer,
|
||||
weight: (srv) => 2
|
||||
weight: ( ) => 2
|
||||
},
|
||||
[SRV_ANALOG_BUTTON]: {
|
||||
component: DashboardAnalogButton,
|
||||
|
@ -272,7 +272,7 @@ export default function DashboardServiceWidget(props: React.Attributes & Dashboa
|
|||
const color = host ? "secondary" : "primary";
|
||||
// no special support
|
||||
if (!component)
|
||||
return createElement(DefaultWidget, props);;
|
||||
return createElement(DefaultWidget, props);
|
||||
|
||||
return <NoSsr>
|
||||
<Suspense fallback={<CircularProgress color={color} disableShrink={true} variant={"indeterminate"} size={"3em"} />}>
|
||||
|
|
|
@ -4,9 +4,7 @@ import React, { useContext, useMemo, useState } from "react";
|
|||
import { useId } from "react-use-id-hook";
|
||||
import hosts, { addHost } from "../../../../src/hosts/hosts";
|
||||
import { VIRTUAL_DEVICE_NODE_NAME } from "../../../../src/jdom/constants";
|
||||
import JDDeviceHost from "../../../../src/jdom/devicehost";
|
||||
import Flags from "../../../../src/jdom/flags";
|
||||
import ServiceHost from "../../../../src/jdom/servicehost";
|
||||
import { delay } from "../../../../src/jdom/utils";
|
||||
import JacdacContext, { JacdacContextProps } from "../../jacdac/Context";
|
||||
import KindIcon from "../KindIcon";
|
||||
|
@ -18,7 +16,6 @@ export default function StartSimulatorDialog(props: { open: boolean, onClose: ()
|
|||
const deviceHostDialogId = useId();
|
||||
const deviceHostLabelId = useId();
|
||||
|
||||
const { } = props;
|
||||
const [selected, setSelected] = useState("button");
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const hostDefinitions = useMemo(() => hosts(), []);
|
||||
|
|
|
@ -11,13 +11,14 @@ import GaugeWidget from "../widgets/GaugeWidget";
|
|||
import useWidgetSize from "../widgets/useWidgetSize";
|
||||
import ValueWithUnitWidget from "../widgets/ValueWithUnitWidget";
|
||||
import useUnitIcon from "../hooks/useUnitIcon";
|
||||
import { PackedSimpleValue } from "../../../../src/jdom/pack";
|
||||
|
||||
export default function MemberInput(props: {
|
||||
specification: jdspec.PacketMember,
|
||||
serviceSpecification: jdspec.ServiceSpec,
|
||||
serviceMemberSpecification: jdspec.PacketInfo,
|
||||
value: any,
|
||||
setValue?: (v: any) => void,
|
||||
value: PackedSimpleValue,
|
||||
setValue?: (v: PackedSimpleValue) => void,
|
||||
showDataType?: boolean,
|
||||
color?: "primary" | "secondary",
|
||||
variant?: RegisterInputVariant,
|
||||
|
@ -46,7 +47,7 @@ export default function MemberInput(props: {
|
|||
|
||||
const minValue = pick(min, typicalMin, absoluteMin, /^u/.test(type) ? 0 : undefined)
|
||||
const maxValue = pick(max, typicalMax, absoluteMax)
|
||||
const errorValue = !!error ? ("±" + roundWithPrecision(error, 1 - Math.floor(Math.log10(error))).toLocaleString()) : undefined;
|
||||
const errorValue = error ? ("±" + roundWithPrecision(error, 1 - Math.floor(Math.log10(error))).toLocaleString()) : undefined;
|
||||
const unit = prettyUnit(specification.unit);
|
||||
const helperText = errorText
|
||||
|| [prettyMemberUnit(specification, showDataType), errorValue]
|
||||
|
@ -72,8 +73,9 @@ export default function MemberInput(props: {
|
|||
setValue(r.value)
|
||||
setErrorText(r.error)
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const handleEnumChange = (event: React.ChangeEvent<{ value: any }>) => {
|
||||
const v = enumInfo.isFlags ? flagsToValue(event.target.value) : event.target.value
|
||||
const v = enumInfo.isFlags ? flagsToValue(event.target.value as number[]) : event.target.value as number
|
||||
setValue(v)
|
||||
}
|
||||
const handleSliderChange = (event: unknown, newValue: number | number[]) => {
|
||||
|
@ -127,7 +129,7 @@ export default function MemberInput(props: {
|
|||
aria-label={label}
|
||||
disabled={disabled}
|
||||
multiple={enumInfo.isFlags}
|
||||
value={enumInfo.isFlags ? valueToFlags(enumInfo, value) : value}
|
||||
value={enumInfo.isFlags ? valueToFlags(enumInfo, value as number) : value}
|
||||
onChange={handleEnumChange}>
|
||||
{Object.keys(enumInfo.members).map(n => <MenuItem key={n} value={enumInfo.members[n]}>{n}</MenuItem>)}
|
||||
</Select>
|
||||
|
@ -141,7 +143,7 @@ export default function MemberInput(props: {
|
|||
return <GaugeWidget
|
||||
tabIndex={0}
|
||||
label={label}
|
||||
value={value}
|
||||
value={value as number}
|
||||
color={color}
|
||||
variant={signed ? "fountain" : undefined}
|
||||
min={min} max={max} step={step}
|
||||
|
@ -154,7 +156,7 @@ export default function MemberInput(props: {
|
|||
return <Slider
|
||||
aria-label={label}
|
||||
color={color}
|
||||
value={value}
|
||||
value={value as number}
|
||||
valueLabelFormat={percentValueFormat}
|
||||
onChange={disabled ? undefined : handleSliderChange}
|
||||
min={min} max={max} step={step}
|
||||
|
@ -180,7 +182,7 @@ export default function MemberInput(props: {
|
|||
return <ValueWithUnitWidget
|
||||
tabIndex={0}
|
||||
label={specification.unit}
|
||||
value={value}
|
||||
value={value as number}
|
||||
min={minValue}
|
||||
max={maxValue}
|
||||
icon={unitIcon}
|
||||
|
@ -190,7 +192,7 @@ export default function MemberInput(props: {
|
|||
onChange={disabled ? undefined : handleSliderChange} />
|
||||
|
||||
return <Slider
|
||||
value={value}
|
||||
value={value as number}
|
||||
color={color}
|
||||
valueLabelFormat={off ? offFormat : valueLabelFormat}
|
||||
onChange={disabled ? undefined : handleSliderChange}
|
||||
|
@ -216,7 +218,7 @@ export default function MemberInput(props: {
|
|||
if (isWidget) // we need min/max to support a slider
|
||||
return <ValueWithUnitWidget
|
||||
tabIndex={0}
|
||||
value={roundWithPrecision(value, 1)}
|
||||
value={roundWithPrecision(value as number, 1)}
|
||||
label={specification.unit}
|
||||
color={color}
|
||||
size={widgetSize} />
|
||||
|
|
|
@ -53,7 +53,7 @@ export default function useFirmwareBlobs() {
|
|||
console.log(`firmwares: change`)
|
||||
const names = await fw?.list()
|
||||
console.log(`import stored uf2`, names)
|
||||
let uf2s: FirmwareBlob[] = [];
|
||||
const uf2s: FirmwareBlob[] = [];
|
||||
if (names?.length) {
|
||||
for (const name of names) {
|
||||
const blob = await fw.get(name)
|
||||
|
|
|
@ -3,10 +3,9 @@ import { deviceSpecificationFromFirmwareIdentifier, deviceSpecifications } from
|
|||
import JacdacContext, { JacdacContextProps } from "../../jacdac/Context";
|
||||
import useEffectAsync from "../useEffectAsync";
|
||||
import { unique } from "../../../../src/jdom/utils";
|
||||
import { BootloaderCmd, CMD_ADVERTISEMENT_DATA, ControlReg, DEVICE_CHANGE, SRV_BOOTLOADER, SRV_CTRL } from "../../../../src/jdom/constants";
|
||||
import { BootloaderCmd, ControlReg, DEVICE_CHANGE, SRV_BOOTLOADER } from "../../../../src/jdom/constants";
|
||||
import useEventRaised from "../../jacdac/useEventRaised";
|
||||
import Packet from "../../../../src/jdom/packet";
|
||||
import { jdunpack } from "../../../../src/jdom/pack";
|
||||
|
||||
export default function useFirmwareRepos(showAllRepos?: boolean) {
|
||||
const { bus } = useContext<JacdacContextProps>(JacdacContext)
|
||||
|
@ -24,7 +23,7 @@ export default function useFirmwareRepos(showAllRepos?: boolean) {
|
|||
if (showAllRepos)
|
||||
repos = deviceSpecifications().map(spec => spec.repo);
|
||||
else {
|
||||
let firmwares: number[] = [];
|
||||
const firmwares: number[] = [];
|
||||
// ask firmware registers
|
||||
for (const register of registers) {
|
||||
await register.refresh(true)
|
||||
|
|
|
@ -1,37 +1,37 @@
|
|||
import useFetch from "./useFetch";
|
||||
import useFetch from "./useFetch"
|
||||
|
||||
const ROOT = "https://api.github.com/"
|
||||
export const GITHUB_API_KEY = "githubtoken"
|
||||
export interface GithubRelease {
|
||||
url: string,
|
||||
html_url: string,
|
||||
tag_name: string,
|
||||
name: string,
|
||||
body: string,
|
||||
url: string
|
||||
html_url: string
|
||||
tag_name: string
|
||||
name: string
|
||||
body: string
|
||||
assets: {
|
||||
url: string,
|
||||
browser_download_url: string,
|
||||
url: string
|
||||
browser_download_url: string
|
||||
name: string
|
||||
}[]
|
||||
}
|
||||
|
||||
export interface GithubUser {
|
||||
login: string;
|
||||
avatar_url: string;
|
||||
html_url: string;
|
||||
login: string
|
||||
avatar_url: string
|
||||
html_url: string
|
||||
}
|
||||
|
||||
export interface GithubRepository {
|
||||
name: string;
|
||||
full_name: string;
|
||||
private: boolean;
|
||||
owner: GithubUser;
|
||||
description: string;
|
||||
fork: boolean;
|
||||
homepage: string;
|
||||
default_branch: string;
|
||||
organization: GithubUser;
|
||||
html_url: string;
|
||||
name: string
|
||||
full_name: string
|
||||
private: boolean
|
||||
owner: GithubUser
|
||||
description: string
|
||||
fork: boolean
|
||||
homepage: string
|
||||
default_branch: string
|
||||
organization: GithubUser
|
||||
html_url: string
|
||||
}
|
||||
|
||||
export function normalizeSlug(slug: string): string {
|
||||
|
@ -39,46 +39,57 @@ export function normalizeSlug(slug: string): string {
|
|||
}
|
||||
|
||||
export interface GitHubApiOptions {
|
||||
ignoreThrottled?: boolean;
|
||||
ignoreThrottled?: boolean
|
||||
}
|
||||
|
||||
export function parseRepoUrl(url: string): { owner: string; name: string; } {
|
||||
const m = /^https:\/\/github\.com\/([^\/ \t]+)\/([^\/ \t]+)\/?$/.exec(url || "")
|
||||
if (m)
|
||||
return { owner: m[1], name: m[2] }
|
||||
return undefined;
|
||||
export function parseRepoUrl(url: string): { owner: string; name: string } {
|
||||
const m = /^https:\/\/github\.com\/([^/ \t]+)\/([^/ \t]+)\/?$/.exec(
|
||||
url || ""
|
||||
)
|
||||
if (m) return { owner: m[1], name: m[2] }
|
||||
return undefined
|
||||
}
|
||||
|
||||
export async function fetchLatestRelease(slug: string, options?: GitHubApiOptions): Promise<GithubRelease> {
|
||||
const uri = `${ROOT}repos/${normalizeSlug(slug)}/releases/latest`;
|
||||
export async function fetchLatestRelease(
|
||||
slug: string,
|
||||
options?: GitHubApiOptions
|
||||
): Promise<GithubRelease> {
|
||||
const uri = `${ROOT}repos/${normalizeSlug(slug)}/releases/latest`
|
||||
const resp = await fetch(uri)
|
||||
// console.log(resp)
|
||||
switch (resp.status) {
|
||||
case 200:
|
||||
case 204:
|
||||
case 204: {
|
||||
const release: GithubRelease = await resp.json()
|
||||
return release;
|
||||
return release
|
||||
}
|
||||
case 404:
|
||||
// unknow repo or no access
|
||||
return undefined;
|
||||
return undefined
|
||||
case 403:
|
||||
// throttled
|
||||
if (options?.ignoreThrottled)
|
||||
return undefined;
|
||||
throw new Error("Too many calls to GitHub, try again later");
|
||||
if (options?.ignoreThrottled) return undefined
|
||||
throw new Error("Too many calls to GitHub, try again later")
|
||||
}
|
||||
throw new Error(`unknown status code ${resp.status}`)
|
||||
}
|
||||
|
||||
export async function fetchReleaseBinary(slug: string, tag: string): Promise<Blob> {
|
||||
export async function fetchReleaseBinary(
|
||||
slug: string,
|
||||
tag: string
|
||||
): Promise<Blob> {
|
||||
// we are not using the release api because of CORS.
|
||||
const downloadUrl = `https://raw.githubusercontent.com/${normalizeSlug(slug)}/${tag}/dist/firmware.uf2`
|
||||
const req = await fetch(downloadUrl, { headers: { "Accept": "application/octet-stream" } })
|
||||
const downloadUrl = `https://raw.githubusercontent.com/${normalizeSlug(
|
||||
slug
|
||||
)}/${tag}/dist/firmware.uf2`
|
||||
const req = await fetch(downloadUrl, {
|
||||
headers: { Accept: "application/octet-stream" },
|
||||
})
|
||||
if (req.status == 200) {
|
||||
const firmware = await req.blob()
|
||||
return firmware;
|
||||
return firmware
|
||||
}
|
||||
return undefined;
|
||||
return undefined
|
||||
}
|
||||
|
||||
function useFetchApi<T>(path: string, options?: GitHubApiOptions) {
|
||||
|
@ -90,45 +101,54 @@ function useFetchApi<T>(path: string, options?: GitHubApiOptions) {
|
|||
case 202:
|
||||
case 203:
|
||||
case 204:
|
||||
break;
|
||||
break
|
||||
case 404:
|
||||
// unknow repo or no access
|
||||
res.response = undefined;
|
||||
break;
|
||||
res.response = undefined
|
||||
break
|
||||
case 403:
|
||||
// throttled
|
||||
if (options?.ignoreThrottled) {
|
||||
res.response = undefined;
|
||||
res.response = undefined
|
||||
return res
|
||||
}
|
||||
else
|
||||
throw new Error("Too many calls to GitHub, try again later");
|
||||
} else
|
||||
throw new Error("Too many calls to GitHub, try again later")
|
||||
|
||||
default:
|
||||
console.log(`unknown status`, res)
|
||||
throw new Error(`Unknown response from GitHub ${res.status}`);
|
||||
throw new Error(`Unknown response from GitHub ${res.status}`)
|
||||
}
|
||||
return res;
|
||||
return res
|
||||
}
|
||||
|
||||
export function useRepository(slug: string) {
|
||||
const path = `repos/${normalizeSlug(slug)}`
|
||||
const res = useFetchApi<GithubRepository>(path, { ignoreThrottled: true });
|
||||
return res;
|
||||
const res = useFetchApi<GithubRepository>(path, { ignoreThrottled: true })
|
||||
return res
|
||||
}
|
||||
|
||||
export function useLatestRelease(slug: string, options?: GitHubApiOptions) {
|
||||
if (!slug)
|
||||
return { response: undefined, loading: false, error: undefined, status: undefined }
|
||||
const uri = `repos/${normalizeSlug(slug)}/releases/latest`;
|
||||
const res = useFetchApi<GithubRelease>(uri, { ignoreThrottled: true });
|
||||
return res;
|
||||
return {
|
||||
response: undefined,
|
||||
loading: false,
|
||||
error: undefined,
|
||||
status: undefined,
|
||||
}
|
||||
const uri = `repos/${normalizeSlug(slug)}/releases/latest`
|
||||
const res = useFetchApi<GithubRelease>(uri, { ...(options || {}), ignoreThrottled: true })
|
||||
return res
|
||||
}
|
||||
|
||||
export function useLatestReleases(slug: string, options?: GitHubApiOptions) {
|
||||
if (!slug)
|
||||
return { response: undefined, loading: false, error: undefined, status: undefined }
|
||||
const uri = `repos/${normalizeSlug(slug)}/releases`;
|
||||
const res = useFetchApi<GithubRelease[]>(uri, { ignoreThrottled: true });
|
||||
return res;
|
||||
}
|
||||
return {
|
||||
response: undefined,
|
||||
loading: false,
|
||||
error: undefined,
|
||||
status: undefined,
|
||||
}
|
||||
const uri = `repos/${normalizeSlug(slug)}/releases`
|
||||
const res = useFetchApi<GithubRelease[]>(uri, { ...(options || {}), ignoreThrottled: true })
|
||||
return res
|
||||
}
|
||||
|
|
|
@ -7,4 +7,4 @@ export default function useMounted(): () => boolean {
|
|||
return () => { mounted.current = false; }
|
||||
}, []);
|
||||
return () => mounted.current;
|
||||
};
|
||||
}
|
|
@ -1,9 +1,10 @@
|
|||
import { SvgIcon, SvgIconProps } from "@material-ui/core";
|
||||
import React from "react";
|
||||
|
||||
export default (props: SvgIconProps) =>
|
||||
<SvgIcon {...props}>
|
||||
export default function MicrobitIcon(props: SvgIconProps) {
|
||||
return <SvgIcon {...props}>
|
||||
<path fill="none" d="M0 18.461h195.474v34.211H0z" />
|
||||
<path d="M37.363 29.377a3.04 3.04 0 01-3.035-3.042 3.035 3.035 0 013.035-3.038 3.035 3.035 0 013.039 3.038 3.038 3.038 0 01-3.039 3.042M15.052 23.3a3.04 3.04 0 00-3.042 3.035 3.044 3.044 0 003.042 3.042 3.042 3.042 0 003.036-3.042 3.037 3.037 0 00-3.036-3.035m-.003-5.99h22.576c4.979 0 9.027 4.047 9.027 9.027 0 4.979-4.049 9.031-9.027 9.031H15.049c-4.977 0-9.03-4.053-9.03-9.031-.001-4.98 4.053-9.027 9.03-9.027m22.576 24.076c8.299 0 15.047-6.75 15.047-15.049s-6.748-15.051-15.047-15.051H15.049C6.75 11.286 0 18.038 0 26.337s6.75 15.049 15.049 15.049h22.576" />
|
||||
</SvgIcon>
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { Grid, TextField, useEventCallback } from "@material-ui/core";
|
||||
import { Grid, TextField } from "@material-ui/core";
|
||||
import React, { ChangeEvent, useEffect, useMemo, useState } from "react";
|
||||
import { clone, JSONTryParse, SMap, uniqueName } from "../../../../src/jdom/utils";
|
||||
import useChange from '../../jacdac/useChange';
|
||||
// tslint:disable-next-line: no-submodule-imports match-default-export-name
|
||||
import DeleteIcon from '@material-ui/icons/Delete';
|
||||
import { resolveMakecodeService, resolveMakecodeServiceFromClassIdentifier, serviceSpecificationFromClassIdentifier, serviceSpecifications } from "../../../../src/jdom/spec";
|
||||
import { resolveMakecodeService, resolveMakecodeServiceFromClassIdentifier, serviceSpecifications } from "../../../../src/jdom/spec";
|
||||
import AddServiceIconButton from "../AddServiceIconButton";
|
||||
import ServiceSpecificationSelect from "../ServiceSpecificationSelect"
|
||||
import { escapeName } from "../../../../src/azure-iot/dtdl"
|
||||
|
@ -88,9 +88,11 @@ function ClientRoleRow(props: { config: Configuration, component: ClientRole, on
|
|||
</Grid>
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function validateClientRole(config: Configuration, role: ClientRole) {
|
||||
let serviceError: string = undefined;
|
||||
let nameError: string = undefined;
|
||||
const serviceError: string = undefined;
|
||||
const nameError: string = undefined;
|
||||
// TODO
|
||||
return { serviceError, nameError }
|
||||
}
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ function MakeCodeSnippetNoSSR(props: { source: string }) {
|
|||
const tabs = ["blocks", "typescript", "sim"]
|
||||
const { editor, setEditor } = useContext(MakeCodeSnippetContext);
|
||||
const [tab, setTab] = useState(tabs.indexOf(editor) || 0);
|
||||
const handleTabChange = (event: React.ChangeEvent<{}>, newValue: number) => {
|
||||
const handleTabChange = (event: React.ChangeEvent<unknown>, newValue: number) => {
|
||||
if (newValue < tabs.length - 1)
|
||||
setEditor(tabs[newValue]);
|
||||
setTab(newValue);
|
||||
|
|
|
@ -160,10 +160,10 @@ export function useMakeCodeRenderer() {
|
|||
|
||||
// listen for messages
|
||||
const handleMessage = (ev: MessageEvent) => {
|
||||
let msg = ev.data;
|
||||
const msg = ev.data;
|
||||
if (msg.source != "makecode") return;
|
||||
switch (msg.type) {
|
||||
case "renderready":
|
||||
case "renderready": {
|
||||
console.log(`mkcd: renderer ready, ${Object.keys(pendingRequests).length} pending`)
|
||||
const iframe = typeof document !== "undefined" && document.getElementById(iframeId)
|
||||
if (iframe) {
|
||||
|
@ -173,17 +173,19 @@ export function useMakeCodeRenderer() {
|
|||
.forEach(k => sendRequest(pendingRequests[k].req));
|
||||
}
|
||||
break;
|
||||
case "renderblocks":
|
||||
}
|
||||
case "renderblocks": {
|
||||
const id = msg.id; // this is the id you sent
|
||||
const r = pendingRequests[id];
|
||||
if (!r) return;
|
||||
delete pendingRequests[id];
|
||||
r.resolve(msg as RenderBlocksResponseMessage);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
useWindowEvent("message", handleMessage, false)
|
||||
useWindowEvent("message", handleMessage, false, [])
|
||||
|
||||
return {
|
||||
render
|
||||
|
|
|
@ -2,7 +2,10 @@ import React from "react"
|
|||
import { graphql } from "gatsby"
|
||||
import { MDXRenderer } from "gatsby-plugin-mdx"
|
||||
|
||||
export default function PageTemplate({ data: { mdx } }) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export default function PageTemplate(props: { data: { mdx: { body: any } } }) {
|
||||
const { data } = props;
|
||||
const { mdx } = data;
|
||||
return <MDXRenderer>{mdx.body}</MDXRenderer>
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Grid, TextField } from "@material-ui/core";
|
||||
import React, { ChangeEvent, useMemo } from "react";
|
||||
import { clone, SMap, uniqueName } from "../../../../src/jdom/utils";
|
||||
import { clone, uniqueName } from "../../../../src/jdom/utils";
|
||||
import useLocalStorage from "../useLocalStorage";
|
||||
// tslint:disable-next-line: no-submodule-imports match-default-export-name
|
||||
import DeleteIcon from '@material-ui/icons/Delete';
|
||||
|
@ -58,7 +58,7 @@ function ComponentRow(props: { twin: DigitalTwinSpec, component: DigitalTwinComp
|
|||
|
||||
function validateTwinComponent(twin: DigitalTwinSpec, component: DigitalTwinComponent) {
|
||||
let serviceError: string = undefined;
|
||||
let nameError: string = undefined;
|
||||
const nameError: string = undefined;
|
||||
const count = twin.components.filter(c => c.service.classIdentifier === component.service.classIdentifier).length
|
||||
if (count > 1)
|
||||
serviceError = `Multiple same service not supported.`
|
||||
|
@ -110,7 +110,7 @@ export default function AzureDeviceTwinDesigner() {
|
|||
variant={variant}
|
||||
/>
|
||||
</Grid>
|
||||
{twin.components.map(c => <ComponentRow twin={twin} component={c} onUpdate={update} />)}
|
||||
{twin.components.map((c, i) => <ComponentRow key={i} twin={twin} component={c} onUpdate={update} />)}
|
||||
<Grid item xs={12}>
|
||||
<AddServiceIconButton onAdd={handleAddService} />
|
||||
</Grid>
|
||||
|
|
|
@ -34,7 +34,8 @@ interface ConnectionString {
|
|||
}
|
||||
|
||||
function parseConnectionString(source: string): ConnectionString {
|
||||
let r: any = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const r: any = {
|
||||
source
|
||||
};
|
||||
source.split(';').map(fragment => fragment.split('=')).forEach(kv => r[kv[0]] = kv[1]);
|
||||
|
@ -117,7 +118,7 @@ class AzureIotHubClient {
|
|||
}
|
||||
}
|
||||
|
||||
export default function AzureIoTHub(props: {}) {
|
||||
export default function AzureIoTHub() {
|
||||
return <>
|
||||
<ConnectAlert />
|
||||
<ApiKeyManager />
|
||||
|
|
|
@ -75,8 +75,7 @@ function createDataSet(bus: JDBus,
|
|||
return set;
|
||||
}
|
||||
|
||||
export default function Collector(props: {}) {
|
||||
const { } = props;
|
||||
export default function Collector() {
|
||||
const { bus } = useContext<JacdacContextProps>(JacdacContext)
|
||||
const classes = useStyles();
|
||||
const { fileStorage } = useContext(ServiceManagerContext)
|
||||
|
|
|
@ -93,7 +93,7 @@ export default function DeviceDesigner() {
|
|||
: deviceSpecifications().find(dev => dev.id == device.id)
|
||||
? "identifer already used"
|
||||
: "";
|
||||
const servicesError = !!device.services?.length
|
||||
const servicesError = device.services?.length
|
||||
? ""
|
||||
: "Select at least one service"
|
||||
const imageError = !imageBase64 ? "missing image" : ""
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useContext, useEffect, useState } from "react"
|
||||
import { Box, Button, Card, CardActions, CardContent, CardHeader, CardMedia, CircularProgress, Collapse, Grid, Switch, TextField, Typography, useEventCallback, useTheme } from '@material-ui/core';
|
||||
import { Box, Button, Card, CardActions, CardContent, CardHeader, CardMedia, CircularProgress, Grid, Switch, Typography, useTheme } from '@material-ui/core';
|
||||
import { Link } from 'gatsby-theme-material-ui';
|
||||
import useDbValue from "../useDbValue";
|
||||
import JacdacContext, { JacdacContextProps } from "../../jacdac/Context";
|
||||
|
@ -10,7 +10,7 @@ import { JDClient } from "../../../../src/jdom/client";
|
|||
import DeviceCardHeader from "../DeviceCardHeader";
|
||||
import Alert from "../ui/Alert";
|
||||
import useEffectAsync from "../useEffectAsync";
|
||||
import { CHANGE, CONNECT, CONNECTING, CONNECTION_STATE, DISCONNECT, ERROR, PACKET_REPORT, PROGRESS, REPORT_RECEIVE, SensorAggregatorReg, SRV_MODEL_RUNNER, SRV_SENSOR_AGGREGATOR } from "../../../../src/jdom/constants";
|
||||
import { CHANGE, CONNECT, CONNECTING, CONNECTION_STATE, DISCONNECT, ERROR, PROGRESS, REPORT_RECEIVE, SRV_MODEL_RUNNER, SRV_SENSOR_AGGREGATOR } from "../../../../src/jdom/constants";
|
||||
import FieldDataSet from "../FieldDataSet";
|
||||
import { deviceSpecificationFromFirmwareIdentifier, isSensor } from "../../../../src/jdom/spec";
|
||||
import CircularProgressWithLabel from "../ui/CircularProgressWithLabel";
|
||||
|
@ -48,6 +48,7 @@ interface EdgeImpulseResponse {
|
|||
|
||||
interface EdgeImpulseHello {
|
||||
hello?: boolean;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
err?: any;
|
||||
}
|
||||
|
||||
|
@ -139,6 +140,7 @@ class EdgeImpulseClient extends JDClient {
|
|||
public samplingState = IDLE;
|
||||
private _hello: EdgeImpulseRemoteManagementInfo;
|
||||
private _sample: EdgeImpulseSampling;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
private _pingInterval: any;
|
||||
private pong: boolean;
|
||||
private aggregatorClient: SensorAggregatorClient;
|
||||
|
@ -182,6 +184,7 @@ class EdgeImpulseClient extends JDClient {
|
|||
w.close();
|
||||
}
|
||||
catch (e) {
|
||||
console.debug(e)
|
||||
}
|
||||
finally {
|
||||
this.setConnectionState(DISCONNECT);
|
||||
|
@ -206,7 +209,7 @@ class EdgeImpulseClient extends JDClient {
|
|||
}
|
||||
}
|
||||
|
||||
private send(msg: any) {
|
||||
private send(msg: unknown) {
|
||||
this._ws?.send(JSON.stringify(msg))
|
||||
}
|
||||
|
||||
|
@ -241,6 +244,7 @@ class EdgeImpulseClient extends JDClient {
|
|||
this.connect();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
private handleMessage(msg: any) {
|
||||
// response to ping?
|
||||
if (msg.data === "pong") {
|
||||
|
@ -485,7 +489,7 @@ class EdgeImpulseClient extends JDClient {
|
|||
}
|
||||
}
|
||||
|
||||
static async apiFetch<T extends EdgeImpulseResponse>(apiKey: string, path: string | number, body?: any): Promise<T> {
|
||||
static async apiFetch<T extends EdgeImpulseResponse>(apiKey: string, path: string | number, body?: unknown): Promise<T> {
|
||||
const API_ROOT = "https://studio.edgeimpulse.com/v1/api/"
|
||||
const url = `${API_ROOT}${path}`
|
||||
const options: RequestInit = {
|
||||
|
@ -646,7 +650,7 @@ function Acquisition(props: {
|
|||
const [connectionState, setConnectionState] = useState(DISCONNECT)
|
||||
const [samplingState, setSamplingState] = useState(IDLE)
|
||||
const [samplingProgress, setSamplingProgress] = useState(0)
|
||||
const [deviceInfo, setDeviceInfo] = useState<EdgeImpulseDeviceInfo>(undefined);
|
||||
const [, setDeviceInfo] = useState<EdgeImpulseDeviceInfo>(undefined);
|
||||
const { deviceId } = device;
|
||||
const deviceName = useDeviceName(device, false);
|
||||
const projectId = info?.id;
|
||||
|
@ -726,7 +730,7 @@ function Acquisition(props: {
|
|||
</Box>
|
||||
}
|
||||
|
||||
export default function EdgeImpulse(props: {}) {
|
||||
export default function EdgeImpulse() {
|
||||
const { value: apiKey } = useDbValue(EDGE_IMPULSE_API_KEY, "")
|
||||
const { bus } = useContext<JacdacContextProps>(JacdacContext);
|
||||
const [model, setModel] = useState<Uint8Array>(undefined)
|
||||
|
|
|
@ -9,7 +9,7 @@ import SafeBootAlert from "../firmware/SafeBootAlert";
|
|||
|
||||
export default function Flash() {
|
||||
const [tab, setTab] = useState(0);
|
||||
const handleTabChange = (event: React.ChangeEvent<{}>, newValue: number) => {
|
||||
const handleTabChange = (event: React.ChangeEvent<unknown>, newValue: number) => {
|
||||
setTab(newValue);
|
||||
}
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ export function ModelActions(props: {
|
|||
</>
|
||||
}
|
||||
|
||||
export default function ModelUploader(props: {}) {
|
||||
export default function ModelUploader() {
|
||||
const classes = useStyles()
|
||||
const [importing, setImporting] = useState(false)
|
||||
const { data: model, setBlob: setModel } = useDbUint8Array("model.tflite")
|
||||
|
@ -154,7 +154,7 @@ export default function ModelUploader(props: {}) {
|
|||
<p>Machine learning models are typically stored in a <code>.tflite</code> file.</p>
|
||||
{model && <Alert severity={'success'}>Model loaded ({prettySize(model.byteLength)})</Alert>}
|
||||
{model && <p />}
|
||||
<ImportButton required={!model} disabled={importing} text={"Import model"} onFilesUploaded={handleTfmodelFiles} />
|
||||
<ImportButton disabled={importing} text={"Import model"} onFilesUploaded={handleTfmodelFiles} />
|
||||
<Button aria-label="clear model" disabled={importing} onClick={handleClearModel}>clear model</Button>
|
||||
{models?.length && <List>
|
||||
{models.map(model => <ListItem key={model.path} button onClick={handleLoadModel(model)}>
|
||||
|
@ -166,7 +166,7 @@ export default function ModelUploader(props: {}) {
|
|||
{sensorConfig && <Alert severity={'success'}>Sensor configuration loaded</Alert>}
|
||||
{sensorConfig && <SensorAggregatorConfigView config={sensorConfig} />}
|
||||
{sensorConfig && <p />}
|
||||
<ImportButton required={!sensorConfig} disabled={importing} text={"Import configuration"} onFilesUploaded={handleSensorConfigFiles} />
|
||||
<ImportButton disabled={importing} text={"Import configuration"} onFilesUploaded={handleSensorConfigFiles} />
|
||||
<Button aria-label="clear configuration" disabled={importing} onClick={handleClearConfiguration}>clear configuration</Button>
|
||||
{inputConfigurations?.length && <List>
|
||||
{inputConfigurations.map(iconfig => <ListItem key={iconfig.path} button onClick={handleLoadInputConfiguration(iconfig)}>
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { PaletteType } from "@material-ui/core";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { ReactNode, useEffect, useState } from "react";
|
||||
import DarkModeContext from './DarkModeContext'
|
||||
|
||||
const DarkModeProvider = ({ children }) => {
|
||||
export default function DarkModeProvider(props: { children: ReactNode }) {
|
||||
const { children } = props;
|
||||
const KEY = 'darkMode'
|
||||
const [darkMode, setDarkMode] = useState<PaletteType>("light");
|
||||
const [darkModeMounted, setMounted] = useState(false);
|
||||
|
@ -37,5 +38,3 @@ const DarkModeProvider = ({ children }) => {
|
|||
{children}
|
||||
</DarkModeContext.Provider>
|
||||
}
|
||||
|
||||
export default DarkModeProvider
|
|
@ -1,3 +1,5 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* eslint-disable react/jsx-key */
|
||||
import React, { useContext, useRef } from 'react'
|
||||
import Highlight, { defaultProps, Language, PrismTheme } from 'prism-react-renderer'
|
||||
// tslint:disable-next-line: no-submodule-imports match-default-export-name
|
||||
|
|
|
@ -29,8 +29,9 @@ export default function IconButtonWithProgress(props: IconButtonWithProgressProp
|
|||
progress={progress}
|
||||
progressColor={progressColor}
|
||||
progressStyle={progressStyle}
|
||||
progressSize={progressSize}
|
||||
children={badge} />}
|
||||
progressSize={progressSize}>
|
||||
{badge}
|
||||
</CircularProgressBox>}
|
||||
</IconButton></span>
|
||||
</Tooltip>
|
||||
}
|
|
@ -1,42 +1,40 @@
|
|||
import { useState } from "react";
|
||||
import useEffectAsync from "./useEffectAsync";
|
||||
import { useState } from "react"
|
||||
import useEffectAsync from "./useEffectAsync"
|
||||
|
||||
export default function useFetch<T>(url: RequestInfo, options?: RequestInit) {
|
||||
const [response, setResponse] = useState<T>(undefined);
|
||||
const [error, setError] = useState<any>(undefined);
|
||||
const [status, setStatus] = useState<number>(undefined);
|
||||
const [loading, setLoading] = useState(true); // start in loading mode
|
||||
const [response, setResponse] = useState<T>(undefined)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const [error, setError] = useState<any>(undefined)
|
||||
const [status, setStatus] = useState<number>(undefined)
|
||||
const [loading, setLoading] = useState(true) // start in loading mode
|
||||
|
||||
useEffectAsync(async (mounted) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await fetch(url, options);
|
||||
if (!mounted())
|
||||
return;
|
||||
const status = res.status;
|
||||
setStatus(status);
|
||||
if (status >= 200 && status <= 204) {
|
||||
const json = await res.json();
|
||||
if (!mounted())
|
||||
return;
|
||||
setResponse(json);
|
||||
useEffectAsync(
|
||||
async mounted => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const res = await fetch(url, options)
|
||||
if (!mounted()) return
|
||||
const status = res.status
|
||||
setStatus(status)
|
||||
if (status >= 200 && status <= 204) {
|
||||
const json = await res.json()
|
||||
if (!mounted()) return
|
||||
setResponse(json)
|
||||
}
|
||||
} catch (error) {
|
||||
if (!mounted()) return
|
||||
setError(error)
|
||||
} finally {
|
||||
if (mounted()) setLoading(false)
|
||||
}
|
||||
} catch (error) {
|
||||
if (!mounted())
|
||||
return;
|
||||
setError(error);
|
||||
}
|
||||
finally {
|
||||
if (!mounted())
|
||||
return;
|
||||
setLoading(false)
|
||||
}
|
||||
}, [url]);
|
||||
},
|
||||
[url]
|
||||
)
|
||||
|
||||
return {
|
||||
response,
|
||||
error,
|
||||
status,
|
||||
loading
|
||||
};
|
||||
};
|
||||
loading,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import DeviceList from "./DeviceList"
|
|||
import ServiceList from "./ServiceList"
|
||||
import DeviceSpecificationList from "./DeviceSpecificationList"
|
||||
import FilteredDeviceSpecificationList from "./FilteredDeviceSpecificationList"
|
||||
import ServiceSpecificationList from "./ServiceSpecificationList"
|
||||
import PacketsPreview from "./PacketsPreview"
|
||||
import UpdateDeviceList from "./UpdateDeviceList";
|
||||
import FlashButton from "./FlashButton";
|
||||
|
|
|
@ -17,7 +17,7 @@ export default function JoystickWidget(props: {
|
|||
onUpdate?: (newx: number, newy: number) => void,
|
||||
hostValues?: () => [number, number]
|
||||
}) {
|
||||
const { color, x, y, onUpdate, widgetSize, hostValues } = props;
|
||||
const { color, x, y, onUpdate, hostValues } = props;
|
||||
const { active, background, controlBackground } = useWidgetTheme(color);
|
||||
const dragSurfaceRef = useRef<SVGCircleElement>();
|
||||
const [grabbing, setGrabbing] = useState(false)
|
||||
|
|
|
@ -5,19 +5,18 @@ import useServiceHost from "../hooks/useServiceHost";
|
|||
import LedPixelServiceHost from "../../../../src/hosts/ledpixelservicehost";
|
||||
import SvgWidget from "../widgets/SvgWidget";
|
||||
import useWidgetTheme from "../widgets/useWidgetTheme";
|
||||
import useWidgetSize from "../widgets/useWidgetSize";
|
||||
import { JDService } from "../../../../src/jdom/service";
|
||||
import { useRegisterUnpackedValue } from "../../jacdac/useRegisterValue"
|
||||
|
||||
function rgbToHsl(r: number, g: number, b: number): [number, number, number] {
|
||||
const [r$, g$, b$] = [r / 255, g / 255, b / 255];
|
||||
let cMin = Math.min(r$, g$, b$);
|
||||
let cMax = Math.max(r$, g$, b$);
|
||||
let cDelta = cMax - cMin;
|
||||
const cMin = Math.min(r$, g$, b$);
|
||||
const cMax = Math.max(r$, g$, b$);
|
||||
const cDelta = cMax - cMin;
|
||||
let h: number;
|
||||
let s: number;
|
||||
let l: number;
|
||||
let maxAndMin = cMax + cMin;
|
||||
const maxAndMin = cMax + cMin;
|
||||
|
||||
//lum
|
||||
l = (maxAndMin / 2) * 100
|
||||
|
@ -75,7 +74,7 @@ function LightStripWidget(props: {
|
|||
numPixels: number,
|
||||
actualBrightness: number,
|
||||
host: LedPixelServiceHost,
|
||||
widgetSize: string,
|
||||
widgetSize?: string,
|
||||
}) {
|
||||
const { lightVariant, numPixels, actualBrightness, host, widgetSize } = props;
|
||||
const { background, controlBackground } = useWidgetTheme()
|
||||
|
@ -192,15 +191,14 @@ function LightMatrixWidget(props: {
|
|||
lightVariant: LedPixelVariant,
|
||||
actualBrightness: number,
|
||||
host: LedPixelServiceHost,
|
||||
widgetSize: string,
|
||||
widgetSize?: string,
|
||||
columns: number,
|
||||
rows: number,
|
||||
}) {
|
||||
const { actualBrightness, columns, rows, host, widgetSize } = props;
|
||||
const { columns, rows, host, widgetSize } = props;
|
||||
const { background, controlBackground } = useWidgetTheme()
|
||||
|
||||
const widgetRef = useRef<SVGGElement>();
|
||||
const clickeable = !!host;
|
||||
// compute size
|
||||
const pw = 8;
|
||||
const ph = 8;
|
||||
|
@ -255,7 +253,7 @@ function LightMatrixWidget(props: {
|
|||
}
|
||||
|
||||
export default function LightWidget(props: { variant?: "icon" | "", service: JDService, widgetCount?: number }) {
|
||||
const { service, widgetCount, variant } = props;
|
||||
const { service } = props;
|
||||
const [numPixels] = useRegisterUnpackedValue<[number]>(service.register(LedPixelReg.NumPixels));
|
||||
const [lightVariant] = useRegisterUnpackedValue<[LedPixelVariant]>(service.register(LedPixelReg.Variant));
|
||||
const [actualBrightness] = useRegisterUnpackedValue<[number]>(service.register(LedPixelReg.ActualBrightness));
|
||||
|
|
|
@ -2,7 +2,6 @@ import React from "react";
|
|||
import { Grid, Slider, Typography } from "@material-ui/core";
|
||||
import { isSet, roundWithPrecision } from "../../../../src/jdom/utils";
|
||||
import { CSSProperties } from "@material-ui/core/styles/withStyles";
|
||||
import useUnitIcons from "../hooks/useUnitIcon";
|
||||
|
||||
export default function ValueWithUnitWidget(props: {
|
||||
value: number,
|
||||
|
@ -23,7 +22,7 @@ export default function ValueWithUnitWidget(props: {
|
|||
const hasValue = !isNaN(value);
|
||||
const valueText = hasValue ? roundWithPrecision(value, precision).toLocaleString() : "--"
|
||||
const valueTextLength = isSet(min) && isSet(max) ? [min, max, min + (min + max) / 3]
|
||||
.map(v => roundWithPrecision(v, precision).toLocaleString().replace(/[,\.]/, '').length)
|
||||
.map(v => roundWithPrecision(v, precision).toLocaleString().replace(/[,.]/, '').length)
|
||||
.reduce((l, r) => Math.max(l, r), 0)
|
||||
+ precision : valueText.length;
|
||||
|
||||
|
|
|
@ -1,54 +1,83 @@
|
|||
export function svgPointerPoint(svg: SVGSVGElement, event: React.PointerEvent): DOMPoint {
|
||||
const point = svg.createSVGPoint();
|
||||
point.x = event.clientX;
|
||||
point.y = event.clientY;
|
||||
const res = point.matrixTransform(svg.getScreenCTM().inverse());
|
||||
return res;
|
||||
import React from "react"
|
||||
|
||||
export function svgPointerPoint(
|
||||
svg: SVGSVGElement,
|
||||
event: React.PointerEvent
|
||||
): DOMPoint {
|
||||
const point = svg.createSVGPoint()
|
||||
point.x = event.clientX
|
||||
point.y = event.clientY
|
||||
const res = point.matrixTransform(svg.getScreenCTM().inverse())
|
||||
return res
|
||||
}
|
||||
|
||||
export function closestPoint(pathNode: SVGPathElement, step: number, point: DOMPoint): number {
|
||||
export function closestPoint(
|
||||
pathNode: SVGPathElement,
|
||||
step: number,
|
||||
point: DOMPoint
|
||||
): number {
|
||||
const pathLength = pathNode.getTotalLength()
|
||||
|
||||
const distance2 = (p: DOMPoint) => {
|
||||
const dx = p.x - point.x
|
||||
const dy = p.y - point.y;
|
||||
return dx * dx + dy * dy;
|
||||
const dy = p.y - point.y
|
||||
return dx * dx + dy * dy
|
||||
}
|
||||
|
||||
let bestLength: number = 0;
|
||||
let bestDistance = Infinity;
|
||||
let bestLength = 0
|
||||
let bestDistance = Infinity
|
||||
for (let scanLength = 0; scanLength <= pathLength; scanLength += step) {
|
||||
const scan = pathNode.getPointAtLength(scanLength);
|
||||
const scanDistance = distance2(scan);
|
||||
const scan = pathNode.getPointAtLength(scanLength)
|
||||
const scanDistance = distance2(scan)
|
||||
if (scanDistance < bestDistance) {
|
||||
bestLength = scanLength;
|
||||
bestDistance = scanDistance;
|
||||
bestLength = scanLength
|
||||
bestDistance = scanDistance
|
||||
}
|
||||
}
|
||||
return bestLength / pathLength;
|
||||
return bestLength / pathLength
|
||||
}
|
||||
|
||||
export function polarToCartesian(centerX: number, centerY: number, radius: number, angleInDegrees: number) {
|
||||
const angleInRadians = (angleInDegrees - 90) * Math.PI / 180.0;
|
||||
export function polarToCartesian(
|
||||
centerX: number,
|
||||
centerY: number,
|
||||
radius: number,
|
||||
angleInDegrees: number
|
||||
) {
|
||||
const angleInRadians = ((angleInDegrees - 90) * Math.PI) / 180.0
|
||||
|
||||
return {
|
||||
x: centerX + (radius * Math.cos(angleInRadians)),
|
||||
y: centerY + (radius * Math.sin(angleInRadians))
|
||||
};
|
||||
x: centerX + radius * Math.cos(angleInRadians),
|
||||
y: centerY + radius * Math.sin(angleInRadians),
|
||||
}
|
||||
}
|
||||
|
||||
export function describeArc(x: number, y: number, radius: number, startAngle: number, endAngle: number, large?: boolean) {
|
||||
export function describeArc(
|
||||
x: number,
|
||||
y: number,
|
||||
radius: number,
|
||||
startAngle: number,
|
||||
endAngle: number,
|
||||
large?: boolean
|
||||
) {
|
||||
const start = polarToCartesian(x, y, radius, endAngle)
|
||||
const end = polarToCartesian(x, y, radius, startAngle)
|
||||
|
||||
const start = polarToCartesian(x, y, radius, endAngle);
|
||||
const end = polarToCartesian(x, y, radius, startAngle);
|
||||
|
||||
const largeArcFlag = large !== true && (endAngle - startAngle <= 180) ? "0" : "1";
|
||||
const largeArcFlag =
|
||||
large !== true && endAngle - startAngle <= 180 ? "0" : "1"
|
||||
|
||||
const d = [
|
||||
"M", start.x, start.y,
|
||||
"A", radius, radius, 0, largeArcFlag, 0,
|
||||
end.x, end.y
|
||||
].join(" ");
|
||||
"M",
|
||||
start.x,
|
||||
start.y,
|
||||
"A",
|
||||
radius,
|
||||
radius,
|
||||
0,
|
||||
largeArcFlag,
|
||||
0,
|
||||
end.x,
|
||||
end.y,
|
||||
].join(" ")
|
||||
|
||||
return d;
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import React from "react";
|
|||
// tslint:disable-next-line: no-submodule-imports
|
||||
import ThemeTopLayout from "gatsby-theme-material-ui-top-layout/src/components/top-layout";
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
export default function TopLayout({ children, theme }) {
|
||||
return <ThemeTopLayout theme={theme}>
|
||||
{children}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import React, { useState, useEffect, ReactNode } from "react";
|
||||
import JacdacContext from "./Context";
|
||||
import { BusState, JDBus } from "../../../src/jdom/bus";
|
||||
import { createUSBBus } from "../../../src/jdom/usb";
|
||||
|
@ -36,7 +36,8 @@ GamepadHostManager.start(bus);
|
|||
if (typeof window !== "undefined")
|
||||
new IFrameBridgeClient(bus, args.frameId); // start bridge
|
||||
|
||||
const JacdacProvider = ({ children }) => {
|
||||
export default function JacdacProvider(props: { children: ReactNode }) {
|
||||
const { children } = props;
|
||||
const [firstConnect, setFirstConnect] = useState(false)
|
||||
const [connectionState, setConnectionState] = useState(bus.connectionState);
|
||||
|
||||
|
@ -60,5 +61,3 @@ const JacdacProvider = ({ children }) => {
|
|||
</JacdacContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export default JacdacProvider;
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit ec18b3e91202f31c6a5165fe3acb11ce2eaad71b
|
||||
Subproject commit 55577e7b798d70eca9704fca22950537779a0c96
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
10
package.json
10
package.json
|
@ -24,7 +24,7 @@
|
|||
},
|
||||
"scripts": {
|
||||
"pullsubmodules": "git submodule update --init --recursive",
|
||||
"lint": "node node_modules/tslint/bin/tslint --project tsconfig.json",
|
||||
"lint": "node node_modules/eslint/bin/eslint.js src/**/*.ts docs/src/**/*.ts docs/src/**/*.tsx",
|
||||
"predist": "rm -rf dist",
|
||||
"dist": "node node_modules/rollup/dist/bin/rollup -c rollup.config.ts",
|
||||
"predistdocs": "cd docs && node node_modules/gatsby/cli.js clean && cd .. && node ./tools/prepare.js",
|
||||
|
@ -134,12 +134,16 @@
|
|||
"@semantic-release/git": "^9.0.0",
|
||||
"@types/expect": "^24.3.0",
|
||||
"@types/mocha": "^8.2.1",
|
||||
"@typescript-eslint/eslint-plugin": "^4.15.2",
|
||||
"@typescript-eslint/parser": "^4.15.2",
|
||||
"cli": "^1.0.1",
|
||||
"colors": "^1.4.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"lint-staged": "^10.5.4",
|
||||
"eslint": "^7.20.0",
|
||||
"eslint-plugin-react": "^7.22.0",
|
||||
"lodash.camelcase": "^4.3.0",
|
||||
"mocha": "^8.3.0",
|
||||
"prettier": "2.2.1",
|
||||
"prompt": "^1.1.0",
|
||||
"replace-in-file": "^6.2.0",
|
||||
"rollup": "^2.38.5",
|
||||
|
@ -151,8 +155,6 @@
|
|||
"semantic-release": "^17.3.8",
|
||||
"shelljs": "^0.8.4",
|
||||
"ts-node": "^7.0.1",
|
||||
"tslint": "^6",
|
||||
"tslint-microsoft-contrib": "^6",
|
||||
"typescript": "^4.1.4",
|
||||
"webusb": "^2.2.0"
|
||||
},
|
||||
|
|
|
@ -1,31 +1,40 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/***
|
||||
* Jacdac service/device specification to DTDL
|
||||
*
|
||||
*
|
||||
* DTDL specification: https://github.com/Azure/opendigitaltwins-dtdl/blob/master/DTDL/v2/dtdlv2.md.
|
||||
*/
|
||||
|
||||
import { serviceSpecificationFromClassIdentifier, serviceSpecificationFromName } from "../jdom/spec";
|
||||
import { uniqueMap } from "../jdom/utils";
|
||||
import {
|
||||
serviceSpecificationFromClassIdentifier,
|
||||
serviceSpecificationFromName,
|
||||
} from "../jdom/spec"
|
||||
import { uniqueMap } from "../jdom/utils"
|
||||
|
||||
export const DTDL_REFERENCE_URL = "https://github.com/Azure/opendigitaltwins-dtdl/blob/master/DTDL/v2/dtdlv2.md"
|
||||
export const DTDL_REFERENCE_URL =
|
||||
"https://github.com/Azure/opendigitaltwins-dtdl/blob/master/DTDL/v2/dtdlv2.md"
|
||||
export const DTDL_NAME = "Digital Twins Definition Language"
|
||||
export const DTDL_CONTEXT = "dtmi:dtdl:context;2";
|
||||
export const DTDL_CONTEXT = "dtmi:dtdl:context;2"
|
||||
|
||||
// https://github.com/Azure/digital-twin-model-identifier
|
||||
// ^dtmi:(?:_+[A-Za-z0-9]|[A-Za-z])(?:[A-Za-z0-9_]*[A-Za-z0-9])?(?::(?:_+[A-Za-z0-9]|[A-Za-z])(?:[A-Za-z0-9_]*[A-Za-z0-9])?)*;[1-9][0-9]{0,8}$
|
||||
function toDTMI(segments: (string | number)[], version?: number) {
|
||||
return `dtmi:jacdac:${[...segments]
|
||||
.map(seg => seg === undefined ? "???" : typeof seg === "string" ? seg : `x${seg.toString(16)}`)
|
||||
.map(seg => seg.replace(/(-|_)/g, ''))
|
||||
.join(':')};${version !== undefined ? version : 1}`.toLowerCase();
|
||||
.map(seg =>
|
||||
seg === undefined
|
||||
? "???"
|
||||
: typeof seg === "string"
|
||||
? seg
|
||||
: `x${seg.toString(16)}`
|
||||
)
|
||||
.map(seg => seg.replace(/(-|_)/g, ""))
|
||||
.join(":")};${version !== undefined ? version : 1}`.toLowerCase()
|
||||
}
|
||||
|
||||
function toUnit(pkt: jdspec.PacketInfo) {
|
||||
if (pkt.fields.length !== 1)
|
||||
return undefined;
|
||||
const field = pkt.fields[0];
|
||||
if (!field.unit)
|
||||
return undefined;
|
||||
if (pkt.fields.length !== 1) return undefined
|
||||
const field = pkt.fields[0]
|
||||
if (!field.unit) return undefined
|
||||
|
||||
/**
|
||||
* type Unit = "m" | "kg" | "g" | "s" | "A" | "K" | "cd" | "mol" | "Hz" | "rad" | "sr" | "N" | "Pa" | "J" | "W" | "C" | "V" | "F" | "Ohm"
|
||||
|
@ -37,94 +46,92 @@ function toUnit(pkt: jdspec.PacketInfo) {
|
|||
| "varh" | "kvarh" | "kVAh" | "Wh/km" | "KiB" | "GB" | "Mbit/s" | "B/s" | "MB/s" | "mV" | "mA" | "dBm" | "ug/m3"
|
||||
| "mm/h" | "m/h" | "ppm" | "/100" | "/1000" | "hPa" | "mm" | "cm" | "km" | "km/h";
|
||||
*/
|
||||
const units: jdspec.SMap<{ semantic: string; unit: string; }> = {
|
||||
const units: jdspec.SMap<{ semantic: string; unit: string }> = {
|
||||
"m/s2": {
|
||||
semantic: "Acceleration",
|
||||
unit: "metrePerSecondSquared"
|
||||
unit: "metrePerSecondSquared",
|
||||
},
|
||||
"rad": {
|
||||
rad: {
|
||||
semantic: "Angle",
|
||||
unit: "radian"
|
||||
unit: "radian",
|
||||
},
|
||||
"rad/s": {
|
||||
semantic: "AngularVelocity",
|
||||
unit: "radianPerSecond"
|
||||
unit: "radianPerSecond",
|
||||
},
|
||||
"rad/s2": {
|
||||
semantic: "AngularAcceleration",
|
||||
unit: "radianPerSecondSquared"
|
||||
unit: "radianPerSecondSquared",
|
||||
},
|
||||
"m": {
|
||||
m: {
|
||||
semantic: "Length",
|
||||
unit: "metre"
|
||||
unit: "metre",
|
||||
},
|
||||
"m2": {
|
||||
m2: {
|
||||
semantic: "Area",
|
||||
unit: "squareMetre"
|
||||
unit: "squareMetre",
|
||||
},
|
||||
"s": {
|
||||
s: {
|
||||
semantic: "TimeSpan",
|
||||
unit: "second"
|
||||
unit: "second",
|
||||
},
|
||||
"ms": {
|
||||
ms: {
|
||||
semantic: "TimeSpan",
|
||||
unit: "millisecond"
|
||||
unit: "millisecond",
|
||||
},
|
||||
"us": {
|
||||
us: {
|
||||
semantic: "TimeSpan",
|
||||
unit: "microsecond"
|
||||
unit: "microsecond",
|
||||
},
|
||||
"K": {
|
||||
K: {
|
||||
semantic: "Temperature",
|
||||
unit: "kelvin"
|
||||
unit: "kelvin",
|
||||
},
|
||||
"C": {
|
||||
C: {
|
||||
semantic: "Temperature",
|
||||
unit: "degreeCelsius"
|
||||
unit: "degreeCelsius",
|
||||
},
|
||||
"F": {
|
||||
F: {
|
||||
semantic: "Temperature",
|
||||
unit: "degreeFahrenheit"
|
||||
unit: "degreeFahrenheit",
|
||||
},
|
||||
"g": {
|
||||
g: {
|
||||
semantic: "Acceleration",
|
||||
unit: "gForce"
|
||||
unit: "gForce",
|
||||
},
|
||||
"mA": {
|
||||
mA: {
|
||||
semantic: "Current",
|
||||
unit: "milliampere"
|
||||
unit: "milliampere",
|
||||
},
|
||||
"uA": {
|
||||
uA: {
|
||||
semantic: "Current",
|
||||
unit: "microampere"
|
||||
unit: "microampere",
|
||||
},
|
||||
"A": {
|
||||
A: {
|
||||
semantic: "Current",
|
||||
unit: "ampere"
|
||||
unit: "ampere",
|
||||
},
|
||||
"mV": {
|
||||
mV: {
|
||||
semantic: "Voltage",
|
||||
unit: "millivolt"
|
||||
unit: "millivolt",
|
||||
},
|
||||
"uV": {
|
||||
uV: {
|
||||
semantic: "Voltage",
|
||||
unit: "microvolt"
|
||||
unit: "microvolt",
|
||||
},
|
||||
"V": {
|
||||
V: {
|
||||
semantic: "Voltage",
|
||||
unit: "volt"
|
||||
unit: "volt",
|
||||
},
|
||||
};
|
||||
const unit = units[field.unit];
|
||||
if (unit)
|
||||
return unit;
|
||||
}
|
||||
const unit = units[field.unit]
|
||||
if (unit) return unit
|
||||
|
||||
// ignoring some known units
|
||||
if (["#", "/"].indexOf(field.unit) > -1)
|
||||
return undefined;
|
||||
if (["#", "/"].indexOf(field.unit) > -1) return undefined
|
||||
|
||||
//console.warn(`unsupported unit ${field.unit}`)
|
||||
return undefined;
|
||||
return undefined
|
||||
}
|
||||
|
||||
// https://github.com/Azure/opendigitaltwins-dtdl/blob/master/DTDL/v2/dtdlv2.md#primitive-schemas
|
||||
|
@ -137,37 +144,36 @@ function enumSchema(srv: jdspec.ServiceSpec, en: jdspec.EnumInfo): DTDLSchema {
|
|||
const dtdl = {
|
||||
"@type": "Enum",
|
||||
"@id": enumDTDI(srv, en),
|
||||
"valueSchema": "integer",
|
||||
"enumValues": Object.keys(en.members).map(k => ({
|
||||
valueSchema: "integer",
|
||||
enumValues: Object.keys(en.members).map(k => ({
|
||||
name: escapeName(k),
|
||||
displayName: k,
|
||||
enumValue: en.members[k]
|
||||
}))
|
||||
enumValue: en.members[k],
|
||||
})),
|
||||
}
|
||||
return dtdl;
|
||||
return dtdl
|
||||
}
|
||||
|
||||
function fieldType(srv: jdspec.ServiceSpec, pkt: jdspec.PacketInfo, field: jdspec.PacketMember) {
|
||||
let type: string;
|
||||
if (field.type == "bool")
|
||||
type = "boolean";
|
||||
else if (field.isFloat)
|
||||
type = "float";
|
||||
function fieldType(
|
||||
srv: jdspec.ServiceSpec,
|
||||
pkt: jdspec.PacketInfo,
|
||||
field: jdspec.PacketMember
|
||||
) {
|
||||
let type: string
|
||||
if (field.type == "bool") type = "boolean"
|
||||
else if (field.isFloat) type = "float"
|
||||
else if (field.isSimpleType) {
|
||||
if (/^(u|i)/.test(field.type))
|
||||
type = "integer";
|
||||
if (/^(u|i)/.test(field.type)) type = "integer"
|
||||
else if (field.type === "B")
|
||||
// base64 encoded binary data
|
||||
type = "string";
|
||||
}
|
||||
else if (field.type === "string" || field.type == "string0")
|
||||
type = "string";
|
||||
else if (field.shift && /^(u|i)/.test(field.type))
|
||||
type = "float"; // decimal type
|
||||
type = "string"
|
||||
} else if (field.type === "string" || field.type == "string0")
|
||||
type = "string"
|
||||
else if (field.shift && /^(u|i)/.test(field.type)) type = "float"
|
||||
// decimal type
|
||||
else {
|
||||
const en = srv.enums[field.type];
|
||||
if (en)
|
||||
type = enumDTDI(srv, en);
|
||||
const en = srv.enums[field.type]
|
||||
if (en) type = enumDTDI(srv, en)
|
||||
}
|
||||
|
||||
//if (!type)
|
||||
|
@ -175,7 +181,7 @@ function fieldType(srv: jdspec.ServiceSpec, pkt: jdspec.PacketInfo, field: jdspe
|
|||
|
||||
return {
|
||||
name: field.name == "_" ? pkt.name : field.name,
|
||||
type: type
|
||||
type: type,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -183,7 +189,7 @@ function fieldType(srv: jdspec.ServiceSpec, pkt: jdspec.PacketInfo, field: jdspe
|
|||
function objectSchema(schemas: DTDLSchema[]): DTDLSchema {
|
||||
return {
|
||||
"@type": "Object",
|
||||
"fields": schemas
|
||||
fields: schemas,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -191,29 +197,31 @@ function objectSchema(schemas: DTDLSchema[]): DTDLSchema {
|
|||
function arraySchema(schema: string | DTDLSchema): DTDLSchema {
|
||||
return {
|
||||
"@type": "Array",
|
||||
"elementSchema": schema
|
||||
elementSchema: schema,
|
||||
}
|
||||
}
|
||||
|
||||
// converts JADAC pkt data layout into a DTDL schema
|
||||
function toSchema(srv: jdspec.ServiceSpec, pkt: jdspec.PacketInfo, supportsArray?: boolean): string | DTDLSchema {
|
||||
const fields = pkt.fields.map(field => fieldType(srv, pkt, field));
|
||||
if (!fields.length)
|
||||
return undefined;
|
||||
function toSchema(
|
||||
srv: jdspec.ServiceSpec,
|
||||
pkt: jdspec.PacketInfo,
|
||||
supportsArray?: boolean
|
||||
): string | DTDLSchema {
|
||||
const fields = pkt.fields.map(field => fieldType(srv, pkt, field))
|
||||
if (!fields.length) return undefined
|
||||
|
||||
// a single data entry
|
||||
if (fields.length === 1 && !pkt.fields[0].startRepeats)
|
||||
return fields[0].type;
|
||||
return fields[0].type
|
||||
|
||||
// map fields into schema
|
||||
const schemas: DTDLSchema[] =
|
||||
fields.map(field => ({
|
||||
name: field.name,
|
||||
schema: field.type
|
||||
}))
|
||||
const schemas: DTDLSchema[] = fields.map(field => ({
|
||||
name: field.name,
|
||||
schema: field.type,
|
||||
}))
|
||||
|
||||
// is there an array?
|
||||
const repeatIndex = pkt.fields.findIndex(field => field.startRepeats);
|
||||
const repeatIndex = pkt.fields.findIndex(field => field.startRepeats)
|
||||
|
||||
if (repeatIndex < 0) {
|
||||
// no array
|
||||
|
@ -224,34 +232,39 @@ function toSchema(srv: jdspec.ServiceSpec, pkt: jdspec.PacketInfo, supportsArray
|
|||
// check if arrays are supported
|
||||
if (!supportsArray) {
|
||||
//console.warn(`arrays not supported in ${srv.shortName}.${pkt.name}`)
|
||||
return undefined;
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (repeatIndex == 0) {
|
||||
// the whole structure is an array
|
||||
return arraySchema(objectSchema(schemas))
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// split fields into prelude and array data
|
||||
const nonRepeat = schemas.slice(0, repeatIndex);
|
||||
const repeats = schemas.slice(repeatIndex);
|
||||
const nonRepeat = schemas.slice(0, repeatIndex)
|
||||
const repeats = schemas.slice(repeatIndex)
|
||||
return objectSchema([
|
||||
...nonRepeat,
|
||||
{
|
||||
name: "repeat",
|
||||
schema: arraySchema(repeats.length > 1 ? objectSchema(repeats) : repeats[0])
|
||||
}
|
||||
]);
|
||||
schema: arraySchema(
|
||||
repeats.length > 1 ? objectSchema(repeats) : repeats[0]
|
||||
),
|
||||
},
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
function packetToDTDL(srv: jdspec.ServiceSpec, pkt: jdspec.PacketInfo): DTDLContent {
|
||||
function packetToDTDL(
|
||||
srv: jdspec.ServiceSpec,
|
||||
pkt: jdspec.PacketInfo
|
||||
): DTDLContent {
|
||||
const types: jdspec.SMap<string> = {
|
||||
"const": "Property",
|
||||
"rw": "Property",
|
||||
"ro": "Telemetry",
|
||||
"event": "Telemetry"
|
||||
const: "Property",
|
||||
rw: "Property",
|
||||
ro: "Telemetry",
|
||||
event: "Telemetry",
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const dtdl: any = {
|
||||
"@type": types[pkt.kind] || `Unsupported${pkt.kind}`,
|
||||
name: pkt.name,
|
||||
|
@ -262,133 +275,142 @@ function packetToDTDL(srv: jdspec.ServiceSpec, pkt: jdspec.PacketInfo): DTDLCont
|
|||
case "report":
|
||||
case "command":
|
||||
// https://docs.microsoft.com/en-us/azure/digital-twins/concepts-models#azure-digital-twins-dtdl-implementation-specifics
|
||||
return undefined;
|
||||
return undefined
|
||||
case "const":
|
||||
case "rw":
|
||||
case "ro":
|
||||
case "event":
|
||||
const unit = toUnit(pkt);
|
||||
case "event": {
|
||||
const unit = toUnit(pkt)
|
||||
if (unit) {
|
||||
dtdl.unit = unit.unit;
|
||||
dtdl.unit = unit.unit
|
||||
}
|
||||
dtdl.schema = toSchema(srv, pkt, false)
|
||||
if (pkt.kind === "rw")
|
||||
dtdl.writable = true;
|
||||
if (pkt.kind === "rw") dtdl.writable = true
|
||||
if (!dtdl.schema && pkt.kind === "event") {
|
||||
// keep a count of the events
|
||||
dtdl["@type"] = [dtdl["@type"], "Event"]
|
||||
dtdl.schema = toDTMI([srv.classIdentifier, "event"]);
|
||||
}
|
||||
else if (unit && unit.semantic)
|
||||
dtdl.schema = toDTMI([srv.classIdentifier, "event"])
|
||||
} else if (unit && unit.semantic)
|
||||
dtdl["@type"] = [dtdl["@type"], unit.semantic]
|
||||
break;
|
||||
break
|
||||
}
|
||||
default:
|
||||
//console.log(`unknown packet kind ${pkt.kind}`)
|
||||
break;
|
||||
break
|
||||
}
|
||||
|
||||
if (!dtdl.schema) {
|
||||
//console.log(`unknown schema for ${srv.name}.${pkt.name}`);
|
||||
return undefined;
|
||||
return undefined
|
||||
}
|
||||
|
||||
return dtdl;
|
||||
return dtdl
|
||||
}
|
||||
|
||||
|
||||
interface DTDLNode {
|
||||
'@type'?: string;
|
||||
'@id'?: string;
|
||||
"@type"?: string
|
||||
"@id"?: string
|
||||
// 1-64 characters
|
||||
// ^[a-zA-Z](?:[a-zA-Z0-9_]*[a-zA-Z0-9])?$
|
||||
name?: string;
|
||||
displayName?: string,
|
||||
description?: string;
|
||||
name?: string
|
||||
displayName?: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
interface DTDLSchema extends DTDLNode {
|
||||
fields?: DTDLSchema[];
|
||||
schema?: string | DTDLSchema;
|
||||
elementSchema?: string | DTDLSchema;
|
||||
fields?: DTDLSchema[]
|
||||
schema?: string | DTDLSchema
|
||||
elementSchema?: string | DTDLSchema
|
||||
}
|
||||
|
||||
interface DTDLContent extends DTDLNode {
|
||||
'@type': "Property" | "Command" | "Component" | "Interface";
|
||||
unit?: string;
|
||||
schema?: string | DTDLSchema;
|
||||
"@type": "Property" | "Command" | "Component" | "Interface"
|
||||
unit?: string
|
||||
schema?: string | DTDLSchema
|
||||
}
|
||||
|
||||
interface DTDLInterface extends DTDLContent {
|
||||
contents: DTDLContent[];
|
||||
extends?: string | string[];
|
||||
schemas?: (DTDLSchema | DTDLInterface)[];
|
||||
'@context'?: string;
|
||||
contents: DTDLContent[]
|
||||
extends?: string | string[]
|
||||
schemas?: (DTDLSchema | DTDLInterface)[]
|
||||
"@context"?: string
|
||||
}
|
||||
|
||||
export function escapeName(name: string) {
|
||||
name = name.trim().replace(/[^a-zA-Z0-9_]/g, '_');
|
||||
if (!/^[a-zA-Z]/.test(name))
|
||||
name = "a" + name;
|
||||
name = name[0].toLowerCase() + name.slice(1);
|
||||
return name.slice(0, 64);
|
||||
name = name.trim().replace(/[^a-zA-Z0-9_]/g, "_")
|
||||
if (!/^[a-zA-Z]/.test(name)) name = "a" + name
|
||||
name = name[0].toLowerCase() + name.slice(1)
|
||||
return name.slice(0, 64)
|
||||
}
|
||||
|
||||
function escapeDisplayName(name: string) {
|
||||
return name.slice(0, 64);
|
||||
return name.slice(0, 64)
|
||||
}
|
||||
|
||||
export function serviceSpecificationToDTDL(srv: jdspec.ServiceSpec): DTDLInterface {
|
||||
export function serviceSpecificationToDTDL(
|
||||
srv: jdspec.ServiceSpec
|
||||
): DTDLInterface {
|
||||
const dtdl: DTDLInterface = {
|
||||
"@type": "Interface",
|
||||
"@id": serviceSpecificationDTMI(srv),
|
||||
"displayName": escapeDisplayName(srv.name),
|
||||
"description": srv.notes["short"],
|
||||
"contents": srv.packets
|
||||
displayName: escapeDisplayName(srv.name),
|
||||
description: srv.notes["short"],
|
||||
contents: srv.packets
|
||||
.filter(pkt => !pkt.derived && !pkt.internal)
|
||||
.map(pkt => {
|
||||
try {
|
||||
return packetToDTDL(srv, pkt)
|
||||
} catch (e) {
|
||||
console.log(`failed to generate DTDL for ${srv.name}`, e);
|
||||
return undefined;
|
||||
console.log(`failed to generate DTDL for ${srv.name}`, e)
|
||||
return undefined
|
||||
}
|
||||
}).filter(c => !!c)
|
||||
})
|
||||
.filter(c => !!c),
|
||||
}
|
||||
if (srv.extends.length)
|
||||
dtdl.extends = srv.extends.map(id => serviceSpecificationDTMI(serviceSpecificationFromName(id)))
|
||||
dtdl.extends = srv.extends.map(id =>
|
||||
serviceSpecificationDTMI(serviceSpecificationFromName(id))
|
||||
)
|
||||
|
||||
const hasEvents = srv.packets.find(pkt => pkt.kind === "event");
|
||||
const hasEnums = Object.keys(srv.enums).length;
|
||||
const hasEvents = srv.packets.find(pkt => pkt.kind === "event")
|
||||
const hasEnums = Object.keys(srv.enums).length
|
||||
if (hasEvents || hasEnums) {
|
||||
dtdl.schemas = [];
|
||||
dtdl.schemas = []
|
||||
if (hasEvents)
|
||||
dtdl.schemas.push({
|
||||
"@id": toDTMI([srv.classIdentifier, "event"]),
|
||||
"@type": "Object",
|
||||
"fields": [{
|
||||
"name": "count",
|
||||
"schema": "integer"
|
||||
}]
|
||||
});
|
||||
fields: [
|
||||
{
|
||||
name: "count",
|
||||
schema: "integer",
|
||||
},
|
||||
],
|
||||
})
|
||||
if (hasEnums)
|
||||
dtdl.schemas = dtdl.schemas.concat(Object.keys(srv.enums).map(en => enumSchema(srv, srv.enums[en])));
|
||||
dtdl.schemas = dtdl.schemas.concat(
|
||||
Object.keys(srv.enums).map(en => enumSchema(srv, srv.enums[en]))
|
||||
)
|
||||
}
|
||||
dtdl["@context"] = DTDL_CONTEXT
|
||||
return dtdl;
|
||||
return dtdl
|
||||
}
|
||||
|
||||
export function serviceSpecificationToComponent(srv: jdspec.ServiceSpec, name: string): any {
|
||||
export function serviceSpecificationToComponent(
|
||||
srv: jdspec.ServiceSpec,
|
||||
name: string
|
||||
): any {
|
||||
const dtdl = {
|
||||
"@type": "Component",
|
||||
"name": name,
|
||||
"displayName": escapeDisplayName(srv.name),
|
||||
"schema": serviceSpecificationDTMI(srv)
|
||||
name: name,
|
||||
displayName: escapeDisplayName(srv.name),
|
||||
schema: serviceSpecificationDTMI(srv),
|
||||
}
|
||||
return dtdl;
|
||||
return dtdl
|
||||
}
|
||||
|
||||
export interface DTDLGenerationOptions {
|
||||
inlineServices?: boolean; // generate all services
|
||||
inlineServices?: boolean // generate all services
|
||||
}
|
||||
|
||||
export function serviceSpecificationDTMI(srv: jdspec.ServiceSpec) {
|
||||
|
@ -396,44 +418,51 @@ export function serviceSpecificationDTMI(srv: jdspec.ServiceSpec) {
|
|||
}
|
||||
|
||||
export function deviceSpecificationDTMI(dev: jdspec.DeviceSpec) {
|
||||
return toDTMI(["devices", dev.id.replace(/-/g, ':')]);
|
||||
return toDTMI(["devices", dev.id.replace(/-/g, ":")])
|
||||
}
|
||||
|
||||
export function DTMIToRoute(dtmi: string) {
|
||||
const route = dtmi.toLowerCase().replace(/;/, '-')
|
||||
.replace(/:/g, "/") + ".json";
|
||||
return route;
|
||||
const route =
|
||||
dtmi.toLowerCase().replace(/;/, "-").replace(/:/g, "/") + ".json"
|
||||
return route
|
||||
}
|
||||
|
||||
export function deviceSpecificationToDTDL(dev: jdspec.DeviceSpec, options?: DTDLGenerationOptions): any {
|
||||
const services = dev.services.map(srv => serviceSpecificationFromClassIdentifier(srv));
|
||||
const uniqueServices = uniqueMap(services, srv => srv.classIdentifier.toString(), srv => srv);
|
||||
const schemas = uniqueServices.map(srv => serviceSpecificationToDTDL(srv));
|
||||
export function deviceSpecificationToDTDL(
|
||||
dev: jdspec.DeviceSpec,
|
||||
options?: DTDLGenerationOptions
|
||||
): any {
|
||||
const services = dev.services.map(srv =>
|
||||
serviceSpecificationFromClassIdentifier(srv)
|
||||
)
|
||||
const uniqueServices = uniqueMap(
|
||||
services,
|
||||
srv => srv.classIdentifier.toString(),
|
||||
srv => srv
|
||||
)
|
||||
const schemas = uniqueServices.map(srv => serviceSpecificationToDTDL(srv))
|
||||
|
||||
// allocate names
|
||||
let names: string[] = [];
|
||||
services.forEach((srv, i) => {
|
||||
let name = escapeName(srv.shortId || srv.shortName)
|
||||
if (names.indexOf(name) < 0)
|
||||
names.push(name)
|
||||
const names: string[] = []
|
||||
services.forEach((srv) => {
|
||||
const name = escapeName(srv.shortId || srv.shortName)
|
||||
if (names.indexOf(name) < 0) names.push(name)
|
||||
else {
|
||||
let count = 2;
|
||||
while (names.indexOf(name + count) > -1)
|
||||
count++;
|
||||
names.push(name + count);
|
||||
let count = 2
|
||||
while (names.indexOf(name + count) > -1) count++
|
||||
names.push(name + count)
|
||||
}
|
||||
})
|
||||
|
||||
const dtdl: DTDLInterface = {
|
||||
"@type": "Interface",
|
||||
"@id": deviceSpecificationDTMI(dev),
|
||||
"displayName": escapeDisplayName(dev.name),
|
||||
"description": dev.description,
|
||||
"contents": services.map((srv, i) => serviceSpecificationToComponent(srv, names[i])),
|
||||
"@context": DTDL_CONTEXT
|
||||
displayName: escapeDisplayName(dev.name),
|
||||
description: dev.description,
|
||||
contents: services.map((srv, i) =>
|
||||
serviceSpecificationToComponent(srv, names[i])
|
||||
),
|
||||
"@context": DTDL_CONTEXT,
|
||||
}
|
||||
if (options?.inlineServices)
|
||||
return [dtdl, ...schemas]
|
||||
else
|
||||
return dtdl
|
||||
if (options?.inlineServices) return [dtdl, ...schemas]
|
||||
else return dtdl
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const cli = require("cli")
|
||||
const fs = require("fs-extra")
|
||||
import { PACKET_PROCESS } from "../jdom/constants"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { SystemReg, READING_SENT } from "../jdom/constants";
|
||||
import { SystemReg } from "../jdom/constants";
|
||||
import RegisterHost from "../jdom/registerhost";
|
||||
import { LevelDetector } from "./leveldetector";
|
||||
import SensorServiceHost, { SensorServiceOptions } from "./sensorservicehost";
|
||||
|
|
|
@ -47,6 +47,7 @@ function getStringOffset(packetType: number) {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
function getMaxStringLength(packetType: number) {
|
||||
switch (packetType) {
|
||||
case PACKET_TYPE_STRING:
|
||||
|
@ -58,6 +59,7 @@ function getMaxStringLength(packetType: number) {
|
|||
return undefined;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
function truncateString(str: string) {
|
||||
// TODO
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
SRV_DISTANCE, SRV_E_CO2, SRV_HUMIDITY, SRV_LED_PIXEL, SRV_MATRIX_KEYPAD,
|
||||
SRV_MOTOR, SRV_POTENTIOMETER,
|
||||
SRV_PROTO_TEST, SRV_RAIN_GAUGE, SRV_RELAY,
|
||||
SRV_ROLE_MANAGER, SRV_JOYSTICK,
|
||||
SRV_JOYSTICK,
|
||||
SRV_ROTARY_ENCODER,
|
||||
SRV_SERVO, SRV_SETTINGS, SRV_SWITCH, SRV_THERMOMETER, SRV_TRAFFIC_LIGHT,
|
||||
SRV_VIBRATION_MOTOR, SRV_TVOC, SRV_WIND_DIRECTION, SRV_WIND_SPEED,
|
||||
|
@ -22,7 +22,7 @@ import {
|
|||
} from "../jdom/constants";
|
||||
import DeviceHost from "../jdom/devicehost";
|
||||
import ProtocolTestServiceHost from "../jdom/protocoltestservicehost";
|
||||
import ServiceHost, { ServiceHostOptions } from "../jdom/servicehost";
|
||||
import ServiceHost from "../jdom/servicehost";
|
||||
import ArcadeGamepadServiceHost from "./arcadegamepadservicehost";
|
||||
import ButtonServiceHost from "./buttonservicehost";
|
||||
import BuzzerServiceHost from "./buzzerservicehost";
|
||||
|
@ -607,7 +607,7 @@ const _hosts: {
|
|||
{
|
||||
name: "relay 4x (SSR/5A)",
|
||||
serviceClasses: [SRV_RELAY],
|
||||
services: () => Array(4).fill(0).map(_ => new ServiceHost(SRV_RELAY, {
|
||||
services: () => Array(4).fill(0).map(() => new ServiceHost(SRV_RELAY, {
|
||||
intensityValues: [false],
|
||||
variant: RelayVariant.SolidState,
|
||||
registerValues: [
|
||||
|
@ -654,12 +654,12 @@ const _hosts: {
|
|||
{
|
||||
name: "servo x 2",
|
||||
serviceClasses: [SRV_SERVO],
|
||||
services: () => Array(2).fill(0).map((_, i) => new ServoServiceHost(microServoOptions))
|
||||
services: () => Array(2).fill(0).map(() => new ServoServiceHost(microServoOptions))
|
||||
},
|
||||
{
|
||||
name: "servo x 4",
|
||||
serviceClasses: [SRV_SERVO],
|
||||
services: () => Array(4).fill(0).map((_, i) => new ServoServiceHost(microServoOptions))
|
||||
services: () => Array(4).fill(0).map(() => new ServoServiceHost(microServoOptions))
|
||||
},
|
||||
{
|
||||
name: "servo x 6",
|
||||
|
|
|
@ -51,7 +51,7 @@ function hsv(hue: number, sat: number, val: number): RGB {
|
|||
const rampup_adj_with_floor = (rampup_amp_adj + brightness_floor);
|
||||
const rampdown_adj_with_floor = (rampdown_amp_adj + brightness_floor);
|
||||
|
||||
let r: number = 0, g: number = 0, b: number = 0;
|
||||
let r = 0, g = 0, b = 0;
|
||||
if (section) {
|
||||
if (section == 1) {
|
||||
// section 1: 0x40..0x7F
|
||||
|
@ -107,20 +107,20 @@ export default class LedPixelServiceHost extends ServiceHost {
|
|||
|
||||
private pxbuffer: Uint8Array = new Uint8Array(0);
|
||||
|
||||
private prog_mode: number = 0;
|
||||
private prog_tmpmode: number = 0;
|
||||
private prog_mode = 0;
|
||||
private prog_tmpmode = 0;
|
||||
|
||||
private range_start: number = 0;
|
||||
private range_end: number = 0;
|
||||
private range_len: number = 0;
|
||||
private range_ptr: number = 0;
|
||||
private range_start = 0;
|
||||
private range_end = 0;
|
||||
private range_len = 0;
|
||||
private range_ptr = 0;
|
||||
|
||||
private prog_ptr: number = 0;
|
||||
private prog_size: number = 0;
|
||||
private prog_ptr = 0;
|
||||
private prog_size = 0;
|
||||
private prog_data = new Uint8Array(0);
|
||||
|
||||
private dirty: boolean = true;
|
||||
private inited: boolean = false;
|
||||
private dirty = true;
|
||||
private inited = false;
|
||||
|
||||
power_enable = false;
|
||||
|
||||
|
@ -215,7 +215,7 @@ export default class LedPixelServiceHost extends ServiceHost {
|
|||
return false;
|
||||
|
||||
const p = this.pxbuffer;
|
||||
let pi = this.range_ptr++ * 3;
|
||||
const pi = this.range_ptr++ * 3;
|
||||
// fast path
|
||||
if (this.prog_tmpmode == LIGHT_MODE_REPLACE) {
|
||||
p[pi + 0] = c.r;
|
||||
|
@ -267,7 +267,7 @@ export default class LedPixelServiceHost extends ServiceHost {
|
|||
let current_prev = 0;
|
||||
let di = 0;
|
||||
while (n--) {
|
||||
let v = pxbuffer[di++];
|
||||
const v = pxbuffer[di++];
|
||||
current += SCALE0(v, intensity);
|
||||
current_prev += SCALE0(v, prev_intensity);
|
||||
current_full += v;
|
||||
|
@ -279,8 +279,8 @@ export default class LedPixelServiceHost extends ServiceHost {
|
|||
current_full *= 46;
|
||||
|
||||
// 14mA is the chip at 48MHz, 930uA per LED is static
|
||||
let base_current = 14000 + 930 * numpixels;
|
||||
let current_limit = maxpower * 1000 - base_current;
|
||||
const base_current = 14000 + 930 * numpixels;
|
||||
const current_limit = maxpower * 1000 - base_current;
|
||||
|
||||
if (current <= current_limit) {
|
||||
this.intensity = intensity;
|
||||
|
@ -443,7 +443,7 @@ export default class LedPixelServiceHost extends ServiceHost {
|
|||
|
||||
let first = range_start * 3;
|
||||
let middle = (range_start + shift) * 3;
|
||||
let last = range_end * 3;
|
||||
const last = range_end * 3;
|
||||
let next = middle;
|
||||
|
||||
while (first != next) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { CHANGE, LedReg, LedVariant, REPORT_UPDATE, SRV_LED } from "../jdom/constants";
|
||||
import { CHANGE, LedReg, LedVariant, SRV_LED } from "../jdom/constants";
|
||||
import { JDEventSource } from "../jdom/eventsource";
|
||||
import RegisterHost from "../jdom/registerhost";
|
||||
import ServiceHost from "../jdom/servicehost";
|
||||
|
|
|
@ -22,7 +22,7 @@ export default class RealTimeClockServiceHost
|
|||
extends SensorServiceHost<RealTimeClockReadingType> {
|
||||
readonly error: RegisterHost<[number]>;
|
||||
readonly precision: RegisterHost<[number]>;
|
||||
private lastSecond: number = 0;
|
||||
private lastSecond = 0;
|
||||
|
||||
constructor() {
|
||||
super(SRV_REAL_TIME_CLOCK, {
|
||||
|
@ -47,7 +47,7 @@ export default class RealTimeClockServiceHost
|
|||
}
|
||||
|
||||
private handleSetTime(pkt: Packet) {
|
||||
console.log(`set time`)
|
||||
console.log(`set time`, { pkt })
|
||||
}
|
||||
|
||||
private refreshTime() {
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import { SystemReg } from "../../jacdac-spec/dist/specconstants";
|
||||
import { CHANGE, READING_SENT, REFRESH, REPORT_UPDATE, SensorReg } from "../jdom/constants";
|
||||
import { CHANGE, READING_SENT, REFRESH, SensorReg } from "../jdom/constants";
|
||||
import { PackedValues } from "../jdom/pack";
|
||||
import RegisterHost from "../jdom/registerhost";
|
||||
import ServiceHost, { ServiceHostOptions } from "../jdom/servicehost";
|
||||
import TrafficLightServiceHost from "./trafficlightservicehost";
|
||||
|
||||
export interface SensorServiceOptions<TReading extends any[]> extends ServiceHostOptions {
|
||||
export interface SensorServiceOptions<TReading extends PackedValues> extends ServiceHostOptions {
|
||||
readingValues?: TReading,
|
||||
readingError?: TReading,
|
||||
streamingInterval?: number,
|
||||
};
|
||||
}
|
||||
|
||||
export default class SensorServiceHost<TReading extends any[]> extends ServiceHost {
|
||||
export default class SensorServiceHost<TReading extends PackedValues> extends ServiceHost {
|
||||
readonly reading: RegisterHost<TReading>;
|
||||
readonly readingError: RegisterHost<TReading>;
|
||||
readonly streamingSamples: RegisterHost<[number]>;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { read16, read32 } from "./utils";
|
||||
import { read16, read32 } from "./utils"
|
||||
|
||||
export enum NumberFormat {
|
||||
Int8LE = 1,
|
||||
|
@ -25,28 +25,49 @@ export enum NumberFormat {
|
|||
|
||||
function fmtInfoCore(fmt: NumberFormat) {
|
||||
switch (fmt) {
|
||||
case NumberFormat.Int8LE: return -1;
|
||||
case NumberFormat.UInt8LE: return 1;
|
||||
case NumberFormat.Int16LE: return -2;
|
||||
case NumberFormat.UInt16LE: return 2;
|
||||
case NumberFormat.Int32LE: return -4;
|
||||
case NumberFormat.UInt32LE: return 4;
|
||||
case NumberFormat.Int64LE: return -8;
|
||||
case NumberFormat.UInt64LE: return 8;
|
||||
case NumberFormat.Int8BE: return -10;
|
||||
case NumberFormat.UInt8BE: return 10;
|
||||
case NumberFormat.Int16BE: return -20;
|
||||
case NumberFormat.UInt16BE: return 20;
|
||||
case NumberFormat.Int32BE: return -40;
|
||||
case NumberFormat.UInt32BE: return 40;
|
||||
case NumberFormat.Int64BE: return -80;
|
||||
case NumberFormat.UInt64BE: return 80;
|
||||
case NumberFormat.Int8LE:
|
||||
return -1
|
||||
case NumberFormat.UInt8LE:
|
||||
return 1
|
||||
case NumberFormat.Int16LE:
|
||||
return -2
|
||||
case NumberFormat.UInt16LE:
|
||||
return 2
|
||||
case NumberFormat.Int32LE:
|
||||
return -4
|
||||
case NumberFormat.UInt32LE:
|
||||
return 4
|
||||
case NumberFormat.Int64LE:
|
||||
return -8
|
||||
case NumberFormat.UInt64LE:
|
||||
return 8
|
||||
case NumberFormat.Int8BE:
|
||||
return -10
|
||||
case NumberFormat.UInt8BE:
|
||||
return 10
|
||||
case NumberFormat.Int16BE:
|
||||
return -20
|
||||
case NumberFormat.UInt16BE:
|
||||
return 20
|
||||
case NumberFormat.Int32BE:
|
||||
return -40
|
||||
case NumberFormat.UInt32BE:
|
||||
return 40
|
||||
case NumberFormat.Int64BE:
|
||||
return -80
|
||||
case NumberFormat.UInt64BE:
|
||||
return 80
|
||||
|
||||
case NumberFormat.Float32LE: return 4;
|
||||
case NumberFormat.Float32BE: return 40;
|
||||
case NumberFormat.Float64LE: return 8;
|
||||
case NumberFormat.Float64BE: return 80;
|
||||
default: throw new Error("unknown format");
|
||||
case NumberFormat.Float32LE:
|
||||
return 4
|
||||
case NumberFormat.Float32BE:
|
||||
return 40
|
||||
case NumberFormat.Float64LE:
|
||||
return 8
|
||||
case NumberFormat.Float64BE:
|
||||
return 80
|
||||
default:
|
||||
throw new Error("unknown format")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,31 +104,35 @@ export function sizeOfNumberFormat(format: NumberFormat) {
|
|||
case NumberFormat.UInt8LE:
|
||||
case NumberFormat.Int8BE:
|
||||
case NumberFormat.UInt8BE:
|
||||
return 1;
|
||||
return 1
|
||||
case NumberFormat.Int16LE:
|
||||
case NumberFormat.UInt16LE:
|
||||
case NumberFormat.Int16BE:
|
||||
case NumberFormat.UInt16BE:
|
||||
return 2;
|
||||
return 2
|
||||
case NumberFormat.Int32LE:
|
||||
case NumberFormat.Int32BE:
|
||||
case NumberFormat.UInt32BE:
|
||||
case NumberFormat.UInt32LE:
|
||||
case NumberFormat.Float32BE:
|
||||
case NumberFormat.Float32LE:
|
||||
return 4;
|
||||
return 4
|
||||
case NumberFormat.UInt64BE:
|
||||
case NumberFormat.Int64BE:
|
||||
case NumberFormat.UInt64LE:
|
||||
case NumberFormat.Int64LE:
|
||||
case NumberFormat.Float64BE:
|
||||
case NumberFormat.Float64LE:
|
||||
return 8;
|
||||
return 8
|
||||
}
|
||||
return 0;
|
||||
return 0
|
||||
}
|
||||
|
||||
export function getNumber(buf: ArrayLike<number>, fmt: NumberFormat, offset: number) {
|
||||
export function getNumber(
|
||||
buf: ArrayLike<number>,
|
||||
fmt: NumberFormat,
|
||||
offset: number
|
||||
) {
|
||||
switch (fmt) {
|
||||
case NumberFormat.UInt8BE:
|
||||
case NumberFormat.UInt8LE:
|
||||
|
@ -126,35 +151,38 @@ export function getNumber(buf: ArrayLike<number>, fmt: NumberFormat, offset: num
|
|||
case NumberFormat.UInt64LE:
|
||||
return read32(buf, offset) + read32(buf, offset + 4) * 0x100000000
|
||||
case NumberFormat.Int64LE:
|
||||
return read32(buf, offset) + (read32(buf, offset + 4) >> 0) * 0x100000000
|
||||
default:
|
||||
return (
|
||||
read32(buf, offset) +
|
||||
(read32(buf, offset + 4) >> 0) * 0x100000000
|
||||
)
|
||||
default: {
|
||||
const inf = fmtInfo(fmt)
|
||||
if (inf.isFloat) {
|
||||
let arr = new Uint8Array(inf.size)
|
||||
const arr = new Uint8Array(inf.size)
|
||||
for (let i = 0; i < inf.size; ++i) {
|
||||
arr[i] = buf[offset + i]
|
||||
}
|
||||
if (inf.swap)
|
||||
arr.reverse()
|
||||
if (inf.size == 4)
|
||||
return new Float32Array(arr.buffer)[0]
|
||||
else
|
||||
return new Float64Array(arr.buffer)[0]
|
||||
if (inf.swap) arr.reverse()
|
||||
if (inf.size == 4) return new Float32Array(arr.buffer)[0]
|
||||
else return new Float64Array(arr.buffer)[0]
|
||||
}
|
||||
throw new Error("unsupported fmt:" + fmt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function setNumber(buf: Uint8Array, fmt: NumberFormat, offset: number, r: number) {
|
||||
let inf = fmtInfo(fmt)
|
||||
export function setNumber(
|
||||
buf: Uint8Array,
|
||||
fmt: NumberFormat,
|
||||
offset: number,
|
||||
r: number
|
||||
) {
|
||||
const inf = fmtInfo(fmt)
|
||||
if (inf.isFloat) {
|
||||
let arr = new Uint8Array(inf.size)
|
||||
if (inf.size == 4)
|
||||
new Float32Array(arr.buffer)[0] = r
|
||||
else
|
||||
new Float64Array(arr.buffer)[0] = r
|
||||
if (inf.swap)
|
||||
arr.reverse()
|
||||
const arr = new Uint8Array(inf.size)
|
||||
if (inf.size == 4) new Float32Array(arr.buffer)[0] = r
|
||||
else new Float64Array(arr.buffer)[0] = r
|
||||
if (inf.swap) arr.reverse()
|
||||
for (let i = 0; i < inf.size; ++i) {
|
||||
buf[offset + i] = arr[i]
|
||||
}
|
||||
|
@ -162,8 +190,8 @@ export function setNumber(buf: Uint8Array, fmt: NumberFormat, offset: number, r:
|
|||
}
|
||||
|
||||
for (let i = 0; i < inf.size; ++i) {
|
||||
let off = !inf.swap ? offset + i : offset + inf.size - i - 1
|
||||
buf[off] = (r & 0xff)
|
||||
const off = !inf.swap ? offset + i : offset + inf.size - i - 1
|
||||
buf[off] = r & 0xff
|
||||
r >>= 8
|
||||
}
|
||||
}
|
||||
|
|
811
src/jdom/bus.ts
811
src/jdom/bus.ts
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -8,7 +8,7 @@ export type ArgType = number | boolean | string | Uint8Array
|
|||
export function packArguments(info: jdspec.PacketInfo, args: ArgType[]) {
|
||||
let repeatIdx = -1
|
||||
let numReps = 0
|
||||
let argIdx = 0
|
||||
const argIdx = 0
|
||||
let dst = 0
|
||||
|
||||
const buf = new Uint8Array(256)
|
||||
|
|
|
@ -168,14 +168,14 @@ export class JDDevice extends JDNode {
|
|||
return this._shortId;
|
||||
}
|
||||
|
||||
get firmwareInfo() {
|
||||
return this._firmwareInfo;
|
||||
}
|
||||
|
||||
get parent(): JDNode {
|
||||
return this.bus
|
||||
}
|
||||
|
||||
get firmwareInfo() {
|
||||
return this._firmwareInfo;
|
||||
}
|
||||
|
||||
set firmwareInfo(info: FirmwareInfo) {
|
||||
const changed = JSON.stringify(this._firmwareInfo) !== JSON.stringify(info);
|
||||
if (changed) {
|
||||
|
@ -283,7 +283,7 @@ export class JDDevice extends JDNode {
|
|||
assert(this.announced)
|
||||
if (!this._services) {
|
||||
const n = this.serviceLength;
|
||||
let s = [];
|
||||
const s = [];
|
||||
for (let i = 0; i < n; ++i)
|
||||
s.push(new JDService(this, i));
|
||||
this._services = s;
|
||||
|
|
|
@ -8,7 +8,7 @@ import { JDServiceMemberNode } from "./servicemembernode";
|
|||
|
||||
export class JDEvent extends JDServiceMemberNode {
|
||||
private _lastReportPkt: Packet;
|
||||
private _count: number = 0;
|
||||
private _count = 0;
|
||||
|
||||
constructor(
|
||||
service: JDService,
|
||||
|
|
|
@ -3,6 +3,7 @@ import { NEW_LISTENER, REMOVE_LISTENER, ERROR, CHANGE } from "./constants";
|
|||
import { Observable, Observer } from "./observable";
|
||||
import Flags from "./flags";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type EventHandler = (...args: any[]) => void;
|
||||
|
||||
interface Listener {
|
||||
|
@ -93,6 +94,7 @@ export class JDEventSource {
|
|||
* @param eventName
|
||||
* @param args
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
emit(eventName: string, ...args: any[]): boolean {
|
||||
if (!eventName) return false;
|
||||
|
||||
|
@ -114,6 +116,7 @@ export class JDEventSource {
|
|||
--i;
|
||||
}
|
||||
try {
|
||||
// eslint-disable-next-line prefer-spread
|
||||
handler.apply(null, args);
|
||||
}
|
||||
catch (e) {
|
||||
|
|
|
@ -86,7 +86,7 @@ class FlashClient {
|
|||
|
||||
private async startFlashAsync() {
|
||||
this.sessionId = (Math.random() * 0x10000000) | 0
|
||||
for (let d of this.classClients) {
|
||||
for (const d of this.classClients) {
|
||||
d.start()
|
||||
log(`flashing ${d.device.shortId}; available flash=${d.flashSize / 1024}kb; page=${d.pageSize}b`)
|
||||
}
|
||||
|
@ -96,7 +96,7 @@ class FlashClient {
|
|||
this.allPending()
|
||||
|
||||
for (let i = 0; i < BL_RETRIES; ++i) {
|
||||
for (let d of this.classClients) {
|
||||
for (const d of this.classClients) {
|
||||
if (d.pending) {
|
||||
if (d.lastStatus && d.lastStatus.getNumber(NumberFormat.UInt32LE, 0) == this.sessionId) {
|
||||
d.pending = false
|
||||
|
@ -118,14 +118,14 @@ class FlashClient {
|
|||
}
|
||||
|
||||
private async endFlashAsync() {
|
||||
for (let f of this.classClients) {
|
||||
for (const f of this.classClients) {
|
||||
await delay(10)
|
||||
await f.device.sendCtrlCommand(ControlCmd.Reset)
|
||||
}
|
||||
}
|
||||
|
||||
private allPending() {
|
||||
for (let c of this.classClients) {
|
||||
for (const c of this.classClients) {
|
||||
c.pending = true
|
||||
c.lastStatus = null
|
||||
}
|
||||
|
@ -133,7 +133,7 @@ class FlashClient {
|
|||
|
||||
private numPending() {
|
||||
let num = 0
|
||||
for (let c of this.classClients)
|
||||
for (const c of this.classClients)
|
||||
if (c.pending) num++
|
||||
return num
|
||||
}
|
||||
|
@ -156,7 +156,7 @@ class FlashClient {
|
|||
if (page.data.length != this.pageSize)
|
||||
throw new Error("invalid page size")
|
||||
|
||||
for (let f of this.classClients)
|
||||
for (const f of this.classClients)
|
||||
f.lastStatus = null
|
||||
|
||||
this.allPending()
|
||||
|
@ -176,7 +176,7 @@ class FlashClient {
|
|||
if (i == 0 || currSubpage < numSubpage)
|
||||
await p.sendAsMultiCommandAsync(this.bus, SRV_BOOTLOADER)
|
||||
else {
|
||||
for (let f of this.classClients)
|
||||
for (const f of this.classClients)
|
||||
if (f.pending) {
|
||||
f.lastStatus = null
|
||||
await f.sendCommandAsync(p)
|
||||
|
@ -187,7 +187,7 @@ class FlashClient {
|
|||
|
||||
await this.waitForStatusAsync()
|
||||
|
||||
for (let f of this.classClients) {
|
||||
for (const f of this.classClients) {
|
||||
if (f.pending) {
|
||||
let err = ""
|
||||
if (f.lastStatus) {
|
||||
|
@ -248,7 +248,7 @@ class FlashClient {
|
|||
}
|
||||
} finally {
|
||||
// even if resetting failed, unregister event listeners
|
||||
for (let d of this.classClients) {
|
||||
for (const d of this.classClients) {
|
||||
d.stop()
|
||||
}
|
||||
}
|
||||
|
@ -265,7 +265,7 @@ export function parseUF2(uf2: Uint8Array, store: string): FirmwareBlob[] {
|
|||
let currBlob: FirmwareBlob
|
||||
for (let off = 0; off < uf2.length; off += 512) {
|
||||
const header = uf2.slice(off, off + 32)
|
||||
let [magic0, magic1, flags, trgaddr, payloadSize, blkNo, numBlocks, familyID] =
|
||||
const [magic0, magic1, flags, trgaddr, payloadSize, blkNo, numBlocks, familyID] =
|
||||
bufferToArray(header, NumberFormat.UInt32LE)
|
||||
if (magic0 != UF2_MAGIC_START0 ||
|
||||
magic1 != UF2_MAGIC_START1 ||
|
||||
|
|
314
src/jdom/hf2.ts
314
src/jdom/hf2.ts
|
@ -1,14 +1,25 @@
|
|||
import { CMSISProto } from "./microbit";
|
||||
import { USBOptions } from "./usb";
|
||||
import { CMSISProto } from "./microbit"
|
||||
import { USBOptions } from "./usb"
|
||||
import {
|
||||
throwError, delay, assert, SMap, PromiseBuffer, PromiseQueue, memcpy, write32, write16, read16,
|
||||
encodeU32LE, read32, bufferToString
|
||||
} from "./utils";
|
||||
throwError,
|
||||
delay,
|
||||
assert,
|
||||
SMap,
|
||||
PromiseBuffer,
|
||||
PromiseQueue,
|
||||
memcpy,
|
||||
write32,
|
||||
write16,
|
||||
read16,
|
||||
encodeU32LE,
|
||||
read32,
|
||||
bufferToString,
|
||||
} from "./utils"
|
||||
|
||||
const controlTransferGetReport = 0x01;
|
||||
const controlTransferSetReport = 0x09;
|
||||
const controlTransferOutReport = 0x200;
|
||||
const controlTransferInReport = 0x100;
|
||||
const controlTransferGetReport = 0x01
|
||||
const controlTransferSetReport = 0x09
|
||||
const controlTransferOutReport = 0x200
|
||||
const controlTransferInReport = 0x100
|
||||
|
||||
// see https://github.com/microsoft/uf2/blob/main/hf2.md for full spec
|
||||
export const HF2_DEVICE_MAJOR = 42
|
||||
|
@ -28,11 +39,11 @@ export const HF2_CMD_INFO = 0x0002
|
|||
// no arguments
|
||||
// results is utf8 character array
|
||||
|
||||
export const HF2_CMD_RESET_INTO_APP = 0x0003// no arguments, no result
|
||||
export const HF2_CMD_RESET_INTO_APP = 0x0003 // no arguments, no result
|
||||
|
||||
export const HF2_CMD_RESET_INTO_BOOTLOADER = 0x0004 // no arguments, no result
|
||||
export const HF2_CMD_RESET_INTO_BOOTLOADER = 0x0004 // no arguments, no result
|
||||
|
||||
export const HF2_CMD_START_FLASH = 0x0005 // no arguments, no result
|
||||
export const HF2_CMD_START_FLASH = 0x0005 // no arguments, no result
|
||||
|
||||
export const HF2_CMD_WRITE_FLASH_PAGE = 0x0006
|
||||
/*
|
||||
|
@ -80,10 +91,10 @@ export const HF2_CMD_DMESG = 0x0010
|
|||
// results is utf8 character array
|
||||
|
||||
export const HF2_FLAG_SERIAL_OUT = 0x80
|
||||
export const HF2_FLAG_SERIAL_ERR = 0xC0
|
||||
export const HF2_FLAG_SERIAL_ERR = 0xc0
|
||||
export const HF2_FLAG_CMDPKT_LAST = 0x40
|
||||
export const HF2_FLAG_CMDPKT_BODY = 0x00
|
||||
export const HF2_FLAG_MASK = 0xC0
|
||||
export const HF2_FLAG_MASK = 0xc0
|
||||
export const HF2_SIZE_MASK = 63
|
||||
|
||||
export const HF2_STATUS_OK = 0x00
|
||||
|
@ -100,27 +111,27 @@ export const HF2_CMD_JDS_SEND = 0x0021
|
|||
export const HF2_EV_JDS_PACKET = 0x800020
|
||||
|
||||
export class Transport {
|
||||
dev: USBDevice;
|
||||
iface: USBInterface;
|
||||
altIface: USBAlternateInterface;
|
||||
epIn: USBEndpoint;
|
||||
epOut: USBEndpoint;
|
||||
readLoopStarted = false;
|
||||
ready = false;
|
||||
rawMode = false;
|
||||
dev: USBDevice
|
||||
iface: USBInterface
|
||||
altIface: USBAlternateInterface
|
||||
epIn: USBEndpoint
|
||||
epOut: USBEndpoint
|
||||
readLoopStarted = false
|
||||
ready = false
|
||||
rawMode = false
|
||||
|
||||
constructor(private usb: USBOptions) { }
|
||||
constructor(private usb: USBOptions) {}
|
||||
|
||||
onData = (v: Uint8Array) => { };
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
onData = (v: Uint8Array) => {}
|
||||
onError = (e: Error) => {
|
||||
console.error("USB error: " + (e ? e.stack : e))
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
log(msg: string, v?: any) {
|
||||
if (v != undefined)
|
||||
console.log("USB: " + msg, v)
|
||||
else
|
||||
console.log("USB: " + msg)
|
||||
if (v != undefined) console.log("USB: " + msg, v)
|
||||
else console.log("USB: " + msg)
|
||||
}
|
||||
|
||||
private mkProto(): Proto {
|
||||
|
@ -139,9 +150,11 @@ export class Transport {
|
|||
this.ready = false
|
||||
if (!this.dev) return Promise.resolve()
|
||||
this.log("close device")
|
||||
return this.dev.close()
|
||||
return this.dev
|
||||
.close()
|
||||
.catch(e => {
|
||||
// just ignore errors closing, most likely device just disconnected
|
||||
console.debug(e)
|
||||
})
|
||||
.then(() => {
|
||||
this.clearDev()
|
||||
|
@ -150,49 +163,53 @@ export class Transport {
|
|||
}
|
||||
|
||||
recvPacketAsync(): Promise<Uint8Array> {
|
||||
if (!this.rawMode)
|
||||
this.error("rawMode required")
|
||||
if (!this.rawMode) this.error("rawMode required")
|
||||
return this.recvPacketCoreAsync()
|
||||
}
|
||||
|
||||
private recvPacketCoreAsync(): Promise<Uint8Array> {
|
||||
let final = (res: USBInTransferResult) => {
|
||||
if (res.status != "ok")
|
||||
this.error("USB IN transfer failed")
|
||||
let arr = new Uint8Array(res.data.buffer)
|
||||
if (arr.length == 0)
|
||||
return this.recvPacketCoreAsync()
|
||||
const final = (res: USBInTransferResult) => {
|
||||
if (res.status != "ok") this.error("USB IN transfer failed")
|
||||
const arr = new Uint8Array(res.data.buffer)
|
||||
if (arr.length == 0) return this.recvPacketCoreAsync()
|
||||
return arr
|
||||
}
|
||||
|
||||
if (!this.dev)
|
||||
return Promise.reject(new Error("Disconnected"))
|
||||
if (!this.dev) return Promise.reject(new Error("Disconnected"))
|
||||
|
||||
if (!this.epIn) {
|
||||
return this.dev.controlTransferIn({
|
||||
requestType: "class",
|
||||
recipient: "interface",
|
||||
request: controlTransferGetReport,
|
||||
value: controlTransferInReport,
|
||||
index: this.iface.interfaceNumber
|
||||
}, 64).then(final)
|
||||
return this.dev
|
||||
.controlTransferIn(
|
||||
{
|
||||
requestType: "class",
|
||||
recipient: "interface",
|
||||
request: controlTransferGetReport,
|
||||
value: controlTransferInReport,
|
||||
index: this.iface.interfaceNumber,
|
||||
},
|
||||
64
|
||||
)
|
||||
.then(final)
|
||||
}
|
||||
|
||||
return this.dev.transferIn(this.epIn.endpointNumber, 64)
|
||||
.then(final)
|
||||
return this.dev.transferIn(this.epIn.endpointNumber, 64).then(final)
|
||||
}
|
||||
|
||||
error(msg: string) {
|
||||
console.error(`USB error on device ${this.dev ? this.dev.productName : "n/a"} (${msg})`)
|
||||
console.error(
|
||||
`USB error on device ${
|
||||
this.dev ? this.dev.productName : "n/a"
|
||||
} (${msg})`
|
||||
)
|
||||
this.onError(new Error("USB error"))
|
||||
}
|
||||
|
||||
private async readLoop() {
|
||||
if (this.rawMode || this.readLoopStarted)
|
||||
return
|
||||
if (this.rawMode || this.readLoopStarted) return
|
||||
this.readLoopStarted = true
|
||||
this.log("start read loop")
|
||||
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
if (!this.ready) {
|
||||
break
|
||||
|
@ -221,60 +238,69 @@ export class Transport {
|
|||
}
|
||||
|
||||
sendPacketAsync(pkt: Uint8Array) {
|
||||
if (!this.dev)
|
||||
return Promise.reject(new Error("Disconnected"))
|
||||
if (!this.dev) return Promise.reject(new Error("Disconnected"))
|
||||
assert(pkt.length <= 64)
|
||||
if (!this.epOut) {
|
||||
return this.dev.controlTransferOut({
|
||||
requestType: "class",
|
||||
recipient: "interface",
|
||||
request: controlTransferSetReport,
|
||||
value: controlTransferOutReport,
|
||||
index: this.iface.interfaceNumber
|
||||
}, pkt).then(res => {
|
||||
if (res.status != "ok")
|
||||
this.error("USB CTRL OUT transfer failed")
|
||||
})
|
||||
return this.dev
|
||||
.controlTransferOut(
|
||||
{
|
||||
requestType: "class",
|
||||
recipient: "interface",
|
||||
request: controlTransferSetReport,
|
||||
value: controlTransferOutReport,
|
||||
index: this.iface.interfaceNumber,
|
||||
},
|
||||
pkt
|
||||
)
|
||||
.then(res => {
|
||||
if (res.status != "ok")
|
||||
this.error("USB CTRL OUT transfer failed")
|
||||
})
|
||||
}
|
||||
return this.dev.transferOut(this.epOut.endpointNumber, pkt)
|
||||
return this.dev
|
||||
.transferOut(this.epOut.endpointNumber, pkt)
|
||||
.then(res => {
|
||||
if (res.status != "ok")
|
||||
this.error("USB OUT transfer failed")
|
||||
if (res.status != "ok") this.error("USB OUT transfer failed")
|
||||
})
|
||||
}
|
||||
|
||||
get isMicrobit() {
|
||||
return this.dev && this.dev.productId == 516 && this.dev.vendorId == 3368
|
||||
return (
|
||||
this.dev && this.dev.productId == 516 && this.dev.vendorId == 3368
|
||||
)
|
||||
}
|
||||
|
||||
private checkDevice() {
|
||||
this.iface = undefined;
|
||||
this.altIface = undefined;
|
||||
if (!this.dev)
|
||||
return false;
|
||||
this.log("connect device: " + this.dev.manufacturerName + " " + this.dev.productName)
|
||||
this.iface = undefined
|
||||
this.altIface = undefined
|
||||
if (!this.dev) return false
|
||||
this.log(
|
||||
"connect device: " +
|
||||
this.dev.manufacturerName +
|
||||
" " +
|
||||
this.dev.productName
|
||||
)
|
||||
// resolve interfaces
|
||||
const subcl = this.isMicrobit ? 0 : HF2_DEVICE_MAJOR
|
||||
for (const iface of this.dev.configuration.interfaces) {
|
||||
const alt = iface.alternates[0]
|
||||
if (alt.interfaceClass == 0xff && alt.interfaceSubclass == subcl) {
|
||||
this.iface = iface;
|
||||
this.altIface = alt;
|
||||
break;
|
||||
this.iface = iface
|
||||
this.altIface = alt
|
||||
break
|
||||
}
|
||||
}
|
||||
if (this.isMicrobit)
|
||||
this.rawMode = true
|
||||
return !!this.iface;
|
||||
if (this.isMicrobit) this.rawMode = true
|
||||
return !!this.iface
|
||||
}
|
||||
|
||||
private async tryReconnectAsync() {
|
||||
try {
|
||||
const devices = await this.usb.getDevices();
|
||||
this.dev = devices[0];
|
||||
const devices = await this.usb.getDevices()
|
||||
this.dev = devices[0]
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
this.dev = undefined;
|
||||
this.dev = undefined
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -286,29 +312,28 @@ export class Transport {
|
|||
// hf2 devices (incl. arcade)
|
||||
classCode: 255,
|
||||
subclassCode: HF2_DEVICE_MAJOR,
|
||||
}, {
|
||||
},
|
||||
{
|
||||
// micro:bit v2
|
||||
vendorId: 3368,
|
||||
productId: 516
|
||||
}
|
||||
]
|
||||
productId: 516,
|
||||
},
|
||||
],
|
||||
})
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
this.dev = undefined;
|
||||
this.dev = undefined
|
||||
}
|
||||
}
|
||||
|
||||
async connectAsync(background: boolean) {
|
||||
await this.tryReconnectAsync();
|
||||
if (!this.dev && !background)
|
||||
await this.requestDeviceAsync();
|
||||
await this.tryReconnectAsync()
|
||||
if (!this.dev && !background) await this.requestDeviceAsync()
|
||||
// background call and no device, just give up for now
|
||||
if (!this.dev && background)
|
||||
throwError("device not paired", true);
|
||||
if (!this.dev && background) throwError("device not paired", true)
|
||||
|
||||
// let's connect!
|
||||
await this.openDeviceAsync();
|
||||
await this.openDeviceAsync()
|
||||
|
||||
const proto = this.mkProto()
|
||||
await proto.postConnectAsync()
|
||||
|
@ -317,18 +342,20 @@ export class Transport {
|
|||
}
|
||||
|
||||
private async openDeviceAsync() {
|
||||
if (!this.dev)
|
||||
throwError("device not found")
|
||||
if (!this.checkDevice())
|
||||
throwError("device does not support HF2")
|
||||
if (!this.dev) throwError("device not found")
|
||||
if (!this.checkDevice()) throwError("device does not support HF2")
|
||||
|
||||
await this.dev.open()
|
||||
await this.dev.selectConfiguration(1)
|
||||
if (this.altIface.endpoints.length) {
|
||||
this.epIn = this.altIface.endpoints.filter(e => e.direction == "in")[0]
|
||||
this.epOut = this.altIface.endpoints.filter(e => e.direction == "out")[0]
|
||||
assert(this.epIn.packetSize == 64);
|
||||
assert(this.epOut.packetSize == 64);
|
||||
this.epIn = this.altIface.endpoints.filter(
|
||||
e => e.direction == "in"
|
||||
)[0]
|
||||
this.epOut = this.altIface.endpoints.filter(
|
||||
e => e.direction == "out"
|
||||
)[0]
|
||||
assert(this.epIn.packetSize == 64)
|
||||
assert(this.epOut.packetSize == 64)
|
||||
}
|
||||
this.log("claim interface")
|
||||
await this.dev.claimInterface(this.iface.interfaceNumber)
|
||||
|
@ -348,17 +375,17 @@ export interface Proto {
|
|||
class HF2Proto implements Proto {
|
||||
eventHandlers: SMap<(buf: Uint8Array) => void> = {}
|
||||
msgs = new PromiseBuffer<Uint8Array>()
|
||||
cmdSeq = (Math.random() * 0xffff) | 0;
|
||||
private lock = new PromiseQueue();
|
||||
cmdSeq = (Math.random() * 0xffff) | 0
|
||||
private lock = new PromiseQueue()
|
||||
|
||||
constructor(public io: Transport) {
|
||||
let frames: Uint8Array[] = []
|
||||
|
||||
io.onData = buf => {
|
||||
let tp = buf[0] & HF2_FLAG_MASK
|
||||
let len = buf[0] & 63
|
||||
const tp = buf[0] & HF2_FLAG_MASK
|
||||
const len = buf[0] & 63
|
||||
//console.log(`msg tp=${tp} len=${len}`)
|
||||
let frame = new Uint8Array(len)
|
||||
const frame = new Uint8Array(len)
|
||||
memcpy(frame, 0, buf, 1, len)
|
||||
if (tp & HF2_FLAG_SERIAL_OUT) {
|
||||
this.onSerial(frame, tp == HF2_FLAG_SERIAL_ERR)
|
||||
|
@ -370,10 +397,10 @@ class HF2Proto implements Proto {
|
|||
} else {
|
||||
assert(tp == HF2_FLAG_CMDPKT_LAST)
|
||||
let total = 0
|
||||
for (let f of frames) total += f.length
|
||||
let r = new Uint8Array(total)
|
||||
for (const f of frames) total += f.length
|
||||
const r = new Uint8Array(total)
|
||||
let ptr = 0
|
||||
for (let f of frames) {
|
||||
for (const f of frames) {
|
||||
memcpy(r, ptr, f)
|
||||
ptr += f.length
|
||||
}
|
||||
|
@ -388,7 +415,6 @@ class HF2Proto implements Proto {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
error(m: string) {
|
||||
return this.io.error(m)
|
||||
}
|
||||
|
@ -396,28 +422,32 @@ class HF2Proto implements Proto {
|
|||
talkAsync(cmd: number, data?: Uint8Array) {
|
||||
let len = 8
|
||||
if (data) len += data.length
|
||||
let pkt = new Uint8Array(len)
|
||||
let seq = ++this.cmdSeq & 0xffff
|
||||
write32(pkt, 0, cmd);
|
||||
write16(pkt, 4, seq);
|
||||
write16(pkt, 6, 0);
|
||||
if (data)
|
||||
memcpy(pkt, 8, data, 0, data.length)
|
||||
const pkt = new Uint8Array(len)
|
||||
const seq = ++this.cmdSeq & 0xffff
|
||||
write32(pkt, 0, cmd)
|
||||
write16(pkt, 4, seq)
|
||||
write16(pkt, 6, 0)
|
||||
if (data) memcpy(pkt, 8, data, 0, data.length)
|
||||
let numSkipped = 0
|
||||
let handleReturnAsync = (): Promise<Uint8Array> =>
|
||||
this.msgs.shiftAsync(1000) // we wait up to a second
|
||||
const handleReturnAsync = (): Promise<Uint8Array> =>
|
||||
this.msgs
|
||||
.shiftAsync(1000) // we wait up to a second
|
||||
.then(res => {
|
||||
if (read16(res, 0) != seq) {
|
||||
if (numSkipped < 3) {
|
||||
numSkipped++
|
||||
this.io.log(`message out of sync, (${seq} vs ${read16(res, 0)}); will re-try`)
|
||||
this.io.log(
|
||||
`message out of sync, (${seq} vs ${read16(
|
||||
res,
|
||||
0
|
||||
)}); will re-try`
|
||||
)
|
||||
return handleReturnAsync()
|
||||
}
|
||||
this.error("out of sync")
|
||||
}
|
||||
let info = ""
|
||||
if (res[3])
|
||||
info = "; info=" + res[3]
|
||||
if (res[3]) info = "; info=" + res[3]
|
||||
switch (res[2]) {
|
||||
case HF2_STATUS_OK:
|
||||
return res.slice(4)
|
||||
|
@ -439,29 +469,28 @@ class HF2Proto implements Proto {
|
|||
})
|
||||
|
||||
return this.lock.enqueue("talk", () =>
|
||||
this.sendMsgAsync(pkt)
|
||||
.then(handleReturnAsync))
|
||||
this.sendMsgAsync(pkt).then(handleReturnAsync)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
private sendMsgAsync(buf: Uint8Array, serial: number = 0) {
|
||||
private sendMsgAsync(buf: Uint8Array, serial = 0) {
|
||||
// Util.assert(buf.length <= this.maxMsgSize)
|
||||
let frame = new Uint8Array(64)
|
||||
let loop = (pos: number): Promise<void> => {
|
||||
const frame = new Uint8Array(64)
|
||||
const loop = (pos: number): Promise<void> => {
|
||||
let len = buf.length - pos
|
||||
if (len <= 0) return Promise.resolve()
|
||||
if (len > 63) {
|
||||
len = 63
|
||||
frame[0] = HF2_FLAG_CMDPKT_BODY;
|
||||
frame[0] = HF2_FLAG_CMDPKT_BODY
|
||||
} else {
|
||||
frame[0] = HF2_FLAG_CMDPKT_LAST;
|
||||
frame[0] = HF2_FLAG_CMDPKT_LAST
|
||||
}
|
||||
if (serial) frame[0] = serial == 1 ? HF2_FLAG_SERIAL_OUT : HF2_FLAG_SERIAL_ERR;
|
||||
frame[0] |= len;
|
||||
for (let i = 0; i < len; ++i)
|
||||
frame[i + 1] = buf[pos + i]
|
||||
return this.io.sendPacketAsync(frame)
|
||||
.then(() => loop(pos + len))
|
||||
if (serial)
|
||||
frame[0] =
|
||||
serial == 1 ? HF2_FLAG_SERIAL_OUT : HF2_FLAG_SERIAL_ERR
|
||||
frame[0] |= len
|
||||
for (let i = 0; i < len; ++i) frame[i + 1] = buf[pos + i]
|
||||
return this.io.sendPacketAsync(frame).then(() => loop(pos + len))
|
||||
}
|
||||
return loop(0)
|
||||
}
|
||||
|
@ -477,8 +506,7 @@ class HF2Proto implements Proto {
|
|||
}
|
||||
|
||||
sendJDMessageAsync(buf: Uint8Array) {
|
||||
return this.talkAsync(HF2_CMD_JDS_SEND, buf)
|
||||
.then(() => { })
|
||||
return this.talkAsync(HF2_CMD_JDS_SEND, buf).then(() => {})
|
||||
}
|
||||
|
||||
handleEvent(buf: Uint8Array) {
|
||||
|
@ -495,7 +523,9 @@ class HF2Proto implements Proto {
|
|||
}
|
||||
}
|
||||
onSerial(data: Uint8Array, iserr: boolean) {
|
||||
console.log("SERIAL:", bufferToString(data))
|
||||
const msg = `hf2 serial: ${bufferToString(data)}`
|
||||
if (iserr) console.error(msg)
|
||||
else console.log(msg)
|
||||
}
|
||||
|
||||
async postConnectAsync() {
|
||||
|
@ -513,7 +543,9 @@ class HF2Proto implements Proto {
|
|||
// all good
|
||||
this.io.log(`device in user-space mode`)
|
||||
} else if (mode == HF2_MODE_BOOTLOADER) {
|
||||
this.io.log(`device in bootloader mode, reseting into user-space mode`)
|
||||
this.io.log(
|
||||
`device in bootloader mode, reseting into user-space mode`
|
||||
)
|
||||
await this.talkAsync(HF2_CMD_RESET_INTO_APP)
|
||||
// and fail
|
||||
throwError("Device in bootloader mode")
|
||||
|
@ -524,6 +556,6 @@ class HF2Proto implements Proto {
|
|||
}
|
||||
|
||||
disconnectAsync(): Promise<void> {
|
||||
return this.io.disconnectAsync();
|
||||
return this.io.disconnectAsync()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ export default class IFrameBridgeClient extends JDIFrameClient {
|
|||
readonly bridgeId = "bridge" + Math.random();
|
||||
packetSent = 0;
|
||||
packetProcessed = 0;
|
||||
private _lastAspectRatio: number = 0;
|
||||
private _lastAspectRatio = 0;
|
||||
|
||||
constructor(readonly bus: JDBus, readonly frameId: string) {
|
||||
super(bus)
|
||||
|
|
|
@ -96,7 +96,7 @@ export function lightEncode(format: string, args: (number | number[])[]) {
|
|||
outarr.push(colors.length)
|
||||
}
|
||||
}
|
||||
for (let c of colors) {
|
||||
for (const c of colors) {
|
||||
outarr.push((c >> 16) & 0xff)
|
||||
outarr.push((c >> 8) & 0xff)
|
||||
outarr.push((c >> 0) & 0xff)
|
||||
|
@ -152,7 +152,7 @@ export function lightEncode(format: string, args: (number | number[])[]) {
|
|||
if (typeof v == "number")
|
||||
colors.push(v)
|
||||
else
|
||||
for (let vv of v) colors.push(vv)
|
||||
for (const vv of v) colors.push(vv)
|
||||
} else {
|
||||
if (token.length == 7) {
|
||||
const b = fromHex(token.slice(1))
|
||||
|
|
|
@ -6,8 +6,8 @@ import Frame from "./frame"
|
|||
import Trace from "./trace"
|
||||
|
||||
export function parseTrace(contents: string): Trace {
|
||||
let description: string[] = [];
|
||||
let packets: Packet[] = []
|
||||
const description: string[] = [];
|
||||
const packets: Packet[] = []
|
||||
contents?.split(/\r?\n/).forEach(ln => {
|
||||
// parse data
|
||||
const m = /(\d+)\s+([a-f0-9]{12,})/i.exec(ln)
|
||||
|
@ -34,7 +34,7 @@ export function parseLogicLog(logcontents: string): Frame[] {
|
|||
const res: Frame[] = []
|
||||
let frameBytes = []
|
||||
let lastTime = 0
|
||||
for (let ln of logcontents.split(/\r?\n/)) {
|
||||
for (const ln of logcontents.split(/\r?\n/)) {
|
||||
let m = /^JD (\d+) ([0-9a-f]+)/i.exec(ln)
|
||||
if (m) {
|
||||
res.push({
|
||||
|
@ -78,7 +78,7 @@ Time [s],Value,Parity Error,Framing Error
|
|||
0.043264800000000,0x00,,Error
|
||||
0.063968960000000,0x00,,Error
|
||||
*/
|
||||
m = /^([\d\.]+),(?:Async Serial,)?.*(0x[A-F0-9][A-F0-9])/.exec(ln)
|
||||
m = /^([\d.]+),(?:Async Serial,)?.*(0x[A-F0-9][A-F0-9])/.exec(ln)
|
||||
if (!m)
|
||||
continue
|
||||
const tm = parseFloat(m[1])
|
||||
|
|
|
@ -352,6 +352,7 @@ export class CMSISProto implements Proto {
|
|||
return 0
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
const a0 = await check(p0)
|
||||
if (a0) return a0
|
||||
|
|
|
@ -71,10 +71,10 @@ export class ModelRunnerClient extends JDServiceClient {
|
|||
})
|
||||
}
|
||||
|
||||
async deployModel(model: Uint8Array, progress: (p: number) => void = () => { }) {
|
||||
progress(0)
|
||||
async deployModel(model: Uint8Array, onProgress?: (p: number) => void) {
|
||||
onProgress?.(0)
|
||||
const resp = await this.service.sendCmdAwaitResponseAsync(Packet.jdpacked(ModelRunnerCmd.SetModel, "u32", [model.length]), 3000)
|
||||
progress(0.05)
|
||||
onProgress?.(0.05)
|
||||
const [pipePort] = jdunpack<[number]>(resp.data, "u16")
|
||||
if (!pipePort)
|
||||
throw new Error("wrong port " + pipePort)
|
||||
|
@ -82,14 +82,14 @@ export class ModelRunnerClient extends JDServiceClient {
|
|||
const chunkSize = 224 // has to be divisible by 8
|
||||
for (let i = 0; i < model.length; i += chunkSize) {
|
||||
await pipe.send(model.slice(i, i + chunkSize))
|
||||
progress(0.05 + (i / model.length) * 0.9)
|
||||
onProgress?.(0.05 + (i / model.length) * 0.9)
|
||||
}
|
||||
try {
|
||||
await pipe.close()
|
||||
} catch {
|
||||
// the device may restart before we manage to close
|
||||
}
|
||||
progress(1)
|
||||
onProgress?.(1)
|
||||
}
|
||||
|
||||
async autoInvoke(everySamples = 1) {
|
||||
|
|
|
@ -43,7 +43,7 @@ function packNumberCore(buf: Uint8Array, offset: number, num: number) {
|
|||
}
|
||||
}
|
||||
}
|
||||
let fmt = tagFormat(tag)
|
||||
const fmt = tagFormat(tag)
|
||||
if (buf) {
|
||||
buf[offset] = tag
|
||||
setNumber(buf, fmt, offset + 1, num)
|
||||
|
@ -55,12 +55,12 @@ function packNumberCore(buf: Uint8Array, offset: number, num: number) {
|
|||
* Unpacks a buffer into a number array.
|
||||
*/
|
||||
export function unpackNumberArray(buf: Uint8Array, offset = 0): number[] {
|
||||
let res: number[] = []
|
||||
const res: number[] = []
|
||||
|
||||
while (offset < buf.length) {
|
||||
let fmt = tagFormat(buf[offset++])
|
||||
const fmt = tagFormat(buf[offset++])
|
||||
if (fmt === null) {
|
||||
let v = getNumber(buf, NumberFormat.Int8BE, offset - 1)
|
||||
const v = getNumber(buf, NumberFormat.Int8BE, offset - 1)
|
||||
if (-31 <= v && v <= 127)
|
||||
res.push(v)
|
||||
else
|
||||
|
@ -82,12 +82,12 @@ export function unpackNumberArray(buf: Uint8Array, offset = 0): number[] {
|
|||
*/
|
||||
export function packNumberArray(nums: number[]): Uint8Array {
|
||||
let off = 0
|
||||
for (let n of nums) {
|
||||
for (const n of nums) {
|
||||
off += packNumberCore(null, off, n)
|
||||
}
|
||||
let buf = new Uint8Array(off)
|
||||
const buf = new Uint8Array(off)
|
||||
off = 0
|
||||
for (let n of nums) {
|
||||
for (const n of nums) {
|
||||
off += packNumberCore(buf, off, n)
|
||||
}
|
||||
return buf
|
||||
|
|
|
@ -66,7 +66,7 @@ export abstract class JDNode extends JDEventSource {
|
|||
|
||||
|
||||
export function visitNodes(node: JDNode, vis: (node: JDNode) => void) {
|
||||
let todo = [node];
|
||||
const todo = [node];
|
||||
while (todo.length) {
|
||||
const node = todo.pop();
|
||||
vis(node)
|
||||
|
|
|
@ -2,6 +2,10 @@ import { getNumber, NumberFormat, setNumber, sizeOfNumberFormat } from "./buffer
|
|||
import { clampToStorage, numberFormatToStorageType } from "./spec"
|
||||
import { bufferEq, bufferToString, stringToBuffer } from "./utils"
|
||||
|
||||
export type PackedSimpleValue = number | boolean | string | Uint8Array
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type PackedValues = any[];
|
||||
|
||||
/*
|
||||
|
||||
## Format strings
|
||||
|
@ -35,8 +39,8 @@ const ch_s = 115
|
|||
const ch_u = 117
|
||||
const ch_x = 120
|
||||
const ch_z = 122
|
||||
const ch_0 = 48
|
||||
const ch_9 = 57
|
||||
//const ch_0 = 48
|
||||
//const ch_9 = 57
|
||||
const ch_colon = 58
|
||||
const ch_sq_open = 91
|
||||
const ch_sq_close = 93
|
||||
|
@ -212,7 +216,7 @@ function jdunpackCore(buf: Uint8Array, fmt: string, repeat: number) {
|
|||
}
|
||||
}
|
||||
|
||||
export function jdunpack<T extends any[]>(buf: Uint8Array, fmt: string): T {
|
||||
export function jdunpack<T extends PackedValues>(buf: Uint8Array, fmt: string): T {
|
||||
if (!buf || !fmt) return undefined;
|
||||
|
||||
// hot path for buffers
|
||||
|
@ -230,7 +234,7 @@ export function jdunpack<T extends any[]>(buf: Uint8Array, fmt: string): T {
|
|||
return jdunpackCore(buf, fmt, 0) as T
|
||||
}
|
||||
|
||||
function jdpackCore(trg: Uint8Array, fmt: string, data: any[], off: number) {
|
||||
function jdpackCore(trg: Uint8Array, fmt: string, data: PackedValues, off: number) {
|
||||
//console.log({ fmt, data })
|
||||
let idx = 0
|
||||
const parser = new TokenParser(fmt)
|
||||
|
@ -243,7 +247,7 @@ function jdpackCore(trg: Uint8Array, fmt: string, data: any[], off: number) {
|
|||
continue
|
||||
}
|
||||
|
||||
let dataItem = data[idx++]
|
||||
const dataItem = data[idx++]
|
||||
|
||||
if (c0 == ch_r && dataItem) {
|
||||
const fmt0 = fmt.slice(parser.fp)
|
||||
|
@ -310,7 +314,7 @@ function jdpackCore(trg: Uint8Array, fmt: string, data: any[], off: number) {
|
|||
return off
|
||||
}
|
||||
|
||||
export function jdpack<T extends any[]>(fmt: string, data: T) {
|
||||
export function jdpack<T extends PackedValues>(fmt: string, data: T) {
|
||||
if (!fmt || !data)
|
||||
return undefined;
|
||||
|
||||
|
@ -332,7 +336,7 @@ export function jdpack<T extends any[]>(fmt: string, data: T) {
|
|||
return res
|
||||
}
|
||||
|
||||
export function jdpackEqual<T extends any[]>(fmt: string, left: T, right: T) {
|
||||
export function jdpackEqual<T extends PackedValues>(fmt: string, left: T, right: T) {
|
||||
if ((!left) !== (!right))
|
||||
return false;
|
||||
if (!left) return true;
|
||||
|
|
|
@ -261,14 +261,14 @@ export class Packet {
|
|||
if (stripped.length == 0)
|
||||
return
|
||||
let sz = -4
|
||||
for (let s of stripped) {
|
||||
for (const s of stripped) {
|
||||
sz += s.length
|
||||
}
|
||||
const data = new Uint8Array(sz)
|
||||
this._header.set(stripped[0], 12)
|
||||
data.set(stripped[0].slice(4), 0)
|
||||
sz = stripped[0].length - 4
|
||||
for (let s of stripped.slice(1)) {
|
||||
for (const s of stripped.slice(1)) {
|
||||
data.set(s, sz)
|
||||
sz += s.length
|
||||
}
|
||||
|
|
|
@ -1,37 +1,37 @@
|
|||
import { SRV_LOGGER } from "../../jacdac-spec/dist/specconstants";
|
||||
import { JDBus } from "./bus";
|
||||
import Packet from "./packet";
|
||||
import { isCommand, isInstanceOf, serviceSpecificationFromName } from "./spec";
|
||||
import { SMap } from "./utils";
|
||||
import { SRV_LOGGER } from "../../jacdac-spec/dist/specconstants"
|
||||
import { JDBus } from "./bus"
|
||||
import Packet from "./packet"
|
||||
import { isInstanceOf, serviceSpecificationFromName } from "./spec"
|
||||
import { SMap } from "./utils"
|
||||
|
||||
export type CompiledPacketFilter = (pkt: Packet) => boolean;
|
||||
export type CompiledPacketFilter = (pkt: Packet) => boolean
|
||||
|
||||
export interface PacketFilterProps {
|
||||
announce?: boolean,
|
||||
repeatedAnnounce?: boolean,
|
||||
requiresAck?: boolean,
|
||||
log?: boolean,
|
||||
firmwareIdentifiers?: number[],
|
||||
flags?: string[],
|
||||
regGet?: boolean,
|
||||
regSet?: boolean,
|
||||
devices?: SMap<{ from?: boolean; to?: boolean }>,
|
||||
serviceClasses?: number[],
|
||||
pkts?: string[],
|
||||
before?: number,
|
||||
after?: number,
|
||||
grouping?: boolean,
|
||||
pipes?: boolean,
|
||||
port?: number,
|
||||
collapseAck?: boolean,
|
||||
collapsePipes?: boolean,
|
||||
announce?: boolean
|
||||
repeatedAnnounce?: boolean
|
||||
requiresAck?: boolean
|
||||
log?: boolean
|
||||
firmwareIdentifiers?: number[]
|
||||
flags?: string[]
|
||||
regGet?: boolean
|
||||
regSet?: boolean
|
||||
devices?: SMap<{ from?: boolean; to?: boolean }>
|
||||
serviceClasses?: number[]
|
||||
pkts?: string[]
|
||||
before?: number
|
||||
after?: number
|
||||
grouping?: boolean
|
||||
pipes?: boolean
|
||||
port?: number
|
||||
collapseAck?: boolean
|
||||
collapsePipes?: boolean
|
||||
collapseGets?: boolean
|
||||
}
|
||||
|
||||
export interface PacketFilter {
|
||||
source: string;
|
||||
props: PacketFilterProps;
|
||||
filter: CompiledPacketFilter;
|
||||
source: string
|
||||
props: PacketFilterProps
|
||||
filter: CompiledPacketFilter
|
||||
}
|
||||
|
||||
export function parsePacketFilter(bus: JDBus, text: string): PacketFilter {
|
||||
|
@ -39,139 +39,143 @@ export function parsePacketFilter(bus: JDBus, text: string): PacketFilter {
|
|||
return {
|
||||
source: text,
|
||||
props: {
|
||||
grouping: true
|
||||
grouping: true,
|
||||
},
|
||||
filter: (pkt) => true
|
||||
filter: () => true,
|
||||
}
|
||||
}
|
||||
|
||||
let flags = new Set<string>()
|
||||
let serviceClasses = new Set<number>();
|
||||
let pkts = new Set<string>();
|
||||
let firmwares = new Set<number>();
|
||||
let repeatedAnnounce: boolean = undefined;
|
||||
let announce: boolean = undefined;
|
||||
let regGet: boolean = undefined;
|
||||
let regSet: boolean = undefined;
|
||||
let requiresAck: boolean = undefined;
|
||||
let log: boolean = undefined;
|
||||
let before: number = undefined;
|
||||
let after: number = undefined;
|
||||
let devices: SMap<{ from: boolean; to: boolean; }> = {};
|
||||
let grouping: boolean = true;
|
||||
let pipes: boolean = undefined;
|
||||
let port: number = undefined;
|
||||
let collapseAck: boolean = true;
|
||||
let collapsePipes: boolean = true;
|
||||
let collapseGets: boolean = true;
|
||||
const flags = new Set<string>()
|
||||
const serviceClasses = new Set<number>()
|
||||
const pkts = new Set<string>()
|
||||
const firmwares = new Set<number>()
|
||||
let repeatedAnnounce: boolean = undefined
|
||||
let announce: boolean = undefined
|
||||
let regGet: boolean = undefined
|
||||
let regSet: boolean = undefined
|
||||
let requiresAck: boolean = undefined
|
||||
let log: boolean = undefined
|
||||
let before: number = undefined
|
||||
let after: number = undefined
|
||||
const devices: SMap<{ from: boolean; to: boolean }> = {}
|
||||
let grouping = true
|
||||
let pipes: boolean = undefined
|
||||
let port: number = undefined
|
||||
let collapseAck = true
|
||||
let collapsePipes = true
|
||||
let collapseGets = true
|
||||
text.split(/\s+/g).forEach(part => {
|
||||
const [match, prefix, _, value] = /([a-z\-_]+)([:=]([^\s]+))?/.exec(part) || [];
|
||||
const [, prefix, , value] =
|
||||
/([a-z\-_]+)([:=]([^\s]+))?/.exec(part) || []
|
||||
switch (prefix || "") {
|
||||
case "kind":
|
||||
case "k":
|
||||
if (!value)
|
||||
break;
|
||||
if (!value) break
|
||||
flags.add(value.toLowerCase())
|
||||
break;
|
||||
break
|
||||
case "service":
|
||||
case "srv":
|
||||
if (!value)
|
||||
break;
|
||||
case "srv": {
|
||||
if (!value) break
|
||||
const service = serviceSpecificationFromName(value)
|
||||
const serviceClass = service?.classIdentifier || parseInt(value, 16);
|
||||
const serviceClass =
|
||||
service?.classIdentifier || parseInt(value, 16)
|
||||
if (serviceClass !== undefined && !isNaN(serviceClass))
|
||||
serviceClasses.add(serviceClass)
|
||||
break;
|
||||
break
|
||||
}
|
||||
case "announce":
|
||||
case "a":
|
||||
announce = parseBoolean(value);
|
||||
break;
|
||||
announce = parseBoolean(value)
|
||||
break
|
||||
case "repeated-announce":
|
||||
case "ra":
|
||||
repeatedAnnounce = parseBoolean(value);
|
||||
break;
|
||||
repeatedAnnounce = parseBoolean(value)
|
||||
break
|
||||
case "requires-ack":
|
||||
case "ack":
|
||||
requiresAck = parseBoolean(value);
|
||||
break;
|
||||
requiresAck = parseBoolean(value)
|
||||
break
|
||||
case "collapse-ack":
|
||||
collapseAck = parseBoolean(value);
|
||||
break;
|
||||
collapseAck = parseBoolean(value)
|
||||
break
|
||||
case "device":
|
||||
case "dev":
|
||||
case "to":
|
||||
case "from":
|
||||
if (!value)
|
||||
break;
|
||||
case "from": {
|
||||
if (!value) break
|
||||
// resolve device by name
|
||||
const deviceId = bus.devices().find(d => d.shortId === value || d.name === value)?.deviceId;
|
||||
const deviceId = bus
|
||||
.devices()
|
||||
.find(d => d.shortId === value || d.name === value)
|
||||
?.deviceId
|
||||
if (deviceId) {
|
||||
const data = devices[deviceId] || (devices[deviceId] = { from: false, to: false })
|
||||
if (prefix === "from")
|
||||
data.from = true;
|
||||
else if (prefix === "to")
|
||||
data.to = true;
|
||||
const data =
|
||||
devices[deviceId] ||
|
||||
(devices[deviceId] = { from: false, to: false })
|
||||
if (prefix === "from") data.from = true
|
||||
else if (prefix === "to") data.to = true
|
||||
}
|
||||
break;
|
||||
break
|
||||
}
|
||||
case "fw":
|
||||
case "firmware-identifier":
|
||||
if (!value) return;
|
||||
case "firmware-identifier": {
|
||||
if (!value) return
|
||||
// find register
|
||||
const fwid = parseInt(value.replace(/^0?x/, ''), 16);
|
||||
if (!isNaN(fwid))
|
||||
firmwares.add(fwid);
|
||||
break;
|
||||
const fwid = parseInt(value.replace(/^0?x/, ""), 16)
|
||||
if (!isNaN(fwid)) firmwares.add(fwid)
|
||||
break
|
||||
}
|
||||
case "pkt":
|
||||
case "reg":
|
||||
case "register":
|
||||
case "cmd":
|
||||
case "command":
|
||||
case "ev":
|
||||
case "event":
|
||||
if (!value) return;
|
||||
case "event": {
|
||||
if (!value) return
|
||||
// find register
|
||||
const id = parseInt(value.replace(/^0?x/, ''), 16);
|
||||
if (!isNaN(id))
|
||||
pkts.add(id.toString(16));
|
||||
const id = parseInt(value.replace(/^0?x/, ""), 16)
|
||||
if (!isNaN(id)) pkts.add(id.toString(16))
|
||||
// support name
|
||||
pkts.add(value);
|
||||
break;
|
||||
pkts.add(value)
|
||||
break
|
||||
}
|
||||
case "reg-get":
|
||||
case "get":
|
||||
regGet = parseBoolean(value);
|
||||
break;
|
||||
regGet = parseBoolean(value)
|
||||
break
|
||||
case "reg-set":
|
||||
case "set":
|
||||
regSet = parseBoolean(value);
|
||||
break;
|
||||
regSet = parseBoolean(value)
|
||||
break
|
||||
case "log":
|
||||
log = parseBoolean(value);
|
||||
break;
|
||||
log = parseBoolean(value)
|
||||
break
|
||||
case "before":
|
||||
before = parseTimestamp(value);
|
||||
break;
|
||||
before = parseTimestamp(value)
|
||||
break
|
||||
case "after":
|
||||
after = parseTimestamp(value);
|
||||
break;
|
||||
after = parseTimestamp(value)
|
||||
break
|
||||
case "grouping":
|
||||
grouping = parseBoolean(value);
|
||||
break;
|
||||
grouping = parseBoolean(value)
|
||||
break
|
||||
case "pipes":
|
||||
pipes = parseBoolean(value);
|
||||
break;
|
||||
pipes = parseBoolean(value)
|
||||
break
|
||||
case "collapse-pipe":
|
||||
case "collapse-pipes":
|
||||
collapsePipes = parseBoolean(value);
|
||||
break;
|
||||
collapsePipes = parseBoolean(value)
|
||||
break
|
||||
case "collapse-get":
|
||||
case "collapse-gets":
|
||||
collapseGets = parseBoolean(value);
|
||||
break;
|
||||
collapseGets = parseBoolean(value)
|
||||
break
|
||||
case "port":
|
||||
port = parseInt(value);
|
||||
break;
|
||||
port = parseInt(value)
|
||||
break
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
const props = {
|
||||
announce,
|
||||
|
@ -184,7 +188,8 @@ export function parsePacketFilter(bus: JDBus, text: string): PacketFilter {
|
|||
regGet,
|
||||
regSet,
|
||||
devices,
|
||||
serviceClasses: !!serviceClasses.size && Array.from(serviceClasses.keys()),
|
||||
serviceClasses:
|
||||
!!serviceClasses.size && Array.from(serviceClasses.keys()),
|
||||
pkts: !!pkts.size && Array.from(pkts.keys()),
|
||||
before,
|
||||
after,
|
||||
|
@ -192,25 +197,22 @@ export function parsePacketFilter(bus: JDBus, text: string): PacketFilter {
|
|||
pipes,
|
||||
collapsePipes,
|
||||
collapseGets,
|
||||
port
|
||||
port,
|
||||
}
|
||||
const filter = compileFilter(props)
|
||||
return {
|
||||
source: text,
|
||||
props,
|
||||
filter,
|
||||
};
|
||||
}
|
||||
function parseBoolean(value: string) {
|
||||
if (value === "false" || value === "no")
|
||||
return false;
|
||||
else if (value === "true" || value === "yes" || !value)
|
||||
return true;
|
||||
else
|
||||
return undefined;
|
||||
if (value === "false" || value === "no") return false
|
||||
else if (value === "true" || value === "yes" || !value) return true
|
||||
else return undefined
|
||||
}
|
||||
function parseTimestamp(value: string) {
|
||||
const t = parseInt(value);
|
||||
return isNaN(t) ? undefined : t;
|
||||
const t = parseInt(value)
|
||||
return isNaN(t) ? undefined : t
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -230,60 +232,71 @@ export function compileFilter(props: PacketFilterProps) {
|
|||
before,
|
||||
after,
|
||||
pipes,
|
||||
port
|
||||
} = props;
|
||||
port,
|
||||
} = props
|
||||
|
||||
let filters: CompiledPacketFilter[] = [];
|
||||
if (before !== undefined)
|
||||
filters.push(pkt => pkt.timestamp <= before)
|
||||
if (after !== undefined)
|
||||
filters.push(pkt => pkt.timestamp >= after)
|
||||
if (announce !== undefined)
|
||||
filters.push(pkt => pkt.isAnnounce === announce)
|
||||
const filters: CompiledPacketFilter[] = []
|
||||
if (before !== undefined) filters.push(pkt => pkt.timestamp <= before)
|
||||
if (after !== undefined) filters.push(pkt => pkt.timestamp >= after)
|
||||
if (announce !== undefined) filters.push(pkt => pkt.isAnnounce === announce)
|
||||
if (repeatedAnnounce !== undefined)
|
||||
filters.push(pkt => !pkt.isAnnounce || (pkt.isRepeatedAnnounce === repeatedAnnounce))
|
||||
filters.push(
|
||||
pkt =>
|
||||
!pkt.isAnnounce || pkt.isRepeatedAnnounce === repeatedAnnounce
|
||||
)
|
||||
if (requiresAck !== undefined)
|
||||
filters.push(pkt => pkt.requiresAck === requiresAck);
|
||||
if (flags)
|
||||
filters.push(pkt => hasAnyFlag(pkt))
|
||||
if (pipes !== undefined)
|
||||
filters.push(pkt => pkt.isPipe)
|
||||
if (port !== undefined)
|
||||
filters.push(pkt => pkt.pipePort === port);
|
||||
filters.push(pkt => pkt.requiresAck === requiresAck)
|
||||
if (flags) filters.push(pkt => hasAnyFlag(pkt))
|
||||
if (pipes !== undefined) filters.push(pkt => pkt.isPipe)
|
||||
if (port !== undefined) filters.push(pkt => pkt.pipePort === port)
|
||||
|
||||
if (regGet !== undefined && regSet !== undefined)
|
||||
filters.push(pkt => (pkt.isRegisterGet === regGet) && (pkt.isRegisterSet === regSet))
|
||||
filters.push(
|
||||
pkt => pkt.isRegisterGet === regGet && pkt.isRegisterSet === regSet
|
||||
)
|
||||
else if (regGet !== undefined)
|
||||
filters.push(pkt => pkt.isRegisterGet === regGet)
|
||||
else if (regSet !== undefined)
|
||||
filters.push(pkt => pkt.isRegisterSet === regSet)
|
||||
|
||||
if (log !== undefined)
|
||||
filters.push(pkt => (pkt.serviceClass === SRV_LOGGER && pkt.isReport) === log);
|
||||
filters.push(
|
||||
pkt => (pkt.serviceClass === SRV_LOGGER && pkt.isReport) === log
|
||||
)
|
||||
if (Object.keys(devices).length)
|
||||
filters.push(pkt => {
|
||||
if (!pkt.device) return false;
|
||||
const f = devices[pkt.device.deviceId];
|
||||
return !!f && (!f.from || !pkt.isCommand) && (!f.to || pkt.isCommand);
|
||||
if (!pkt.device) return false
|
||||
const f = devices[pkt.device.deviceId]
|
||||
return (
|
||||
!!f && (!f.from || !pkt.isCommand) && (!f.to || pkt.isCommand)
|
||||
)
|
||||
})
|
||||
if (serviceClasses) {
|
||||
filters.push(pkt => serviceClasses.some(serviceClass => isInstanceOf(pkt.serviceClass, serviceClass)));
|
||||
filters.push(pkt =>
|
||||
serviceClasses.some(serviceClass =>
|
||||
isInstanceOf(pkt.serviceClass, serviceClass)
|
||||
)
|
||||
)
|
||||
}
|
||||
if (pkts) {
|
||||
filters.push(pkt => pkts.indexOf(pkt.decoded?.info.identifier.toString(16)) > -1
|
||||
|| pkts.indexOf(pkt.decoded?.info.name) > -1);
|
||||
filters.push(
|
||||
pkt =>
|
||||
pkts.indexOf(pkt.decoded?.info.identifier.toString(16)) > -1 ||
|
||||
pkts.indexOf(pkt.decoded?.info.name) > -1
|
||||
)
|
||||
}
|
||||
if (firmwareIdentifiers)
|
||||
filters.push(pkt => {
|
||||
const fwid = pkt.device?.firmwareIdentifier;
|
||||
return fwid === undefined || firmwareIdentifiers.indexOf(fwid) > -1;
|
||||
const fwid = pkt.device?.firmwareIdentifier
|
||||
return fwid === undefined || firmwareIdentifiers.indexOf(fwid) > -1
|
||||
})
|
||||
|
||||
const filter: CompiledPacketFilter = (pkt: Packet) => filters.every(filter => filter(pkt));
|
||||
return filter;
|
||||
const filter: CompiledPacketFilter = (pkt: Packet) =>
|
||||
filters.every(filter => filter(pkt))
|
||||
return filter
|
||||
|
||||
function hasAnyFlag(pkt: Packet) {
|
||||
const k = pkt.decoded?.info.kind;
|
||||
return !!k && flags.indexOf(k) > -1;
|
||||
const k = pkt.decoded?.info.kind
|
||||
return !!k && flags.indexOf(k) > -1
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ export class OutPipe {
|
|||
const cmd = (this.port << PIPE_PORT_SHIFT) | flags | (this._count & PIPE_COUNTER_MASK)
|
||||
const pkt = Packet.from(cmd, buf)
|
||||
pkt.serviceIndex = JD_SERVICE_INDEX_PIPE
|
||||
const p = this.device.sendPktWithAck(pkt)
|
||||
this.device.sendPktWithAck(pkt)
|
||||
.then(
|
||||
() => { },
|
||||
err => {
|
||||
|
@ -107,6 +107,7 @@ export class InPipe extends JDClient {
|
|||
}
|
||||
|
||||
private allocPort() {
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
this._port = 1 + randomUInt(511)
|
||||
const info = this.bus.selfDevice.port(this._port)
|
||||
|
|
|
@ -227,8 +227,8 @@ export function decodeMember(
|
|||
}
|
||||
|
||||
export function valueToFlags(enumInfo: jdspec.EnumInfo, value: number) {
|
||||
let r = [];
|
||||
let curr = value
|
||||
const r = [];
|
||||
const curr = value
|
||||
for (const key of Object.keys(enumInfo.members)) {
|
||||
const val = enumInfo.members[key]
|
||||
if (curr & val) {
|
||||
|
@ -336,7 +336,7 @@ function decodeEvent(service: jdspec.ServiceSpec, pkt: Packet): DecodedPacket {
|
|||
|| syntheticPktInfo("event", evCode)
|
||||
|
||||
const decoded = decodeMembers(service, evInfo, pkt)
|
||||
let description = `EVENT[${pkt.eventCounter}] ${evInfo.name}` + wrapDecodedMembers(decoded)
|
||||
const description = `EVENT[${pkt.eventCounter}] ${evInfo.name}` + wrapDecodedMembers(decoded)
|
||||
|
||||
return {
|
||||
service,
|
||||
|
@ -418,7 +418,7 @@ export function decodePacketData(pkt: Packet): DecodedPacket {
|
|||
}
|
||||
|
||||
function reverseLookup(map: SMap<number>, n: number) {
|
||||
for (let k of Object.keys(map)) {
|
||||
for (const k of Object.keys(map)) {
|
||||
if (map[k] == n)
|
||||
return k
|
||||
}
|
||||
|
@ -546,7 +546,7 @@ export function printPacket(pkt: Packet, opts: PrintPacketOptions = {}): string
|
|||
if (decoded) {
|
||||
pdesc += "; " + decoded.description
|
||||
} else if (0 < d.length && d.length <= 4) {
|
||||
let v0 = pkt.uintData, v1 = pkt.intData
|
||||
const v0 = pkt.uintData, v1 = pkt.intData
|
||||
pdesc += "; " + num2str(v0)
|
||||
if (v0 != v1)
|
||||
pdesc += "; signed: " + num2str(v1)
|
||||
|
|
|
@ -12,15 +12,15 @@ import { isRegister, isReading } from "./spec";
|
|||
import { JDField } from "./field";
|
||||
import { JDServiceMemberNode } from "./servicemembernode";
|
||||
import { JDNode } from "./node";
|
||||
import { jdpack, jdunpack } from "./pack";
|
||||
import { jdpack, jdunpack, PackedValues } from "./pack";
|
||||
|
||||
|
||||
export class JDRegister extends JDServiceMemberNode {
|
||||
private _lastReportPkt: Packet;
|
||||
private _fields: JDField[];
|
||||
private _lastSetTimestamp: number = -Infinity;
|
||||
private _lastGetTimestamp: number = -Infinity;
|
||||
private _lastGetAttempts: number = 0;
|
||||
private _lastSetTimestamp = -Infinity;
|
||||
private _lastGetTimestamp = -Infinity;
|
||||
private _lastGetAttempts = 0;
|
||||
|
||||
constructor(
|
||||
service: JDService,
|
||||
|
@ -77,7 +77,7 @@ export class JDRegister extends JDServiceMemberNode {
|
|||
.then(() => { this.emit(GET_ATTEMPT) });
|
||||
}
|
||||
|
||||
sendSetPackedAsync(fmt: string, values: any[], autoRefresh?: boolean): Promise<void> {
|
||||
sendSetPackedAsync(fmt: string, values: PackedValues, autoRefresh?: boolean): Promise<void> {
|
||||
return this.sendSetAsync(jdpack(fmt, values), autoRefresh)
|
||||
}
|
||||
|
||||
|
@ -105,7 +105,7 @@ export class JDRegister extends JDServiceMemberNode {
|
|||
return this._lastReportPkt?.timestamp
|
||||
}
|
||||
|
||||
get unpackedValue(): any[] {
|
||||
get unpackedValue(): PackedValues {
|
||||
const d = this.data;
|
||||
const fmt = this.specification?.packFormat;
|
||||
return d && fmt && jdunpack(this.data, fmt);
|
||||
|
@ -189,9 +189,8 @@ export class JDRegister extends JDServiceMemberNode {
|
|||
}
|
||||
|
||||
compareTo(b: JDRegister) {
|
||||
const a = this;
|
||||
return a.code - b.code ||
|
||||
a.service.compareTo(b.service);
|
||||
return this.code - b.code ||
|
||||
this.service.compareTo(b.service);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -116,6 +116,7 @@ export class SensorAggregatorClient extends JDServiceClient {
|
|||
}
|
||||
|
||||
async stats(): Promise<SensorAggregatorStats> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const info: any = {
|
||||
"numSamples": this.getReg(SensorAggregatorReg.NumSamples, r => r.intValue),
|
||||
"sampleSize": this.getReg(SensorAggregatorReg.SampleSize, r => r.intValue),
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче