diff --git a/src/org/openlcb/cdi/CdiRep.java b/src/org/openlcb/cdi/CdiRep.java index 735658dc..9c190312 100644 --- a/src/org/openlcb/cdi/CdiRep.java +++ b/src/org/openlcb/cdi/CdiRep.java @@ -103,6 +103,14 @@ public static interface IntegerRep extends Item { public int getSliderDivisions(); } + public static interface FloatRep extends Item { + public double getDefault(); + public double getMin(); + public double getMax(); + + public int getSize(); + } + public static interface BitRep extends Item { public boolean getDefault(); diff --git a/src/org/openlcb/cdi/impl/ConfigRepresentation.java b/src/org/openlcb/cdi/impl/ConfigRepresentation.java index 1c332dff..7dd14342 100644 --- a/src/org/openlcb/cdi/impl/ConfigRepresentation.java +++ b/src/org/openlcb/cdi/impl/ConfigRepresentation.java @@ -5,6 +5,7 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.Reader; +import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Date; @@ -24,6 +25,7 @@ import org.openlcb.cdi.jdom.JdomCdiReader; import org.openlcb.cdi.jdom.XmlHelper; import org.openlcb.implementations.MemoryConfigurationService; +import org.openlcb.implementations.throttle.Float16; /** * Maintains a parsed cache of the CDI config of a remote node. Responsible for fetching the CDI, @@ -249,6 +251,8 @@ private long processGroup(String baseName, int segment, List items, entry = new GroupEntry(name, (CdiRep.Group) it, segment, origin); } else if (it instanceof CdiRep.IntegerRep) { entry = new IntegerEntry(name, (CdiRep.IntegerRep) it, segment, origin); + } else if (it instanceof CdiRep.FloatRep) { + entry = new FloatEntry(name, (CdiRep.FloatRep) it, segment, origin); } else if (it instanceof CdiRep.EventID) { entry = new EventEntry(name, (CdiRep.EventID) it, segment, origin); } else if (it instanceof CdiRep.StringRep) { @@ -305,6 +309,8 @@ public void visitEntry(CdiEntry e) { visitString((StringEntry) e); } else if (e instanceof IntegerEntry) { visitInt((IntegerEntry) e); + } else if (e instanceof FloatEntry) { + visitFloat((FloatEntry) e); } else if (e instanceof EventEntry) { visitEvent((EventEntry) e); } else if (e instanceof ActionButtonEntry) { @@ -335,6 +341,10 @@ public void visitInt(IntegerEntry e) { visitLeaf(e); } + public void visitFloat(FloatEntry e) { + visitLeaf(e); + } + public void visitEvent(EventEntry e) { visitLeaf(e); } @@ -620,6 +630,70 @@ public void setValue(long value) { } } + /** + * Represents a float variable. + */ + public class FloatEntry extends CdiEntry { + public CdiRep.FloatRep rep; + + FloatEntry(String name, CdiRep.FloatRep rep, int segment, long origin) { + this.key = name; + this.space = segment; + this.origin = origin; + this.rep = rep; + this.size = rep.getSize(); + } + + @Override + public CdiRep.Item getCdiItem() { + return rep; + } + + @Override + protected void updateVisibleValue() { + lastVisibleValue = Double.toString(getValue()); + } + + public double getValue() { + MemorySpaceCache cache = getCacheForSpace(space); + byte[] b = cache.read(origin, size); + if (b == null) return 0.0; + switch (size) { + case 8: + return ByteBuffer.wrap(b).getDouble(); + case 4: + return ByteBuffer.wrap(b).getFloat(); + case 2: + Float16 f = new Float16(b[0], b[1]); + return f.getFloat(); + + } + return 0.0; + } + + public void setValue(double value) { + MemorySpaceCache cache = getCacheForSpace(space); + byte[] bytes; + switch (size) { + case 8: + bytes = new byte[8]; + ByteBuffer.wrap(bytes).putDouble(value); + cache.write(origin, bytes, this); + break; + case 4: + bytes = new byte[4]; + ByteBuffer.wrap(bytes).putFloat((float)value); + cache.write(origin, bytes, this); + break; + case 2: + Float16 f = new Float16(value, value>=0.0); + bytes = new byte[]{f.getByte1(), f.getByte2()}; + cache.write(origin, bytes, this); + break; + } + } + } + /** * Represents an event variable. */ diff --git a/src/org/openlcb/cdi/jdom/JdomCdiRep.java b/src/org/openlcb/cdi/jdom/JdomCdiRep.java index c6e261b3..4da77808 100644 --- a/src/org/openlcb/cdi/jdom/JdomCdiRep.java +++ b/src/org/openlcb/cdi/jdom/JdomCdiRep.java @@ -112,6 +112,9 @@ public java.util.List getItems() { case "int": list.add(new IntRep(element)); break; + case "float": + list.add(new FloatRep(element)); + break; case "eventid": list.add(new EventID(element)); break; @@ -364,6 +367,7 @@ int indexOfFirstTrailingDigit(String input) { public static class EventID extends Item implements CdiRep.EventID { EventID(Element e) { super(e); } } + public static class IntRep extends Item implements CdiRep.IntegerRep { IntRep(Element e) { super(e); } @@ -470,6 +474,82 @@ public int getSliderDivisions() { } + + public static class FloatRep extends Item implements CdiRep.FloatRep { + FloatRep(Element e) { super(e); } + + @Override + public double getDefault() { + Element target = e.getChild("default"); + if (target != null) { + String text = target.getTextNormalize(); + try { + return Double.valueOf(text); + } catch (NumberFormatException ex) { + logger.severe("Invalid content for default element: "+text); + // and return the default value from length + } + } + // otherwise, return default value of 0 + return 0.0; + } + + @Override + public double getMin() { + Element target = e.getChild("min"); + if (target != null) { + String text = target.getTextNormalize(); + try { + return Double.valueOf(text); + } catch (NumberFormatException ex) { + logger.severe("Invalid content for min element: "+text); + // and return the default value + } + } + // otherwise, return default + return 0.0; + } + + @Override + public double getMax() { + Element target = e.getChild("max"); + if (target != null) { + String text = target.getTextNormalize(); + try { + return Integer.valueOf(text); + } catch (NumberFormatException ex) { + logger.severe("Invalid content for max element: "+text); + // and return the value computed from length + } + } + // otherwise, return value computed from size + switch (getSize()) { + case 8: + return Double.MAX_VALUE-1.0; + case 4: + return Float.MAX_VALUE-1.0; + case 2: + // Float16 + return 65504.0-1.0; + default: + logger.severe("Invalid size when finding default max: "+getSize()); + return 1.0; + } + } + + @Override + public int getSize() { + Attribute a = e.getAttribute("size"); + try { + if (a == null) { + return 1; + } else { + return a.getIntValue(); // this doesn't check value for validity, that's the Schema's job + } + } catch (org.jdom2.DataConversionException e1) { return 0; } + } + } + public static class UnknownRep extends Item implements CdiRep.UnknownRep { UnknownRep(Element e) { super(e); } diff --git a/src/org/openlcb/cdi/swing/CdiPanel.java b/src/org/openlcb/cdi/swing/CdiPanel.java index 53786bb2..6b6c1100 100644 --- a/src/org/openlcb/cdi/swing/CdiPanel.java +++ b/src/org/openlcb/cdi/swing/CdiPanel.java @@ -1265,6 +1265,11 @@ public void visitInt(ConfigRepresentation.IntegerEntry e) { writeEntry(e.key, Long.toString(e.getValue())); } + @Override + public void visitFloat(ConfigRepresentation.FloatEntry e) { + writeEntry(e.key, Double.toString(e.getValue())); + } + @Override public void visitEvent(ConfigRepresentation.EventEntry e) { writeEntry(e.key, org.openlcb.Utilities.toHexDotsString(e.getValue @@ -1390,6 +1395,12 @@ public void visitInt(ConfigRepresentation.IntegerEntry e) { super.visitInt(e); } + @Override + public void visitFloat(ConfigRepresentation.FloatEntry e) { + currentLeaf = new FloatPane(e); + super.visitFloat(e); + } + @Override public void visitEvent(ConfigRepresentation.EventEntry e) { currentLeaf = new EventIdPane(e); @@ -2567,6 +2578,92 @@ void updateWriteButton() { } } + private class FloatPane extends EntryPane { + JTextField textField = null; + private final ConfigRepresentation.FloatEntry entry; + + + FloatPane(ConfigRepresentation.FloatEntry e) { + super(e, "Float"); + this.entry = e; + + // display an entry box + textField = new JTextField(24) { + public java.awt.Dimension getMaximumSize() { + return getPreferredSize(); + } + }; + textComponent = textField; + textField.setToolTipText("Float from " + +entry.rep.getMin()+" to "+entry.rep.getMax() + +" ("+entry.size+" bytes)"); + + init(); + } + + @Override + protected void writeDisplayTextToNode() { + double value = Double.parseDouble(textField.getText()); + entry.setValue(value); + _changeMade = true; + notifyTabColorRefresh(); + } + + @Override + protected void updateDisplayText(@NonNull String value) { + textField.setText(value); + } + + @NonNull + @Override + protected String getDisplayText() { + String s = textField.getText(); + return s == null ? "" : s; + } + + /** + * Get the current value as a numerical String. + * Usually, this is the display text, but in the case of a + * a map it's the integer value of the + * current selection. + * @return Current value for storage as a String. + */ + @NonNull + protected String getCurrentValue() { + String s = (String) textField.getText(); + return s == null ? "" : s; + } + + boolean isDataInvalid() { + try { + double value = Double.valueOf(getCurrentValue()); + if (value >= entry.rep.getMin() && value <= entry.rep.getMax() ) { + return false; + } else { + return true; + } + } catch (NumberFormatException ex) { + return true; + } + } + + /** + * check that the current (String) value is + * valid and within the min and max range. + * Disable/Enable write button as appropriate. + */ + @Override + void updateWriteButton() { + if (writeButton == null) { + // skip these until the write button has been created + return; + } + + writeButton.setEnabled( ! isDataInvalid()); + + } + } + // Define font to be used in certain types of StringPanes Font textAreaFont; {