diff --git a/backend/cmd/main.go b/backend/cmd/main.go index ff2a9fc57..e2354301b 100644 --- a/backend/cmd/main.go +++ b/backend/cmd/main.go @@ -181,11 +181,9 @@ func main() { connectionTopic := connection_topic.NewUpdateTopic() orderTopic := order_topic.NewSendTopic() loggerTopic := logger_topic.NewEnableTopic() - boardIdToBoard := make(map[abstraction.BoardId]string) - for name, id := range adj.Info.BoardIds { - boardIdToBoard[abstraction.BoardId(id)] = name - } - messageTopic := message_topic.NewUpdateTopic(boardIdToBoard) + loggerTopic.SetDataLogger(subloggers[data_logger.Name].(*data_logger.Logger)) + + messageTopic := message_topic.NewUpdateTopic() stateOrderTopic := order_topic.NewState(idToBoard, trace.Logger) broker.AddTopic(data_topic.UpdateName, dataTopic) @@ -194,6 +192,7 @@ func main() { broker.AddTopic(order_topic.StateName, stateOrderTopic) broker.AddTopic(logger_topic.EnableName, loggerTopic) broker.AddTopic(logger_topic.ResponseName, loggerTopic) + broker.AddTopic(logger_topic.VariablesName, loggerTopic) broker.AddTopic(message_topic.UpdateName, messageTopic) connections := make(chan *websocket.Client) diff --git a/backend/pkg/broker/topics/logger/enable.go b/backend/pkg/broker/topics/logger/enable.go index c346bda2f..77f464f26 100644 --- a/backend/pkg/broker/topics/logger/enable.go +++ b/backend/pkg/broker/topics/logger/enable.go @@ -7,6 +7,7 @@ import ( "sync/atomic" "github.com/HyperloopUPV-H8/h9-backend/pkg/abstraction" + data_logger "github.com/HyperloopUPV-H8/h9-backend/pkg/logger/data" "github.com/HyperloopUPV-H8/h9-backend/pkg/websocket" "github.com/google/uuid" ws "github.com/gorilla/websocket" @@ -14,6 +15,7 @@ import ( const EnableName abstraction.BrokerTopic = "logger/enable" const ResponseName abstraction.BrokerTopic = "logger/response" +const VariablesName abstraction.BrokerTopic = "logger/variables" type Enable struct { isRunning *atomic.Bool @@ -21,6 +23,7 @@ type Enable struct { connectionMx *sync.Mutex subscribers map[websocket.ClientId]struct{} api abstraction.BrokerAPI + data_logger *data_logger.Logger } func NewEnableTopic() *Enable { @@ -58,6 +61,12 @@ func (enable *Enable) ClientMessage(id websocket.ClientId, message *websocket.Me fmt.Printf("logger/response subscribed %s\n", uuid.UUID(id).String()) enable.subscribers[id] = struct{}{} + + case VariablesName: + err := enable.handleVariables(id, message) + if err != nil { + fmt.Printf("error handling logger/variables: %v\n", err) + } default: enable.connectionMx.Lock() defer enable.connectionMx.Unlock() @@ -85,6 +94,16 @@ func (enable *Enable) handleToggle(_ websocket.ClientId, message *websocket.Mess return nil } +func (enable *Enable) handleVariables(_ websocket.ClientId, message *websocket.Message) error { + var allowedVars []string + err := json.Unmarshal(message.Payload, &allowedVars) + if err != nil { + return err + } + enable.data_logger.SetAllowedVars(allowedVars) + return nil +} + func (enable *Enable) broadcastState() error { payload, err := json.Marshal(enable.isRunning.Load()) if err != nil { @@ -123,6 +142,10 @@ func (enable *Enable) SetAPI(api abstraction.BrokerAPI) { enable.api = api } +func (enable *Enable) SetDataLogger(logger *data_logger.Logger) { + enable.data_logger = logger +} + type Status struct { request bool response chan bool diff --git a/backend/pkg/broker/topics/message/message_test.go b/backend/pkg/broker/topics/message/message_test.go index 2c920bad5..520af3e1c 100644 --- a/backend/pkg/broker/topics/message/message_test.go +++ b/backend/pkg/broker/topics/message/message_test.go @@ -34,12 +34,12 @@ func TestMessageTopic_Push(t *testing.T) { client := websocket.NewClient(c) clientChan <- client - messageTopic := data.NewUpdateTopic(map[abstraction.BoardId]string{}) + messageTopic := data.NewUpdateTopic() messageTopic.SetAPI(api) messageTopic.SetPool(pool) // Simulate sending a download request - request := data.Push("test", 0) + request := data.Push("test", "test_board") err = messageTopic.Push(request) if err != nil { t.Fatal("Error pushing download request:", err) @@ -80,11 +80,11 @@ func TestMessageTopic_ClientMessage(t *testing.T) { logger := zerolog.New(os.Stdout).With().Timestamp().Logger() api := broker.New(logger) - messageTopic := data.NewUpdateTopic(map[abstraction.BoardId]string{}) + messageTopic := data.NewUpdateTopic() messageTopic.SetAPI(api) packet := protection.NewPacket(0, protection.OkSeverity) - payload := data.Push(packet, 0) + payload := data.Push(packet, "test_board") payloadBytes, _ := json.Marshal(payload) messageTopic.ClientMessage(websocket.ClientId{0}, &websocket.Message{ Topic: data.SubscribeName, diff --git a/backend/pkg/broker/topics/message/update.go b/backend/pkg/broker/topics/message/update.go index f9291f74b..1adecd145 100644 --- a/backend/pkg/broker/topics/message/update.go +++ b/backend/pkg/broker/topics/message/update.go @@ -7,28 +7,29 @@ import ( "github.com/HyperloopUPV-H8/h9-backend/pkg/abstraction" "github.com/HyperloopUPV-H8/h9-backend/pkg/broker/topics" + "github.com/HyperloopUPV-H8/h9-backend/pkg/transport/packet/data" "github.com/HyperloopUPV-H8/h9-backend/pkg/transport/packet/protection" "github.com/HyperloopUPV-H8/h9-backend/pkg/websocket" "github.com/google/uuid" ws "github.com/gorilla/websocket" ) +var _ = data.Packet{} + const UpdateName abstraction.BrokerTopic = "message/update" const SubscribeName abstraction.BrokerTopic = "message/update" type Update struct { subscribersMx *sync.Mutex subscribers map[websocket.ClientId]struct{} - idToBoard map[abstraction.BoardId]string pool *websocket.Pool api abstraction.BrokerAPI } -func NewUpdateTopic(idToBoard map[abstraction.BoardId]string) *Update { +func NewUpdateTopic() *Update { return &Update{ subscribersMx: &sync.Mutex{}, subscribers: make(map[websocket.ClientId]struct{}), - idToBoard: idToBoard, } } @@ -42,7 +43,7 @@ func (update *Update) Push(p abstraction.BrokerPush) error { return topics.ErrUnexpectedPush{Push: p} } - raw, err := json.Marshal(push.Data(push.boardId, update.idToBoard)) + raw, err := json.Marshal(push.Data(push.boardName)) if err != nil { return err } @@ -100,19 +101,19 @@ func (update *Update) SetAPI(api abstraction.BrokerAPI) { } type pushStruct struct { - data any - boardId abstraction.BoardId + data any + boardName string } -func Push(data any, boardId abstraction.BoardId) *pushStruct { - return &pushStruct{data: data, boardId: boardId} +func Push(data any, boardName string) *pushStruct { + return &pushStruct{data: data, boardName: boardName} } func (push *pushStruct) Topic() abstraction.BrokerTopic { return UpdateName } -func (push *pushStruct) Data(boardID abstraction.BoardId, idToBoard map[abstraction.BoardId]string) wrapper { +func (push *pushStruct) Data(boardName string) wrapper { switch data := push.data.(type) { case *protection.Packet: return wrapper{ @@ -124,12 +125,21 @@ func (push *pushStruct) Data(boardID abstraction.BoardId, idToBoard map[abstract Kind: string(data.Data.Name()), Data: data.Data, }, - Board: string(idToBoard[boardID]), + Board: boardName, Name: string(data.Name), Timestamp: data.Timestamp, } + case *data.Packet: + return wrapper{ + Kind: "info", + Payload: "Order Sent", + Board: boardName, + Name: string(data.Id()), + Timestamp: protection.NowTimestamp(), + } } return wrapper{} + } type wrapper struct { diff --git a/backend/pkg/logger/data/logger.go b/backend/pkg/logger/data/logger.go index 7bbd3fcb5..b9796287b 100644 --- a/backend/pkg/logger/data/logger.go +++ b/backend/pkg/logger/data/logger.go @@ -27,6 +27,8 @@ type Logger struct { fileLock *sync.RWMutex // saveFiles is a map that contains the file of each value saveFiles map[data.ValueName]*file.CSV + // allowedVars contains the full names (board/valueName) to be logged + allowedVars map[string]struct{} } // Record is a struct that implements the abstraction.LoggerRecord interface @@ -41,15 +43,25 @@ func (*Record) Name() abstraction.LoggerName { return Name } func NewLogger() *Logger { logger := &Logger{ - saveFiles: make(map[data.ValueName]*file.CSV), - running: &atomic.Bool{}, - fileLock: &sync.RWMutex{}, + saveFiles: make(map[data.ValueName]*file.CSV), + running: &atomic.Bool{}, + fileLock: &sync.RWMutex{}, + allowedVars: nil, // no filter by default } logger.running.Store(false) return logger } +// SetAllowedVars allows updating the list of allowed variables at runtime +func (sublogger *Logger) SetAllowedVars(allowed []string) { + allowedMap := make(map[string]struct{}, len(allowed)) + for _, v := range allowed { + allowedMap[v] = struct{}{} + } + sublogger.allowedVars = allowedMap +} + func (sublogger *Logger) Start() error { if !sublogger.running.CompareAndSwap(false, true) { fmt.Println("Logger already running") @@ -85,7 +97,13 @@ func (sublogger *Logger) PushRecord(record abstraction.LoggerRecord) error { writeErr := error(nil) for valueName, value := range dataRecord.Packet.GetValues() { - + // Filter: only log allowed variables + if sublogger.allowedVars != nil { + key := dataRecord.From + "/" + string(valueName) + if _, ok := sublogger.allowedVars[key]; !ok { + continue + } + } var valueRepresentation string switch value := value.(type) { case numeric: @@ -102,7 +120,7 @@ func (sublogger *Logger) PushRecord(record abstraction.LoggerRecord) error { } err = saveFile.Write([]string{ - fmt.Sprint(dataRecord.Packet.Timestamp().UnixMilli()), + fmt.Sprint(dataRecord.Packet.Timestamp().Format(time.StampMicro)), dataRecord.From, dataRecord.To, valueRepresentation, diff --git a/backend/pkg/vehicle/notification.go b/backend/pkg/vehicle/notification.go index 72fde59c3..ffc4ddfa4 100644 --- a/backend/pkg/vehicle/notification.go +++ b/backend/pkg/vehicle/notification.go @@ -84,7 +84,7 @@ func (vehicle *Vehicle) handlePacketNotification(notification transport.PacketNo case *protection.Packet: boardId := vehicle.ipToBoardId[strings.Split(notification.From, ":")[0]] - err := vehicle.broker.Push(message_topic.Push(p, boardId)) + err := vehicle.broker.Push(message_topic.Push(p, vehicle.idToBoardName[uint16(p.Id())])) if err != nil { vehicle.trace.Error().Stack().Err(err).Msg("broker push") return errors.Join(fmt.Errorf("update protection to frontend (%s protection with id %d and kind %d from %s to %s)", p.Severity(), p.Id(), p.Kind, notification.From, notification.To), err) diff --git a/backend/pkg/vehicle/vehicle.go b/backend/pkg/vehicle/vehicle.go index b067bef3c..e2e1ec262 100644 --- a/backend/pkg/vehicle/vehicle.go +++ b/backend/pkg/vehicle/vehicle.go @@ -60,13 +60,18 @@ func (vehicle *Vehicle) UserPush(push abstraction.BrokerPush) error { return err } + err = vehicle.broker.Push(message_topic.Push(packet, vehicle.idToBoardName[uint16(packet.Id())])) + if err != nil { + fmt.Fprintf(os.Stderr, "error sending info packet to the frontend: %v\n", err) + return err + } + err = vehicle.logger.PushRecord(&order_logger.Record{ Packet: packet, From: "backend", To: vehicle.idToBoardName[uint16(packet.Id())], Timestamp: packet.Timestamp(), }) - if err != nil && !errors.Is(err, logger.ErrLoggerNotRunning{}) { fmt.Fprintln(os.Stderr, "Error pushing record to logger: ", err) } @@ -178,5 +183,5 @@ func (vehicle *Vehicle) notifyError(name string, err error) { packet.Data = &protection.ErrorHandler{ Error: err.Error(), } - vehicle.broker.Push(message_topic.Push(packet, 255)) + vehicle.broker.Push(message_topic.Push(packet, "Error")) } diff --git a/common-front/lib/broker/BrokerStructure.ts b/common-front/lib/broker/BrokerStructure.ts index da75ee1f4..390ebe0e5 100644 --- a/common-front/lib/broker/BrokerStructure.ts +++ b/common-front/lib/broker/BrokerStructure.ts @@ -25,6 +25,7 @@ export type BrokerStructure = { }; "message/update": { request: never; response: MessageAdapter }; "logger/enable": { request: boolean; response: boolean }; + "logger/variables": { request: string[]; response: boolean }; "blcu/upload": { request: BootloaderUploadRequest; response: BootloaderUploadResponse; diff --git a/common-front/lib/components/MessagesContainer/Messages/MessageView/filterMessage.tsx b/common-front/lib/components/MessagesContainer/Messages/MessageView/filterMessage.tsx new file mode 100644 index 000000000..57350456e --- /dev/null +++ b/common-front/lib/components/MessagesContainer/Messages/MessageView/filterMessage.tsx @@ -0,0 +1,40 @@ +//Implementa un filtro para que si un mensaje es igual +// a uno anterior, no se muestre. + +//Existen dos tipos de mensajes: +//Boundaries: envían un nombre de variable, un valor límite y el valor actual. +// Si el valor actual es diferente del último enviado, debe mostrarse siempre. + +//ErrorHandler & InfoWarning: estos mensajes son básicamente un string. +// Se deben filtrar si el mensaje es igual al último o si es igual dentro de un marco temporal +// (cómo implementar este filtro queda a tu criterio). + +//debo: +//1ro: identificar el tipo de mensaje, si es BOUNDARY o ERRORHANDLER +//si es el 1ro => comparar el valor actual de este tipo con el ultimo valor de este tipo => diferente = se muestra SIEMPRE +//si es el 2do => comparar el string con el ultimo recibido: igual y dentro de un t<10s = no se muestra, else = mostrarlo + +import { Message, Protection } from "../../../../models/Message"; + +export function filterMessages (messages: Message[]): Message[]{ + const filtered: Message[] = []; //array de msg filtrados + const lastMessageName = new Map (); + for (const msg of messages){ + const last = lastMessageName.get(msg.name); //obtengo el ultimo msg + //2do caso + //comprobamos si el msg actual NO es boundaries => vemos si el msg es de alguno de estos tipos que contienen texto (ProtectionMessage) + if (msg.kind == "warning" || msg.kind == "fault" || msg.kind == "ok"){ + if (!last || last.payload !== msg.payload){ //evalua si el payload (contenido) es igual al ultimo recibido + filtered.push(msg); //se añade al array + lastMessageName.set(msg.name, msg); //actualiza last con el nuevo valor + } + }else{ + //1er caso => es boundary + if (!last || JSON.stringify((last.payload as unknown as Protection)) !== JSON.stringify((msg.payload as unknown as Protection).data)){ //como payload es del tipo boundary hay que tratarlo con Protection, antes le pongo 'unknown' xq si no lanza error sl hacer la conversion antes + filtered.push(msg); //se añade al array + lastMessageName.set(msg.name, msg); //actualiza con el ultimomensaje + } + } + } + return filtered; +} \ No newline at end of file diff --git a/common-front/lib/models/PodData/Measurement.ts b/common-front/lib/models/PodData/Measurement.ts index 5e55ca9cc..74d46852c 100644 --- a/common-front/lib/models/PodData/Measurement.ts +++ b/common-front/lib/models/PodData/Measurement.ts @@ -9,6 +9,7 @@ export type Measurement = type AbstractMeasurement = { id: string; name: string; + log?: boolean; }; export type NumericMeasurement = AbstractMeasurement & { @@ -23,7 +24,7 @@ export type NumericValue = { last: number; average: number; showLatest: boolean; -}; +} export type BooleanMeasurement = AbstractMeasurement & { type: 'bool'; diff --git a/common-front/lib/store/measurementsStore.ts b/common-front/lib/store/measurementsStore.ts index 315179d40..fb5fb2e5d 100644 --- a/common-front/lib/store/measurementsStore.ts +++ b/common-front/lib/store/measurementsStore.ts @@ -1,7 +1,6 @@ import { Measurement, NumericMeasurement, - isNumericMeasurement, } from '../models'; import { getBooleanMeasurement, @@ -62,6 +61,8 @@ export interface MeasurementsStore { getEnumMeasurementInfo: (id: MeasurementId) => EnumMeasurementInfo; getMeasurementFallback: (id: MeasurementId) => Measurement; clearMeasurements: (board: string) => void; + setLogAll: (log: boolean) => void; + getLogVariables: () => string[]; } export const useMeasurementsStore = create((set, get) => ({ @@ -240,6 +241,25 @@ export const useMeasurementsStore = create((set, get) => ({ } ); }, + + setLogAll: (log: boolean) => { + const measurementsDraft = get().measurements; + for (const id in measurementsDraft) { + const m = measurementsDraft[id]; + m.log = log; + } + set((state) => ({ + ...state, + measurements: measurementsDraft, + })); + }, + + getLogVariables: () => { + const measurements = get().measurements; + return Object.values(measurements) + .filter(m => m.log !== false) + .map(m => m.id); + }, })); function createMeasurementsFromPodDataAdapter( diff --git a/common-front/lib/wsHandler/HandlerMessages.ts b/common-front/lib/wsHandler/HandlerMessages.ts index 2c1c4f44a..4c145005c 100644 --- a/common-front/lib/wsHandler/HandlerMessages.ts +++ b/common-front/lib/wsHandler/HandlerMessages.ts @@ -19,6 +19,7 @@ export type HandlerMessages = { "message/update": Subscription; "logger/enable": PostRequest; "logger/response": Subscription; + "logger/variables": PostRequest; "blcu/upload": Exchange; "blcu/download": Exchange< BootloaderDownloadRequest, diff --git a/control-station/src/components/GuiModules/Module.tsx b/control-station/src/components/GuiModules/Module.tsx index eb7f449b6..f610eec50 100644 --- a/control-station/src/components/GuiModules/Module.tsx +++ b/control-station/src/components/GuiModules/Module.tsx @@ -11,14 +11,14 @@ interface CellProps { const Module: React.FC<{ id: string | number }> = ({ id }) => { const moduleMinCell = useMeasurementsStore( - (state) => (state.getNumericMeasurementInfo(`module_${id}_min_cell`)?.getUpdate() ?? 0) + (state) => (state.getNumericMeasurementInfo(`HVSCU-Cabinet/module_${id}_min_cell`)?.getUpdate() ?? 0) ); const moduleMaxCell = useMeasurementsStore( - (state) => (state.getNumericMeasurementInfo(`module_${id}_max_cell`)?.getUpdate() ?? 0) + (state) => (state.getNumericMeasurementInfo(`HVSCU-Cabinet/module_${id}_max_cell`)?.getUpdate() ?? 0) ); const moduleTotalVoltage = useMeasurementsStore( - (state) => (state.getNumericMeasurementInfo(`module_${id}_voltage`)?.getUpdate() ?? 0) + (state) => (state.getNumericMeasurementInfo(`HVSCU-Cabinet/module_${id}_voltage`)?.getUpdate() ?? 0) ); // Estado para las celdas @@ -28,7 +28,7 @@ const Module: React.FC<{ id: string | number }> = ({ id }) => { const intervalId = setInterval(() => { setCellValues(() => Array.from({ length: 48 }, (_, i) => { - const variableName = `module_${id}_cell_${i + 1}_voltage`; + const variableName = `HVSCU-Cabinet/module_${id}_cell_${i + 1}_voltage`; return useMeasurementsStore.getState().getNumericMeasurementInfo(variableName)?.getUpdate() ?? 0; }) ); diff --git a/control-station/src/pages/VehiclePage/GuiBoosterPage/GuiPage.tsx b/control-station/src/pages/VehiclePage/GuiBoosterPage/GuiPage.tsx index 525f6c2af..b84b32462 100644 --- a/control-station/src/pages/VehiclePage/GuiBoosterPage/GuiPage.tsx +++ b/control-station/src/pages/VehiclePage/GuiBoosterPage/GuiPage.tsx @@ -18,21 +18,21 @@ const modules: ModuleData[] = [ export function GuiPage() { // Medidas const totalSupercapsVoltageInfo = useMeasurementsStore((state) => - state.getNumericMeasurementInfo("total_supercaps_voltage") + state.getNumericMeasurementInfo("HVSCU-Cabinet/total_supercaps_voltage") ); const currentMeasurementInfo = useMeasurementsStore((state) => - state.getNumericMeasurementInfo("output_current") + state.getNumericMeasurementInfo("HVSCU-Cabinet/output_current") ); const temperatureMeasurementInfo = useMeasurementsStore((state) => - state.getNumericMeasurementInfo("temperature_total") + state.getNumericMeasurementInfo("HVSCU-Cabinet/temperature_total") ); // Enums const contactorsStateInfo = useMeasurementsStore((state) => - state.getMeasurement("contactors_state") + state.getMeasurement("HVSCU-Cabinet/contactors_state") ); const bcuGeneralStateInfo = useMeasurementsStore((state) => - state.getMeasurement("bcu_general_state") + state.getMeasurement("HVSCU-Cabinet/BCU_state_master_nested") ); // Estados diff --git a/ethernet-view/src/components/ChartMenu/ChartElement/ChartElement.tsx b/ethernet-view/src/components/ChartMenu/ChartElement/ChartElement.tsx index 39c2e3db7..cb2ef696d 100644 --- a/ethernet-view/src/components/ChartMenu/ChartElement/ChartElement.tsx +++ b/ethernet-view/src/components/ChartMenu/ChartElement/ChartElement.tsx @@ -3,32 +3,31 @@ import { AiOutlineCloseCircle } from 'react-icons/ai' import { MeasurementId, NumericMeasurementInfo, useMeasurementsStore } from 'common'; import { ChartCanvas } from './ChartCanvas/ChartCanvas'; import { ChartLegend } from './ChartLegend/ChartLegend'; -import { memo, useCallback, useState } from "react"; +import { memo } from "react"; import { ChartId } from "../ChartMenu"; -type Props = { +interface Props { chartId: ChartId; initialMeasurementId: MeasurementId; removeChart: (chartId: ChartId) => void; -}; + measurementsInChart: MeasurementId[]; + setMeasurementsInChart: (ids: MeasurementId[]) => void; +} // React component that keeps the chart render and measurements represented on it. -export const ChartElement = memo(({ chartId, initialMeasurementId, removeChart }: Props) => { - +export const ChartElement = memo(({ chartId, removeChart, measurementsInChart, setMeasurementsInChart }: Props) => { const getNumericMeasurementInfo = useMeasurementsStore(state => state.getNumericMeasurementInfo); - const initialMeasurement = getNumericMeasurementInfo(initialMeasurementId); - - const [measurementsInChart, setMeasurementsInChart] = useState([initialMeasurement]); + const measurementInfos: NumericMeasurementInfo[] = measurementsInChart.map(getNumericMeasurementInfo); const addMeasurementToChart = (measurement: NumericMeasurementInfo) => { - if(!measurementsInChart.some(measurementInChart => measurementInChart.id === measurement.id)) { - setMeasurementsInChart([...measurementsInChart, measurement]); + if(!measurementsInChart.includes(measurement.id)) { + setMeasurementsInChart([...measurementsInChart, measurement.id]); } } - const removeMeasurementFromChart = useCallback((measurementId: MeasurementId) => { - setMeasurementsInChart(prevMeasurements => prevMeasurements.filter(measurement => measurement.id !== measurementId)); - }, []); + const removeMeasurementFromChart = (measurementId: MeasurementId) => { + setMeasurementsInChart(measurementsInChart.filter(id => id !== measurementId)); + }; const handleDrop = (ev: React.DragEvent) => { ev.stopPropagation(); @@ -51,11 +50,11 @@ export const ChartElement = memo(({ chartId, initialMeasurementId, removeChart } onClick={() => removeChart(chartId)} /> diff --git a/ethernet-view/src/components/ChartMenu/ChartMenu.tsx b/ethernet-view/src/components/ChartMenu/ChartMenu.tsx index 324cc7b0d..931b69f6a 100644 --- a/ethernet-view/src/components/ChartMenu/ChartMenu.tsx +++ b/ethernet-view/src/components/ChartMenu/ChartMenu.tsx @@ -1,5 +1,5 @@ import styles from "components/ChartMenu/ChartMenu.module.scss"; -import { DragEvent, memo, useCallback, useState } from "react"; +import { DragEvent, memo, useCallback } from "react"; import Sidebar from "components/ChartMenu/Sidebar/Sidebar"; import { Section } from "./Sidebar/Section/Section"; import { MeasurementId, useMeasurementsStore } from "common"; @@ -13,23 +13,30 @@ export type ChartInfo = { initialMeasurementId: MeasurementId; }; -type Props = { +interface Props { sidebarSections: Section[]; -}; - -export const ChartMenu = memo(({ sidebarSections }: Props) => { + charts: ChartInfo[]; + setCharts: React.Dispatch>; + measurementsByChart: Record; + setMeasurementsByChart: React.Dispatch>>; +} +export const ChartMenu = memo(({ sidebarSections, charts, setCharts, measurementsByChart, setMeasurementsByChart }: Props) => { const getNumericMeasurementInfo = useMeasurementsStore((state) => state.getNumericMeasurementInfo); - const [charts, setCharts] = useState([]); - - const addChart = ((chartId: ChartId, initialMeasurementId: MeasurementId) => { - setCharts([...charts, { chartId, initialMeasurementId }]); - }); + const addChart = (chartId: ChartId, initialMeasurementId: MeasurementId) => { + setCharts(prev => [...prev, { chartId, initialMeasurementId }]); + setMeasurementsByChart(prev => ({ ...prev, [chartId]: [initialMeasurementId] })); + }; const removeChart = useCallback((chartId: ChartId) => { setCharts(prevCharts => prevCharts.filter(chart => chart.chartId !== chartId)); - }, []); + setMeasurementsByChart(prev => { + const copy = { ...prev }; + delete copy[chartId]; + return copy; + }); + }, [setCharts, setMeasurementsByChart]); const handleDrop = (ev: DragEvent) => { ev.preventDefault(); @@ -62,6 +69,8 @@ export const ChartMenu = memo(({ sidebarSections }: Props) => { chartId={chart.chartId} initialMeasurementId={chart.initialMeasurementId} removeChart={removeChart} + measurementsInChart={measurementsByChart[chart.chartId] || []} + setMeasurementsInChart={(measurementIds) => setMeasurementsByChart(prev => ({ ...prev, [chart.chartId]: measurementIds }))} /> ))} @@ -69,3 +78,4 @@ export const ChartMenu = memo(({ sidebarSections }: Props) => { ); } }); + diff --git a/ethernet-view/src/components/Logger/useLogger.ts b/ethernet-view/src/components/Logger/useLogger.ts index 4d9e2b169..c4fc57df9 100644 --- a/ethernet-view/src/components/Logger/useLogger.ts +++ b/ethernet-view/src/components/Logger/useLogger.ts @@ -1,21 +1,24 @@ import { useSubscribe, useWsHandler } from "common"; import { useState } from "react"; +import { useMeasurementsStore } from "common"; export function useLogger() { const [state, setState] = useState(false); const handler = useWsHandler(); - const log = (enable: boolean) => { - handler.post("logger/enable", enable); - }; + function getLoggedVariableIds() { + return useMeasurementsStore.getState().getLogVariables(); + } function startLogging() { - log(true); + const variables = getLoggedVariableIds(); + handler.post("logger/variables", variables); + handler.post("logger/enable", true); } function stopLogging() { - log(false); + handler.post("logger/enable", false); } useSubscribe("logger/response", (result) => { diff --git a/ethernet-view/src/components/ReceiveTable/BoardView/PacketView/MeasurementView/MeasurementView.tsx b/ethernet-view/src/components/ReceiveTable/BoardView/PacketView/MeasurementView/MeasurementView.tsx index 97bd6bbc8..a1991c47a 100644 --- a/ethernet-view/src/components/ReceiveTable/BoardView/PacketView/MeasurementView/MeasurementView.tsx +++ b/ethernet-view/src/components/ReceiveTable/BoardView/PacketView/MeasurementView/MeasurementView.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import styles from './MeasurementView.module.scss'; import { Measurement, @@ -31,18 +32,50 @@ export const MeasurementView = ({ measurement }: Props) => { setShowMeasurementLatest(event.currentTarget.checked); }; + const setLog = (log: boolean) => { + useMeasurementsStore.setState(state => { + const measurements = { ...state.measurements }; + measurements[measurement.id] = { ...measurements[measurement.id], log }; + return { ...state, measurements }; + }); + }; + + const logChecked = useMeasurementsStore(state => state.measurements[measurement.id]?.log !== false); + + React.useEffect(() => { + const handler = (e: any) => { + setLog(e.detail); + }; + window.addEventListener('log-all', handler); + return () => window.removeEventListener('log-all', handler); + }, [setLog]); + return ( <> - {measurement.name} + + setLog(e.currentTarget.checked)} + /> + + {measurement.name} + + {isNumeric && ( <> - + + + {measurement.units} {measurement.type} diff --git a/ethernet-view/src/components/ReceiveTable/ReceiveTable.tsx b/ethernet-view/src/components/ReceiveTable/ReceiveTable.tsx index 17dc07dde..88d57526f 100644 --- a/ethernet-view/src/components/ReceiveTable/ReceiveTable.tsx +++ b/ethernet-view/src/components/ReceiveTable/ReceiveTable.tsx @@ -2,16 +2,34 @@ import styles from "./ReceiveTable.module.scss"; import { BoardView } from "./BoardView/BoardView"; import { Header } from "./Header/Header"; import { TableUpdater } from "./TableUpdater"; -import { Board } from "common"; +import { Board, useMeasurementsStore} from "common"; type Props = { boards: Board[]; }; export const ReceiveTable = ({ boards }: Props) => { + const handleLogAll = (log: boolean) => { + useMeasurementsStore.getState().setLogAll(log); + }; return (
+
+
+ + +
{boards diff --git a/ethernet-view/src/pages/TestingPage/ChartsColumn/ChartsColumn.tsx b/ethernet-view/src/pages/TestingPage/ChartsColumn/ChartsColumn.tsx index 7a19edbb2..1aa6c6fb7 100644 --- a/ethernet-view/src/pages/TestingPage/ChartsColumn/ChartsColumn.tsx +++ b/ethernet-view/src/pages/TestingPage/ChartsColumn/ChartsColumn.tsx @@ -1,11 +1,18 @@ import { TabLayout } from "layouts/TabLayout/TabLayout"; -import { ChartMenu } from "components/ChartMenu/ChartMenu"; +import { ChartMenu, ChartInfo, ChartId } from "components/ChartMenu/ChartMenu"; import { ReactComponent as Chart } from "assets/svg/chart.svg"; import { useMemo } from "react"; import { useMeasurementsStore, usePodDataStore, useSubscribe } from "common"; import { createSidebarSections } from "components/ChartMenu/sidebar"; -export const ChartsColumn = () => { +interface ChartsColumnProps { + charts: ChartInfo[]; + setCharts: React.Dispatch>; + measurementsByChart: Record; + setMeasurementsByChart: React.Dispatch>>; +} + +export const ChartsColumn = ({ charts, setCharts, measurementsByChart, setMeasurementsByChart }: ChartsColumnProps) => { const podData = usePodDataStore(state => state.podData); const updatePodData = usePodDataStore(state => state.updatePodData) const updateMeasurements = useMeasurementsStore(state => state.updateMeasurements) @@ -17,16 +24,22 @@ export const ChartsColumn = () => { const sections = useMemo(() => { return createSidebarSections(podData); - }, []); + }, [podData]); const chartsColumnTabItems = [ - { - id: "charts", - name: "Charts", - icon: , - component: , - }, - ] + { + id: "charts", + name: "Charts", + icon: , + component: , + }, + ]; return ; }; \ No newline at end of file diff --git a/ethernet-view/src/pages/TestingPage/TestingPage.tsx b/ethernet-view/src/pages/TestingPage/TestingPage.tsx index 3af59baf3..25b01b8be 100644 --- a/ethernet-view/src/pages/TestingPage/TestingPage.tsx +++ b/ethernet-view/src/pages/TestingPage/TestingPage.tsx @@ -10,15 +10,20 @@ import incomingMessage from "assets/svg/incoming-message.svg"; import paperAirplane from "assets/svg/paper-airplane.svg"; import outgoingMessage from "assets/svg/outgoing-message.svg"; import chart from "assets/svg/chart.svg"; +import { ChartInfo, ChartId } from "components/ChartMenu/ChartMenu"; +import { MeasurementId } from "common"; export const TestingPage = () => { const [collapsed, setCollapsed] = useState({ charts: false, - receive: false, - order: false, + packets: false, + orders: false, messages: false, }); + const [charts, setCharts] = useState([]); + const [measurementsByChart, setMeasurementsByChart] = useState>({}); + const toggleCollapse = (key: keyof typeof collapsed) => { setCollapsed((prev) => ({ ...prev, [key]: !prev[key] })); }; @@ -27,20 +32,20 @@ export const TestingPage = () => { { key: "charts" as const, icon: chart, - component: , + component: , // props nuevos collapsed: collapsed.charts, }, { - key: "receive" as const, + key: "packets" as const, icon: incomingMessage, component: , - collapsed: collapsed.receive, + collapsed: collapsed.packets, }, { - key: "order" as const, + key: "orders" as const, icon: paperAirplane, component: , - collapsed: collapsed.order, + collapsed: collapsed.orders, }, { key: "messages" as const, @@ -62,7 +67,7 @@ export const TestingPage = () => { className={`btn btn-sm btn-${collapsed[key] ? "outline-primary" : "primary"}`} onClick={() => toggleCollapse(key)} > - {collapsed[key] ? `Mostrar ${key}` : `Ocultar ${key}`} + {collapsed[key] ? `Show ${key}` : `Hide ${key}`} ))}