Skip to content

Commit 87cdf32

Browse files
committed
Allow writes to float32 and change formatting when displaying float32 reads to reduce number of decimal spaces
1 parent 2b6610e commit 87cdf32

2 files changed

Lines changed: 289 additions & 132 deletions

File tree

frontend/src/components/ReadTable.tsx

Lines changed: 188 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,16 @@
1-
import { Menu, Box, Text, Stack, Container, Group, NumberInput, Select, Table, ActionIcon, Button } from "@mantine/core";
1+
import {
2+
Menu,
3+
Box,
4+
Text,
5+
Stack,
6+
Container,
7+
Group,
8+
NumberInput,
9+
Select,
10+
Table,
11+
ActionIcon,
12+
Button,
13+
} from "@mantine/core";
214
import { IconCaretDownFilled, IconRefresh } from "@tabler/icons-react";
315
import { useEffect, useState } from "react";
416
import { Read } from "../../wailsjs/go/main/App";
@@ -19,12 +31,14 @@ export enum dataTypes {
1931
export function ReadTable() {
2032
// rawData array of modbusData
2133
const [rawData, setRawData] = useState<modbusData[]>([]);
22-
const [typeArray, setTypeArray] = useState<dataTypes[]>(Array(rawData.length).fill(dataTypes.U16));
23-
const [error, setError] = useState<string>('')
34+
const [typeArray, setTypeArray] = useState<dataTypes[]>(
35+
Array(rawData.length).fill(dataTypes.U16)
36+
);
37+
const [error, setError] = useState<string>("");
2438

2539
// Form data TODO: replace with mantine form handler
26-
const [address, setAddress] = useState<string | number>('');
27-
const [quantity, setQuantity] = useState<string | number>('');
40+
const [address, setAddress] = useState<string | number>("");
41+
const [quantity, setQuantity] = useState<string | number>("");
2842
const [type, setReadType] = useState<string>("Holding Register");
2943

3044
// Update the array of data types whenever the number of fields returned changes.
@@ -36,62 +50,77 @@ export function ReadTable() {
3650

3751
// Copy of raw data as getting the typed data consumes the array.
3852
const rawDataCopy = [...rawData];
39-
const typedData: ({ address: number, value: any } | undefined)[] = typeArray.map((type, i) => {
40-
// Iterate through the array of data types and convert respectively.
41-
// Consumes the array since there will be a different number of output elements depending
42-
// on the data types selected
43-
if (rawDataCopy.length == 0) return { address: 0, value: 0 };
44-
switch (type) {
45-
case dataTypes.BIN:
46-
{
53+
const typedData: ({ address: number; value: any } | undefined)[] =
54+
typeArray.map((type, i) => {
55+
// Iterate through the array of data types and convert respectively.
56+
// Consumes the array since there will be a different number of output elements depending
57+
// on the data types selected
58+
if (rawDataCopy.length == 0) return { address: 0, value: 0 };
59+
switch (type) {
60+
case dataTypes.BIN: {
4761
const val = rawDataCopy.shift();
4862
// Convert to binary showing leading zeros
49-
const stringVal = val!.Value.toString(2).padStart(16, '0')
50-
const strings = stringVal.match(/.{1,4}/g)
51-
return { address: val!.Address, value: strings?.join(' ') };
63+
const stringVal = val!.Value.toString(2).padStart(16, "0");
64+
const strings = stringVal.match(/.{1,4}/g);
65+
return { address: val!.Address, value: strings?.join(" ") };
5266
}
53-
case dataTypes.U16:
54-
{
67+
case dataTypes.U16: {
5568
const val = rawDataCopy.shift();
5669
return { address: val!.Address, value: val!.Value };
5770
}
58-
case dataTypes.U32:
59-
{
71+
case dataTypes.U32: {
6072
const val1 = rawDataCopy.shift();
6173
const val2 = rawDataCopy.shift();
62-
return { address: val1!.Address, value: ((val1!.Value << 16) >>> 0) + (val2!.Value >>> 0) };
74+
return {
75+
address: val1!.Address,
76+
value: ((val1!.Value << 16) >>> 0) + (val2!.Value >>> 0),
77+
};
6378
}
64-
case dataTypes.I16:
65-
{
79+
case dataTypes.I16: {
6680
const val = rawDataCopy.shift();
6781
const value = val!.Value;
6882
// Convert U16 to I16 using 2's complement
69-
const signedValue = value > 0x7FFF ? value - 0x10000 : value;
83+
const signedValue = value > 0x7fff ? value - 0x10000 : value;
7084
return { address: val!.Address, value: signedValue };
7185
}
72-
case dataTypes.I32:
73-
{
86+
case dataTypes.I32: {
7487
const val1 = rawDataCopy.shift();
7588
const val2 = rawDataCopy.shift();
7689
const combinedValue = (val1!.Value << 16) + val2!.Value;
77-
const signedValue = combinedValue > 0x7FFFFFFF ? combinedValue - 0x100000000 : combinedValue;
90+
const signedValue =
91+
combinedValue > 0x7fffffff
92+
? combinedValue - 0x100000000
93+
: combinedValue;
7894
return { address: val1!.Address, value: signedValue };
7995
}
80-
case dataTypes.F32:
81-
{
96+
case dataTypes.F32: {
8297
const val1 = rawDataCopy.shift();
8398
const val2 = rawDataCopy.shift();
84-
const u32 = new Uint32Array([((val1!.Value << 16) >>> 0) + (val2!.Value >>> 0)])
85-
return { address: val1!.Address, value: new Float32Array(u32.buffer)[0] };
99+
const u32 = new Uint32Array([
100+
((val1!.Value << 16) >>> 0) + (val2!.Value >>> 0),
101+
]);
102+
let val = new Float32Array(u32.buffer)[0];
103+
let displayVal = "";
104+
if (val < 1) {
105+
displayVal = val.toPrecision(4);
106+
} else if (val % 1 < 0.001) {
107+
displayVal = val.toFixed(0);
108+
} else {
109+
displayVal = val.toFixed(3);
110+
}
111+
return {
112+
address: val1!.Address,
113+
value: displayVal,
114+
};
86115
}
87-
}
88-
});
116+
}
117+
});
89118

90119
// Sets an index to an array type. Needs to also handle removing or re-adding the following element
91120
// depending on the number of registers the type requires
92121
function setType(i: number, type: dataTypes) {
93122
if (typeArray[i] === type) {
94-
return
123+
return;
95124
}
96125
switch (type) {
97126
// 16 bit types
@@ -101,7 +130,11 @@ export function ReadTable() {
101130
setTypeArray((prev) => {
102131
let previousType = prev[i];
103132
prev[i] = type;
104-
if (previousType === dataTypes.U32 || previousType === dataTypes.I32 || previousType === dataTypes.F32) {
133+
if (
134+
previousType === dataTypes.U32 ||
135+
previousType === dataTypes.I32 ||
136+
previousType === dataTypes.F32
137+
) {
105138
prev.splice(i + 1, 0, dataTypes.U16);
106139
}
107140
return [...prev];
@@ -118,9 +151,17 @@ export function ReadTable() {
118151
let previousType = prev[i];
119152
prev[i] = type;
120153
// if previous type was 16 bit
121-
if (previousType === dataTypes.U16 || previousType === dataTypes.I16 || previousType === dataTypes.BIN) {
154+
if (
155+
previousType === dataTypes.U16 ||
156+
previousType === dataTypes.I16 ||
157+
previousType === dataTypes.BIN
158+
) {
122159
// if next type in a rray is 32 bit then convert it back to 16 bit as the first register has just been nicked
123-
if (prev[i + 1] === dataTypes.U32 || prev[i + 1] === dataTypes.I32 || prev[i + 1] === dataTypes.F32) {
160+
if (
161+
prev[i + 1] === dataTypes.U32 ||
162+
prev[i + 1] === dataTypes.I32 ||
163+
prev[i + 1] === dataTypes.F32
164+
) {
124165
prev[i + 1] = dataTypes.U16;
125166
} else {
126167
prev.splice(i + 1, 1);
@@ -135,103 +176,169 @@ export function ReadTable() {
135176
// Call the read function from the go backend
136177
function ReadModbus() {
137178
if (address === "" || quantity === "") {
138-
setError("Enter an address and quantity.")
139-
setRefreshRate("None")
140-
return
179+
setError("Enter an address and quantity.");
180+
setRefreshRate("None");
181+
return;
141182
}
142183
Read(type, address as number, quantity as number)
143184
.then((data) => {
144185
if (data.length < rawData.length) {
145186
setTypeArray(Array(data.length).fill(dataTypes.U16));
146187
}
147188
setRawData(data);
148-
setError('')
189+
setError("");
149190
})
150191
.catch((err) => {
151-
setError(err)
152-
setRefreshRate("None")
192+
setError(err);
193+
setRefreshRate("None");
153194
notifications.show({
154195
title: "Failed to Read",
155-
message: err
156-
})
196+
message: err,
197+
});
157198
});
158199
}
159200

160-
let content = error ? (<Text>{error}</Text>) : (
161-
<ResultDisplay type={type} typeArray={typeArray} typedData={typedData} setType={setType} />
162-
)
201+
let content = error ? (
202+
<Text>{error}</Text>
203+
) : (
204+
<ResultDisplay
205+
type={type}
206+
typeArray={typeArray}
207+
typedData={typedData}
208+
setType={setType}
209+
/>
210+
);
163211

164-
const [refreshRate, setRefreshRate] = useState<string>("None")
212+
const [refreshRate, setRefreshRate] = useState<string>("None");
165213
const [opened, setOpened] = useState(false);
166214

167215
useEffect(() => {
168216
// Don't auto fetch if set to none or the menu is open
169217
if (refreshRate == "None" || opened) return;
170-
let refresh = 0
218+
let refresh = 0;
171219
switch (refreshRate) {
172220
case "1s":
173221
refresh = 1000;
174-
break
222+
break;
175223
case "2s":
176224
refresh = 2000;
177-
break
225+
break;
178226
case "5s":
179227
refresh = 5000;
180-
break
228+
break;
181229
case "10s":
182230
refresh = 10000;
183-
break
231+
break;
184232
case "30s":
185233
refresh = 30000;
186-
break
234+
break;
187235
}
188-
const interval = setInterval(ReadModbus, refresh)
189-
return () => clearInterval(interval)
190-
}, [refreshRate, address, quantity, opened]) // Needs to depend on all of these or ReadModbus will become out of date compared to the inputs
191-
236+
const interval = setInterval(ReadModbus, refresh);
237+
return () => clearInterval(interval);
238+
}, [refreshRate, address, quantity, opened]); // Needs to depend on all of these or ReadModbus will become out of date compared to the inputs
192239

193240
return (
194241
<Stack>
195242
<Group justify="">
196-
<NumberInput label="Start Register:" min={0} placeholder="Start Register" size="xs"
197-
radius="xs" onChange={setAddress} />
198-
<NumberInput placeholder="Quantity" label="Quantity:" size="xs" min={1} max={125}
199-
radius="xs" onChange={setQuantity} />
200-
<Select data={["Holding Register", "Discrete Input", "Input Register", "Coil"]} value={type} size="xs" label="Register Type"
201-
radius="xs" onChange={value => { setReadType(value!); setRawData([]) }} />
243+
<NumberInput
244+
label="Start Register:"
245+
min={0}
246+
placeholder="Start Register"
247+
size="xs"
248+
radius="xs"
249+
onChange={setAddress}
250+
/>
251+
<NumberInput
252+
placeholder="Quantity"
253+
label="Quantity:"
254+
size="xs"
255+
min={1}
256+
max={125}
257+
radius="xs"
258+
onChange={setQuantity}
259+
/>
260+
<Select
261+
data={[
262+
"Holding Register",
263+
"Discrete Input",
264+
"Input Register",
265+
"Coil",
266+
]}
267+
value={type}
268+
size="xs"
269+
label="Register Type"
270+
radius="xs"
271+
onChange={(value) => {
272+
setReadType(value!);
273+
setRawData([]);
274+
}}
275+
/>
202276
<br />
203-
<RefreshSelector opened={opened} setOpened={setOpened} refreshRate={refreshRate} setRate={setRefreshRate} ReadModbus={ReadModbus} />
277+
<RefreshSelector
278+
opened={opened}
279+
setOpened={setOpened}
280+
refreshRate={refreshRate}
281+
setRate={setRefreshRate}
282+
ReadModbus={ReadModbus}
283+
/>
204284
</Group>
205285
{content}
206286
</Stack>
207287
);
208288

209-
function RefreshSelector(props: { opened: boolean, setOpened: (b: boolean) => void, setRate: (rate: string) => void, refreshRate: string, ReadModbus: () => void }) {
289+
function RefreshSelector(props: {
290+
opened: boolean;
291+
setOpened: (b: boolean) => void;
292+
setRate: (rate: string) => void;
293+
refreshRate: string;
294+
ReadModbus: () => void;
295+
}) {
210296
return (
211297
<div>
212-
<Button size="xs" onClick={props.ReadModbus} variant="default" p={"2px"} style={{ borderBottomRightRadius: 0, borderTopRightRadius: 0, borderRight: "0px" }}>
298+
<Button
299+
size="xs"
300+
onClick={props.ReadModbus}
301+
variant="default"
302+
p={"2px"}
303+
style={{
304+
borderBottomRightRadius: 0,
305+
borderTopRightRadius: 0,
306+
borderRight: "0px",
307+
}}
308+
>
213309
<IconRefresh />
214310
</Button>
215-
<Menu shadow="md" width={80} opened={props.opened} onChange={props.setOpened}>
311+
<Menu
312+
shadow="md"
313+
width={80}
314+
opened={props.opened}
315+
onChange={props.setOpened}
316+
>
216317
<Menu.Target>
217-
<Button size="xs" variant="default" style={{ borderBottomLeftRadius: 0, borderTopLeftRadius: 0, borderLeft: "0px" }}>
218-
{
219-
props.refreshRate == "None" ?
220-
<IconCaretDownFilled /> : <Text>{props.refreshRate}</Text>
221-
}
318+
<Button
319+
size="xs"
320+
variant="default"
321+
style={{
322+
borderBottomLeftRadius: 0,
323+
borderTopLeftRadius: 0,
324+
borderLeft: "0px",
325+
}}
326+
>
327+
{props.refreshRate == "None" ? (
328+
<IconCaretDownFilled />
329+
) : (
330+
<Text>{props.refreshRate}</Text>
331+
)}
222332
</Button>
223333
</Menu.Target>
224334
<Menu.Dropdown>
225335
<Menu.Label>Refresh Rate</Menu.Label>
226-
{
227-
["None", "1s", "5s", "10s", "30s"].map((v) => (
228-
<Menu.Item onClick={() => props.setRate(v)}>{v}</Menu.Item>
229-
))
230-
}
336+
{["None", "1s", "5s", "10s", "30s"].map((v) => (
337+
<Menu.Item onClick={() => props.setRate(v)}>{v}</Menu.Item>
338+
))}
231339
</Menu.Dropdown>
232340
</Menu>
233341
</div>
234-
)
342+
);
235343
}
236344
}
237-

0 commit comments

Comments
 (0)