From a83c50849b80e568f45c63e788d07d6bd55638c5 Mon Sep 17 00:00:00 2001 From: hama Date: Sun, 24 Jul 2016 13:23:45 +0200 Subject: [PATCH] new: Expression, PWM output --- pom.xml | 192 ++++--- src/main/java/de/republib/expr/DataType.java | 14 + .../java/de/republib/expr/Expression.java | 178 ++++++ src/main/java/de/republib/expr/OpCode.java | 78 +++ .../de/republib/expr/ParserException.java | 65 +++ src/main/java/de/republib/expr/Scanner.java | 254 ++++++++ src/main/java/de/republib/expr/Token.java | 269 +++++++++ src/main/java/de/republib/expr/TokenType.java | 12 + src/main/java/de/republib/expr/Variable.java | 51 ++ src/main/java/de/republib/expr/Variant.java | 541 ++++++++++++++++++ .../de/republib/expr/VariantException.java | 24 + src/main/java/de/republib/gui/Channel.java | 18 +- src/main/java/de/republib/gui/GuiUtils.java | 41 ++ .../de/republib/gui/MultiChannelSheet.java | 37 +- src/main/java/de/republib/net/TcpClient.java | 4 +- src/main/java/de/republib/pinet/Function.java | 61 ++ .../java/de/republib/pinet/GpioClient.java | 55 +- .../de/republib/pinet/gui/ControlCenter.java | 113 +++- .../de/republib/pinet/gui/GPIOSettings.java | 483 ++++++++++++---- .../java/de/republib/util/StringUtils.java | 29 + src/main/resources/icons/focus16.png | Bin 0 -> 275 bytes src/main/resources/icons/input16.png | Bin 0 -> 517 bytes src/main/resources/icons/output16.png | Bin 0 -> 398 bytes src/main/resources/icons/undef16.png | Bin 0 -> 91 bytes .../republib/expr/ExpressionParserTest.java | 58 ++ .../java/de/republib/expr/ScannerTest.java | 274 +++++++++ .../java/de/republib/expr/VariableTest.java | 25 + .../java/de/republib/expr/VariantTest.java | 226 ++++++++ .../java/de/republib/gui/GuiUtilsTest.java | 22 + 29 files changed, 2912 insertions(+), 212 deletions(-) create mode 100644 src/main/java/de/republib/expr/DataType.java create mode 100644 src/main/java/de/republib/expr/Expression.java create mode 100644 src/main/java/de/republib/expr/OpCode.java create mode 100644 src/main/java/de/republib/expr/ParserException.java create mode 100644 src/main/java/de/republib/expr/Scanner.java create mode 100644 src/main/java/de/republib/expr/Token.java create mode 100644 src/main/java/de/republib/expr/TokenType.java create mode 100644 src/main/java/de/republib/expr/Variable.java create mode 100644 src/main/java/de/republib/expr/Variant.java create mode 100644 src/main/java/de/republib/expr/VariantException.java create mode 100644 src/main/java/de/republib/gui/GuiUtils.java create mode 100644 src/main/java/de/republib/pinet/Function.java create mode 100644 src/main/resources/icons/focus16.png create mode 100644 src/main/resources/icons/input16.png create mode 100644 src/main/resources/icons/output16.png create mode 100644 src/main/resources/icons/undef16.png create mode 100644 src/test/java/de/republib/expr/ExpressionParserTest.java create mode 100644 src/test/java/de/republib/expr/ScannerTest.java create mode 100644 src/test/java/de/republib/expr/VariableTest.java create mode 100644 src/test/java/de/republib/expr/VariantTest.java create mode 100644 src/test/java/de/republib/gui/GuiUtilsTest.java diff --git a/pom.xml b/pom.xml index 20b04d8..ca1e11d 100644 --- a/pom.xml +++ b/pom.xml @@ -1,92 +1,126 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> - 4.0.0 + 4.0.0 - de.republlb - pinet - jar - 1.0-SNAPSHOT + de.republlb + pinet + jar + 1.0-SNAPSHOT - Controlling a Raspberry Pi - https://sourcefourge.net + Controlling a Raspberry Pi + https://sourcefourge.net - - UTF-8 - UTF-8 - 1.1.7 - 1.7.21 - + + UTF-8 + UTF-8 + 1.1.7 + 1.7.21 + - - - - org.slf4j - slf4j-api - ${slf4j.version} - - - ch.qos.logback - logback-classic - ${logback.version} - - - ch.qos.logback - logback-core - ${logback.version} - - - org.testng - testng - 6.9.8 - test - - + + + + org.slf4j + slf4j-api + ${slf4j.version} + + + ch.qos.logback + logback-classic + ${logback.version} + + + ch.qos.logback + logback-core + ${logback.version} + + + org.testng + testng + 6.9.8 + test + + + + com.miglayout + miglayout-swing + 4.2 + - - install + - - - org.apache.maven.plugins - maven-compiler-plugin - 3.5.1 - - 1.7 - 1.7 - - - - org.apache.maven.plugins - maven-resources-plugin - 2.6 - - UTF-8 - - + + install - - org.apache.maven.plugins - maven-surefire-plugin - 2.12.4 - - methods - 4 - - - - - org.codehaus.mojo - exec-maven-plugin - 1.4.0 - - de.republlb.MainApp - false - - + + + org.apache.maven.plugins + maven-compiler-plugin + 3.5.1 + + 1.7 + 1.7 + + + + org.apache.maven.plugins + maven-resources-plugin + 2.6 + + UTF-8 + + - - + + org.apache.maven.plugins + maven-surefire-plugin + 2.12.4 + + methods + 4 + + + + + org.codehaus.mojo + exec-maven-plugin + 1.4.0 + + de.republlb.MainApp + false + + + + maven-assembly-plugin + + + package + + single + + + + + + jar-with-dependencies + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + diff --git a/src/main/java/de/republib/expr/DataType.java b/src/main/java/de/republib/expr/DataType.java new file mode 100644 index 0000000..8ad6904 --- /dev/null +++ b/src/main/java/de/republib/expr/DataType.java @@ -0,0 +1,14 @@ +package de.republib.expr; +/** + * + */ + +/** + * Enumerates the data types which characterizes the storage of a value. + * + * @author hm + * + */ +public enum DataType { + UNDEF, BOOLEAN, LONG, DOUBLE, STRING, OBJECT, VARIABLE +} diff --git a/src/main/java/de/republib/expr/Expression.java b/src/main/java/de/republib/expr/Expression.java new file mode 100644 index 0000000..a995173 --- /dev/null +++ b/src/main/java/de/republib/expr/Expression.java @@ -0,0 +1,178 @@ +/** + * + */ +package de.republib.expr; + +import java.util.HashMap; +import java.util.Stack; + +import de.republib.util.I18N; + +/** + * Implements a parser for arithmetic expressions (formulas). + * + *
+ * Syntax:
+ *  ::=  {  ::= '('  ')' |  | 
+ *  ::= 
+ *  ::= '+' | '-' | '*' | '/' | '%' | '**' | '='
+ * 
+ * + * @author hm + * + */ +public class Expression { + protected Scanner scanner; + protected HashMap variables = new HashMap<>(); + protected Stack values = new Stack<>(); + protected Stack operators = new Stack<>(); + + /** + * Constructor. + * + * @param text + * the text with the formula + */ + public Expression(String text) { + this.scanner = new Scanner(text); + } + + /** + * Calculates the value of the expression. + * + *
+	 *  ::=  {   }*
+	 * 
+ * + * @return the value of the expression stored in a token + * @throws ParserException + * @throws VariantException + */ + public Variant expr() throws ParserException { + final Variant rc = term(); + if (rc == null) { + throw new ParserException(I18N.tr("unexpected end of string"), this.scanner.getLastToken(), false); + } + Token token; + final boolean again = true; + while (again && !(token = this.scanner.nextNotSpaceToken()).isType(TokenType.END_OF_STRING)) { + if (!token.isType(TokenType.OP)) { + throw new ParserException( + String.format(I18N.tr("operator expected (like '+', '-'...). Found: %s"), token.getType()), + token); + } + final OpCode op = token.getOpCode(); + Variant value2; + final Token tokenOp = token.copy(); + if (op == OpCode.ASSIGN) { + if (!rc.isType(DataType.VARIABLE)) { + throw new ParserException(I18N.tr("Assignment misses a rvalue (e.g. a variable) on the left side"), + token); + } + value2 = expr(); + } else { + value2 = term(); + } + try { + rc.binaryOperation(op, value2); + } catch (final VariantException e) { + // transform in a message with position info: + throw new ParserException(e.getMessage(), tokenOp); + } + } + return rc; + } + + /** + * Returns the variable given by the name. + * + * If the variable with this name does not exist it will be created. + * + * @param id + * variable name + * @return the variable + */ + public Variable findVariable(String id) { + Variable rc = this.variables.getOrDefault(id, null); + if (rc == null) { + this.variables.put(id, rc = new Variable(id)); + } + return rc; + } + + /** + * Sets a new text for parsing. + * + * @param text + * the formula text + * @return the instance (for chaining) + */ + public Expression reset(String text) { + this.scanner.reset(text); + return this; + } + + /** + * Calculates the value of the expression. + * + *
+	 *  ::= '('  ')' |  |  |  
+	 *  ::= '+' | '-'
+	 * 
+ * + * @return the value of the term + * @throws ParserException + * @throws VariantException + */ + public Variant term() throws ParserException { + Variant rc = null; + Token token; + boolean isNegative = false; + do { + token = this.scanner.nextNotSpaceToken(); + if (token.isOp(OpCode.MINUS)) { + isNegative = !isNegative; + } + } while (token.isType(TokenType.OP) && token.getOpCode().isUnary()); + + switch (token.getType()) { + case ID: + rc = new Variant(findVariable(token.getString())); + break; + case OP: + switch (token.getOpCode()) { + case LEFT_PARENTH: + rc = expr(); + if (!this.scanner.getLastToken().isOp(OpCode.RIGHT_PARENT)) { + throw new ParserException(I18N.tr("missing ')'"), token, false); + } + break; + case RIGHT_PARENT: + break; + default: + throw new ParserException(String.format(I18N.tr("operand (e.g. constant) expected. Found operator %s"), + token.getOpCode().name()), token); + } + break; + case STRING: + rc = new Variant(token.getString()); + break; + case INTEGER: + rc = new Variant(token.getLongValue()); + break; + case END_OF_STRING: + default: + break; + } + if (isNegative) { + if (rc.isType(DataType.LONG)) { + rc.setLongValue(-rc.getLongValue()); + } else if (rc.isType(DataType.DOUBLE)) { + rc.setDoubleValue(-rc.getDoubleValue()); + } + } + return rc; + } + +} diff --git a/src/main/java/de/republib/expr/OpCode.java b/src/main/java/de/republib/expr/OpCode.java new file mode 100644 index 0000000..90007e1 --- /dev/null +++ b/src/main/java/de/republib/expr/OpCode.java @@ -0,0 +1,78 @@ +package de.republib.expr; + +public enum OpCode { + UNDEF(0), ASSIGN(1, false, true), // + PLUS(10, true), MINUS(10, true), // + TIMES(11), DIV(11), MOD(11), // + POWER(12), // + LEFT_PARENTH(20), RIGHT_PARENT(20), // + NOT(0, true); + + private int priority; + private boolean rightAssociative; + private boolean unary; + + /** + * Constructor. + * + * @param priority + * priority of the operator. Higher priority binds stronger. + */ + private OpCode(int priority) { + this.priority = priority; + this.rightAssociative = false; + this.unary = false; + } + + /** + * Constructor. + * + * @param priority + * priority of the operator. Higher priority binds stronger. + * @param isUnary + * true: the operator is an unary operator. May be a + * binary operator too + */ + private OpCode(int priority, boolean isUnary) { + this.priority = priority; + this.rightAssociative = false; + this.unary = isUnary; + } + + /** + * Constructor. + * + * @param priority + * priority of the operator. Higher priority binds stronger. + * @param isUnary + * true: the operator is an unary operator. May be a + * binary operator too + * @param rightAssociative + * true: the right term must be evaluated before the + * operation can be done. Typical example is assignment '=' + */ + private OpCode(int priority, boolean isUnary, boolean rightAssociative) { + this.priority = priority; + this.rightAssociative = rightAssociative; + this.unary = isUnary; + } + + /** + * @return the priority + */ + public int getPriority() { + return this.priority; + } + + /** + * @return the rightAssociative + */ + public boolean isRightAssociative() { + return this.rightAssociative; + } + + public boolean isUnary() { + // TODO Auto-generated method stub + return false; + } +} diff --git a/src/main/java/de/republib/expr/ParserException.java b/src/main/java/de/republib/expr/ParserException.java new file mode 100644 index 0000000..d871be0 --- /dev/null +++ b/src/main/java/de/republib/expr/ParserException.java @@ -0,0 +1,65 @@ +/** + * + */ +package de.republib.expr; + +/** + * Handles scanner errors like "unknown token". + * + * @author hm + * + */ +public class ParserException extends Exception { + private static final long serialVersionUID = 1L; + + /** + * Constructs the message of the exception. + * + * @param message + * message describing the error + * @param token + * additional information about the context + * @param startPosition + * true: the start position is the interesting point
+ * false: the positon behind the token is the interesting + * point + * @return the message with position information + */ + private static String buildMessage(String message, Token token, boolean startPosition) { + String rc; + if (startPosition) { + rc = String.format("%d-%d ==> [%s]: %s", token.getLineNo(), token.getPosition(), token.toString(), message); + } else { + rc = String.format("%d-%d [%s] <==: %s", token.getLineNo(), token.getPosition(), token.toString(), message); + } + return rc; + } + + /** + * Constructor. + * + * @param message + * message describing the error + * @param token + * additional information about the context + */ + public ParserException(String message, Token token) { + super(ParserException.buildMessage(message, token, true)); + } + + /** + * Constructor. + * + * @param message + * message describing the error + * @param token + * additional information about the context + * @param startPosition + * true: the start position is the interesting point
+ * false: the positon behind the token is the interesting + * point + */ + public ParserException(String message, Token token, boolean startPosition) { + super(ParserException.buildMessage(message, token, startPosition)); + } +} diff --git a/src/main/java/de/republib/expr/Scanner.java b/src/main/java/de/republib/expr/Scanner.java new file mode 100644 index 0000000..9f39431 --- /dev/null +++ b/src/main/java/de/republib/expr/Scanner.java @@ -0,0 +1,254 @@ +/** + * + */ +package de.republib.expr; + +import de.republib.util.I18N; + +/** + * Divides a text into tokens to parse formulas. + * + * @author hm + * + */ +public class Scanner { + int position = 0; + int lineNo = 1; + String text; + Token[] tokens = new Token[3]; + int indexToken = 0; + Token lastToken = null; + StringBuilder string = new StringBuilder(8096); + + /** + * Constructor. + * + * @param text + * the text to parse + */ + public Scanner(final String text) { + this.text = text; + for (int ix = 0; ix < this.tokens.length; ix++) { + this.tokens[ix] = new Token(); + } + } + + /** + * @return the lastToken + */ + public Token getLastToken() { + return this.lastToken; + } + + /** + * Handles a escaped character of a string, e.g. '\n'. + * + * @param buffer + * the character will appended to this buffer + */ + private void handleEscapedChar(StringBuilder buffer) { + char cc; + int value; + int maxLength; + switch (cc = this.text.charAt(this.position++)) { + case 'b': + buffer.append('\b'); + break; + case 'f': + buffer.append('\f'); + break; + case 't': + buffer.append('\t'); + break; + case 'n': + buffer.append('\n'); + break; + case 'r': + buffer.append('\r'); + break; + case '0': { + value = 0; + maxLength = 3; + while (maxLength-- > 0 && this.position < this.text.length() + && (cc = this.text.charAt(this.position++)) >= '0' && cc < '9') { + value = value * 8 + cc - '0'; + } + buffer.append((char) value); + break; + } + case 'X': + case 'x': + value = 0; + maxLength = 2; + while (maxLength-- > 0 && this.position < this.text.length() + && ((cc = this.text.charAt(this.position)) >= '0' && cc < '9' || cc >= 'a' && cc <= 'f' + || cc >= 'A' && cc <= 'F')) { + ++this.position; + final int digit = cc <= '9' ? cc - '0' : cc <= 'F' ? cc - 'A' + 10 : cc - 'a' + 10; + value = value * 16 + digit; + } + buffer.append((char) value); + break; + case 'U': + case 'u': + value = 0; + maxLength = 4; + while (maxLength-- > 0 && this.position < this.text.length() + && ((cc = this.text.charAt(this.position++)) >= '0' && cc < '9' || cc >= 'a' && cc <= 'f' + || cc >= 'A' && cc <= 'F')) { + final int digit = cc <= '9' ? cc - '0' : cc <= 'F' ? cc - 'A' + 10 : cc - 'a' + 10; + value = value * 16 + digit; + } + buffer.append((char) value); + break; + case '\\': + default: + buffer.append(cc); + break; + } + } + + private boolean handleOp(char cc, Token token) { + OpCode op = OpCode.UNDEF; + final int start = this.position - 1; + boolean rc = true; + switch (cc) { + case '+': + op = OpCode.PLUS; + break; + case '-': + op = OpCode.MINUS; + break; + case '*': + if (this.position < this.text.length() && this.text.charAt(this.position) == '*') { + op = OpCode.POWER; + this.position++; + } else { + op = OpCode.TIMES; + } + break; + case '/': + op = OpCode.DIV; + break; + case '%': + op = OpCode.MOD; + break; + case '(': + op = OpCode.LEFT_PARENTH; + break; + case ')': + op = OpCode.RIGHT_PARENT; + break; + default: + rc = false; + break; + } + if (rc) { + token.setOp(this.text.substring(start, this.position), op); + } + return rc; + } + + /** + * Returns the next token which is not a SPACE token. + * + * @return the next token + * @throws ParserException + */ + public Token nextNotSpaceToken() throws ParserException { + Token token; + while ((token = nextToken()).isType(TokenType.SPACE)) { + // do nothing + } + return token; + } + + /** + * Scans the next token. + * + * @return the next token + * @throws ParserException + */ + public Token nextToken() throws ParserException { + final Token token = this.tokens[this.indexToken++]; + final int tokenStart = this.position; + if (this.indexToken >= this.tokens.length) { + this.indexToken = 0; + } + token.setPosition(this.position); + token.setLineNo(this.lineNo); + if (this.position >= this.text.length()) { + token.setEndOfString(); + } else { + char cc = this.text.charAt(this.position++); + if (Character.isWhitespace(cc)) { + if (cc == '\n') { + ++this.lineNo; + } + while (this.position < this.text.length() + && Character.isWhitespace(cc = this.text.charAt(this.position))) { + if (cc == '\n') { + ++this.lineNo; + } + ++this.position; + } + token.setSpace(this.text.substring(tokenStart, this.position)); + } else if (Character.isDigit(cc)) { + long value = cc - '0'; + long digit; + if (this.position < this.text.length() && (cc = this.text.charAt(this.position)) == 'x' || cc == 'X') { + ++this.position; + while (this.position < this.text.length() + && (Character.isDigit(cc = this.text.charAt(this.position)) || cc >= 'A' && cc <= 'F' + || cc >= 'a' && cc <= 'f')) { + this.position++; + digit = cc <= '9' ? cc - '0' : cc <= 'F' ? cc - 'A' + 10 : cc - 'a' + 10; + value = 16 * value + digit; + } + } else { + while (this.position < this.text.length() + && Character.isDigit(cc = this.text.charAt(this.position))) { + this.position++; + value = 10 * value + cc - '0'; + } + } + token.setLong(value); + } else if (cc == '\'' || cc == '"') { + this.string.setLength(0); + final char delimiter = cc; + while (this.position < this.text.length() && (cc = this.text.charAt(this.position++)) != delimiter) { + if (cc != '\\') { + this.string.append(cc); + } else { + handleEscapedChar(this.string); + } + } + token.setString(this.string.toString(), this.text.substring(tokenStart, this.position), delimiter); + } else if (Character.isJavaIdentifierStart(cc)) { + while (this.position < this.text.length() + && Character.isJavaIdentifierPart(cc = this.text.charAt(this.position))) { + ++this.position; + } + token.setId(this.text.substring(tokenStart, this.position)); + } else if (!handleOp(cc, token)) { + token.setUnknown("" + cc); + throw new ParserException(I18N.tr("unknown token"), token); + } + } + return token; + } + + /** + * Resets the instance with a new text and position 0. + * + * @param text + * the input text to scan + * @return the instance (for chaining) + */ + public Scanner reset(String text) { + this.text = text; + this.position = 0; + this.lineNo = 1; + return this; + } +} diff --git a/src/main/java/de/republib/expr/Token.java b/src/main/java/de/republib/expr/Token.java new file mode 100644 index 0000000..1aca0af --- /dev/null +++ b/src/main/java/de/republib/expr/Token.java @@ -0,0 +1,269 @@ +/** + * + */ +package de.republib.expr; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Describes a syntactical element of a formula. + * + * @author hm + * + */ +public class Token { + static Logger logger = LoggerFactory.getLogger(Token.class); + protected TokenType type = TokenType.UNDEF; + protected long longValue = 0; + protected String string = null; + /// string in printable form, with delimiters and escaped characters, + /// e.g."x\\n" + protected String rawString = null; + protected char delimiter = '\0'; + protected OpCode opCode = OpCode.UNDEF; + protected int position; + protected int lineNo; + + /** + * Constructor. + */ + public Token() { + + } + + /** + * Returns a flat copy of the instance. + * + * @return a copy of th instance + */ + public Token copy() { + Token rc = null; + try { + rc = (Token) clone(); + } catch (final CloneNotSupportedException e) { + Token.logger.error("Token.copy()", e); + } + return rc; + } + + /** + * @return the delimiter + */ + public char getDelimiter() { + return this.delimiter; + } + + /** + * @return the lineNo + */ + public int getLineNo() { + return this.lineNo; + } + + /** + * @return the longValue + */ + public long getLongValue() { + return this.longValue; + } + + /** + * @return the opCode + */ + public OpCode getOpCode() { + return this.opCode; + } + + /** + * @return the position + */ + public int getPosition() { + return this.position; + } + + /** + * @return the rawString + */ + public String getRawString() { + return this.rawString; + } + + /** + * @return the string + */ + public String getString() { + return this.string; + } + + /** + * @return the type + */ + public TokenType getType() { + return this.type; + } + + /** + * Tests whether the token has a given type. + * + * @param type + * token type to compare + * @return truethe type matches + */ + public boolean isType(TokenType type) { + return this.type == type; + } + + /** + * Tests whether the token is a given operator. + * + * @param opCode + * operator to compare + * @return true: the token is an operator and has the given opcode + */ + public boolean isOp(OpCode opCode) { + final boolean rc = this.type == TokenType.OP && this.opCode == opCode; + return rc; + } + + /** + * Sets the token as identifier. + * + * @param id + * name of the identifier + * @param delimiter + * @return the instance (for chaining) + */ + public Token setEndOfString() { + this.type = TokenType.END_OF_STRING; + return this; + } + + /** + * Sets the token as identifier. + * + * @param id + * name of the identifier + * @param delimiter + * @return the instance (for chaining) + */ + public Token setId(String id) { + this.type = TokenType.ID; + this.string = id; + return this; + } + + /** + * @param lineNo + * the lineNo to set + */ + public void setLineNo(int lineNo) { + this.lineNo = lineNo; + } + + /** + * Sets the token as long constant. + * + * @param value + * the long value + * @return the instance (for chaining) + */ + public Token setLong(long value) { + this.type = TokenType.INTEGER; + this.longValue = value; + return this; + } + + /** + * Sets the token as operator. + * + * @param string + * the string of the operator, e.g. "+" + * @param op + * the operator like "+" + * @return the instance (for chaining) + */ + public Token setOp(String string, OpCode op) { + this.type = TokenType.OP; + this.string = string; + this.opCode = op; + return this; + } + + /** + * @param position + * the position to set + */ + public void setPosition(int position) { + this.position = position; + } + + /** + * Sets the token as space sequence. + * + * @param spaces + * the space sequence + * @return the instance (for chaining) + */ + public Token setSpace(String spaces) { + this.type = TokenType.SPACE; + this.string = spaces; + return this; + } + + /** + * Sets the token as string. + * + * @param spaces + * the space sequence + * @return the instance (for chaining) + */ + public Token setString(String string, String rawString, char delimiter) { + this.type = TokenType.STRING; + this.string = string; + this.rawString = rawString; + this.delimiter = delimiter; + return this; + } + + /** + * Sets the token as space sequence. + * + * @param spaces + * the space sequence + * @return the instance (for chaining) + */ + public Token setUnknown(String info) { + this.type = TokenType.UNKNOWN; + this.string = info; + return this; + } + + @Override + public String toString() { + String rc = null; + switch (this.type) { + case END_OF_STRING: + rc = "EOS"; + break; + case INTEGER: + rc = String.valueOf(this.getLongValue()); + break; + case STRING: + rc = this.getRawString(); + break; + case UNKNOWN: + case ID: + case OP: + rc = this.getString(); + break; + case SPACE: + rc = ""; + break; + default: + rc = "?"; + break; + } + return rc; + } +} diff --git a/src/main/java/de/republib/expr/TokenType.java b/src/main/java/de/republib/expr/TokenType.java new file mode 100644 index 0000000..8a87ce6 --- /dev/null +++ b/src/main/java/de/republib/expr/TokenType.java @@ -0,0 +1,12 @@ +/** + * + */ +package de.republib.expr; + +/** + * @author hm + * + */ +public enum TokenType { + UNDEF, INTEGER, OP, ID, STRING, SPACE, END_OF_STRING, UNKNOWN +} diff --git a/src/main/java/de/republib/expr/Variable.java b/src/main/java/de/republib/expr/Variable.java new file mode 100644 index 0000000..ccd6fe1 --- /dev/null +++ b/src/main/java/de/republib/expr/Variable.java @@ -0,0 +1,51 @@ +/** + * + */ +package de.republib.expr; + +/** + * Manages a variable. + * + * A variable is a name and a value. + * + * @author hm + * + */ +public class Variable { + protected String id; + protected Variant value; + + /** + * Constructor. + * + * @param id + * name of the variable + */ + public Variable(String id) { + this.id = id; + this.value = new Variant(""); + } + + /** + * Assigns a new value to the instance. + * + * @param value + */ + public void assign(Variant value) { + this.value.redefine(value); + } + + /** + * @return the id + */ + public String getId() { + return this.id; + } + + /** + * @return the value + */ + public Variant getValue() { + return this.value; + } +} diff --git a/src/main/java/de/republib/expr/Variant.java b/src/main/java/de/republib/expr/Variant.java new file mode 100644 index 0000000..02102ac --- /dev/null +++ b/src/main/java/de/republib/expr/Variant.java @@ -0,0 +1,541 @@ +/** + * + */ +package de.republib.expr; + +import de.republib.util.I18N; + +/** + * Implements a container for a value of different data types. + * + * @author hm + * + */ +public class Variant { + protected DataType type; + protected long longValue = Long.MIN_VALUE; + protected double doubleValue = Double.NaN; + protected Boolean boolValue = null; + protected String stringValue; + protected Object objectValue; + protected Variable variableValue; + + /** + * Constructor. + * + * @param value + * the instance becomes the type BOOLEAN with this value + */ + public Variant(boolean value) { + this.boolValue = value; + this.type = DataType.BOOLEAN; + } + + /** + * Constructor. + * + * @param longValue + * the instance becomes the type INTEGER with this value + */ + public Variant(double value) { + this.doubleValue = value; + this.type = DataType.DOUBLE; + } + + /** + * Constructor. + * + * @param value + * the instance becomes the type INTEGER with this value + */ + public Variant(long value) { + this.longValue = value; + this.type = DataType.LONG; + } + + /** + * Constructor. + * + * @param value + * the instance becomes the type OBJECT with this value + */ + public Variant(Object value) { + this.objectValue = value; + this.type = DataType.STRING; + } + + /** + * Constructor. + * + * @param string + * the instance becomes the type STRING with this value + */ + public Variant(String string) { + this.stringValue = string; + this.type = DataType.STRING; + } + + /** + * Constructor. + * + * @param value + * the instance becomes the type VARIABLE with this value + */ + public Variant(Variable value) { + this.variableValue = value; + this.type = DataType.VARIABLE; + } + + /** + * Returns the value as long integer. + * + * @param defaultValue + * return code for unexpected states + * @return a long representation of the value or the default value + */ + public long asLong(long defaultValue) { + long rc = defaultValue; + if (this.longValue != Long.MIN_VALUE) { + rc = this.longValue; + } else { + switch (this.type) { + case BOOLEAN: + rc = this.longValue = this.boolValue ? 1L : 0L; + break; + case DOUBLE: + rc = this.longValue = Math.round(this.doubleValue); + break; + case LONG: + rc = this.longValue; + break; + case STRING: { + try { + final Token token = new Scanner(this.stringValue).nextNotSpaceToken(); + if (token.isType(TokenType.INTEGER)) { + rc = this.longValue = token.getLongValue(); + } + } catch (final ParserException e) { + // do nothing + } + break; + } + case VARIABLE: + rc = this.longValue = this.variableValue.getValue().asLong(defaultValue); + break; + default: + break; + } + } + return rc; + } + + /** + * Returns the value as string. + * + * @return a string representation of the value + */ + public String asString() { + if (this.stringValue == null) { + switch (this.type) { + case BOOLEAN: + this.stringValue = this.boolValue ? "true" : "false"; + break; + case DOUBLE: + this.stringValue = String.valueOf(this.doubleValue); + break; + case LONG: + this.stringValue = String.valueOf(this.longValue); + break; + case OBJECT: + this.stringValue = this.objectValue == null ? "" : this.objectValue.toString(); + break; + case VARIABLE: + this.stringValue = this.variableValue.getValue().asString(); + break; + case STRING: + default: + this.stringValue = ""; + break; + } + } + return this.stringValue; + } + + /** + * @return the boolValue + */ + public Boolean getBoolValue() { + return this.boolValue; + } + + /** + * @return the doubleValue + */ + public double getDoubleValue() { + return this.doubleValue; + } + + /** + * @return the longValue + */ + public long getLongValue() { + return this.longValue; + } + + /** + * @return the objectValue + */ + public Object getObjectValue() { + return this.objectValue; + } + + /** + * @return the string + */ + public String getStringValue() { + return this.stringValue; + } + + /** + * @return the type + */ + public DataType getType() { + return this.type; + } + + /** + * @return the variableValue + */ + public Variable getVariableValue() { + return this.variableValue; + } + + /** + * Tests whether the data type is equal to a given type. + * + * @param expected + * the type to compare + * @return true: the type is the expected + */ + public boolean isType(DataType expected) { + return this.type == expected; + } + + /** + * Does an operation with another value. + * + * @param op + * the operation to do + * @param operand + * the other value + * @return the instance (for chaining) + * @throws VariantException + */ + Variant binaryOperation(OpCode op, Variant operand) throws VariantException { + switch (op) { + case ASSIGN: + if (this.type != DataType.VARIABLE) { + throw new VariantException(I18N.tr("Assignment misses a rvalue (e.g. a variable) on the left side")); + } + this.variableValue.assign(operand); + break; + case DIV: + case MOD: + if (this.type == DataType.LONG) { + final long op2 = operand.asLong(0); + if (op2 == 0L) { + throw new VariantException(I18N.tr("division by 0")); + } else { + if (op == OpCode.MOD) { + this.longValue %= op2; + } else { + this.longValue /= op2; + } + } + } else { + wrongDataType(this, op, operand); + } + break; + case MINUS: + if (this.type == DataType.LONG) { + final long op2 = operand.asLong(0); + this.longValue -= op2; + } else { + wrongDataType(this, op, operand); + } + break; + case PLUS: + if (this.type == DataType.LONG) { + final long op2 = operand.asLong(0); + this.longValue += op2; + } else if (this.type == DataType.STRING) { + this.stringValue += operand.asString(); + } else { + wrongDataType(this, op, operand); + } + break; + case POWER: + if (this.type == DataType.LONG) { + long op2 = operand.asLong(0); + if (op2 < 0) { + throw new VariantException(I18N.tr("exponent < 0: ") + operand.asString()); + } else if (op2 == 0) { + this.longValue = 1; + } else if (op2 > 100) { + throw new VariantException(I18N.tr("exponent too large: ") + operand.asString()); + } else { + long value = this.longValue; + while (op2-- > 1) { + value *= this.longValue; + } + this.longValue = value; + } + } else { + wrongDataType(this, op, operand); + } + break; + case TIMES: + if (this.type == DataType.LONG) { + final long op2 = operand.asLong(0); + this.longValue *= op2; + } else if (this.type == DataType.STRING) { + long factor = operand.asLong(0); + if (factor > 100000) { + throw new VariantException(I18N.tr("string multiplier too large: ") + factor); + } else { + final StringBuilder buffer = new StringBuilder((int) factor * this.stringValue.length()); + while (factor-- > 0) { + buffer.append(this.stringValue); + } + this.stringValue = buffer.toString(); + } + } else { + wrongDataType(this, op, operand); + } + break; + default: + throw new VariantException(String.format(I18N.tr("unexpected operation %s"), op.name())); + } + return this; + } + + /** + * Takes the datat ype from another instance. + * + * @param source + * @return the instance (for chaining) + */ + public Variant redefine(Variant source) { + switch (source.getType()) { + case BOOLEAN: + redefineBool(source.getBoolValue()); + break; + case DOUBLE: + redefineDouble(source.getDoubleValue()); + break; + case LONG: + redefineLong(source.getLongValue()); + break; + case OBJECT: + redefineObject(source.getObjectValue()); + break; + case STRING: + redefineString(source.getStringValue()); + break; + case VARIABLE: + redefineVariable(source.getVariableValue()); + break; + default: + break; + } + return this; + } + + /** + * Change the data type to boolean. + * + * @param value + * the boolean value + * @return the instance (for chaining) + */ + public Variant redefineBool(Boolean value) { + this.type = DataType.BOOLEAN; + this.stringValue = null; + this.longValue = Long.MIN_VALUE; + this.boolValue = value; + this.doubleValue = Double.NaN; + this.objectValue = null; + this.variableValue = null; + return this; + } + + /** + * Change the data type to double. + * + * @param value + * the double value + * @return the instance (for chaining) + */ + public Variant redefineDouble(double value) { + this.type = DataType.DOUBLE; + this.stringValue = null; + this.longValue = Long.MIN_VALUE; + this.boolValue = null; + this.doubleValue = value; + this.objectValue = null; + this.variableValue = null; + return this; + } + + /** + * Change the data type to integer. + * + * @param value + * the value value + * @return the instance (for chaining) + */ + public Variant redefineLong(long value) { + this.type = DataType.LONG; + this.stringValue = null; + this.longValue = value; + this.boolValue = null; + this.doubleValue = Double.NaN; + this.objectValue = null; + this.variableValue = null; + return this; + } + + /** + * Change the data type to string. + * + * @param value + * the string value + * @return the instance (for chaining) + */ + public Variant redefineObject(Object value) { + this.type = DataType.OBJECT; + this.stringValue = null; + this.longValue = Long.MIN_VALUE; + this.boolValue = null; + this.doubleValue = Double.NaN; + this.objectValue = value; + this.variableValue = null; + return this; + } + + /** + * Change the data type to string. + * + * @param value + * the string value + * @return the instance (for chaining) + */ + public Variant redefineString(String value) { + this.type = DataType.STRING; + this.stringValue = value; + this.longValue = Long.MIN_VALUE; + this.boolValue = null; + this.doubleValue = Double.NaN; + this.objectValue = null; + this.variableValue = null; + return this; + } + + /** + * Change the data type to VARIABLE. + * + * @param value + * the variable + * @return the instance (for chaining) + */ + public Variant redefineVariable(Variable value) { + this.type = DataType.VARIABLE; + this.stringValue = null; + this.longValue = Long.MIN_VALUE; + this.boolValue = null; + this.doubleValue = Double.NaN; + this.objectValue = null; + this.variableValue = value; + return this; + } + + /** + * @param boolValue + * the boolValue to set + */ + public void setBoolValue(boolean boolValue) { + this.boolValue = boolValue; + } + + /** + * @param boolValue + * the boolValue to set + */ + public void setBoolValue(Boolean boolValue) { + this.boolValue = boolValue; + } + + /** + * @param doubleValue + * the doubleValue to set + */ + public void setDoubleValue(double doubleValue) { + this.doubleValue = doubleValue; + } + + /** + * @param longValue + * the longValue to set + */ + public void setLongValue(long longValue) { + this.longValue = longValue; + } + + /** + * @param objectValue + * the objectValue to set + */ + public void setObjectValue(Object objectValue) { + this.objectValue = objectValue; + } + + /** + * @param string + * the string to set + */ + public void setStringValue(String string) { + this.stringValue = string; + } + + /** + * @param type + * the type to set + */ + public void setType(DataType type) { + this.type = type; + } + + /** + * @param variableValue + * the variableValue to set + */ + public void setVariableValue(Variable variableValue) { + this.variableValue = variableValue; + } + + /** + * Informs about a wrong combination of data types and/or operator. + * + * @param op1 + * first operand + * @param op + * operator + * @param op2 + * second operand + * @throws VariantException + */ + protected void wrongDataType(Variant op1, OpCode op, Variant op2) throws VariantException { + throw new VariantException(String.format(I18N.tr("wrong operators (%s - %s) for operator %s"), + op1.getType().name(), op2.getType().name(), op.name())); + } +} diff --git a/src/main/java/de/republib/expr/VariantException.java b/src/main/java/de/republib/expr/VariantException.java new file mode 100644 index 0000000..bf37af5 --- /dev/null +++ b/src/main/java/de/republib/expr/VariantException.java @@ -0,0 +1,24 @@ +/** + * + */ +package de.republib.expr; + +/** + * Handles errors in Variant + * + * @author hm + * + */ +public class VariantException extends Exception { + private static final long serialVersionUID = 1L; + + /** + * Constructor. + * + * @param message + * describes the reason of the error + */ + VariantException(String message) { + super(message); + } +} diff --git a/src/main/java/de/republib/gui/Channel.java b/src/main/java/de/republib/gui/Channel.java index 0e1f9c8..1b13b16 100644 --- a/src/main/java/de/republib/gui/Channel.java +++ b/src/main/java/de/republib/gui/Channel.java @@ -170,14 +170,16 @@ public class Channel { graphics.setColor(Channel.colorLegend); final int x = this.x0 + this.width / 2; final int y = this.y0 + this.parent.getFontSizeLegend() + 5; - final IPairData data = this.curves.get(0); - graphics.drawString(data.getName(), x, y); - String text = String.format("(%.1g, %.1g)", data.getX(0), data.getY(0)); - graphics.drawString(text, this.x0 + 5, this.y0 + this.getHeight() - this.parent.getFontSizeLegend()); - final int lastIx = data.getSteps() - 1; - text = String.format("(%.1g, %.1g)", data.getX(lastIx), data.getY(lastIx)); - final int width = graphics.getFontMetrics().stringWidth(text); - graphics.drawString(text, this.x0 + this.width - width - 5, y); + if (this.curves.size() > 0) { + final IPairData data = this.curves.get(0); + graphics.drawString(data.getName(), x, y); + String text = String.format("(%.1g, %.1g)", data.getX(0), data.getY(0)); + graphics.drawString(text, this.x0 + 5, this.y0 + this.getHeight() - this.parent.getFontSizeLegend()); + final int lastIx = data.getSteps() - 1; + text = String.format("(%.1g, %.1g)", data.getX(lastIx), data.getY(lastIx)); + final int width = graphics.getFontMetrics().stringWidth(text); + graphics.drawString(text, this.x0 + this.width - width - 5, y); + } } /** diff --git a/src/main/java/de/republib/gui/GuiUtils.java b/src/main/java/de/republib/gui/GuiUtils.java new file mode 100644 index 0000000..afed63b --- /dev/null +++ b/src/main/java/de/republib/gui/GuiUtils.java @@ -0,0 +1,41 @@ +/** + * + */ +package de.republib.gui; + +import javax.swing.JTextField; + +/** + * Helper functions for Graphical User Interface elements. + * + * Only static methods. + * + * @author hm + * + */ +public class GuiUtils { + /** + * Gets an integer from a text field. + * + * @param field + * the field holding the integer + * @param defaultValue + * return value if the field does not contain an integer + * @param repair + * trueIf the field value is not an integer the default + * value is set as field value + * @return defaultValue the field value has not a correct integer
+ * the value of the field as integer + */ + public static int numberOf(JTextField field, int defaultValue, boolean repair) { + int rc = defaultValue; + try { + rc = Integer.parseInt(field.getText()); + } catch (final NumberFormatException e) { + if (repair) { + field.setText(String.valueOf(defaultValue)); + } + } + return rc; + } +} diff --git a/src/main/java/de/republib/gui/MultiChannelSheet.java b/src/main/java/de/republib/gui/MultiChannelSheet.java index 1619e4f..aabd91c 100644 --- a/src/main/java/de/republib/gui/MultiChannelSheet.java +++ b/src/main/java/de/republib/gui/MultiChannelSheet.java @@ -43,8 +43,8 @@ public class MultiChannelSheet extends JPanel { */ private static final long serialVersionUID = 1L; - private final int columnCount = 2; - private final int rowCount = 4; + private int columnCount = 2; + private int rowCount = 4; private final ArrayList channels = new ArrayList<>(); private final int widthGridLine = 1; private final Color colorGridLine = Color.BLACK; @@ -187,21 +187,28 @@ public class MultiChannelSheet extends JPanel { } /** - * @param channelCount - * the channelCount to set + * @param rows + * count of rows + * @param columns + * count of columns */ - public void setChannelCount(int channelCount) { + public void setChannelCount(int rows, int columns) { boolean changed = false; - while (this.channels.size() > channelCount) { - this.channels.remove(this.channels.size() - 1); - changed = true; - } - while (this.channels.size() < channelCount) { - this.channels.add(new Channel(this.channels.size(), this)); - changed = true; - } - if (changed) { - resizeChannels(); + if (rows > 0 && columns > 0) { + final int channelCount = rows * columns; + this.rowCount = rows; + this.columnCount = columns; + while (this.channels.size() > channelCount) { + this.channels.remove(this.channels.size() - 1); + changed = true; + } + while (this.channels.size() < channelCount) { + this.channels.add(new Channel(this.channels.size(), this)); + changed = true; + } + if (changed) { + resizeChannels(); + } } } diff --git a/src/main/java/de/republib/net/TcpClient.java b/src/main/java/de/republib/net/TcpClient.java index 2e072ce..2a8aea6 100644 --- a/src/main/java/de/republib/net/TcpClient.java +++ b/src/main/java/de/republib/net/TcpClient.java @@ -76,7 +76,9 @@ public class TcpClient { buffer.setLength(0 + 0); length = this.inStream.read(buffer.getBuffer(), 0, buffer.getCapacity()); buffer.setLength(length); - if (this.logger.isDebugEnabled()) { + if (buffer.startsWith("ERR")) { + this.logger.error("received: " + buffer.toUtf8()); + } else if (this.logger.isDebugEnabled()) { this.logger.debug(String.format("received: %s[%d]", buffer.toUtf8(4), length)); } } catch (final IOException e) { diff --git a/src/main/java/de/republib/pinet/Function.java b/src/main/java/de/republib/pinet/Function.java new file mode 100644 index 0000000..5498528 --- /dev/null +++ b/src/main/java/de/republib/pinet/Function.java @@ -0,0 +1,61 @@ +/** + * + */ +package de.republib.pinet; + +/** + * @author hm + * + */ +public enum Function { + CONSTANT(0, 'c'), SIN(1, 's'), FALLING_SLOPE(3, 'f'), RISING_SLOPE(2, 'r'), DUAL_SLOPE(4, 'd'); + + /** + * Search a function by its id. + * + * @param id + * id to search + * @return null: not found
+ * otherwise: the function with the id + */ + public static Function findById(int id) { + Function rc = null; + for (final Function func : Function.values()) { + if (func.getId() == id) { + rc = func; + } + } + return rc; + } + + private int id; + + private byte code; + + /** + * Constructor. + * + * @param id + * a current number + * @param code + * the code used in the TCP requests + */ + private Function(int id, char code) { + this.id = id; + this.code = (byte) code; + } + + /** + * @return the code + */ + public byte getCode() { + return this.code; + } + + /** + * @return the id + */ + public int getId() { + return this.id; + } +} diff --git a/src/main/java/de/republib/pinet/GpioClient.java b/src/main/java/de/republib/pinet/GpioClient.java index 7583176..43ef611 100644 --- a/src/main/java/de/republib/pinet/GpioClient.java +++ b/src/main/java/de/republib/pinet/GpioClient.java @@ -31,7 +31,7 @@ public class GpioClient extends TcpClient { } /** - * Toggles an output pin between high and low. + * Lets the server toggle an output pin between high and low. * * @param pin * pin for output @@ -57,4 +57,57 @@ public class GpioClient extends TcpClient { rc = sendAndReceive(this.buffer); return rc; } + + /* + */ + /** + * Lets the server put Pulse Code Modulation signals to a given pin. + * + *
+	 * Syntax: OPWM[pin][clock][steps][startvalue][function][functionSteps]
+ * [pin]: 1 byte. Numbering like PinNumber.
+ * [clock]: 4 byte little endian. duration of a time slice in micro seconds.
+ * [steps]: 2 byte little endian, number of time slices for one period. + * [startvalue]: 2 byte little endian, first value when starting the sequence. + * [function]: 's': sin, 'r': rising slope, 'f': falling slope, 'd': dual slope + * [functionSteps]: 2 byte little endian, number of PWM periods defining one function period + * [count]: 4 byte little endian.number of function periods + *
+ * + * @param pin + * pin for output + * @param clock + * minimal time slice of the pwm period in microseconds + * @param steps + * number of time slices + * @param startValue + * value of the first PWM period (in steps) + * @param function + * function controlling the pwm values + * @param functionSteps + * number of steps + * @param count + * number of function periods + * @return the answer from the server + */ + public DynBytes pwmOutput(PinNumber pin, int period, double startValue, Function function, int functionSteps, + int count) { + DynBytes rc = null; + if (this.logger.isDebugEnabled()) { + this.logger + .debug(String.format("output pwm: pin: %d period: %d start: %f function: %s f.steps: %d count: %d", + pin.getNumber(), period, startValue, function.name(), functionSteps, count)); + } + this.buffer.setLength(0).append("OPWM"); + this.buffer.append((byte) pin.getNumber()); + this.buffer.appendLittleEndian(period, 4); + final int value = Math.max(0, Math.min(10000, (int) Math.round(startValue * 1E4))); + this.buffer.appendLittleEndian(value, 2); + this.buffer.append(function.getCode()); + this.buffer.appendLittleEndian(functionSteps, 2); + this.buffer.appendLittleEndian(count, 4); + rc = sendAndReceive(this.buffer); + return rc; + } + } diff --git a/src/main/java/de/republib/pinet/gui/ControlCenter.java b/src/main/java/de/republib/pinet/gui/ControlCenter.java index df3949e..9bfa090 100644 --- a/src/main/java/de/republib/pinet/gui/ControlCenter.java +++ b/src/main/java/de/republib/pinet/gui/ControlCenter.java @@ -4,13 +4,20 @@ import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.GridLayout; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.util.LinkedList; +import java.util.List; +import javax.swing.DefaultComboBoxModel; +import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTabbedPane; import javax.swing.JTextField; +import de.republib.gui.GuiUtils; import de.republib.gui.MultiChannelSheet; import de.republib.pinet.GpioClient; import de.republib.util.Announcer; @@ -21,19 +28,22 @@ import de.republib.util.MathFunction; /** * Created by hm on 03.07.16. */ -public class ControlCenter implements Announcer { +public class ControlCenter implements Announcer, FocusListener { private GpioClient client = null; private JFrame frame; - + private final List> channelComboBoxes = new LinkedList<>(); private JPanel panelCenter; private GPIOSettings panelGPIOSettings; private JPanel panelLog; private JLabel labelStatusLine; - private MultiChannelSheet panelData; + private MultiChannelSheet panelMultiChannels; private JPanel panelHead; private JTextField textFieldServer; private JTextField textFieldPort; + private JTextField textFieldRows; + private JTextField textFieldColumns; private JTabbedPane tabbedPane; + private int channelCount = 8; /** * Constructor. @@ -42,6 +52,56 @@ public class ControlCenter implements Announcer { } + /** + * Adds a combobox with channels in the list to the internal list. + * + * @param comboBox + * combobox to add + */ + public void addChannelComboBox(JComboBox comboBox) { + this.channelComboBoxes.add(comboBox); + fillChannelCombo(comboBox, this.channelCount); + } + + /** + * Builds the list of a given channel combo box. + * + * @param combobox + * combo box to modify + * @param count + * number of channels + */ + public void fillChannelCombo(JComboBox combobox, int count) { + final String[] names = new String[count]; + for (int ix = 0; ix < count; ix++) { + names[ix] = String.valueOf(ix); + } + combobox.setModel(new DefaultComboBoxModel<>(names)); + } + + /* + * (non-Javadoc) + * + * @see java.awt.event.FocusListener#focusGained(java.awt.event.FocusEvent) + */ + @Override + public void focusGained(FocusEvent e) { + } + + /* + * (non-Javadoc) + * + * @see java.awt.event.FocusListener#focusLost(java.awt.event.FocusEvent) + */ + @Override + public void focusLost(FocusEvent e) { + if (e.getSource() == this.textFieldRows || e.getSource() == this.textFieldColumns) { + setChannelCount(GuiUtils.numberOf(this.textFieldRows, 4, true), + GuiUtils.numberOf(this.textFieldColumns, 2, true)); + } + + } + /** * @return the client */ @@ -56,6 +116,14 @@ public class ControlCenter implements Announcer { return this.client; } + /** + * Populates the main panel with widgets. + * + * @param host + * the host shown in the text field + * @param port + * the port shown in the text field + */ public void populate(String host, int port) { this.frame = new JFrame(I18N.tr("Pinet Control Center")); this.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); @@ -71,11 +139,17 @@ public class ControlCenter implements Announcer { this.panelHead.add(this.textFieldServer = new JTextField(host)); this.panelHead.add(new JLabel(I18N.tr("Port:"))); this.panelHead.add(this.textFieldPort = new JTextField(String.valueOf(port))); + this.panelHead.add(new JLabel(I18N.tr("Rows:"))); + this.panelHead.add(this.textFieldRows = new JTextField(String.valueOf(4))); + this.textFieldRows.addFocusListener(this); + this.panelHead.add(new JLabel(I18N.tr("Column:"))); + this.panelHead.add(this.textFieldColumns = new JTextField(String.valueOf(2))); + this.textFieldColumns.addFocusListener(this); this.panelCenter.add(this.tabbedPane = new JTabbedPane()); this.tabbedPane.addTab(I18N.tr("GPIO settings"), this.panelGPIOSettings = new GPIOSettings(this)); this.panelGPIOSettings.populate(); - this.tabbedPane.addTab(I18N.tr("Diagram"), this.panelData = new MultiChannelSheet()); + this.tabbedPane.addTab(I18N.tr("Diagram"), this.panelMultiChannels = new MultiChannelSheet()); populateCurves(); this.tabbedPane.addTab(I18N.tr("Log"), this.panelLog = new JPanel()); } @@ -84,23 +158,23 @@ public class ControlCenter implements Announcer { FunctionPairData function = new FunctionPairData(MathFunction.X, 0.0, 10.0, 790); function.setFactor(9.0); function.setOffset(1.0); - this.panelData.addChannel(function); + this.panelMultiChannels.addChannel(function); function = new FunctionPairData(MathFunction.SIN, 0.0, 10.0, 790); function.setFactor(4.0); function.setOffset(5.0); - this.panelData.addChannel(function); + this.panelMultiChannels.addChannel(function); function = new FunctionPairData(MathFunction.COS, 0.0, 10.0, 790); function.setFactor(4.0); function.setOffset(5.0); - this.panelData.addChannel(function); + this.panelMultiChannels.addChannel(function); function = new FunctionPairData(MathFunction.LOG, 0.01, 10.0, 790); - this.panelData.addChannel(function); + this.panelMultiChannels.addChannel(function); function = new FunctionPairData(MathFunction.EXP, -5.0, 5.0, 790); - this.panelData.addChannel(function); + this.panelMultiChannels.addChannel(function); function = new FunctionPairData(MathFunction.X2, -1, 2, 790); - this.panelData.addChannel(function); + this.panelMultiChannels.addChannel(function); function = new FunctionPairData(MathFunction.TAN, 0.0, 1.0, 790); - this.panelData.addChannel(function); + this.panelMultiChannels.addChannel(function); } @@ -133,4 +207,21 @@ public class ControlCenter implements Announcer { this.labelStatusLine.setText(prefix + message); } } + + /** + * Sets a new channel count. + * + * @param rows + * number of rows of the multi channel sheet + * @param columns + * number of columns of the multi channel sheet + */ + public void setChannelCount(int rows, int columns) { + this.channelCount = rows * columns; + for (final JComboBox combo : this.channelComboBoxes) { + fillChannelCombo(combo, this.channelCount); + } + this.panelMultiChannels.setChannelCount(rows, columns); + this.panelMultiChannels.repaint(); + } } diff --git a/src/main/java/de/republib/pinet/gui/GPIOSettings.java b/src/main/java/de/republib/pinet/gui/GPIOSettings.java index 66fc633..ef2ca04 100644 --- a/src/main/java/de/republib/pinet/gui/GPIOSettings.java +++ b/src/main/java/de/republib/pinet/gui/GPIOSettings.java @@ -6,9 +6,16 @@ package de.republib.pinet.gui; import java.awt.Component; import java.awt.FlowLayout; import java.awt.GridLayout; +import java.awt.Image; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.io.IOException; +import java.net.URL; +import javax.imageio.ImageIO; +import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JLabel; @@ -18,11 +25,14 @@ import javax.swing.JTextField; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import de.republib.gui.GuiUtils; +import de.republib.pinet.Function; import de.republib.pinet.GpioClient; import de.republib.pinet.PinNumber; import de.republib.util.Announcer; import de.republib.util.DynBytes; import de.republib.util.I18N; +import net.miginfocom.swing.MigLayout; /** * Manages the register page "GPIO Settings". @@ -38,8 +48,10 @@ public class GPIOSettings extends JPanel { // private final Logger logger = // LoggerFactory.getLogger(GPIOSettings.class); private GPIOTable gpioTable; - private OutputData panelOutputData; - private InputData panelInputData; + private OutputBlinkPanel panelOutputBlinkData; + private OutputPwmPanel panelOutputPwmData; + private InputPanel panelInputData; + private InputPwmPanel panelInputPwmData; private final ControlCenter center; /** @@ -55,28 +67,49 @@ public class GPIOSettings extends JPanel { /** * @return the panelInputData */ - public InputData getPanelInputData() { + public InputPanel getPanelInputData() { return this.panelInputData; } + /** + * @return the panelInputPwmData + */ + public InputPwmPanel getPanelInputPwmData() { + return this.panelInputPwmData; + } + + /** + * @return the panelOutputData + */ + public OutputBlinkPanel getPanelOutputBlinkData() { + return this.panelOutputBlinkData; + } + /** * @return the panelOutputData */ - public OutputData getPanelOutputData() { - return this.panelOutputData; + public OutputPwmPanel getPanelOutputPwmData() { + return this.panelOutputPwmData; } + /** + * Populates the panel with the widgets. + */ public void populate() { this.gpioTable = new GPIOTable(this); add(this.gpioTable); - final PinData panelPinData = new PinData(this.gpioTable); + final PinPanel panelPinData = new PinPanel(this.gpioTable); this.gpioTable.setPanelPinData(panelPinData); add(panelPinData); - add(this.panelOutputData = new OutputData(this.center, panelPinData)); - this.panelOutputData.setVisible(false); - add(this.panelInputData = new InputData(this.center, panelPinData)); - this.panelOutputData.setVisible(false); + add(this.panelOutputBlinkData = new OutputBlinkPanel(this.center, panelPinData)); + this.panelOutputBlinkData.setVisible(false); + add(this.panelOutputPwmData = new OutputPwmPanel(this.center, panelPinData)); + this.panelOutputPwmData.setVisible(true); + add(this.panelInputData = new InputPanel(this.center, panelPinData)); + this.panelInputData.setVisible(false); + add(this.panelInputPwmData = new InputPwmPanel(this.center, panelPinData)); + this.panelInputPwmData.setVisible(false); } } @@ -92,7 +125,7 @@ class GPIOTable extends JPanel implements ActionListener { new JLabel(I18N.tr("Pin")), new JLabel(I18N.tr("Name")) }; private final Object[][] cells; - PinData panelPinData; + PinPanel panelPinData; GPIOSettings gpioSettings; /** @@ -101,45 +134,48 @@ class GPIOTable extends JPanel implements ActionListener { public GPIOTable(GPIOSettings parent) { super(new GridLayout(21, 4)); this.gpioSettings = parent; - this.pinButtons[0] = new PinButton(0, I18N.tr("+3.3V"), null); - this.pinButtons[1] = new PinButton(1, I18N.tr("+5V"), null); + final String TEXT_GROUND = "GND"; + final String TEXT_3V = "+3.3V"; + final String TEXT_5V = "+5.0V"; + this.pinButtons[0] = new PinButton(0, I18N.tr(TEXT_3V), null); + this.pinButtons[1] = new PinButton(1, I18N.tr(TEXT_5V), null); this.pinButtons[2] = new PinButton(2, "SDA1", PinNumber.PIN_RPi2_03); - this.pinButtons[3] = new PinButton(3, I18N.tr("+5V"), null); + this.pinButtons[3] = new PinButton(3, I18N.tr(TEXT_5V), null); this.pinButtons[4] = new PinButton(4, "SCL1", PinNumber.PIN_RPi2_05); - this.pinButtons[5] = new PinButton(5, I18N.tr("GND"), null); + this.pinButtons[5] = new PinButton(5, I18N.tr(TEXT_GROUND), null); this.pinButtons[6] = new PinButton(6, "GCLK", PinNumber.PIN_RPi2_07); this.pinButtons[7] = new PinButton(7, "TXD0", PinNumber.PIN_RPi2_08); - this.pinButtons[8] = new PinButton(8, I18N.tr("GND"), null); + this.pinButtons[8] = new PinButton(8, I18N.tr(TEXT_GROUND), null); this.pinButtons[9] = new PinButton(9, "RXD0", PinNumber.PIN_RPi2_10); this.pinButtons[10] = new PinButton(10, "GEN0", PinNumber.PIN_RPi2_11); this.pinButtons[11] = new PinButton(11, "GEN1", PinNumber.PIN_RPi2_12); this.pinButtons[12] = new PinButton(12, "GEN2", PinNumber.PIN_RPi2_13); - this.pinButtons[13] = new PinButton(13, I18N.tr("GND"), null); + this.pinButtons[13] = new PinButton(13, I18N.tr(TEXT_GROUND), null); this.pinButtons[14] = new PinButton(14, "GEN3", PinNumber.PIN_RPi2_15); this.pinButtons[15] = new PinButton(15, "GEN4", PinNumber.PIN_RPi2_16); - this.pinButtons[16] = new PinButton(16, I18N.tr("+3.3V"), null); + this.pinButtons[16] = new PinButton(16, I18N.tr(TEXT_3V), null); this.pinButtons[17] = new PinButton(17, "GEN5", PinNumber.PIN_RPi2_18); this.pinButtons[18] = new PinButton(18, "SPI_MOSI", PinNumber.PIN_RPi2_19); - this.pinButtons[19] = new PinButton(19, I18N.tr("GND"), null); + this.pinButtons[19] = new PinButton(19, I18N.tr(TEXT_GROUND), null); this.pinButtons[20] = new PinButton(20, "SPI_MISO", PinNumber.PIN_RPi2_21); this.pinButtons[21] = new PinButton(21, "GEN6", PinNumber.PIN_RPi2_22); this.pinButtons[22] = new PinButton(22, "SPI_SCLK", PinNumber.PIN_RPi2_23); this.pinButtons[23] = new PinButton(23, "SPI_CE0", PinNumber.PIN_RPi2_24); - this.pinButtons[24] = new PinButton(24, I18N.tr("GND"), null); + this.pinButtons[24] = new PinButton(24, I18N.tr(TEXT_GROUND), null); this.pinButtons[25] = new PinButton(25, "SPI_CE1", PinNumber.PIN_RPi2_26); this.pinButtons[26] = new PinButton(26, "I2C_ID_SC", PinNumber.PIN_RPi2_27); this.pinButtons[27] = new PinButton(27, "I2C_ID_SD", PinNumber.PIN_RPi2_28); this.pinButtons[28] = new PinButton(28, null, PinNumber.PIN_RPi2_29); - this.pinButtons[29] = new PinButton(29, I18N.tr("GND"), null); + this.pinButtons[29] = new PinButton(29, I18N.tr(TEXT_GROUND), null); this.pinButtons[30] = new PinButton(30, null, PinNumber.PIN_RPi2_31); this.pinButtons[31] = new PinButton(31, null, PinNumber.PIN_RPi2_32); this.pinButtons[32] = new PinButton(32, null, PinNumber.PIN_RPi2_33); - this.pinButtons[33] = new PinButton(33, I18N.tr("GND"), null); + this.pinButtons[33] = new PinButton(33, I18N.tr(TEXT_GROUND), null); this.pinButtons[34] = new PinButton(34, null, PinNumber.PIN_RPi2_35); this.pinButtons[35] = new PinButton(35, null, PinNumber.PIN_RPi2_36); this.pinButtons[36] = new PinButton(36, null, PinNumber.PIN_RPi2_37); this.pinButtons[37] = new PinButton(37, null, PinNumber.PIN_RPi2_38); - this.pinButtons[38] = new PinButton(38, I18N.tr("GND"), null); + this.pinButtons[38] = new PinButton(38, I18N.tr(TEXT_GROUND), null); this.pinButtons[39] = new PinButton(39, null, PinNumber.PIN_RPi2_40); for (final PinButton pinButton : this.pinButtons) { pinButton.addActionListener(this); @@ -206,39 +242,125 @@ class GPIOTable extends JPanel implements ActionListener { * @param panelPinData * the panelPinData to set */ - public void setPanelPinData(PinData panelPinData) { + public void setPanelPinData(PinPanel panelPinData) { this.panelPinData = panelPinData; } + + /** + * Updates the widgets related to a given pin button. + * + * @param button + * the button which should be updated + */ + public void updateButton(PinButton button) { + final int row = button.getIndex() / 2; + if (button.getIndex() % 2 == 0) { + ((JLabel) this.cells[row][0]).setText(button.getNickname()); + } else { + ((JLabel) this.cells[row][3]).setText(button.getNickname()); + } + } + } -class InputData extends JPanel { +class InputPanel extends JPanel implements ActionListener { private static final long serialVersionUID = 1L; - // private final Logger logger = LoggerFactory.getLogger(OutputData.class); + private final Logger logger = LoggerFactory.getLogger(this.getClass()); private JComboBox comboChannel; - private JTextField textChannel; + private JTextField textInterval; private final ControlCenter center; - private final PinData pinData; + private final PinPanel pinData; + JButton buttonRun; /** * Constructor. + * + * @param center + * the control center + * @param pinData + * the parent panel */ - public InputData(ControlCenter center, PinData pinData) { - super(new GridLayout(0, 2)); + public InputPanel(ControlCenter center, PinPanel pinData) { + super(new MigLayout("fillx", "[left]rel[grow,fill]", "[]7[]")); this.center = center; this.pinData = pinData; + add(new JLabel(I18N.tr("Input parameters:")), "span 2, wrap"); + add(new JLabel(I18N.tr("Channel:"))); - add(this.textChannel = new JTextField("0")); + add(this.comboChannel = new JComboBox(), "wrap"); + this.center.addChannelComboBox(this.comboChannel); add(new JLabel(I18N.tr("Interval:"))); - add(this.textChannel = new JTextField("0")); - add(new JLabel()); - add(new JLabel()); - add(new JLabel()); - add(new JLabel()); - add(new JLabel()); - add(new JLabel()); - add(new JLabel()); - add(new JLabel()); + add(this.textInterval = new JTextField("0"), "wrap"); + add(this.buttonRun = new JButton(I18N.tr("Start")), "span 2, wrap"); + this.buttonRun.addActionListener(this); } + + /* + * + */ + @Override + public void actionPerformed(ActionEvent e) { + if (e.getSource() == this.buttonRun) { + final int interval = GuiUtils.numberOf(this.textInterval, 1000, true); + final int channel = this.comboChannel.getSelectedIndex(); + this.logger.info(String.format("channel: %d interval: %d", channel, interval)); + } + } + +} + +/** + * Manages the panel for input with Pulse Width Modulation. + * + * @author hm + * + */ +class InputPwmPanel extends JPanel implements ActionListener { + private static final long serialVersionUID = 1L; + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + private JComboBox comboChannel; + private JTextField textFieldClock; + private JTextField textFieldSteps; + private final ControlCenter center; + private final PinPanel pinData; + private JButton buttonRun; + + /** + * Constructor. + * + * @param center + * the control center + * @param pinData + * the parent panel + */ + public InputPwmPanel(ControlCenter center, PinPanel pinData) { + super(new MigLayout("fillx", "[left]rel[grow,fill]", "[]7[]")); + this.center = center; + this.pinData = pinData; + add(new JLabel(I18N.tr("Input parameters:")), "span 2, wrap"); + + add(new JLabel(I18N.tr("Channel:"))); + add(this.comboChannel = new JComboBox(), "wrap"); + this.center.addChannelComboBox(this.comboChannel); + add(new JLabel(I18N.tr("Clock:"))); + add(this.textFieldClock = new JTextField("1000"), "wrap"); + add(new JLabel(I18N.tr("Period:"))); + add(this.textFieldSteps = new JTextField("1024"), "wrap"); + add(this.buttonRun = new JButton(I18N.tr("Start")), "span 2, wrap"); + this.buttonRun.addActionListener(this); + + } + + @Override + public void actionPerformed(ActionEvent e) { + if (e.getSource() == this.buttonRun) { + final int channel = this.comboChannel.getSelectedIndex(); + final int clock = GuiUtils.numberOf(this.textFieldClock, 1000, true); + final int steps = GuiUtils.numberOf(this.textFieldSteps, 1024, true); + this.logger.info(String.format("channel: %d clock: %d steps: %d", channel, clock, steps)); + } + } + } /** @@ -247,12 +369,11 @@ class InputData extends JPanel { * @author hm * */ -class OutputData extends JPanel implements ActionListener { +class OutputBlinkPanel extends JPanel implements ActionListener { /** * */ private static final long serialVersionUID = 1L; - private final static String[] modes = { I18N.tr("Blink") }; // private final Logger logger = LoggerFactory.getLogger(OutputData.class); private JComboBox comboMode; private JTextField textCount; @@ -260,26 +381,23 @@ class OutputData extends JPanel implements ActionListener { private JTextField textLow; private JButton buttonRun; private final ControlCenter center; - private final PinData pinData; + private final PinPanel pinData; /** * Constructor. */ - public OutputData(ControlCenter center, PinData pinData) { - super(new GridLayout(0, 2)); + public OutputBlinkPanel(ControlCenter center, PinPanel pinData) { + super(new MigLayout("fillx", "[left]rel[grow,fill]", "[]7[]")); this.center = center; this.pinData = pinData; - add(new JLabel(I18N.tr("Mode:"))); - add(this.comboMode = new JComboBox<>(OutputData.modes)); + add(new JLabel(I18N.tr("Blink parameters:")), "span 2, wrap"); add(new JLabel(I18N.tr("Count:"))); - add(this.textCount = new JTextField("10")); + add(this.textCount = new JTextField("10"), "wrap"); add(new JLabel(I18N.tr("High:"))); - add(this.textHigh = new JTextField("500")); + add(this.textHigh = new JTextField("500"), "wrap"); add(new JLabel(I18N.tr("Low:"))); - add(this.textLow = new JTextField("500")); - add(new JLabel("")); - add(this.buttonRun = new JButton(I18N.tr("Start"))); - this.comboMode.addActionListener(this); + add(this.textLow = new JTextField("500"), "wrap"); + add(this.buttonRun = new JButton(I18N.tr("Start")), "span 2"); this.buttonRun.addActionListener(this); } @@ -294,44 +412,116 @@ class OutputData extends JPanel implements ActionListener { if (e.getSource() == this.buttonRun) { final GpioClient client = this.center.getClient(); final PinButton current = this.pinData.getCurrentPin(); - final int count = numberOf(this.textCount, 10); - final int high = numberOf(this.textHigh, 500); - final int low = numberOf(this.textLow, 500); + final int count = GuiUtils.numberOf(this.textCount, 10, true); + final int high = GuiUtils.numberOf(this.textHigh, 500, true); + final int low = GuiUtils.numberOf(this.textLow, 500, true); this.center.say(Announcer.LOG, - String.format(I18N.tr("blinking started on pin %d"), current.getPinNumber())); + String.format(I18N.tr("blinking started on pin %d"), current.getPinNumber().getNumber())); final DynBytes answer = client.blink(current.getPinNumber(), count, high, low); if (answer.startsWith("OK")) { this.center.say(Announcer.LOG, - String.format(I18N.tr("pin %d blinks %d times"), current.getPinNumber(), count)); + String.format(I18N.tr("pin %d blinks %d times"), current.getPinNumber().getNumber(), count)); } } else if (e.getSource() == this.comboMode) { } } +} + +/** + * Manages the panel for output with Pulse Width Modulation. + * + * @author hm + * + */ +class OutputPwmPanel extends JPanel implements ActionListener { + /** + * + */ + private static final long serialVersionUID = 1L; + // private final Logger logger = LoggerFactory.getLogger(OutputData.class); + static String[] modes = { I18N.tr("constant"), I18N.tr("sin"), I18N.tr("falling slope"), I18N.tr("rising slope"), + I18N.tr("dual slope") }; + private JComboBox comboMode; + private JTextField textFieldPeriod; + private JTextField textFieldStartValue; + private JTextField textFieldFunctionSteps; + private JTextField textFieldCount; + private JButton buttonRun; + private final ControlCenter center; + private final PinPanel pinData; /** - * Gets a number from a text field. + * Constructor. * - * @param text - * the text field - * @param defaultValue - * the return value for invalid field values - * @return defaultValue: invalid field value
- * otherwise: the value of the field as integer - */ - private int numberOf(JTextField text, int defaultValue) { - int rc = defaultValue; - try { - rc = Integer.parseInt(text.getText()); - } catch (final NumberFormatException e) { - this.center.say(Announcer.ERROR, - String.format(I18N.tr("not a number: '%s' used instead: %d"), text.getText(), rc)); - text.setText(String.valueOf(rc)); + * @param center + * the control center + * @param pinData + * the parent panel + */ + public OutputPwmPanel(ControlCenter center, PinPanel pinData) { + super(new MigLayout("fillx", "[left]rel[grow,fill]", "[]7[]")); + this.center = center; + this.pinData = pinData; + add(new JLabel(I18N.tr("Output Parameters:")), "span 2, wrap"); + add(new JLabel(I18N.tr("Clock:"))); + add(this.textFieldPeriod = new JTextField("10000"), "wrap"); + this.textFieldPeriod.setToolTipText(I18N.tr("Duration of the pwm signal in microseconds")); + add(new JLabel(I18N.tr("Start value:"))); + add(this.textFieldStartValue = new JTextField("0"), "wrap"); + this.textFieldStartValue.setToolTipText(I18N.tr("First pwm value, multiplied with 1E4, e.g. 223 means 0.0223")); + add(new JLabel(I18N.tr("Function:"))); + add(this.comboMode = new JComboBox(OutputPwmPanel.modes), "wrap"); + this.comboMode.setToolTipText(I18N.tr("A periodic function defining the pwm values.")); + add(new JLabel(I18N.tr("Function steps:"))); + add(this.textFieldFunctionSteps = new JTextField("20"), "wrap"); + this.textFieldFunctionSteps.setToolTipText(I18N + .tr("Count of pwm periods building a 'function period': After this time the pwm values are repeated.")); + add(new JLabel(I18N.tr("Count:"))); + add(this.textFieldCount = new JTextField("2147483647"), "wrap"); + + add(this.buttonRun = new JButton(I18N.tr("Start")), "span 2"); + this.buttonRun.addActionListener(this); + } + + /* + * (non-Javadoc) + * + * @see + * java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) + */ + @Override + public void actionPerformed(ActionEvent e) { + if (e.getSource() == this.buttonRun) { + final GpioClient client = this.center.getClient(); + final PinButton current = this.pinData.getCurrentPin(); + final int period = GuiUtils.numberOf(this.textFieldPeriod, 10000, true); + final double startValue = GuiUtils.numberOf(this.textFieldStartValue, 0, true) / 1E4; + final int mode = this.comboMode.getSelectedIndex(); + final Function function = Function.findById(mode); + final int functionSteps = GuiUtils.numberOf(this.textFieldFunctionSteps, 0, true); + final int count = GuiUtils.numberOf(this.textFieldCount, 0, true); + + this.center.say(Announcer.LOG, + String.format(I18N.tr("PWM output: period: %d start: %f Mode: %s"), period, startValue, mode)); + final DynBytes answer = client.pwmOutput(current.getPinNumber(), period, startValue, function, + functionSteps, count); + if (answer.startsWith("OK")) { + this.center.say(Announcer.LOG, + String.format(I18N.tr("pin %d puts pwm %d times"), current.getPinNumber().getNumber(), count)); + } } - return rc; } } +/** + * Manages the properties of a GPIO pin button. + * + * This button can be used to set the properties of a pin. + * + * @author hm + * + */ class PinButton extends JButton { /** * @@ -339,12 +529,20 @@ class PinButton extends JButton { private static final long serialVersionUID = 1L; public static final int MODE_UNDEF = 0; public static final int MODE_INPUT = 1; - public static final int MODE_OUTPUT = 2; + public static final int MODE_INPUT_PWM = 2; + public static final int MODE_OUTPUT_BLINK = 3; + public static final int MODE_OUTPUT_PWM = 4; + public static final int MODE_FOCUS = 5; + public static final int MODE_COUNT = PinButton.MODE_FOCUS + 1; + static ImageIcon[] icons = null; + private final Logger logger = LoggerFactory.getLogger(PinButton.class); + private JLabel labelPin; private final PinNumber pinNumber; private final String title; + private String nickname; private int mode = PinButton.MODE_UNDEF; - int index; + private final int index; /** * Constructor. @@ -358,10 +556,36 @@ class PinButton extends JButton { * otherwise: the pin number */ public PinButton(int index, String title, PinNumber pinNumber) { - super(pinNumber == null ? title : String.valueOf(index + 1)); + super(pinNumber == null ? title : String.format("P%02d", index + 1)); + if (PinButton.icons == null) { + PinButton.icons = new ImageIcon[PinButton.MODE_COUNT]; + PinButton.icons[PinButton.MODE_UNDEF] = null; + PinButton.icons[PinButton.MODE_INPUT] = null; + PinButton.icons[PinButton.MODE_INPUT_PWM] = null; + PinButton.icons[PinButton.MODE_OUTPUT_BLINK] = null; + PinButton.icons[PinButton.MODE_OUTPUT_PWM] = null; + try { + final URL url = getClass().getResource("/icons/input16.png"); + final Image image = url == null ? null : ImageIO.read(url); + ImageIcon icon = image == null ? null : new ImageIcon(image); + PinButton.icons[PinButton.MODE_INPUT] = icon; + PinButton.icons[PinButton.MODE_INPUT_PWM] = icon; + icon = new ImageIcon(ImageIO.read(getClass().getResource("/icons/output16.png"))); + PinButton.icons[PinButton.MODE_OUTPUT_BLINK] = icon; + PinButton.icons[PinButton.MODE_OUTPUT_PWM] = icon; + icon = new ImageIcon(ImageIO.read(getClass().getResource("/icons/undef16.png"))); + PinButton.icons[PinButton.MODE_UNDEF] = icon; + icon = new ImageIcon(ImageIO.read(getClass().getResource("/icons/focus16.png"))); + PinButton.icons[PinButton.MODE_FOCUS] = icon; + } catch (final IOException e) { + this.logger.error("cannot load icon", e); + } + + } this.index = index; this.title = title == null ? pinNumber.getName() : title; this.pinNumber = pinNumber; + setIcon(PinButton.icons[this.mode]); } /** @@ -378,6 +602,13 @@ class PinButton extends JButton { return this.mode; } + /** + * @return the nickname + */ + public String getNickname() { + return this.nickname == null || this.nickname.isEmpty() ? this.title : this.nickname; + } + /** * @return the pinNumber */ @@ -397,7 +628,18 @@ class PinButton extends JButton { * the mode to set */ public void setMode(int mode) { - this.mode = mode; + if (mode != PinButton.MODE_FOCUS) { + this.mode = mode; + } + setIcon(PinButton.icons[this.mode]); + } + + /** + * @param nickname + * the nickname to set + */ + public void setNickname(String nickname) { + this.nickname = nickname; } } @@ -407,17 +649,19 @@ class PinButton extends JButton { * @author hm * */ -class PinData extends JPanel implements ActionListener { +class PinPanel extends JPanel implements ActionListener, FocusListener { /** * */ private static final long serialVersionUID = 1L; - private final Logger logger = LoggerFactory.getLogger(PinData.class); + private final Logger logger = LoggerFactory.getLogger(PinPanel.class); private JLabel labelPin; private JLabel labelInternalNo; private JLabel labelTitle; + private JTextField textFieldName; private JComboBox comboMode; - private final String[] modes = { I18N.tr("undefined"), I18N.tr("input"), I18N.tr("output") }; + private final String[] modes = { I18N.tr("undefined"), I18N.tr("input"), I18N.tr("input-pwm"), + I18N.tr("output-blink"), I18N.tr("output-pwm") }; private PinButton currentPin = null; private final GPIOTable parent; @@ -427,15 +671,19 @@ class PinData extends JPanel implements ActionListener { * @param table * info about the pins */ - public PinData(GPIOTable table) { - super(new GridLayout(0, 2)); + public PinPanel(GPIOTable table) { + super(new MigLayout("fillx", "[left]rel[grow,fill]", "[]7[]")); this.parent = table; - add(new JLabel(I18N.tr("Pin No:"))); - add(this.labelPin = new JLabel()); - add(new JLabel(I18N.tr("GPIO number:"))); - add(this.labelInternalNo = new JLabel()); + add(new JLabel(I18N.tr("Pin description:")), "span 2, wrap"); + add(new JLabel(I18N.tr("Pin no:"))); + add(this.labelPin = new JLabel(), "wrap"); + add(new JLabel(I18N.tr("GPIO id:"))); + add(this.labelInternalNo = new JLabel(), "wrap"); add(new JLabel(I18N.tr("Title:"))); - add(this.labelTitle = new JLabel()); + add(this.labelTitle = new JLabel(), "wrap"); + add(new JLabel(I18N.tr("Name:"))); + add(this.textFieldName = new JTextField(), "wrap"); + this.textFieldName.addFocusListener(this); add(new JLabel(I18N.tr("Mode:"))); add(this.comboMode = new JComboBox<>(this.modes)); this.comboMode.addActionListener(this); @@ -454,10 +702,15 @@ class PinData extends JPanel implements ActionListener { if (event.getSource() == this.comboMode) { final int index = this.comboMode.getSelectedIndex(); if (this.currentPin.getMode() != index) { - this.logger.debug("actionPerformed(); changing mode: " + index); + final boolean isOutputPWM = index == PinButton.MODE_OUTPUT_PWM; + this.logger.debug( + "actionPerformed(); changing mode: " + index + " isOutPwm: " + String.valueOf(isOutputPWM)); this.currentPin.setMode(index); - this.parent.getGpioSettings().getPanelOutputData().setVisible(index == PinButton.MODE_OUTPUT); + this.parent.getGpioSettings().getPanelOutputBlinkData() + .setVisible(index == PinButton.MODE_OUTPUT_BLINK); + this.parent.getGpioSettings().getPanelOutputPwmData().setVisible(isOutputPWM); this.parent.getGpioSettings().getPanelInputData().setVisible(index == PinButton.MODE_INPUT); + this.parent.getGpioSettings().getPanelInputPwmData().setVisible(index == PinButton.MODE_INPUT_PWM); } } @@ -470,21 +723,57 @@ class PinData extends JPanel implements ActionListener { * properties of the pin */ public void fillData(PinButton button) { + if (this.currentPin != null) { + // change the icon: + this.currentPin.setMode(this.currentPin.getMode()); + } this.currentPin = button; final PinNumber number = button.getPinNumber(); this.labelPin.setText(String.valueOf(button.getIndex() + 1)); this.labelInternalNo.setText(number == null ? "-" : String.valueOf(number.getNumber())); this.labelTitle.setText(button.getTitle()); this.comboMode.setVisible(number != null); - final int index = button.getMode(); - this.comboMode.setSelectedIndex(index); - final OutputData output = this.parent.getGpioSettings().getPanelOutputData(); + final int mode = button.getMode(); + this.comboMode.setSelectedIndex(mode); + final OutputBlinkPanel output = this.parent.getGpioSettings().getPanelOutputBlinkData(); if (output != null) { - output.setVisible(index == PinButton.MODE_OUTPUT); + output.setVisible(mode == PinButton.MODE_OUTPUT_BLINK); + } + final OutputPwmPanel outputPwm = this.parent.getGpioSettings().getPanelOutputPwmData(); + if (outputPwm != null) { + outputPwm.setVisible(mode == PinButton.MODE_OUTPUT_PWM); } - final InputData input = this.parent.getGpioSettings().getPanelInputData(); + final InputPanel input = this.parent.getGpioSettings().getPanelInputData(); if (input != null) { - input.setVisible(index == PinButton.MODE_INPUT); + input.setVisible(mode == PinButton.MODE_INPUT); + } + final InputPwmPanel inputPwm = this.parent.getGpioSettings().getPanelInputPwmData(); + if (inputPwm != null) { + inputPwm.setVisible(mode == PinButton.MODE_INPUT); + } + button.setMode(PinButton.MODE_FOCUS); + } + + /* + * (non-Javadoc) + * + * @see java.awt.event.FocusListener#focusGained(java.awt.event.FocusEvent) + */ + @Override + public void focusGained(FocusEvent e) { + } + + /* + * (non-Javadoc) + * + * @see java.awt.event.FocusListener#focusLost(java.awt.event.FocusEvent) + */ + @Override + public void focusLost(FocusEvent e) { + if (e.getSource() == this.textFieldName && this.currentPin != null) { + final String nickname = this.textFieldName.getText(); + this.currentPin.setNickname(nickname); + this.parent.updateButton(this.currentPin); } } diff --git a/src/main/java/de/republib/util/StringUtils.java b/src/main/java/de/republib/util/StringUtils.java index 26b2aa1..84f2d10 100644 --- a/src/main/java/de/republib/util/StringUtils.java +++ b/src/main/java/de/republib/util/StringUtils.java @@ -83,4 +83,33 @@ public class StringUtils { } return rc; } + + /** + * Evaluate a arithmetic expression. + * + * @param expr + * a formula with '+', '-', '*', '/', "**" and numbers but no + * parentheses + * @return the result + */ + long numberOf(final String expr) throws Exception { + long rc = 0; + final String[] terms = expr.split("[-+]"); + if (terms.length == 1) { + rc = Long.parseLong(expr); + } else { + for (final String term : terms) { + final String[] factors = term.split("[*/]"); + if (factors.length == 1) { + + } else { + for (final String factor : factors) { + final long rc2 = 1; + final String[] parts = factor.split("\\*\\*"); + } + } + } + } + return rc; + } } diff --git a/src/main/resources/icons/focus16.png b/src/main/resources/icons/focus16.png new file mode 100644 index 0000000000000000000000000000000000000000..54a21d75332d4de7813bcd4cd15f6b142aef7f1f GIT binary patch literal 275 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf<{9 literal 0 HcmV?d00001 diff --git a/src/main/resources/icons/input16.png b/src/main/resources/icons/input16.png new file mode 100644 index 0000000000000000000000000000000000000000..4055e3387a21651da7266edf162e93e6a47c7f63 GIT binary patch literal 517 zcmV+g0{Z=lP)v zacL@wN!xA89EWwP)lRusET=>ZK zfFa8U0G!<2oy5sJoZ*fD09$KoTLAD71P@6CoZ*g;BqT)ufWh0_004F*X(vhG40i+o z@O<9`fadOQGpT?x+z|obc`|vfZ*SMtTu%L%&p*1OQTI35)^*#`G%Jjvu;#io5=BG= z01`#y<^BDoQYuvrHa8E{d|nL*30}kSby*OW^+G{EJv=-{;OXBf}Euq)aq00000NkvXX Hu0mjfF>>yw literal 0 HcmV?d00001 diff --git a/src/main/resources/icons/output16.png b/src/main/resources/icons/output16.png new file mode 100644 index 0000000000000000000000000000000000000000..4218f220186475780fdad83554fa84f9b1a7e1ff GIT binary patch literal 398 zcmV;90df9`P)*mHT!n;}EBrQQeF-LHE)+ s?lBo{g6@TT;MUaf58q&t{)Ip16Uei7I2sLO=Kufz07*qoM6N<$g0nQGhX4Qo literal 0 HcmV?d00001 diff --git a/src/main/resources/icons/undef16.png b/src/main/resources/icons/undef16.png new file mode 100644 index 0000000000000000000000000000000000000000..07f7b0e2bcb9558045ec5d3906066fd26b76641d GIT binary patch literal 91 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf 0); + Assert.assertTrue(e.getMessage().indexOf(DataType.STRING.name()) > 0); + Assert.assertTrue(e.getMessage().indexOf(OpCode.POWER.name()) > 0); + } + } + + @Test + public void shouldThrowOnDivBy0() { + try { + final Variant var1 = new Variant(-7L); + final Variant var2 = new Variant(0L); + var1.binaryOperation(OpCode.DIV, var2); + } catch (final VariantException e) { + Assert.assertTrue(e.getMessage().indexOf("0") > 0); + } + } + + @Test + public void shouldThrowOnPower() { + try { + final Variant var1 = new Variant(2L); + final Variant var2 = new Variant(-1L); + var1.binaryOperation(OpCode.POWER, var2); + } catch (final VariantException e) { + Assert.assertTrue(e.getMessage().indexOf("<") > 0); + } + try { + final Variant var1 = new Variant(2L); + final Variant var2 = new Variant(1024 * 1024); + var1.binaryOperation(OpCode.POWER, var2); + } catch (final VariantException e) { + Assert.assertTrue(e.getMessage().indexOf(String.valueOf(1024 * 1024)) > 0); + Assert.assertFalse(e.getMessage().indexOf("<") > 0); + } + } + + @Test + public void shouldThrowOnTimesForString() { + try { + final Variant var1 = new Variant("x"); + final Variant var2 = new Variant(1024L * 1024); + var1.binaryOperation(OpCode.TIMES, var2); + } catch (final VariantException e) { + Assert.assertTrue(e.getMessage().indexOf(String.valueOf(1024 * 1024)) > 0); + } + } + + @Test + public void shouldThrowOnUnexpectedOp() { + final Variant var1 = new Variant("Abc"); + final Variant var2 = new Variant("...ZZZ"); + try { + var1.binaryOperation(OpCode.UNDEF, var2); + } catch (final VariantException e) { + Assert.assertTrue(e.getMessage().indexOf(OpCode.UNDEF.name()) > 0); + } + } + + @Test + public void shouldWorkasLong() throws VariantException { + final Variant varLong = new Variant(477L); + Assert.assertEquals(varLong.asLong(-3), 477L); + + final Variant varBool = new Variant(true); + Assert.assertEquals(varBool.asLong(5), 1L); + + final Variant varFloat = new Variant(1.33E2); + Assert.assertEquals(varFloat.asLong(99), 133L); + + final Variant varString = new Variant("744 x"); + Assert.assertEquals(varString.asLong(44), 744L); + } + + @Test + public void shouldWorkAsString() { + final Variant varLong = new Variant(477L); + Assert.assertEquals(varLong.asString(), "477"); + + final Variant varBool = new Variant(true); + Assert.assertEquals(varBool.asString(), "true"); + + final Variant varFloat = new Variant(1.33E-99); + Assert.assertEquals(varFloat.asString(), "1.33E-99"); + + final Variant varString = new Variant("Hi!"); + Assert.assertEquals(varString.asString(), "Hi!"); + } +} diff --git a/src/test/java/de/republib/gui/GuiUtilsTest.java b/src/test/java/de/republib/gui/GuiUtilsTest.java new file mode 100644 index 0000000..3fd8e21 --- /dev/null +++ b/src/test/java/de/republib/gui/GuiUtilsTest.java @@ -0,0 +1,22 @@ +package de.republib.gui; + +import javax.swing.JTextField; + +import org.testng.Assert; +import org.testng.annotations.Test; + +public class GuiUtilsTest { + + @Test + public void numberOf() { + final JTextField field = new JTextField(); + field.setText("-4711"); + Assert.assertEquals(GuiUtils.numberOf(field, -1, true), -4711); + field.setText(""); + Assert.assertEquals(GuiUtils.numberOf(field, -1, true), -1); + Assert.assertEquals(field.getText(), "-1"); + field.setText("100k"); + Assert.assertEquals(GuiUtils.numberOf(field, -44, false), -44); + Assert.assertEquals(field.getText(), "100k"); + } +} -- 2.39.5