chore: decode data in frontend
This commit is contained in:
@@ -68,13 +68,14 @@ export const selectTopicWithMouseOver = (doSelect: boolean) => (dispatch: Dispat
|
|||||||
dispatch(storeSettings())
|
dispatch(storeSettings())
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setValueDisplayMode = (valueRendererDisplayMode: ValueRendererDisplayMode) => (dispatch: Dispatch<any>) => {
|
export const setValueDisplayMode =
|
||||||
dispatch({
|
(valueRendererDisplayMode: ValueRendererDisplayMode) => (dispatch: Dispatch<any>) => {
|
||||||
valueRendererDisplayMode,
|
dispatch({
|
||||||
type: ActionTypes.SETTINGS_SET_VALUE_RENDERER_DISPLAY_MODE,
|
valueRendererDisplayMode,
|
||||||
})
|
type: ActionTypes.SETTINGS_SET_VALUE_RENDERER_DISPLAY_MODE,
|
||||||
dispatch(storeSettings())
|
})
|
||||||
}
|
dispatch(storeSettings())
|
||||||
|
}
|
||||||
|
|
||||||
export const toggleHighlightTopicUpdates = () => (dispatch: Dispatch<any>) => {
|
export const toggleHighlightTopicUpdates = () => (dispatch: Dispatch<any>) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
@@ -117,7 +118,7 @@ export const filterTopics = (filterStr: string) => (dispatch: Dispatch<any>, get
|
|||||||
const messageMatches =
|
const messageMatches =
|
||||||
node.message &&
|
node.message &&
|
||||||
node.message.payload &&
|
node.message.payload &&
|
||||||
Base64Message.toUnicodeString(node.message.payload).toLowerCase().indexOf(filterStr) !== -1
|
node.message.payload.toUnicodeString().toLowerCase().indexOf(filterStr) !== -1
|
||||||
|
|
||||||
return Boolean(messageMatches)
|
return Boolean(messageMatches)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ function renderStat(tree: q.Tree<TopicViewModel>, stat: Stats) {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const str = node.message.payload ? Base64Message.toUnicodeString(node.message.payload) : ''
|
const str = node.message.payload ? node.message.payload.toUnicodeString() : ''
|
||||||
let value = node.message && node.message.payload ? parseFloat(str) : NaN
|
let value = node.message && node.message.payload ? parseFloat(str) : NaN
|
||||||
value = !isNaN(value) ? abbreviate(value) : str
|
value = !isNaN(value) ? abbreviate(value) : str
|
||||||
|
|
||||||
|
|||||||
@@ -23,13 +23,6 @@ const TopicPanel = (props: { node?: q.TreeNode<any>; actions: typeof sidebarActi
|
|||||||
props.actions.clearTopic(topic, recursive)
|
props.actions.clearTopic(topic, recursive)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const setTopicType = useCallback((node?: q.TreeNode<any>, type: q.TopicDataType = 'string') => {
|
|
||||||
if (!node) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
node.type = type
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => (
|
() => (
|
||||||
<Panel disabled={!Boolean(node)}>
|
<Panel disabled={!Boolean(node)}>
|
||||||
@@ -37,7 +30,7 @@ const TopicPanel = (props: { node?: q.TreeNode<any>; actions: typeof sidebarActi
|
|||||||
Topic {copyTopic}
|
Topic {copyTopic}
|
||||||
<TopicDeleteButton node={node} deleteTopicAction={deleteTopic} />
|
<TopicDeleteButton node={node} deleteTopicAction={deleteTopic} />
|
||||||
<RecursiveTopicDeleteButton node={node} deleteTopicAction={deleteTopic} />
|
<RecursiveTopicDeleteButton node={node} deleteTopicAction={deleteTopic} />
|
||||||
<TopicTypeButton node={node} setTopicType={setTopicType} />
|
<TopicTypeButton node={node} />
|
||||||
</span>
|
</span>
|
||||||
<Topic node={node} />
|
<Topic node={node} />
|
||||||
</Panel>
|
</Panel>
|
||||||
|
|||||||
@@ -1,46 +1,59 @@
|
|||||||
import React, { useCallback } from 'react'
|
import React, { useCallback, useMemo } from 'react'
|
||||||
import * as q from '../../../../../backend/src/Model'
|
import * as q from '../../../../../backend/src/Model'
|
||||||
import CustomIconButton from '../../helper/CustomIconButton'
|
|
||||||
import Code from '@material-ui/icons/Code'
|
|
||||||
import ClickAwayListener from '@material-ui/core/ClickAwayListener'
|
import ClickAwayListener from '@material-ui/core/ClickAwayListener'
|
||||||
import Grow from '@material-ui/core/Grow'
|
import Grow from '@material-ui/core/Grow'
|
||||||
|
import Button from '@material-ui/core/Button'
|
||||||
import Paper from '@material-ui/core/Paper'
|
import Paper from '@material-ui/core/Paper'
|
||||||
import Popper from '@material-ui/core/Popper'
|
import Popper from '@material-ui/core/Popper'
|
||||||
import MenuItem from '@material-ui/core/MenuItem'
|
import MenuItem from '@material-ui/core/MenuItem'
|
||||||
import MenuList from '@material-ui/core/MenuList'
|
import MenuList from '@material-ui/core/MenuList'
|
||||||
|
import WarningRounded from '@material-ui/icons/WarningRounded'
|
||||||
|
import { IDecoder, decoders } from '../../../../../backend/src/Model/sparkplugb'
|
||||||
|
import { Tooltip } from '@material-ui/core'
|
||||||
|
|
||||||
// const options: q.TopicDataType[] = ['json', 'string', 'hex', 'integer', 'unsigned int', 'floating point']
|
// const options: q.TopicDataType[] = ['json', 'string', 'hex', 'integer', 'unsigned int', 'floating point']
|
||||||
const options: q.TopicDataType[] = ['json', 'string', 'hex', 'uint8', 'uint16', 'uint32', 'uint64', 'int8', 'int16', 'int32', 'int64', 'float', 'double']
|
const options: q.TopicDataType[] = [
|
||||||
|
'json',
|
||||||
|
'string',
|
||||||
|
'hex',
|
||||||
|
'uint8',
|
||||||
|
'uint16',
|
||||||
|
'uint32',
|
||||||
|
'uint64',
|
||||||
|
'int8',
|
||||||
|
'int16',
|
||||||
|
'int32',
|
||||||
|
'int64',
|
||||||
|
'float',
|
||||||
|
'double',
|
||||||
|
]
|
||||||
|
|
||||||
export const TopicTypeButton = (props: {
|
export const TopicTypeButton = (props: { node?: q.TreeNode<any> }) => {
|
||||||
node?: q.TreeNode<any>
|
|
||||||
setTopicType: (node: q.TreeNode<any>, type: q.TopicDataType) => void
|
|
||||||
}) => {
|
|
||||||
const { node } = props
|
const { node } = props
|
||||||
if (!node || !node.message || !node.message.payload) {
|
if (!node || !node.message || !node.message.payload) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
const options = decoders.flatMap(decoder => decoder.formats.map(format => [decoder, format] as const))
|
||||||
|
|
||||||
|
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null)
|
||||||
const [open, setOpen] = React.useState(false)
|
const [open, setOpen] = React.useState(false)
|
||||||
|
|
||||||
const handleMenuItemClick = useCallback(
|
const selectOption = useCallback((decoder: IDecoder, format: string) => {
|
||||||
(mouseEvent: React.MouseEvent, element: q.TreeNode<any>, type: q.TopicDataType) => {
|
if (!node) {
|
||||||
if (!element || !type) {
|
return
|
||||||
return
|
}
|
||||||
}
|
node.decoder = decoder
|
||||||
props.setTopicType(element, type as q.TopicDataType)
|
node.decoderFormat = format
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
},
|
}, [])
|
||||||
[props.setTopicType]
|
|
||||||
)
|
|
||||||
|
|
||||||
const handleToggle = (event: React.MouseEvent<HTMLElement>) => {
|
const handleToggle = (event: React.MouseEvent<HTMLElement>) => {
|
||||||
if (open === true) {
|
if (open === true) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setAnchorEl(event.currentTarget)
|
setAnchorEl(event.currentTarget)
|
||||||
setOpen((prevOpen) => !prevOpen)
|
setOpen(prevOpen => !prevOpen)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleClose = (event: React.MouseEvent<Document, MouseEvent>) => {
|
const handleClose = (event: React.MouseEvent<Document, MouseEvent>) => {
|
||||||
@@ -51,8 +64,8 @@ export const TopicTypeButton = (props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CustomIconButton tooltip="" onClick={handleToggle}>
|
<Button onClick={handleToggle}>
|
||||||
<Code />
|
{props.node?.decoderFormat ?? props.node?.type}
|
||||||
<Popper open={open} anchorEl={anchorEl} role={undefined} transition>
|
<Popper open={open} anchorEl={anchorEl} role={undefined} transition>
|
||||||
{({ TransitionProps, placement }) => (
|
{({ TransitionProps, placement }) => (
|
||||||
<Grow
|
<Grow
|
||||||
@@ -64,13 +77,13 @@ export const TopicTypeButton = (props: {
|
|||||||
<Paper>
|
<Paper>
|
||||||
<ClickAwayListener onClickAway={handleClose}>
|
<ClickAwayListener onClickAway={handleClose}>
|
||||||
<MenuList id="topicTypeMode">
|
<MenuList id="topicTypeMode">
|
||||||
{options.map((option, index) => (
|
{options.map(([decoder, format], index) => (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
key={option}
|
key={format}
|
||||||
selected={node && option === node.type}
|
selected={node && format === node.type}
|
||||||
onClick={(event) => handleMenuItemClick(event, node, option)}
|
onClick={() => selectOption(decoder, format)}
|
||||||
>
|
>
|
||||||
{option}
|
<DecoderStatus decoder={decoder} format={format} node={node} />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</MenuList>
|
</MenuList>
|
||||||
@@ -79,6 +92,22 @@ export const TopicTypeButton = (props: {
|
|||||||
</Grow>
|
</Grow>
|
||||||
)}
|
)}
|
||||||
</Popper>
|
</Popper>
|
||||||
</CustomIconButton>
|
</Button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function DecoderStatus({ node, decoder, format }: { node: q.TreeNode<any>; decoder: IDecoder; format: string }) {
|
||||||
|
const decoded = useMemo(() => {
|
||||||
|
return node.message?.payload && decoder.decode(node.message?.payload, format)
|
||||||
|
}, [node.message, decoder, format])
|
||||||
|
|
||||||
|
return decoded?.error ? (
|
||||||
|
<Tooltip title={decoded.error}>
|
||||||
|
<div>
|
||||||
|
{format} <WarningRounded />
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
<>{format}</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -82,9 +82,10 @@ class MessageHistory extends React.PureComponent<Props, State> {
|
|||||||
const history = node.messageHistory.toArray()
|
const history = node.messageHistory.toArray()
|
||||||
let previousMessage: q.Message | undefined = node.message
|
let previousMessage: q.Message | undefined = node.message
|
||||||
const historyElements = [...history].reverse().map((message, idx) => {
|
const historyElements = [...history].reverse().map((message, idx) => {
|
||||||
const [value, ignore] = Base64Message.format(message.payload, node.type)
|
const value = node.message ? node.decodeMessage(node.message)?.format()[0] ?? null : null
|
||||||
|
|
||||||
const element = {
|
const element = {
|
||||||
value,
|
value: value ?? '',
|
||||||
key: `${message.messageNumber}-${message.received}`,
|
key: `${message.messageNumber}-${message.received}`,
|
||||||
title: (
|
title: (
|
||||||
<span>
|
<span>
|
||||||
@@ -102,7 +103,7 @@ class MessageHistory extends React.PureComponent<Props, State> {
|
|||||||
<MessageId message={message} />
|
<MessageId message={message} />
|
||||||
</span>
|
</span>
|
||||||
<div style={{ float: 'right' }}>
|
<div style={{ float: 'right' }}>
|
||||||
<Copy value={value ? value : ''} />
|
<Copy value={value ?? ''} />
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
@@ -112,7 +113,8 @@ class MessageHistory extends React.PureComponent<Props, State> {
|
|||||||
return element
|
return element
|
||||||
})
|
})
|
||||||
|
|
||||||
const [value, ignore] = node.message && node.message.payload ? Base64Message.format(node.message.payload, node.type) : [null, undefined]
|
const value = node.message ? node.decodeMessage(node.message)?.format()[0] ?? null : null
|
||||||
|
|
||||||
const isMessagePlottable = isPlottable(value)
|
const isMessagePlottable = isPlottable(value)
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -85,10 +85,9 @@ function ValuePanel(props: Props) {
|
|||||||
[compareMessage]
|
[compareMessage]
|
||||||
)
|
)
|
||||||
|
|
||||||
const [value, ignore] = node && node.message && node.message.payload ? Base64Message.format(node.message.payload, node.type) : [null, undefined]
|
const [value] =
|
||||||
const copyValue = value ? (
|
node && node.message && node.message.payload ? node.message.payload?.format(node.type) : [null, undefined]
|
||||||
<Copy value={value} />
|
const copyValue = value ? <Copy value={value} /> : null
|
||||||
) : null
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Panel>
|
<Panel>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import * as q from '../../../../../backend/src/Model'
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import CodeDiff from '../CodeDiff'
|
import CodeDiff from '../CodeDiff'
|
||||||
import { AppState } from '../../../reducers'
|
import { AppState } from '../../../reducers'
|
||||||
import { Base64Message } from '../../../../../backend/src/Model/Base64Message'
|
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import { ValueRendererDisplayMode } from '../../../reducers/Settings'
|
import { ValueRendererDisplayMode } from '../../../reducers/Settings'
|
||||||
import { Fade } from '@material-ui/core'
|
import { Fade } from '@material-ui/core'
|
||||||
@@ -47,9 +46,8 @@ class ValueRenderer extends React.Component<Props, State> {
|
|||||||
const previousMessage = previousMessages[previousMessages.length - 2]
|
const previousMessage = previousMessages[previousMessages.length - 2]
|
||||||
const compareMessage = compare || previousMessage || message
|
const compareMessage = compare || previousMessage || message
|
||||||
|
|
||||||
const compareValue = compareMessage.payload || message.payload
|
const [currentStr, currentType] = treeNode.decodeMessage(message)?.format(treeNode.type) ?? []
|
||||||
const [currentStr, currentType] = Base64Message.format(message.payload, treeNode.type)
|
const [compareStr, compareType] = treeNode.decodeMessage(compareMessage)?.format(treeNode.type) ?? []
|
||||||
const [compareStr, compareType] = Base64Message.format(compareValue, treeNode.type)
|
|
||||||
|
|
||||||
const language = currentType === compareType && compareType === 'json' ? 'json' : undefined
|
const language = currentType === compareType && compareType === 'json' ? 'json' : undefined
|
||||||
|
|
||||||
@@ -61,9 +59,9 @@ class ValueRenderer extends React.Component<Props, State> {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const [currentStr, currentType] = Base64Message.format(message.payload, treeNode.type)
|
const [currentStr, currentType] = treeNode.decodeMessage(message)?.format(treeNode.type) ?? []
|
||||||
const [compareStr, compareType] =
|
const [compareStr, compareType] =
|
||||||
compare && compare.payload ? Base64Message.format(compare.payload, treeNode.type) : [undefined, undefined]
|
compare && compare.payload ? treeNode.decodeMessage(compare)?.format(treeNode.type) ?? [] : []
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -26,23 +26,28 @@ function filterUsingTimeRange(startTime: number | undefined, data: Array<q.Messa
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
function nodeToHistory(startTime: number | undefined, history: q.MessageHistory, type: q.TopicDataType) {
|
function nodeToHistory(startTime: number | undefined, history: q.MessageHistory, node: q.TreeNode<any>) {
|
||||||
return filterUsingTimeRange(startTime, history.toArray())
|
return filterUsingTimeRange(startTime, history.toArray())
|
||||||
.map((message: q.Message) => {
|
.map((message: q.Message) => {
|
||||||
const [value, ignore] = message.payload ? Base64Message.format(message.payload, type) : [NaN, undefined]
|
const decoded = node.decodeMessage(message)?.toUnicodeString()
|
||||||
// const value = message.payload ? toPlottableValue(Base64Message.toUnicodeString(message.payload)) : NaN
|
return { x: message.received.getTime(), y: toPlottableValue(decoded) }
|
||||||
return { x: message.received.getTime(), y: toPlottableValue(value) }
|
|
||||||
})
|
})
|
||||||
.filter(data => !isNaN(data.y as any)) as any
|
.filter(data => !isNaN(data.y as any)) as any
|
||||||
}
|
}
|
||||||
|
|
||||||
function nodeDotPathToHistory(startTime: number | undefined, history: q.MessageHistory, dotPath: string, type: q.TopicDataType) {
|
function nodeDotPathToHistory(
|
||||||
|
startTime: number | undefined,
|
||||||
|
history: q.MessageHistory,
|
||||||
|
dotPath: string,
|
||||||
|
node: q.TreeNode<any>
|
||||||
|
) {
|
||||||
return filterUsingTimeRange(startTime, history.toArray())
|
return filterUsingTimeRange(startTime, history.toArray())
|
||||||
.map((message: q.Message) => {
|
.map((message: q.Message) => {
|
||||||
let json: any = {}
|
let json: any = {}
|
||||||
try {
|
try {
|
||||||
json = message.payload ? JSON.parse(Base64Message.toUnicodeString(message.payload)) : {}
|
const decoded = node.decodeMessage(message)
|
||||||
} catch (ignore) { }
|
json = decoded ? JSON.parse(decoded.toUnicodeString()) : {}
|
||||||
|
} catch (ignore) {}
|
||||||
|
|
||||||
const value = dotProp.get(json, dotPath)
|
const value = dotProp.get(json, dotPath)
|
||||||
|
|
||||||
@@ -53,13 +58,15 @@ function nodeDotPathToHistory(startTime: number | undefined, history: q.MessageH
|
|||||||
|
|
||||||
function TopicPlot(props: Props) {
|
function TopicPlot(props: Props) {
|
||||||
const startOffset = props.timeInterval ? parseDuration(props.timeInterval) : undefined
|
const startOffset = props.timeInterval ? parseDuration(props.timeInterval) : undefined
|
||||||
const data = React.useMemo(
|
const data = React.useMemo(() => {
|
||||||
() =>
|
if (!props.node) {
|
||||||
props.dotPath
|
return []
|
||||||
? nodeDotPathToHistory(startOffset, props.history, props.dotPath, props.node ? props.node.type : 'string')
|
}
|
||||||
: nodeToHistory(startOffset, props.history, props.node ? props.node.type : 'string'),
|
|
||||||
[props.history.last(), startOffset, props.dotPath]
|
return props.dotPath
|
||||||
)
|
? nodeDotPathToHistory(startOffset, props.history, props.dotPath, props.node)
|
||||||
|
: nodeToHistory(startOffset, props.history, props.node)
|
||||||
|
}, [props.history.last(), startOffset, props.dotPath])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PlotHistory
|
<PlotHistory
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import * as q from '../../../../../backend/src/Model'
|
import * as q from '../../../../../backend/src/Model'
|
||||||
import React, { memo } from 'react'
|
import React, { memo } from 'react'
|
||||||
import { Base64Message } from '../../../../../backend/src/Model/Base64Message'
|
|
||||||
import { Theme, withStyles } from '@material-ui/core'
|
import { Theme, withStyles } from '@material-ui/core'
|
||||||
import { TopicViewModel } from '../../../model/TopicViewModel'
|
import { TopicViewModel } from '../../../model/TopicViewModel'
|
||||||
|
|
||||||
@@ -30,8 +29,9 @@ class TreeNodeTitle extends React.PureComponent<TreeNodeProps, {}> {
|
|||||||
if (!this.props.treeNode.message || !this.props.treeNode.message.payload) {
|
if (!this.props.treeNode.message || !this.props.treeNode.message.payload) {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
const [value = ''] =
|
||||||
|
this.props.treeNode.decodeMessage(this.props.treeNode.message)?.format(this.props.treeNode.type) ?? []
|
||||||
|
|
||||||
const [value, ignore] = Base64Message.format(this.props.treeNode.message.payload, this.props.treeNode.type)
|
|
||||||
return value.length > limit ? `${value.slice(0, limit)}…` : value
|
return value.length > limit ? `${value.slice(0, limit)}…` : value
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,11 +39,11 @@ class TreeNodeTitle extends React.PureComponent<TreeNodeProps, {}> {
|
|||||||
return this.props.treeNode.message &&
|
return this.props.treeNode.message &&
|
||||||
this.props.treeNode.message.payload &&
|
this.props.treeNode.message.payload &&
|
||||||
this.props.treeNode.message.length > 0 ? (
|
this.props.treeNode.message.length > 0 ? (
|
||||||
<span key="value" className={this.props.classes.value}>
|
<span key="value" className={this.props.classes.value}>
|
||||||
{' '}
|
{' '}
|
||||||
= {this.truncatedMessage()}
|
= {this.truncatedMessage()}
|
||||||
</span>
|
</span>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderExpander() {
|
private renderExpander() {
|
||||||
@@ -66,8 +66,9 @@ class TreeNodeTitle extends React.PureComponent<TreeNodeProps, {}> {
|
|||||||
const messages = this.props.treeNode.leafMessageCount()
|
const messages = this.props.treeNode.leafMessageCount()
|
||||||
const topicCount = this.props.treeNode.childTopicCount()
|
const topicCount = this.props.treeNode.childTopicCount()
|
||||||
return (
|
return (
|
||||||
<span key="metadata" className={this.props.classes.collapsedSubnodes}>{` (${topicCount} ${topicCount === 1 ? 'topic' : 'topics'
|
<span key="metadata" className={this.props.classes.collapsedSubnodes}>{` (${topicCount} ${
|
||||||
}, ${messages} ${messages === 1 ? 'message' : 'messages'})`}</span>
|
topicCount === 1 ? 'topic' : 'topics'
|
||||||
|
}, ${messages} ${messages === 1 ? 'message' : 'messages'})`}</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as compareVersions from 'compare-versions'
|
import compareVersions from 'compare-versions'
|
||||||
import * as electron from 'electron'
|
import electron from 'electron'
|
||||||
import * as os from 'os'
|
import os from 'os'
|
||||||
import * as React from 'react'
|
import React from 'react'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import Close from '@material-ui/icons/Close'
|
import Close from '@material-ui/icons/Close'
|
||||||
import CloudDownload from '@material-ui/icons/CloudDownload'
|
import CloudDownload from '@material-ui/icons/CloudDownload'
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as moment from 'moment'
|
import moment from 'moment'
|
||||||
import * as React from 'react'
|
import React from 'react'
|
||||||
import { AppState } from '../../reducers'
|
import { AppState } from '../../reducers'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
|
|
||||||
|
|||||||
@@ -4,38 +4,26 @@
|
|||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
"strictNullChecks": true,
|
"strictNullChecks": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"lib": [
|
"lib": ["es2019", "dom"],
|
||||||
"es2017",
|
|
||||||
"dom"
|
|
||||||
],
|
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"outDir": "./build/",
|
"outDir": "./build/",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
"target": "es2017",
|
"target": "ES2017",
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
"paths": {
|
"paths": {
|
||||||
"react": [
|
"react": ["./node_modules/@types/react"]
|
||||||
"./node_modules/@types/react"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"types": [
|
"types": ["react"],
|
||||||
"react"
|
|
||||||
],
|
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"skipLibCheck": true
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["./src/**/*"],
|
||||||
"./src/**/*"
|
"exclude": ["**/*.d.ts", ".src/**/*.png", "./node_modules"],
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"**/*.d.ts",
|
|
||||||
".src/**/*.png",
|
|
||||||
"./node_modules"
|
|
||||||
],
|
|
||||||
"awesomeTypescriptLoaderOptions": {
|
"awesomeTypescriptLoaderOptions": {
|
||||||
"useCache": true,
|
"useCache": true,
|
||||||
"transpileModule": true,
|
"transpileModule": true,
|
||||||
"errorsAsWarnings": true
|
"errorsAsWarnings": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,15 @@ module.exports = {
|
|||||||
// All files with a '.ts' or '.tsx' extension will be handled by 'awesome-typescript-loader'.
|
// All files with a '.ts' or '.tsx' extension will be handled by 'awesome-typescript-loader'.
|
||||||
{
|
{
|
||||||
test: /\.tsx?$/,
|
test: /\.tsx?$/,
|
||||||
loader: 'ts-loader',
|
use: [
|
||||||
|
{
|
||||||
|
loader: 'ts-loader',
|
||||||
|
// options: {
|
||||||
|
// configFile: './tsconfig.json',
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
exclude: /node_modules/,
|
||||||
},
|
},
|
||||||
// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
|
// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
|
||||||
{ enforce: 'pre', test: /\.js$/, loader: 'source-map-loader' },
|
{ enforce: 'pre', test: /\.js$/, loader: 'source-map-loader' },
|
||||||
@@ -96,4 +104,7 @@ module.exports = {
|
|||||||
// "react": "React",
|
// "react": "React",
|
||||||
// "react-dom": "ReactDOM"
|
// "react-dom": "ReactDOM"
|
||||||
},
|
},
|
||||||
|
cache: {
|
||||||
|
type: 'filesystem',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as FileAsync from 'lowdb/adapters/FileAsync'
|
import FileAsync from 'lowdb/adapters/FileAsync'
|
||||||
import * as fs from 'fs-extra'
|
import fs from 'fs-extra'
|
||||||
import * as lowdb from 'lowdb'
|
import lowdb from 'lowdb'
|
||||||
import * as path from 'path'
|
import path from 'path'
|
||||||
import { backendRpc } from '../../events'
|
import { backendRpc } from '../../events'
|
||||||
import { storageClearEvent, storageLoadEvent, storageStoreEvent } from '../../events/StorageEvents'
|
import { storageClearEvent, storageLoadEvent, storageStoreEvent } from '../../events/StorageEvents'
|
||||||
|
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export class MqttSource implements DataSource<MqttOptions> {
|
|||||||
|
|
||||||
public publish(msg: MqttMessage) {
|
public publish(msg: MqttMessage) {
|
||||||
if (this.client) {
|
if (this.client) {
|
||||||
this.client.publish(msg.topic, msg.payload ? Base64Message.toUnicodeString(msg.payload) : '', {
|
this.client.publish(msg.topic, msg.payload?.toBuffer() ?? '', {
|
||||||
qos: msg.qos,
|
qos: msg.qos,
|
||||||
retain: msg.retain,
|
retain: msg.retain,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,93 +3,55 @@ import { Decoder } from './Decoder'
|
|||||||
import { TopicDataType } from './TreeNode'
|
import { TopicDataType } from './TreeNode'
|
||||||
|
|
||||||
export class Base64Message {
|
export class Base64Message {
|
||||||
private base64Message: string
|
public base64Message: string
|
||||||
private unicodeValue: string
|
private unicodeValue: string
|
||||||
|
public error?: string
|
||||||
public decoder: Decoder
|
public decoder: Decoder
|
||||||
public length: number
|
public length: number
|
||||||
|
|
||||||
private constructor(base64Str: string) {
|
constructor(base64Str?: string, error?: string) {
|
||||||
this.base64Message = base64Str
|
this.base64Message = base64Str ?? ''
|
||||||
this.unicodeValue = Base64.decode(base64Str)
|
this.error = error
|
||||||
this.length = base64Str.length
|
this.unicodeValue = Base64.decode(base64Str ?? '')
|
||||||
|
this.length = base64Str?.length ?? 0
|
||||||
this.decoder = Decoder.NONE
|
this.decoder = Decoder.NONE
|
||||||
}
|
}
|
||||||
|
|
||||||
public static toUnicodeString(message: Base64Message) {
|
public toUnicodeString() {
|
||||||
return message.unicodeValue || ''
|
return this.unicodeValue || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
public static fromBuffer(buffer: Buffer) {
|
public static fromBuffer(buffer: Buffer) {
|
||||||
return new Base64Message(buffer.toString('base64'))
|
return new Base64Message(buffer.toString('base64'))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public toBuffer(): Buffer {
|
||||||
|
return Buffer.from(this.base64Message, 'base64')
|
||||||
|
}
|
||||||
|
|
||||||
public static fromString(str: string) {
|
public static fromString(str: string) {
|
||||||
return new Base64Message(Base64.encode(str))
|
return new Base64Message(Base64.encode(str))
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Raw message conversions ('uint8' | 'uint16' | 'uint32' | 'uint64' | 'int8' | 'int16' | 'int32' | 'int64' | 'float' | 'double') */
|
/* Raw message conversions ('uint8' | 'uint16' | 'uint32' | 'uint64' | 'int8' | 'int16' | 'int32' | 'int64' | 'float' | 'double') */
|
||||||
public static format(message: Base64Message | null, type: TopicDataType = 'string'): [string, 'json' | undefined] {
|
public format(type: TopicDataType = 'string'): [string, 'json' | undefined] {
|
||||||
if (!message) {
|
|
||||||
return ['', undefined]
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'json': {
|
case 'json': {
|
||||||
const json = JSON.parse(Base64Message.toUnicodeString(message))
|
const json = JSON.parse(this.toUnicodeString())
|
||||||
return [JSON.stringify(json, undefined, ' '), 'json']
|
return [JSON.stringify(json, undefined, ' '), 'json']
|
||||||
}
|
}
|
||||||
case 'hex': {
|
case 'hex': {
|
||||||
const hex = Base64Message.toHex(message)
|
const hex = Base64Message.toHex(this)
|
||||||
return [hex, undefined]
|
return [hex, undefined]
|
||||||
}
|
}
|
||||||
case 'uint8': {
|
|
||||||
const uint = Base64Message.toUInt(message, 1)
|
|
||||||
return [uint ? uint : '', undefined]
|
|
||||||
}
|
|
||||||
case 'uint16': {
|
|
||||||
const uint = Base64Message.toUInt(message, 2)
|
|
||||||
return [uint ? uint : '', undefined]
|
|
||||||
}
|
|
||||||
case 'uint32': {
|
|
||||||
const uint = Base64Message.toUInt(message, 4)
|
|
||||||
return [uint ? uint : '', undefined]
|
|
||||||
}
|
|
||||||
case 'uint64': {
|
|
||||||
const uint = Base64Message.toUInt(message, 8)
|
|
||||||
return [uint ? uint : '', undefined]
|
|
||||||
}
|
|
||||||
case 'int8': {
|
|
||||||
const int = Base64Message.toInt(message, 1)
|
|
||||||
return [int ? int : '', undefined]
|
|
||||||
}
|
|
||||||
case 'int16': {
|
|
||||||
const int = Base64Message.toInt(message, 2)
|
|
||||||
return [int ? int : '', undefined]
|
|
||||||
}
|
|
||||||
case 'int32': {
|
|
||||||
const int = Base64Message.toInt(message, 4)
|
|
||||||
return [int ? int : '', undefined]
|
|
||||||
}
|
|
||||||
case 'int64': {
|
|
||||||
const int = Base64Message.toInt(message, 8)
|
|
||||||
return [int ? int : '', undefined]
|
|
||||||
}
|
|
||||||
case 'float': {
|
|
||||||
const float = Base64Message.toFloat(message, 4)
|
|
||||||
return [float ? float : '', undefined]
|
|
||||||
}
|
|
||||||
case 'double': {
|
|
||||||
const float = Base64Message.toFloat(message, 8)
|
|
||||||
return [float ? float : '', undefined]
|
|
||||||
}
|
|
||||||
default: {
|
default: {
|
||||||
const str = Base64Message.toUnicodeString(message)
|
const str = this.toUnicodeString()
|
||||||
return [str, undefined]
|
return [str, undefined]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const str = Base64Message.toUnicodeString(message)
|
const str = this.toUnicodeString()
|
||||||
return [str, undefined]
|
return [str, undefined]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -105,89 +67,6 @@ export class Base64Message {
|
|||||||
return str.trimRight()
|
return str.trimRight()
|
||||||
}
|
}
|
||||||
|
|
||||||
public static toUInt(message: Base64Message, bytes: number) {
|
|
||||||
const buf = Buffer.from(message.base64Message, 'base64')
|
|
||||||
|
|
||||||
let str: String[] = []
|
|
||||||
switch (bytes) {
|
|
||||||
case 1:
|
|
||||||
for (let index = 0; index < buf.length; index += bytes) {
|
|
||||||
str.push(buf.readUInt8(index).toString())
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 2:
|
|
||||||
for (let index = 0; index < buf.length; index += bytes) {
|
|
||||||
str.push(buf.readUInt16LE(index).toString())
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 4:
|
|
||||||
for (let index = 0; index < buf.length; index += bytes) {
|
|
||||||
str.push(buf.readUInt32LE(index).toString())
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 8:
|
|
||||||
for (let index = 0; index < buf.length; index += bytes) {
|
|
||||||
str.push(buf.readBigUInt64LE(index).toString())
|
|
||||||
}
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
return str.join(', ')
|
|
||||||
}
|
|
||||||
|
|
||||||
public static toInt(message: Base64Message, bytes: number) {
|
|
||||||
const buf = Buffer.from(message.base64Message, 'base64')
|
|
||||||
|
|
||||||
let str: String[] = []
|
|
||||||
switch (bytes) {
|
|
||||||
case 1:
|
|
||||||
for (let index = 0; index < buf.length; index += bytes) {
|
|
||||||
str.push(buf.readInt8(index).toString())
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 2:
|
|
||||||
for (let index = 0; index < buf.length; index += bytes) {
|
|
||||||
str.push(buf.readInt16LE(index).toString())
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 4:
|
|
||||||
for (let index = 0; index < buf.length; index += bytes) {
|
|
||||||
str.push(buf.readInt32LE(index).toString())
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 8:
|
|
||||||
for (let index = 0; index < buf.length; index += bytes) {
|
|
||||||
str.push(buf.readBigInt64LE(index).toString())
|
|
||||||
}
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
return str.join(', ')
|
|
||||||
}
|
|
||||||
|
|
||||||
public static toFloat(message: Base64Message, bytes: number) {
|
|
||||||
const buf = Buffer.from(message.base64Message, 'base64')
|
|
||||||
|
|
||||||
let str: String[] = []
|
|
||||||
switch (bytes) {
|
|
||||||
case 4:
|
|
||||||
for (let index = 0; index < buf.length; index += bytes) {
|
|
||||||
str.push(buf.readFloatLE(index).toString())
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 8:
|
|
||||||
for (let index = 0; index < buf.length; index += bytes) {
|
|
||||||
str.push(buf.readDoubleLE(index).toString())
|
|
||||||
}
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
return str.join(', ')
|
|
||||||
}
|
|
||||||
|
|
||||||
public static toDataUri(message: Base64Message, mimeType: string) {
|
public static toDataUri(message: Base64Message, mimeType: string) {
|
||||||
return `data:${mimeType};base64,${message.base64Message}`
|
return `data:${mimeType};base64,${message.base64Message}`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,31 @@
|
|||||||
import { Destroyable } from './Destroyable'
|
import { Destroyable } from './Destroyable'
|
||||||
import { Edge, Message, RingBuffer, MessageHistory } from './'
|
import { Edge, Message, RingBuffer, MessageHistory } from './'
|
||||||
import { EventDispatcher } from '../../../events'
|
import { EventDispatcher } from '../../../events'
|
||||||
|
import { IDecoder, decoders } from './sparkplugb'
|
||||||
|
import { Base64Message } from './Base64Message'
|
||||||
|
|
||||||
// export type TopicDataType = 'json' | 'string' | 'hex' | 'integer' | 'unsigned int' | 'floating point'
|
// export type TopicDataType = 'json' | 'string' | 'hex' | 'integer' | 'unsigned int' | 'floating point'
|
||||||
export type TopicDataType = 'json' | 'string' | 'hex' | 'uint8' | 'uint16' | 'uint32' | 'uint64' | 'int8' | 'int16' | 'int32' | 'int64' | 'float' | 'double'
|
export type TopicDataType =
|
||||||
|
| 'json'
|
||||||
|
| 'string'
|
||||||
|
| 'hex'
|
||||||
|
| 'uint8'
|
||||||
|
| 'uint16'
|
||||||
|
| 'uint32'
|
||||||
|
| 'uint64'
|
||||||
|
| 'int8'
|
||||||
|
| 'int16'
|
||||||
|
| 'int32'
|
||||||
|
| 'int64'
|
||||||
|
| 'float'
|
||||||
|
| 'double'
|
||||||
|
|
||||||
|
function findDecoder<T extends Destroyable>(node: TreeNode<T>): IDecoder | undefined {
|
||||||
|
return decoders.find(
|
||||||
|
decoder =>
|
||||||
|
decoder.canDecodeTopic?.(node.path()) || (node.message?.payload && decoder.canDecodeData?.(node.message?.payload))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export class TreeNode<ViewModel extends Destroyable> {
|
export class TreeNode<ViewModel extends Destroyable> {
|
||||||
public sourceEdge?: Edge<ViewModel>
|
public sourceEdge?: Edge<ViewModel>
|
||||||
@@ -22,6 +44,28 @@ export class TreeNode<ViewModel extends Destroyable> {
|
|||||||
public isTree = false
|
public isTree = false
|
||||||
public type: TopicDataType = 'json'
|
public type: TopicDataType = 'json'
|
||||||
|
|
||||||
|
private _decoder?: IDecoder
|
||||||
|
|
||||||
|
public decoderFormat?: string
|
||||||
|
|
||||||
|
get decoder(): IDecoder | undefined {
|
||||||
|
if (!this._decoder) {
|
||||||
|
this._decoder = findDecoder(this)
|
||||||
|
}
|
||||||
|
return this._decoder
|
||||||
|
}
|
||||||
|
|
||||||
|
set decoder(override: IDecoder | undefined) {
|
||||||
|
this._decoder = override
|
||||||
|
this.message && this.onMerge.dispatch()
|
||||||
|
}
|
||||||
|
|
||||||
|
decodeMessage(message: Message): Base64Message | null {
|
||||||
|
const decoder = this.decoder
|
||||||
|
|
||||||
|
return this.decoder && message.payload ? this.decoder.decode(message.payload, this.decoderFormat) : message.payload
|
||||||
|
}
|
||||||
|
|
||||||
private cachedPath?: string
|
private cachedPath?: string
|
||||||
private cachedChildTopics?: Array<TreeNode<ViewModel>>
|
private cachedChildTopics?: Array<TreeNode<ViewModel>>
|
||||||
private cachedLeafMessageCount?: number
|
private cachedLeafMessageCount?: number
|
||||||
@@ -157,7 +201,7 @@ export class TreeNode<ViewModel extends Destroyable> {
|
|||||||
|
|
||||||
public path(): string {
|
public path(): string {
|
||||||
if (!this.cachedPath) {
|
if (!this.cachedPath) {
|
||||||
return this.branch()
|
this.cachedPath = this.branch()
|
||||||
.map(node => node.sourceEdge && node.sourceEdge.name)
|
.map(node => node.sourceEdge && node.sourceEdge.name)
|
||||||
.filter(name => name !== undefined)
|
.filter(name => name !== undefined)
|
||||||
.join('/')
|
.join('/')
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Destroyable } from './Destroyable'
|
import { Destroyable } from './Destroyable'
|
||||||
import { Edge, Tree, TreeNode } from './'
|
import { Edge, Tree, TreeNode } from './'
|
||||||
import { MqttMessage } from '../../../events'
|
import { MqttMessage } from '../../../events'
|
||||||
|
import { Base64Message } from './Base64Message'
|
||||||
|
|
||||||
export abstract class TreeNodeFactory {
|
export abstract class TreeNodeFactory {
|
||||||
private static messageCounter = 0
|
private static messageCounter = 0
|
||||||
@@ -30,6 +31,7 @@ export abstract class TreeNodeFactory {
|
|||||||
mqttMessage.retain
|
mqttMessage.retain
|
||||||
node.setMessage({
|
node.setMessage({
|
||||||
...mqttMessage,
|
...mqttMessage,
|
||||||
|
payload: mqttMessage.payload && new Base64Message(mqttMessage.payload?.base64Message),
|
||||||
length: mqttMessage.payload?.length ?? 0,
|
length: mqttMessage.payload?.length ?? 0,
|
||||||
received: receiveDate,
|
received: receiveDate,
|
||||||
messageNumber: this.messageCounter,
|
messageNumber: this.messageCounter,
|
||||||
|
|||||||
@@ -1,21 +1,98 @@
|
|||||||
import { Base64Message } from './Base64Message'
|
import { Base64Message } from './Base64Message'
|
||||||
import { Decoder } from './Decoder'
|
import { Decoder } from './Decoder'
|
||||||
import { get } from 'sparkplug-payload'
|
import { get } from 'sparkplug-payload'
|
||||||
var sparkplug = get("spBv1.0")
|
var sparkplug = get('spBv1.0')
|
||||||
|
|
||||||
export const SparkplugDecoder = {
|
export interface IDecoder<T = string> {
|
||||||
decode(input: Buffer): Base64Message {
|
/**
|
||||||
|
* Can be used to
|
||||||
|
* @param topic
|
||||||
|
*/
|
||||||
|
formats: T[]
|
||||||
|
canDecodeTopic?(topic: string): boolean
|
||||||
|
canDecodeData?(data: Base64Message): boolean
|
||||||
|
decode(input: Base64Message, format: T | string | undefined): Base64Message
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this is just an intermediate decoder, next-decoder can be defined
|
||||||
|
*/
|
||||||
|
nextDecoder?: IDecoder
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SparkplugDecoder: IDecoder = {
|
||||||
|
formats: ['Sparkplug'],
|
||||||
|
canDecodeTopic(topic: string) {
|
||||||
|
return !!topic.match(/spBv1\.0\/[^/]+\/(DDATA|NDATA|NCMD|DCMD|NBIRTH|DBIRTH|NDEATH|DDEATH\/[^/]+\/)/u)
|
||||||
|
},
|
||||||
|
decode(input: Base64Message): Base64Message {
|
||||||
try {
|
try {
|
||||||
const message = Base64Message.fromString(JSON.stringify(
|
const message = Base64Message.fromString(
|
||||||
// @ts-ignore
|
JSON.stringify(
|
||||||
sparkplug.decodePayload(new Uint8Array(input)))
|
// @ts-ignore
|
||||||
|
sparkplug.decodePayload(new Uint8Array(input.toBuffer()))
|
||||||
|
)
|
||||||
)
|
)
|
||||||
message.decoder = Decoder.SPARKPLUG
|
message.decoder = Decoder.SPARKPLUG
|
||||||
return message
|
return message
|
||||||
} catch {
|
} catch {
|
||||||
const message = Base64Message.fromString("Failed to decode sparkplugb payload")
|
const message = new Base64Message(undefined, 'Failed to decode sparkplugb payload')
|
||||||
message.decoder = Decoder.NONE
|
message.decoder = Decoder.NONE
|
||||||
return message
|
return message
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const StringDecoder: IDecoder = {
|
||||||
|
formats: ['string'],
|
||||||
|
decode(input: Base64Message): Base64Message {
|
||||||
|
return input
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type BinaryFormats =
|
||||||
|
| 'int8'
|
||||||
|
| 'int16'
|
||||||
|
| 'int32'
|
||||||
|
| 'int64'
|
||||||
|
| 'uint8'
|
||||||
|
| 'uint16'
|
||||||
|
| 'uint32'
|
||||||
|
| 'uint64'
|
||||||
|
| 'float'
|
||||||
|
| 'double'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binary decode primitive binary data type and arrays of these
|
||||||
|
*/
|
||||||
|
export const BinaryDecoder: IDecoder<BinaryFormats> = {
|
||||||
|
formats: ['int8', 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32', 'uint64', 'float', 'double'],
|
||||||
|
decode(input: Base64Message, format: BinaryFormats): Base64Message {
|
||||||
|
const decodingOption = {
|
||||||
|
int8: [Buffer.prototype.readInt8, 1],
|
||||||
|
int16: [Buffer.prototype.readInt16LE, 2],
|
||||||
|
int32: [Buffer.prototype.readInt32LE, 4],
|
||||||
|
int64: [Buffer.prototype.readBigInt64LE, 8],
|
||||||
|
uint8: [Buffer.prototype.readUint8, 1],
|
||||||
|
uint16: [Buffer.prototype.readUint16LE, 2],
|
||||||
|
uint32: [Buffer.prototype.readUint32LE, 4],
|
||||||
|
uint64: [Buffer.prototype.readBigUint64LE, 8],
|
||||||
|
float: [Buffer.prototype.readFloatLE, 4],
|
||||||
|
double: [Buffer.prototype.readDoubleLE, 8],
|
||||||
|
} as const
|
||||||
|
|
||||||
|
const [readNumber, bytesToRead] = decodingOption[format]
|
||||||
|
|
||||||
|
const buf = input.toBuffer()
|
||||||
|
let str: String[] = []
|
||||||
|
if (buf.length % bytesToRead !== 0) {
|
||||||
|
return new Base64Message(undefined, 'Data type does not align with message')
|
||||||
|
}
|
||||||
|
for (let index = 0; index < buf.length; index += bytesToRead) {
|
||||||
|
str.push((readNumber as any).apply(buf, [index]).toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
return Base64Message.fromString(JSON.stringify(str.length === 1 ? str[0] : str))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const decoders = [SparkplugDecoder, BinaryDecoder, StringDecoder] as const
|
||||||
|
|||||||
@@ -48,12 +48,7 @@ export class ConnectionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let decoded_payload = null
|
let decoded_payload = null
|
||||||
// spell-checker: disable-next-line
|
decoded_payload = Base64Message.fromBuffer(buffer)
|
||||||
if (topic.match(/spBv1\.0\/[^/]+\/(DDATA|NDATA|NCMD|DCMD|NBIRTH|DBIRTH|NDEATH|DDEATH\/[^/]+\/)/u)) {
|
|
||||||
decoded_payload = SparkplugDecoder.decode(buffer)
|
|
||||||
} else {
|
|
||||||
decoded_payload = Base64Message.fromBuffer(buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
backendEvents.emit(messageEvent, {
|
backendEvents.emit(messageEvent, {
|
||||||
topic,
|
topic,
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ registerCrashReporter()
|
|||||||
// const electronTelemetry = electronTelemetryFactory('9b0c8ca04a361eb8160d98c5', buildOptions)
|
// const electronTelemetry = electronTelemetryFactory('9b0c8ca04a361eb8160d98c5', buildOptions)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
app.commandLine.appendSwitch('--no-sandbox')
|
app.commandLine.appendSwitch('--no-sandbox --disable-dev-shm-usage')
|
||||||
app.whenReady().then(() => {
|
app.whenReady().then(() => {
|
||||||
backendRpc.on(makeOpenDialogRpc(), async request => {
|
backendRpc.on(makeOpenDialogRpc(), async request => {
|
||||||
return dialog.showOpenDialog(BrowserWindow.getFocusedWindow() ?? BrowserWindow.getAllWindows()[0], request)
|
return dialog.showOpenDialog(BrowserWindow.getFocusedWindow() ?? BrowserWindow.getAllWindows()[0], request)
|
||||||
@@ -70,7 +70,7 @@ async function createWindow() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
console.log('icon path', iconPath)
|
console.log('icon path', iconPath)
|
||||||
|
mainWindow.webContents.openDevTools({ mode: 'detach' })
|
||||||
// Load the index.html of the app.
|
// Load the index.html of the app.
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
mainWindow.loadURL('http://localhost:8080')
|
mainWindow.loadURL('http://localhost:8080')
|
||||||
|
|||||||
@@ -9,15 +9,11 @@
|
|||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"sourceRoot": "src/",
|
"sourceRoot": "src/",
|
||||||
"target": "ES2017",
|
"target": "ES2017",
|
||||||
"lib": [
|
"lib": ["ES2017", "dom"],
|
||||||
"es2017",
|
|
||||||
"dom"
|
|
||||||
],
|
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"types": [
|
"types": ["node"],
|
||||||
"node"
|
"skipLibCheck": true,
|
||||||
],
|
"esModuleInterop": true
|
||||||
"skipLibCheck": true
|
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src/electron.ts",
|
"src/electron.ts",
|
||||||
@@ -26,7 +22,5 @@
|
|||||||
"src/spec/leakTest.ts",
|
"src/spec/leakTest.ts",
|
||||||
"scripts/*.ts"
|
"scripts/*.ts"
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": ["node_modules"]
|
||||||
"node_modules"
|
}
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user