001// Copyright (c) FIRST and other WPILib contributors.
002// Open Source Software; you can modify and/or share it under the terms of
003// the WPILib BSD license file in the root directory of this project.
004
005package edu.wpi.first.epilogue.logging;
006
007import edu.wpi.first.networktables.BooleanArrayPublisher;
008import edu.wpi.first.networktables.BooleanPublisher;
009import edu.wpi.first.networktables.DoubleArrayPublisher;
010import edu.wpi.first.networktables.DoublePublisher;
011import edu.wpi.first.networktables.FloatArrayPublisher;
012import edu.wpi.first.networktables.FloatPublisher;
013import edu.wpi.first.networktables.IntegerArrayPublisher;
014import edu.wpi.first.networktables.IntegerPublisher;
015import edu.wpi.first.networktables.NetworkTableInstance;
016import edu.wpi.first.networktables.Publisher;
017import edu.wpi.first.networktables.RawPublisher;
018import edu.wpi.first.networktables.StringArrayPublisher;
019import edu.wpi.first.networktables.StringPublisher;
020import edu.wpi.first.networktables.StructArrayPublisher;
021import edu.wpi.first.networktables.StructPublisher;
022import edu.wpi.first.util.struct.Struct;
023import java.util.HashMap;
024import java.util.Map;
025
026/**
027 * A backend implementation that sends data over network tables. Be careful when using this, since
028 * sending too much data may cause bandwidth or CPU starvation.
029 */
030public class NTEpilogueBackend implements EpilogueBackend {
031  private final NetworkTableInstance m_nt;
032
033  private final Map<String, Publisher> m_publishers = new HashMap<>();
034  private final Map<String, NestedBackend> m_nestedBackends = new HashMap<>();
035
036  /**
037   * Creates a logging backend that sends information to NetworkTables.
038   *
039   * @param nt the NetworkTable instance to use to send data to
040   */
041  public NTEpilogueBackend(NetworkTableInstance nt) {
042    this.m_nt = nt;
043  }
044
045  @Override
046  public EpilogueBackend getNested(String path) {
047    return m_nestedBackends.computeIfAbsent(path, k -> new NestedBackend(k, this));
048  }
049
050  @Override
051  public void log(String identifier, int value) {
052    ((IntegerPublisher)
053            m_publishers.computeIfAbsent(identifier, k -> m_nt.getIntegerTopic(k).publish()))
054        .set(value);
055  }
056
057  @Override
058  public void log(String identifier, long value) {
059    ((IntegerPublisher)
060            m_publishers.computeIfAbsent(identifier, k -> m_nt.getIntegerTopic(k).publish()))
061        .set(value);
062  }
063
064  @Override
065  public void log(String identifier, float value) {
066    ((FloatPublisher)
067            m_publishers.computeIfAbsent(identifier, k -> m_nt.getFloatTopic(k).publish()))
068        .set(value);
069  }
070
071  @Override
072  public void log(String identifier, double value) {
073    ((DoublePublisher)
074            m_publishers.computeIfAbsent(identifier, k -> m_nt.getDoubleTopic(k).publish()))
075        .set(value);
076  }
077
078  @Override
079  public void log(String identifier, boolean value) {
080    ((BooleanPublisher)
081            m_publishers.computeIfAbsent(identifier, k -> m_nt.getBooleanTopic(k).publish()))
082        .set(value);
083  }
084
085  @Override
086  public void log(String identifier, byte[] value) {
087    ((RawPublisher)
088            m_publishers.computeIfAbsent(identifier, k -> m_nt.getRawTopic(k).publish("raw")))
089        .set(value);
090  }
091
092  @Override
093  @SuppressWarnings("PMD.UnnecessaryCastRule")
094  public void log(String identifier, int[] value) {
095    // NT backend only supports int64[], so we have to manually widen to 64 bits before sending
096    long[] widened = new long[value.length];
097
098    for (int i = 0; i < value.length; i++) {
099      widened[i] = (long) value[i];
100    }
101
102    ((IntegerArrayPublisher)
103            m_publishers.computeIfAbsent(identifier, k -> m_nt.getIntegerArrayTopic(k).publish()))
104        .set(widened);
105  }
106
107  @Override
108  public void log(String identifier, long[] value) {
109    ((IntegerArrayPublisher)
110            m_publishers.computeIfAbsent(identifier, k -> m_nt.getIntegerArrayTopic(k).publish()))
111        .set(value);
112  }
113
114  @Override
115  public void log(String identifier, float[] value) {
116    ((FloatArrayPublisher)
117            m_publishers.computeIfAbsent(identifier, k -> m_nt.getFloatArrayTopic(k).publish()))
118        .set(value);
119  }
120
121  @Override
122  public void log(String identifier, double[] value) {
123    ((DoubleArrayPublisher)
124            m_publishers.computeIfAbsent(identifier, k -> m_nt.getDoubleArrayTopic(k).publish()))
125        .set(value);
126  }
127
128  @Override
129  public void log(String identifier, boolean[] value) {
130    ((BooleanArrayPublisher)
131            m_publishers.computeIfAbsent(identifier, k -> m_nt.getBooleanArrayTopic(k).publish()))
132        .set(value);
133  }
134
135  @Override
136  public void log(String identifier, String value) {
137    ((StringPublisher)
138            m_publishers.computeIfAbsent(identifier, k -> m_nt.getStringTopic(k).publish()))
139        .set(value);
140  }
141
142  @Override
143  public void log(String identifier, String[] value) {
144    ((StringArrayPublisher)
145            m_publishers.computeIfAbsent(identifier, k -> m_nt.getStringArrayTopic(k).publish()))
146        .set(value);
147  }
148
149  @Override
150  @SuppressWarnings("unchecked")
151  public <S> void log(String identifier, S value, Struct<S> struct) {
152    m_nt.addSchema(struct);
153    ((StructPublisher<S>)
154            m_publishers.computeIfAbsent(identifier, k -> m_nt.getStructTopic(k, struct).publish()))
155        .set(value);
156  }
157
158  @Override
159  @SuppressWarnings("unchecked")
160  public <S> void log(String identifier, S[] value, Struct<S> struct) {
161    m_nt.addSchema(struct);
162    ((StructArrayPublisher<S>)
163            m_publishers.computeIfAbsent(
164                identifier, k -> m_nt.getStructArrayTopic(k, struct).publish()))
165        .set(value);
166  }
167}