diff --git a/doc/images/noobs.jpg b/doc/images/noobs.jpg
new file mode 100644
index 0000000..e9b8cd7
Binary files /dev/null and b/doc/images/noobs.jpg differ
diff --git a/pom.xml b/pom.xml
index bde219e..1377c14 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
4.0.0
de.srsoftware
web4rail
- 1.4.33
+ 1.4.34
Web4Rail
jar
Java Model Railway Control
diff --git a/src/main/java/de/srsoftware/web4rail/BaseClass.java b/src/main/java/de/srsoftware/web4rail/BaseClass.java
index f8d97a7..ddfa88a 100644
--- a/src/main/java/de/srsoftware/web4rail/BaseClass.java
+++ b/src/main/java/de/srsoftware/web4rail/BaseClass.java
@@ -55,6 +55,7 @@ public abstract class BaseClass implements Constants{
public static final Logger LOG = LoggerFactory.getLogger(BaseClass.class);
private static final String CUSTOM_FIELDS = "custom_Fields";
private static final String NEW_CUSTOM_FIELD_NAME = "new_custom_field_name";
+ protected static final String PROPS_BASIC = "props-basic";
protected HashMap customFieldValues = new HashMap();
private BaseClass parent;
@@ -517,7 +518,7 @@ public abstract class BaseClass implements Constants{
formInputs.add(new AbstractMap.SimpleEntry(t("Notes"),new TextArea(NOTES,notes)));
form(getClass().getSimpleName()+"-prop-form",formInputs)
- .addTo(new Fieldset(t("Basic properties")).id("props-basic"))
+ .addTo(new Fieldset(t("Basic properties")).id(PROPS_BASIC))
.addTo(win);
postForm.forEach(fieldset -> fieldset.addTo(win));
diff --git a/src/main/java/de/srsoftware/web4rail/Plan.java b/src/main/java/de/srsoftware/web4rail/Plan.java
index 6989e2c..0374717 100644
--- a/src/main/java/de/srsoftware/web4rail/Plan.java
+++ b/src/main/java/de/srsoftware/web4rail/Plan.java
@@ -26,6 +26,7 @@ import org.slf4j.LoggerFactory;
import de.srsoftware.localconfig.Configuration;
import de.srsoftware.tools.Tag;
+import de.srsoftware.web4rail.devices.Device;
import de.srsoftware.web4rail.moving.Car;
import de.srsoftware.web4rail.moving.Train;
import de.srsoftware.web4rail.tags.Button;
diff --git a/src/main/java/de/srsoftware/web4rail/devices/Decoder.java b/src/main/java/de/srsoftware/web4rail/devices/Decoder.java
new file mode 100644
index 0000000..24a7b6c
--- /dev/null
+++ b/src/main/java/de/srsoftware/web4rail/devices/Decoder.java
@@ -0,0 +1,206 @@
+package de.srsoftware.web4rail.devices;
+
+import java.util.List;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.Map.Entry;
+import java.util.concurrent.TimeoutException;
+
+import org.json.JSONObject;
+
+import de.srsoftware.tools.Tag;
+import de.srsoftware.tools.Tools;
+import de.srsoftware.web4rail.BaseClass;
+import de.srsoftware.web4rail.Command;
+import de.srsoftware.web4rail.Command.Reply;
+import de.srsoftware.web4rail.tags.Button;
+import de.srsoftware.web4rail.tags.Fieldset;
+import de.srsoftware.web4rail.tags.Form;
+import de.srsoftware.web4rail.tags.Input;
+import de.srsoftware.web4rail.tags.Radio;
+import de.srsoftware.web4rail.tags.Table;
+import de.srsoftware.web4rail.tags.Window;
+import de.srsoftware.web4rail.Constants;
+import de.srsoftware.web4rail.Protocol;
+
+public class Decoder extends BaseClass implements Constants, Device {
+
+ public static final String DECODER = "decoder";
+ public static final String CVS = "cvs";
+ private Integer address = null;
+ public TreeMap cvs = new TreeMap();
+ private boolean init = false;
+ private Protocol proto = Protocol.DCC128;
+ public static final Integer CV_ADDR = 1;
+ private static final String ACTION_PROGRAM = "program";
+ private static final String MODE = "mode";
+ private static final String POM = "pom";
+ private static final String TRACK = "track";
+ private static final String VALUE = "val";
+ private static final String CV = "cv";
+
+
+ @Override
+ public int address() {
+ if (isNull(address)) address = cvs.get(CV_ADDR);
+ return address;
+ }
+
+ public void init() {
+ if (init) return;
+ String proto = null;
+ switch (this.proto) {
+ case FLEISCH:
+ proto = "F"; break;
+ case MOTO:
+ proto = "M 2 100 0"; break; // TODO: make configurable
+ case DCC14:
+ case DCC27:
+ case DCC28:
+ case DCC128:
+ proto = "N 1 "+this.proto.steps+" 5"; break; // TODO: make configurable
+ case SELECTRIX:
+ proto = "S"; break;
+ }
+ plan.queue(new Command("INIT {} GL "+address()+" "+proto) {
+
+ @Override
+ public void onSuccess() {
+ super.onSuccess();
+ plan.stream(t("{} initialized.",this));
+ }
+
+ @Override
+ public void onFailure(Reply r) {
+ super.onFailure(r);
+ plan.stream(t("Was not able to initialize {}!",this));
+ }
+ });
+ init = true;
+ }
+
+ @Override
+ public JSONObject json() {
+ JSONObject json = super.json();
+ json.put(CVS, cvs);
+ json.put(PROTOCOL, proto);
+ return json;
+ }
+
+ @Override
+ public Tag link(String... args) {
+ Tools.notImplemented("Decoder.link(…)");
+ return new Tag("span").content("[[Decoder.link() not implemented]]");
+ }
+
+ @Override
+ public Decoder load(JSONObject json) {
+ super.load(json);
+ if (json.has(PROTOCOL)) proto = Protocol.valueOf(json.getString(PROTOCOL));
+ if (json.has(CVS)) { // Legacy
+ JSONObject jCvs = json.getJSONObject(CVS);
+ for (String key : jCvs.keySet()) cvs.put(Integer.parseInt(key),jCvs.getInt(key));
+ }
+ return this;
+ }
+
+ private String program(int cv,int val,boolean pom) {
+ if (cv != 0) {
+ if (val < 0) {
+ cvs.remove(cv);
+ return null;
+ }
+ init();
+ Command command = new Command("SET {} SM "+(pom?address():-1)+" CV "+cv+" "+val);
+ try {
+ Reply reply = plan.queue(command).reply();
+ if (reply.succeeded()) {
+ cvs.put(cv, val);
+ address = cvs.get(CV_ADDR); // update address field:
+ return null;
+ }
+
+ return reply.message();
+ } catch (TimeoutException e) {
+ return t("Timeout while sending programming command!");
+ }
+ }
+ return null;
+ }
+
+ private Fieldset programming() {
+ Fieldset fieldset = new Fieldset(t("Programming")).id("props-cv");
+
+ Form form = new Form("cv-form");
+ new Input(REALM,REALM_LOCO).hideIn(form);
+ new Input(ID,id()).hideIn(form);
+ new Input(ACTION,ACTION_PROGRAM).hideIn(form);
+
+ Table table = new Table();
+ table.addHead(t("setting"),t("CV"),t("value"),t("actions"));
+ for (int cv=1; cv<19; cv++) {
+ Object val = cvs.get(cv);
+ if (isNull(val)) {
+ if (Set.of(7, 8, 10, 11, 12, 13, 14, 15, 16).contains(cv)) continue;
+ val = t("no value");
+ }
+ table.addRow(setting(cv),cv,val,new Button(t("edit"), "copyCv(this);"));
+ }
+ for (Entry entry : cvs.entrySet()){
+ int cv = entry.getKey();
+ if (cv<10) continue;
+ int val = entry.getValue();
+ table.addRow(setting(cv),cv,val,new Button(t("edit"), "copyCv(this);"));
+ }
+ Tag mode = new Tag("div");
+ new Radio(MODE, POM, t("program on main"), true).addTo(mode);
+ new Radio(MODE, TRACK, t("prgramming track"), false).addTo(mode);
+ table.addRow(mode,new Input(CV,0).numeric(),new Input(VALUE,0).numeric(),new Button(t("Apply"),form));
+ return table.addTo(form).addTo(fieldset);
+ }
+
+ @Override
+ protected Window properties(List