Compare commits
2
Commits
master
...
jse_stahlwerk
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8673c2113 | ||
|
|
ba91f2266d |
No files matched your search
@@ -0,0 +1,113 @@
|
||||
import { ControlCard } from "@/control/ControlCard";
|
||||
import { Page } from "@/components/Page";
|
||||
import React from "react";
|
||||
import { ControlGrid } from "@/control/ControlGrid";
|
||||
import { TimeSeriesValueNumeric } from "@/control/TimeSeriesValue";
|
||||
import { EditValue } from "@/control/EditValue";
|
||||
import { useMock1 } from "./use";
|
||||
import { SelectionGroup } from "@/control/SelectionGroup";
|
||||
import { Mode } from "./namespace";
|
||||
import { TimeSeries } from "@/lib/timeseries";
|
||||
|
||||
type SineWaveCardProps = {
|
||||
title: string;
|
||||
timeseries: TimeSeries;
|
||||
};
|
||||
|
||||
function SineWaveCard({ title, timeseries }: SineWaveCardProps) {
|
||||
return (
|
||||
<ControlCard title={title}>
|
||||
<TimeSeriesValueNumeric
|
||||
label="Current Value"
|
||||
timeseries={timeseries}
|
||||
renderValue={(value) => value.toFixed(3)}
|
||||
/>
|
||||
</ControlCard>
|
||||
);
|
||||
}
|
||||
|
||||
export function Mock1ControlPage() {
|
||||
const {
|
||||
state,
|
||||
defaultState,
|
||||
sineWave1,
|
||||
sineWave2,
|
||||
sineWave3,
|
||||
sineWaveSum,
|
||||
setFrequency1,
|
||||
setFrequency2,
|
||||
setFrequency3,
|
||||
setMode,
|
||||
isDisabled,
|
||||
} = useMock1();
|
||||
|
||||
// Controlled local states synced with consolidated state
|
||||
const frequency1 = state?.frequency1 ?? 1.0;
|
||||
const frequency2 = state?.frequency2 ?? 2.0;
|
||||
const frequency3 = state?.frequency3 ?? 5.0;
|
||||
const mode = state?.mode_state?.mode ?? "Standby";
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<ControlGrid columns={2}>
|
||||
<SineWaveCard title="Sine Wave Sum" timeseries={sineWaveSum} />
|
||||
<SineWaveCard title="Sine Wave 1" timeseries={sineWave1} />
|
||||
<SineWaveCard title="Sine Wave 2" timeseries={sineWave2} />
|
||||
<SineWaveCard title="Sine Wave 3" timeseries={sineWave3} />
|
||||
|
||||
<ControlCard title="Frequency">
|
||||
<div className="flex flex-row gap-2">
|
||||
<EditValue
|
||||
title="Frequency 1"
|
||||
unit="mHz"
|
||||
value={frequency1}
|
||||
defaultValue={defaultState?.frequency1}
|
||||
min={0.0}
|
||||
max={1000}
|
||||
step={0.1}
|
||||
renderValue={(value) => value.toFixed(0)}
|
||||
onChange={setFrequency1}
|
||||
/>
|
||||
<EditValue
|
||||
title="Frequency 2"
|
||||
unit="mHz"
|
||||
value={frequency2}
|
||||
defaultValue={defaultState?.frequency2}
|
||||
min={0.0}
|
||||
max={1000}
|
||||
step={0.1}
|
||||
renderValue={(value) => value.toFixed(0)}
|
||||
onChange={setFrequency2}
|
||||
/>
|
||||
<EditValue
|
||||
title="Frequency 3"
|
||||
unit="mHz"
|
||||
value={frequency3}
|
||||
defaultValue={defaultState?.frequency3}
|
||||
min={0.0}
|
||||
max={1000}
|
||||
step={0.1}
|
||||
renderValue={(value) => value.toFixed(0)}
|
||||
onChange={setFrequency3}
|
||||
/>
|
||||
</div>
|
||||
</ControlCard>
|
||||
|
||||
<ControlCard title="Mode">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="text-sm font-medium">Mode</div>
|
||||
<SelectionGroup
|
||||
value={mode}
|
||||
onChange={(newMode: Mode) => setMode(newMode)}
|
||||
disabled={isDisabled}
|
||||
options={{
|
||||
Standby: { children: "Standby" },
|
||||
Running: { children: "Running" },
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</ControlCard>
|
||||
</ControlGrid>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Topbar } from "@/components/Topbar";
|
||||
import { mock1SerialRoute } from "@/routes/routes";
|
||||
import React from "react";
|
||||
|
||||
export function Mock1Page() {
|
||||
const { serial } = mock1SerialRoute.useParams();
|
||||
return (
|
||||
<Topbar
|
||||
pathname={`/_sidebar/machines/mock1/${serial}`}
|
||||
items={[
|
||||
{
|
||||
link: "control",
|
||||
activeLink: "control",
|
||||
title: "Control",
|
||||
icon: "lu:CirclePlay",
|
||||
},
|
||||
{
|
||||
link: "graph",
|
||||
activeLink: "graph",
|
||||
title: "Graph",
|
||||
icon: "lu:CirclePlay",
|
||||
},
|
||||
{
|
||||
link: "manual",
|
||||
activeLink: "manual",
|
||||
title: "Manual",
|
||||
icon: "lu:BookOpen",
|
||||
},
|
||||
{
|
||||
link: "presets",
|
||||
activeLink: "presets",
|
||||
title: "Presets",
|
||||
icon: "lu:Save",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import React from "react";
|
||||
import { useMock1 } from "./use";
|
||||
import { mock1 } from "@/machines/properties";
|
||||
|
||||
import { PresetsPage } from "@/components/preset/PresetsPage";
|
||||
import { Preset } from "@/lib/preset/preset";
|
||||
import { PresetPreviewEntries } from "@/components/preset/PresetPreviewTable";
|
||||
import { z } from "zod";
|
||||
|
||||
const mock1PresetDataSchema = z
|
||||
.object({
|
||||
frequency1: z.number(),
|
||||
frequency2: z.number(),
|
||||
frequency3: z.number(),
|
||||
})
|
||||
.partial();
|
||||
|
||||
type Mock1 = z.infer<typeof mock1PresetDataSchema>;
|
||||
|
||||
const schemas = new Map([[1, mock1PresetDataSchema]]);
|
||||
|
||||
const previewEntries: PresetPreviewEntries<Mock1> = [
|
||||
{
|
||||
name: "Frequency 1",
|
||||
unit: "mHz",
|
||||
renderValue: (data: Mock1) => data?.frequency1?.toFixed(3),
|
||||
},
|
||||
{
|
||||
name: "Frequency 2",
|
||||
unit: "mHz",
|
||||
renderValue: (data: Mock1) => data?.frequency2?.toFixed(3),
|
||||
},
|
||||
{
|
||||
name: "Frequency 3",
|
||||
unit: "mHz",
|
||||
renderValue: (data: Mock1) => data?.frequency3?.toFixed(3),
|
||||
},
|
||||
];
|
||||
|
||||
export function Mock1PresetsPage() {
|
||||
const { setFrequency1, setFrequency2, setFrequency3, defaultState, state } =
|
||||
useMock1();
|
||||
|
||||
const applyPreset = (preset: Preset<Mock1>) => {
|
||||
const frequency1 = preset.data?.frequency1 ?? 100;
|
||||
const frequency2 = preset.data?.frequency2 ?? 200;
|
||||
const frequency3 = preset.data?.frequency3 ?? 500;
|
||||
|
||||
setFrequency1(frequency1);
|
||||
setFrequency2(frequency2);
|
||||
setFrequency3(frequency3);
|
||||
};
|
||||
|
||||
const toPresetData = (s: typeof state): Mock1 => ({
|
||||
frequency1: s?.frequency1 ?? defaultState?.frequency1,
|
||||
frequency2: s?.frequency2 ?? defaultState?.frequency2,
|
||||
frequency3: s?.frequency3 ?? defaultState?.frequency3,
|
||||
});
|
||||
|
||||
return (
|
||||
<PresetsPage
|
||||
machine_identification={mock1.machine_identification}
|
||||
currentState={toPresetData(state)}
|
||||
schemas={schemas}
|
||||
schemaVersion={1}
|
||||
applyPreset={applyPreset}
|
||||
previewEntries={previewEntries}
|
||||
defaultState={toPresetData(defaultState)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
/**
|
||||
* @file mock1Namespace.ts
|
||||
* @description TypeScript implementation of Mock1 namespace with Zod schema validation.
|
||||
*/
|
||||
|
||||
import { StoreApi } from "zustand";
|
||||
import { create } from "zustand";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
EventHandler,
|
||||
eventSchema,
|
||||
Event,
|
||||
handleUnhandledEventError,
|
||||
NamespaceId,
|
||||
createNamespaceHookImplementation,
|
||||
ThrottledStoreUpdater,
|
||||
} from "../../../../client/socketioStore";
|
||||
import { MachineIdentificationUnique } from "@/machines/types";
|
||||
import {
|
||||
createTimeSeries,
|
||||
TimeSeries,
|
||||
TimeSeriesValue,
|
||||
} from "@/lib/timeseries";
|
||||
|
||||
// ========== Event Schema Definitions ==========
|
||||
/**
|
||||
* Mode enum for Mock Machine
|
||||
*/
|
||||
export const modeSchema = z.enum(["Standby", "Running"]);
|
||||
|
||||
/**
|
||||
* Mode state schema
|
||||
*/
|
||||
export const modeStateSchema = z.object({
|
||||
mode: modeSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* Live values event schema (time-series data)
|
||||
*/
|
||||
export const liveValuesEventDataSchema = z.object({
|
||||
amplitude_sum: z.number(),
|
||||
amplitude1: z.number(),
|
||||
amplitude2: z.number(),
|
||||
amplitude3: z.number(),
|
||||
});
|
||||
|
||||
/**
|
||||
* State event schema (consolidated state)
|
||||
*/
|
||||
export const stateEventDataSchema = z.object({
|
||||
is_default_state: z.boolean(),
|
||||
frequency1: z.number(),
|
||||
frequency2: z.number(),
|
||||
frequency3: z.number(),
|
||||
mode_state: modeStateSchema,
|
||||
});
|
||||
|
||||
// ========== Event Schemas with Wrappers ==========
|
||||
export const liveValuesEventSchema = eventSchema(liveValuesEventDataSchema);
|
||||
export const stateEventSchema = eventSchema(stateEventDataSchema);
|
||||
|
||||
// ========== Type Inferences ==========
|
||||
export type Mode = z.infer<typeof modeSchema>;
|
||||
export type ModeState = z.infer<typeof modeStateSchema>;
|
||||
export type LiveValuesEvent = z.infer<typeof liveValuesEventSchema>;
|
||||
export type StateEvent = z.infer<typeof stateEventSchema>;
|
||||
|
||||
export type Mock1NamespaceStore = {
|
||||
// Single state event from server
|
||||
state: StateEvent | null;
|
||||
defaultState: StateEvent | null;
|
||||
|
||||
// Time series data for live values
|
||||
sineWaveSum: TimeSeries;
|
||||
sineWave1: TimeSeries;
|
||||
sineWave2: TimeSeries;
|
||||
sineWave3: TimeSeries;
|
||||
};
|
||||
|
||||
const { initialTimeSeries: sineWave, insert: addSineWave } = createTimeSeries();
|
||||
|
||||
/**
|
||||
* Factory function to create a new Mock1 namespace store
|
||||
* @returns A new Zustand store instance for Mock1 namespace
|
||||
*/
|
||||
export const createMock1NamespaceStore = (): StoreApi<Mock1NamespaceStore> => {
|
||||
return create<Mock1NamespaceStore>(() => {
|
||||
return {
|
||||
state: null,
|
||||
defaultState: null,
|
||||
sineWaveSum: sineWave,
|
||||
sineWave1: sineWave,
|
||||
sineWave2: sineWave,
|
||||
sineWave3: sineWave,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a message handler for Mock1 namespace events with validation and appropriate caching strategies
|
||||
* @param store The store to update when messages are received
|
||||
* @param throttledUpdater Throttled updater for batching updates at 30 FPS
|
||||
* @returns A message handler function
|
||||
*/
|
||||
export function mock1MessageHandler(
|
||||
store: StoreApi<Mock1NamespaceStore>,
|
||||
throttledUpdater: ThrottledStoreUpdater<Mock1NamespaceStore>,
|
||||
): EventHandler {
|
||||
return (event: Event<any>) => {
|
||||
const eventName = event.name;
|
||||
|
||||
// Helper function to update store through buffer
|
||||
const updateStore = (
|
||||
updater: (state: Mock1NamespaceStore) => Mock1NamespaceStore,
|
||||
) => {
|
||||
throttledUpdater.updateWith(updater);
|
||||
};
|
||||
|
||||
try {
|
||||
// State events (latest only)
|
||||
if (eventName === "StateEvent") {
|
||||
const stateEvent = stateEventSchema.parse(event);
|
||||
console.log("StateEvent", stateEvent);
|
||||
updateStore((state) => ({
|
||||
...state,
|
||||
state: stateEvent,
|
||||
// only set default state if is_default_state is true
|
||||
defaultState: stateEvent.data.is_default_state
|
||||
? stateEvent
|
||||
: state.defaultState,
|
||||
}));
|
||||
}
|
||||
// Live values events (time-series data)
|
||||
else if (eventName === "LiveValuesEvent") {
|
||||
const liveValuesEvent = liveValuesEventSchema.parse(event);
|
||||
const wave1Value: TimeSeriesValue = {
|
||||
value: liveValuesEvent.data.amplitude1 ?? 0,
|
||||
timestamp: liveValuesEvent.ts,
|
||||
};
|
||||
const wave2Value: TimeSeriesValue = {
|
||||
value: liveValuesEvent.data.amplitude2 ?? 0,
|
||||
timestamp: liveValuesEvent.ts,
|
||||
};
|
||||
const wave3Value: TimeSeriesValue = {
|
||||
value: liveValuesEvent.data.amplitude3 ?? 0,
|
||||
timestamp: liveValuesEvent.ts,
|
||||
};
|
||||
const waveSumValue: TimeSeriesValue = {
|
||||
value: liveValuesEvent.data.amplitude_sum ?? 0,
|
||||
timestamp: liveValuesEvent.ts,
|
||||
};
|
||||
updateStore((state) => ({
|
||||
...state,
|
||||
sineWaveSum: addSineWave(state.sineWaveSum, waveSumValue),
|
||||
sineWave1: addSineWave(state.sineWave1, wave1Value),
|
||||
sineWave2: addSineWave(state.sineWave2, wave2Value),
|
||||
sineWave3: addSineWave(state.sineWave3, wave3Value),
|
||||
}));
|
||||
} else {
|
||||
handleUnhandledEventError(eventName);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Unexpected error processing ${eventName} event:`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the Mock1 namespace implementation
|
||||
*/
|
||||
const useMock1NamespaceImplementation =
|
||||
createNamespaceHookImplementation<Mock1NamespaceStore>({
|
||||
createStore: createMock1NamespaceStore,
|
||||
createEventHandler: mock1MessageHandler,
|
||||
});
|
||||
|
||||
export function useMock1Namespace(
|
||||
machine_identification_unique: MachineIdentificationUnique,
|
||||
): Mock1NamespaceStore {
|
||||
// Generate namespace ID from validated machine ID
|
||||
const namespaceId: NamespaceId = {
|
||||
type: "machine",
|
||||
machine_identification_unique,
|
||||
};
|
||||
|
||||
// Use the implementation with validated namespace ID
|
||||
return useMock1NamespaceImplementation(namespaceId);
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
import { toastError } from "@/components/Toast";
|
||||
import { useMachineMutate as useMachineMutation } from "@/client/useClient";
|
||||
import { MachineIdentificationUnique } from "@/machines/types";
|
||||
import { mock1 } from "@/machines/properties";
|
||||
import { mock1SerialRoute } from "@/routes/routes";
|
||||
import { z } from "zod";
|
||||
import { useMock1Namespace, Mode, StateEvent } from "./namespace";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { useStateOptimistic } from "@/lib/useStateOptimistic";
|
||||
import { produce } from "immer";
|
||||
|
||||
function useMock(machine_identification_unique: MachineIdentificationUnique) {
|
||||
// Get consolidated state and live values from namespace
|
||||
const { state, defaultState, sineWaveSum, sineWave1, sineWave2, sineWave3 } =
|
||||
useMock1Namespace(machine_identification_unique);
|
||||
|
||||
// Single optimistic state for all state management
|
||||
const stateOptimistic = useStateOptimistic<StateEvent>();
|
||||
|
||||
useEffect(() => {
|
||||
if (state) {
|
||||
stateOptimistic.setReal(state);
|
||||
}
|
||||
}, [state]);
|
||||
|
||||
// Helper function for optimistic updates using produce
|
||||
const updateStateOptimistically = (
|
||||
producer: (current: StateEvent) => void,
|
||||
serverRequest: () => Promise<any>,
|
||||
) => {
|
||||
const currentState = stateOptimistic.value;
|
||||
if (currentState && !stateOptimistic.isOptimistic) {
|
||||
stateOptimistic.setOptimistic(produce(currentState, producer));
|
||||
}
|
||||
serverRequest()
|
||||
.then((response) => {
|
||||
if (!response.success) stateOptimistic.resetToReal();
|
||||
})
|
||||
.catch(() => stateOptimistic.resetToReal());
|
||||
};
|
||||
|
||||
// Action functions with verb-first names
|
||||
const setSchemaFrequency1 = z.object({ SetFrequency1: z.number() });
|
||||
const { request: requestSetFrequency1 } =
|
||||
useMachineMutation(setSchemaFrequency1);
|
||||
const setFrequency1 = (frequency: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.frequency1 = frequency;
|
||||
},
|
||||
() =>
|
||||
requestSetFrequency1({
|
||||
machine_identification_unique,
|
||||
data: {
|
||||
SetFrequency1: frequency,
|
||||
},
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setSchemaFrequency2 = z.object({ SetFrequency2: z.number() });
|
||||
const { request: requestSetFrequency2 } =
|
||||
useMachineMutation(setSchemaFrequency2);
|
||||
const setFrequency2 = (frequency: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.frequency2 = frequency;
|
||||
},
|
||||
() =>
|
||||
requestSetFrequency2({
|
||||
machine_identification_unique,
|
||||
data: {
|
||||
SetFrequency2: frequency,
|
||||
},
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setSchemaFrequency3 = z.object({ SetFrequency3: z.number() });
|
||||
const { request: requestSetFrequency3 } =
|
||||
useMachineMutation(setSchemaFrequency3);
|
||||
const setFrequency3 = (frequency: number) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.frequency3 = frequency;
|
||||
},
|
||||
() =>
|
||||
requestSetFrequency3({
|
||||
machine_identification_unique,
|
||||
data: {
|
||||
SetFrequency3: frequency,
|
||||
},
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const schemaSetMode = z.object({ SetMode: z.enum(["Standby", "Running"]) });
|
||||
const { request: requestSetMode } = useMachineMutation(schemaSetMode);
|
||||
const setMode = (mode: Mode) => {
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.mode_state.mode = mode;
|
||||
},
|
||||
() =>
|
||||
requestSetMode({
|
||||
machine_identification_unique,
|
||||
data: {
|
||||
SetMode: mode,
|
||||
},
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
// Consolidated state
|
||||
state: stateOptimistic.value?.data,
|
||||
|
||||
// Default state for initial values
|
||||
defaultState: defaultState?.data,
|
||||
|
||||
// Individual live values (TimeSeries)
|
||||
sineWave1,
|
||||
sineWave2,
|
||||
sineWave3,
|
||||
sineWaveSum,
|
||||
|
||||
// Loading states
|
||||
isLoading: stateOptimistic.isOptimistic,
|
||||
isDisabled: !stateOptimistic.isInitialized,
|
||||
|
||||
// Action functions (verb-first)
|
||||
setFrequency1,
|
||||
setFrequency2,
|
||||
setFrequency3,
|
||||
setMode,
|
||||
};
|
||||
}
|
||||
|
||||
export function useMock1() {
|
||||
const { serial: serialString } = mock1SerialRoute.useParams();
|
||||
|
||||
// Memoize the machine identification to keep it stable between renders
|
||||
const machineIdentification: MachineIdentificationUnique = useMemo(() => {
|
||||
const serial = parseInt(serialString); // Use 0 as fallback if NaN
|
||||
|
||||
if (isNaN(serial)) {
|
||||
toastError(
|
||||
"Invalid Serial Number",
|
||||
`"${serialString}" is not a valid serial number.`,
|
||||
);
|
||||
|
||||
return {
|
||||
machine_identification: {
|
||||
vendor: 0,
|
||||
machine: 0,
|
||||
},
|
||||
serial: 0,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
machine_identification: mock1.machine_identification,
|
||||
serial,
|
||||
};
|
||||
}, [serialString]); // Only recreate when serialString changes
|
||||
|
||||
const mock = useMock(machineIdentification);
|
||||
|
||||
return {
|
||||
...mock,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { VENDOR_QITECH } from "../../machines/properties";
|
||||
import { MachineProperties } from "../../machines/types";
|
||||
|
||||
export const ff01_mock: MachineProperties =
|
||||
{
|
||||
name: "FF01",
|
||||
version: "V1",
|
||||
slug: "stahlwerk_ff01",
|
||||
icon: "lu:FlaskConical",
|
||||
machine_identification:
|
||||
{
|
||||
vendor: VENDOR_QITECH,
|
||||
machine: 0x0007,
|
||||
},
|
||||
device_roles: [],
|
||||
};
|
||||
|
||||
export const machineProperties: MachineProperties[] = [
|
||||
ff01_mock
|
||||
];
|
||||
@@ -0,0 +1,34 @@
|
||||
import { createRoute, Outlet } from "@tanstack/react-router";
|
||||
|
||||
import React from "react";
|
||||
import { z } from "zod";
|
||||
|
||||
import { Laser1ControlPage } from "@/extensions/stahlwerk/machines/ff01_mock/Page";
|
||||
import { Laser1GraphsPage } from "@/machines/laser/laser1/Laser1Graph";
|
||||
import { Laser1Page } from "@/machines/laser/laser1/Laser1Page";
|
||||
import { Laser1PresetsPage } from "@/machines/laser/laser1/Laser1PresetsPage";
|
||||
import { machinesRoute } from "@/routes/routes";
|
||||
|
||||
export const mock1SerialRoute = createRoute({
|
||||
getParentRoute: () => machinesRoute,
|
||||
path: "mock1/$serial",
|
||||
component: () => <Mock1Page />,
|
||||
});
|
||||
|
||||
export const mock1ControlRoute = createRoute({
|
||||
getParentRoute: () => mock1SerialRoute,
|
||||
path: "control",
|
||||
component: () => <Mock1ControlPage />,
|
||||
});
|
||||
|
||||
export const mock1GraphRoute = createRoute({
|
||||
getParentRoute: () => mock1SerialRoute,
|
||||
path: "graph",
|
||||
component: () => <Mock1GraphPage />,
|
||||
});
|
||||
|
||||
export const mock1ManualRoute = createRoute({
|
||||
getParentRoute: () => mock1SerialRoute,
|
||||
path: "manual",
|
||||
component: () => <Mock1ManualPage />,
|
||||
});
|
||||
@@ -5,6 +5,10 @@ import {
|
||||
machineIdentificationEquals,
|
||||
} from "./types";
|
||||
|
||||
import {
|
||||
machineProperties as stahlwerkMachineProperties
|
||||
} from "../extensions/stahlwerk/properties";
|
||||
|
||||
export const VENDOR_QITECH = 0x0001;
|
||||
|
||||
export type VendorProperties = {
|
||||
@@ -708,6 +712,7 @@ export const machineProperties: MachineProperties[] = [
|
||||
wagoDoTestMachine,
|
||||
TestMotor,
|
||||
testmachinestepper,
|
||||
...stahlwerkMachineProperties
|
||||
];
|
||||
|
||||
export const getMachineProperties = (
|
||||
|
||||
@@ -39,6 +39,8 @@ pub mod wago_do_test_machine;
|
||||
pub mod wago_power;
|
||||
pub mod winder2;
|
||||
|
||||
pub mod stahlwerk;
|
||||
|
||||
pub const VENDOR_QITECH: u16 = 0x0001;
|
||||
pub const MACHINE_WINDER_V1: u16 = 0x0002;
|
||||
pub const MACHINE_EXTRUDER_V1: u16 = 0x0004;
|
||||
|
||||
@@ -49,7 +49,13 @@ impl MachineIdentification {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn slug(&self) -> String {
|
||||
pub fn slug(&self) -> String
|
||||
{
|
||||
if let Some(slug) = stahlwerk::get_slug(self.machine)
|
||||
{
|
||||
return slug.to_string();
|
||||
}
|
||||
|
||||
match self.machine {
|
||||
x if x == MACHINE_WINDER_V1 => "winder_v1".to_string(),
|
||||
x if x == MACHINE_EXTRUDER_V1 => "extruder_v1".to_string(),
|
||||
@@ -190,6 +196,7 @@ use crate::TEST_MACHINE;
|
||||
use crate::VENDOR_QITECH;
|
||||
use crate::WAGO_AI_TEST_MACHINE;
|
||||
use crate::WAGO_DO_TEST_MACHINE;
|
||||
use crate::stahlwerk;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MachineIdentificationAddresses {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::analog_input_test_machine::AnalogInputTestMachine;
|
||||
use crate::digital_input_test_machine::DigitalInputTestMachine;
|
||||
use crate::ip20_test_machine::IP20TestMachine;
|
||||
use crate::stahlwerk::{self, register_machines};
|
||||
use crate::wago_8ch_dio_test_machine::Wago8chDigitalIOTestMachine;
|
||||
use crate::wago_ai_test_machine::WagoAiTestMachine;
|
||||
use crate::wago_do_test_machine::WagoDOTestMachine;
|
||||
@@ -145,6 +146,10 @@ lazy_static! {
|
||||
);
|
||||
|
||||
mc.register::<TestMachineStepper>(TestMachineStepper::MACHINE_IDENTIFICATION);
|
||||
|
||||
// register all machines from stahlwerk
|
||||
stahlwerk::register_machines(&mut mc);
|
||||
|
||||
mc
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
use std::{fmt::Debug, sync::Arc, time::Instant};
|
||||
|
||||
use crate::{MachineMessage, machine_identification::MachineIdentificationUnique};
|
||||
use smol::channel::{Receiver, Sender};
|
||||
use tracing::instrument;
|
||||
use crate::AsyncThreadMessage;
|
||||
|
||||
use control_core::socketio::namespace::{
|
||||
CacheableEvents,
|
||||
Namespace,
|
||||
NamespaceCacheingLogic
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BaseMachine
|
||||
{
|
||||
pub machine_uid: MachineIdentificationUnique,
|
||||
pub main_sender: Option<Sender<AsyncThreadMessage>>,
|
||||
|
||||
pub namespace: NamespaceImpl,
|
||||
|
||||
// socketio
|
||||
pub last_measurement_emit: Instant,
|
||||
|
||||
// api
|
||||
pub api_sender: Sender<MachineMessage>,
|
||||
pub api_receiver: Receiver<MachineMessage>,
|
||||
|
||||
pub emitted_default_state: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NamespaceImpl
|
||||
{
|
||||
pub namespace: Option<Namespace>,
|
||||
}
|
||||
|
||||
impl NamespaceImpl
|
||||
{
|
||||
pub fn new(namespace: Option<Namespace>) -> Self
|
||||
{
|
||||
Self { namespace }
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: CacheableEvents<E>> NamespaceCacheingLogic<E> for NamespaceImpl
|
||||
{
|
||||
#[instrument(skip_all)]
|
||||
fn emit(&mut self, events: E)
|
||||
{
|
||||
let event = Arc::new(events.event_value());
|
||||
let buffer_fn = events.event_cache_fn();
|
||||
|
||||
match &mut self.namespace
|
||||
{
|
||||
Some(ns) => ns.emit(event, &buffer_fn),
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use super::DerivedMachine;
|
||||
|
||||
impl DerivedMachine
|
||||
{
|
||||
pub fn act_ext(&mut self, now: Instant)
|
||||
{
|
||||
_ = now;
|
||||
// So quiet in here
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
use control_core_derive::BuildEvent;
|
||||
use serde::{ Deserialize, Serialize };
|
||||
|
||||
use super::DerivedMachine;
|
||||
|
||||
#[derive(Serialize, Debug, Clone, Default)]
|
||||
pub struct LiveValuesEvent
|
||||
{
|
||||
pub counter: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, PartialEq, BuildEvent)]
|
||||
pub struct StateEvent
|
||||
{
|
||||
pub is_default_state: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub enum Mutation
|
||||
{
|
||||
Increment,
|
||||
}
|
||||
|
||||
impl DerivedMachine
|
||||
{
|
||||
pub fn get_live_values(&self) -> LiveValuesEvent
|
||||
{
|
||||
LiveValuesEvent { counter: self.counter }
|
||||
}
|
||||
|
||||
pub fn get_state(&self) -> StateEvent
|
||||
{
|
||||
StateEvent { is_default_state: true }
|
||||
}
|
||||
|
||||
pub fn mutate(&mut self, mutation: Mutation)
|
||||
{
|
||||
match mutation
|
||||
{
|
||||
Mutation::Increment => self.increment(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
// external deps
|
||||
use anyhow::{Error};
|
||||
use serde_json::Value;
|
||||
use smol::channel::Sender;
|
||||
|
||||
// local deps
|
||||
use control_core::socketio::{
|
||||
event::{
|
||||
BuildEvent,
|
||||
Event,
|
||||
GenericEvent
|
||||
},
|
||||
namespace::{
|
||||
CacheFn,
|
||||
CacheableEvents,
|
||||
Namespace,
|
||||
NamespaceCacheingLogic,
|
||||
cache_first_and_last_event
|
||||
}
|
||||
};
|
||||
|
||||
use crate::{
|
||||
AsyncThreadMessage,
|
||||
Machine,
|
||||
MachineAct,
|
||||
MachineApi,
|
||||
MachineMessage,
|
||||
MachineNewParams,
|
||||
MachineNewTrait,
|
||||
MachineValues,
|
||||
machine_identification::MachineIdentificationUnique,
|
||||
};
|
||||
|
||||
use crate::stahlwerk::{
|
||||
r#abstract::{
|
||||
BaseMachine,
|
||||
NamespaceImpl
|
||||
}
|
||||
};
|
||||
|
||||
use super::{
|
||||
DerivedMachine,
|
||||
api::{
|
||||
LiveValuesEvent,
|
||||
StateEvent,
|
||||
Mutation,
|
||||
}
|
||||
};
|
||||
|
||||
impl DerivedMachine
|
||||
{
|
||||
/// Emit live values data event with the current sine wave amplitude
|
||||
pub fn emit_live_values(&mut self)
|
||||
{
|
||||
let event = self.get_live_values().build();
|
||||
self.base.namespace.emit(Events::LiveValues(event));
|
||||
}
|
||||
|
||||
/// Emit the current state of the mock machine only if values have changed
|
||||
pub fn emit_state(&mut self)
|
||||
{
|
||||
let state = self.get_state();
|
||||
let event = state.build();
|
||||
self.base.namespace.emit(Events::State(event));
|
||||
self.base.emitted_default_state = true;
|
||||
}
|
||||
}
|
||||
|
||||
impl MachineNewTrait for DerivedMachine
|
||||
{
|
||||
fn new(params: &MachineNewParams<'_, '_, '_, '_, '_, '_, '_>) -> Result<Self, Error>
|
||||
where
|
||||
Self: Sized
|
||||
{
|
||||
let (api_sender, api_receiver) = smol::channel::unbounded();
|
||||
|
||||
let base = BaseMachine {
|
||||
machine_uid: params.get_machine_identification_unique(),
|
||||
main_sender: params.main_thread_channel.clone(),
|
||||
namespace: NamespaceImpl::new(params.namespace.clone()),
|
||||
last_measurement_emit: Instant::now(),
|
||||
api_sender,
|
||||
api_receiver,
|
||||
emitted_default_state: false,
|
||||
};
|
||||
|
||||
let mut instance = Self::new(base);
|
||||
|
||||
instance.emit_state();
|
||||
|
||||
Ok(instance)
|
||||
}
|
||||
}
|
||||
|
||||
impl Machine for DerivedMachine
|
||||
{
|
||||
fn get_machine_identification_unique(&self) -> MachineIdentificationUnique
|
||||
{
|
||||
self.base.machine_uid.clone()
|
||||
}
|
||||
|
||||
fn get_main_sender(&self) -> Option<Sender<AsyncThreadMessage>>
|
||||
{
|
||||
self.base.main_sender.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl MachineApi for DerivedMachine
|
||||
{
|
||||
fn api_get_sender(&self) -> Sender<MachineMessage>
|
||||
{
|
||||
self.base.api_sender.clone()
|
||||
}
|
||||
|
||||
fn api_event_namespace(&mut self) -> Option<Namespace>
|
||||
{
|
||||
self.base.namespace.namespace.clone()
|
||||
}
|
||||
|
||||
fn api_mutate(&mut self, value: Value) -> Result<(), Error>
|
||||
{
|
||||
let mutation: Mutation = serde_json::from_value(value)?;
|
||||
self.mutate(mutation);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl MachineAct for DerivedMachine
|
||||
{
|
||||
fn act_machine_message(&mut self, msg: MachineMessage)
|
||||
{
|
||||
use MachineMessage::*;
|
||||
use crate::MachineApi;
|
||||
|
||||
match msg
|
||||
{
|
||||
SubscribeNamespace(namespace) =>
|
||||
{
|
||||
self.base.namespace.namespace = Some(namespace);
|
||||
self.emit_state();
|
||||
}
|
||||
UnsubscribeNamespace => self.base.namespace.namespace = None,
|
||||
HttpApiJsonRequest(value) => _ = self.api_mutate(value),
|
||||
ConnectToMachine(_) => {}
|
||||
DisconnectMachine(_) => {}
|
||||
RequestValues(sender) =>
|
||||
{
|
||||
sender
|
||||
.send_blocking(MachineValues {
|
||||
state: serde_json::to_value(self.get_state())
|
||||
.expect("Failed to serialize state"),
|
||||
live_values: serde_json::to_value(self.get_live_values())
|
||||
.expect("Failed to serialize live values"),
|
||||
})
|
||||
.expect("Failed to send values");
|
||||
|
||||
sender.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn act(&mut self, now: Instant)
|
||||
{
|
||||
let target_duration: Duration = Duration::from_secs_f64(1.0 / 30.0);
|
||||
|
||||
match self.base.api_receiver.try_recv()
|
||||
{
|
||||
Ok(msg) => _ = self.act_machine_message(msg),
|
||||
Err(_) => (),
|
||||
};
|
||||
|
||||
self.act_ext(now);
|
||||
|
||||
let now = Instant::now();
|
||||
|
||||
// more than 33ms have passed since last emit (30 "fps" target)
|
||||
if now.duration_since(self.base.last_measurement_emit) > target_duration
|
||||
{
|
||||
self.emit_state();
|
||||
self.emit_live_values();
|
||||
self.base.last_measurement_emit = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LiveValuesEvent
|
||||
{
|
||||
pub fn build(&self) -> Event<Self>
|
||||
{
|
||||
Event::new("LiveValuesEvent", self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Events
|
||||
{
|
||||
LiveValues(Event<LiveValuesEvent>),
|
||||
State(Event<StateEvent>),
|
||||
}
|
||||
|
||||
impl CacheableEvents<Self> for Events
|
||||
{
|
||||
fn event_value(&self) -> GenericEvent
|
||||
{
|
||||
match self
|
||||
{
|
||||
Self::LiveValues(event) => event.into(),
|
||||
Self::State(event) => event.into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn event_cache_fn(&self) -> CacheFn
|
||||
{
|
||||
cache_first_and_last_event()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
use crate::stahlwerk::{ r#abstract::BaseMachine };
|
||||
|
||||
pub mod common;
|
||||
pub mod api;
|
||||
pub mod act;
|
||||
|
||||
pub(super) use FF01MachineMock as DerivedMachine;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FF01MachineMock
|
||||
{
|
||||
base: BaseMachine,
|
||||
|
||||
counter: u64,
|
||||
|
||||
// todo: current processed entry HERE
|
||||
}
|
||||
|
||||
impl FF01MachineMock
|
||||
{
|
||||
pub fn new(base: BaseMachine) -> Self
|
||||
{
|
||||
Self { base, counter: 0 }
|
||||
}
|
||||
|
||||
pub fn increment(&mut self)
|
||||
{
|
||||
self.counter += 1;
|
||||
self.emit_state();
|
||||
}
|
||||
|
||||
pub fn finalize(&mut self)
|
||||
{
|
||||
todo!("Submit backflush")
|
||||
// self.emit_state();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MachineId
|
||||
{
|
||||
pub category_id: u8,
|
||||
pub index: u8,
|
||||
pub slug: &'static str,
|
||||
}
|
||||
|
||||
impl MachineId
|
||||
{
|
||||
pub const fn new(category_id: u8, index: u8, slug: &'static str) -> Self
|
||||
{
|
||||
Self { category_id, index, slug }
|
||||
}
|
||||
|
||||
pub const fn to_u16(&self) -> u16
|
||||
{
|
||||
((self.category_id as u16) << 8) | (self.index as u16)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
use crate::{
|
||||
VENDOR_QITECH,
|
||||
machine_identification::MachineIdentification,
|
||||
registry::MachineRegistry,
|
||||
stahlwerk::{ machine_id::MachineId }
|
||||
};
|
||||
|
||||
pub mod r#abstract;
|
||||
pub mod template;
|
||||
pub mod machine_id;
|
||||
|
||||
pub mod ff01_mock;
|
||||
|
||||
pub const CATEGORY_ID: u8 = 0x02; // stahlwerk category
|
||||
|
||||
pub const FF01_MOCK: MachineId = MachineId::new(CATEGORY_ID, 0, "stahlwerk_ff01");
|
||||
|
||||
pub const fn get_slug(id: u16) -> Option<&'static str>
|
||||
{
|
||||
match id
|
||||
{
|
||||
id if id == FF01_MOCK.to_u16() => Some(FF01_MOCK.slug),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_machines(registry: &mut MachineRegistry)
|
||||
{
|
||||
#[cfg(feature = "mock-machine")]
|
||||
registry.register::<FF01MachineMock>(new_identification(FF01_MOCK));
|
||||
}
|
||||
|
||||
fn new_identification(id: MachineId) -> MachineIdentification
|
||||
{
|
||||
MachineIdentification {
|
||||
vendor: VENDOR_QITECH,
|
||||
machine: id.to_u16(),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use super::DerivedMachine;
|
||||
|
||||
impl DerivedMachine
|
||||
{
|
||||
pub fn act_ext(&mut self, now: Instant)
|
||||
{
|
||||
_ = now;
|
||||
// So quiet in here
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
use control_core_derive::BuildEvent;
|
||||
use serde::{ Deserialize, Serialize };
|
||||
|
||||
use super::DerivedMachine;
|
||||
|
||||
#[derive(Serialize, Debug, Clone, Default)]
|
||||
pub struct LiveValuesEvent {}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, PartialEq, BuildEvent)]
|
||||
pub struct StateEvent
|
||||
{
|
||||
pub is_default_state: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub enum Mutation {}
|
||||
|
||||
impl DerivedMachine
|
||||
{
|
||||
pub fn get_live_values(&self) -> LiveValuesEvent
|
||||
{
|
||||
LiveValuesEvent {}
|
||||
}
|
||||
|
||||
pub fn get_state(&self) -> StateEvent
|
||||
{
|
||||
StateEvent { is_default_state: true }
|
||||
}
|
||||
|
||||
pub fn mutate(&mut self, mutation: Mutation)
|
||||
{
|
||||
match mutation {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
// external deps
|
||||
use anyhow::{Error};
|
||||
use serde_json::Value;
|
||||
use smol::channel::Sender;
|
||||
|
||||
// local deps
|
||||
use control_core::socketio::{
|
||||
event::{
|
||||
BuildEvent,
|
||||
Event,
|
||||
GenericEvent
|
||||
},
|
||||
namespace::{
|
||||
CacheFn,
|
||||
CacheableEvents,
|
||||
Namespace,
|
||||
NamespaceCacheingLogic,
|
||||
cache_first_and_last_event
|
||||
}
|
||||
};
|
||||
|
||||
use crate::{
|
||||
AsyncThreadMessage,
|
||||
Machine,
|
||||
MachineAct,
|
||||
MachineApi,
|
||||
MachineMessage,
|
||||
MachineNewParams,
|
||||
MachineNewTrait,
|
||||
MachineValues,
|
||||
machine_identification::MachineIdentificationUnique,
|
||||
};
|
||||
|
||||
use crate::stahlwerk::{
|
||||
r#abstract::{
|
||||
BaseMachine,
|
||||
NamespaceImpl
|
||||
}
|
||||
};
|
||||
|
||||
use super::{
|
||||
DerivedMachine,
|
||||
api::{
|
||||
LiveValuesEvent,
|
||||
StateEvent,
|
||||
Mutation,
|
||||
}
|
||||
};
|
||||
|
||||
impl DerivedMachine
|
||||
{
|
||||
/// Emit live values data event with the current sine wave amplitude
|
||||
pub fn emit_live_values(&mut self)
|
||||
{
|
||||
let event = self.get_live_values().build();
|
||||
self.base.namespace.emit(Events::LiveValues(event));
|
||||
}
|
||||
|
||||
/// Emit the current state of the mock machine only if values have changed
|
||||
pub fn emit_state(&mut self)
|
||||
{
|
||||
let state = self.get_state();
|
||||
let event = state.build();
|
||||
self.base.namespace.emit(Events::State(event));
|
||||
self.base.emitted_default_state = true;
|
||||
}
|
||||
}
|
||||
|
||||
impl MachineNewTrait for DerivedMachine
|
||||
{
|
||||
fn new(params: &MachineNewParams<'_, '_, '_, '_, '_, '_, '_>) -> Result<Self, Error>
|
||||
where
|
||||
Self: Sized
|
||||
{
|
||||
let (api_sender, api_receiver) = smol::channel::unbounded();
|
||||
|
||||
let base = BaseMachine {
|
||||
machine_uid: params.get_machine_identification_unique(),
|
||||
main_sender: params.main_thread_channel.clone(),
|
||||
namespace: NamespaceImpl::new(params.namespace.clone()),
|
||||
last_measurement_emit: Instant::now(),
|
||||
api_sender,
|
||||
api_receiver,
|
||||
emitted_default_state: false,
|
||||
};
|
||||
|
||||
let mut instance = Self::new(base);
|
||||
|
||||
instance.emit_state();
|
||||
|
||||
Ok(instance)
|
||||
}
|
||||
}
|
||||
|
||||
impl Machine for DerivedMachine
|
||||
{
|
||||
fn get_machine_identification_unique(&self) -> MachineIdentificationUnique
|
||||
{
|
||||
self.base.machine_uid.clone()
|
||||
}
|
||||
|
||||
fn get_main_sender(&self) -> Option<Sender<AsyncThreadMessage>>
|
||||
{
|
||||
self.base.main_sender.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl MachineApi for DerivedMachine
|
||||
{
|
||||
fn api_get_sender(&self) -> Sender<MachineMessage>
|
||||
{
|
||||
self.base.api_sender.clone()
|
||||
}
|
||||
|
||||
fn api_event_namespace(&mut self) -> Option<Namespace>
|
||||
{
|
||||
self.base.namespace.namespace.clone()
|
||||
}
|
||||
|
||||
fn api_mutate(&mut self, value: Value) -> Result<(), Error>
|
||||
{
|
||||
let mutation: Mutation = serde_json::from_value(value)?;
|
||||
self.mutate(mutation);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl MachineAct for DerivedMachine
|
||||
{
|
||||
fn act_machine_message(&mut self, msg: MachineMessage)
|
||||
{
|
||||
use MachineMessage::*;
|
||||
use crate::MachineApi;
|
||||
|
||||
match msg
|
||||
{
|
||||
SubscribeNamespace(namespace) =>
|
||||
{
|
||||
self.base.namespace.namespace = Some(namespace);
|
||||
self.emit_state();
|
||||
}
|
||||
UnsubscribeNamespace => self.base.namespace.namespace = None,
|
||||
HttpApiJsonRequest(value) => _ = self.api_mutate(value),
|
||||
ConnectToMachine(_) => {}
|
||||
DisconnectMachine(_) => {}
|
||||
RequestValues(sender) =>
|
||||
{
|
||||
sender
|
||||
.send_blocking(MachineValues {
|
||||
state: serde_json::to_value(self.get_state())
|
||||
.expect("Failed to serialize state"),
|
||||
live_values: serde_json::to_value(self.get_live_values())
|
||||
.expect("Failed to serialize live values"),
|
||||
})
|
||||
.expect("Failed to send values");
|
||||
|
||||
sender.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn act(&mut self, now: Instant)
|
||||
{
|
||||
let target_duration: Duration = Duration::from_secs_f64(1.0 / 30.0);
|
||||
|
||||
match self.base.api_receiver.try_recv()
|
||||
{
|
||||
Ok(msg) => _ = self.act_machine_message(msg),
|
||||
Err(_) => (),
|
||||
};
|
||||
|
||||
self.act_ext(now);
|
||||
|
||||
let now = Instant::now();
|
||||
|
||||
// more than 33ms have passed since last emit (30 "fps" target)
|
||||
if now.duration_since(self.base.last_measurement_emit) > target_duration
|
||||
{
|
||||
self.emit_state();
|
||||
self.emit_live_values();
|
||||
self.base.last_measurement_emit = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LiveValuesEvent
|
||||
{
|
||||
pub fn build(&self) -> Event<Self>
|
||||
{
|
||||
Event::new("LiveValuesEvent", self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Events
|
||||
{
|
||||
LiveValues(Event<LiveValuesEvent>),
|
||||
State(Event<StateEvent>),
|
||||
}
|
||||
|
||||
impl CacheableEvents<Self> for Events
|
||||
{
|
||||
fn event_value(&self) -> GenericEvent
|
||||
{
|
||||
match self
|
||||
{
|
||||
Self::LiveValues(event) => event.into(),
|
||||
Self::State(event) => event.into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn event_cache_fn(&self) -> CacheFn
|
||||
{
|
||||
cache_first_and_last_event()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
use crate::stahlwerk::{ r#abstract::BaseMachine };
|
||||
|
||||
pub mod common;
|
||||
pub mod api;
|
||||
pub mod act;
|
||||
|
||||
pub(super) use TemplateMachine as DerivedMachine;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TemplateMachine
|
||||
{
|
||||
base: BaseMachine,
|
||||
}
|
||||
|
||||
impl TemplateMachine
|
||||
{
|
||||
pub fn new(base: BaseMachine) -> Self
|
||||
{
|
||||
Self { base }
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user