();
+
+ public static class CronTask implements Callable {
+ private String taskName;
+ private Runnable runnable;
+ private Callable callable;
+ private CronPredictor predictor;
+ private ScheduledFuture future;
+ private boolean canceledTask;
+
+ public CronTask(String taskName, Runnable runnable, SchedulingPattern pattern) {
+ this.taskName = taskName;
+ this.runnable = runnable;
+ this.predictor = new CronPredictor(pattern);
+ }
+
+ public CronTask(String taskName, Callable callable, SchedulingPattern pattern) {
+ this.taskName = taskName;
+ this.callable = callable;
+ this.predictor = new CronPredictor(pattern);
+ }
+
+ public synchronized void scheduleNextRun() throws RejectedExecutionException {
+ if (canceledTask) {
+ throw new RejectedExecutionException("Task cannot be executed again since it has been cancelled.");
+ }
+ future = Main.GLOBAL_EXECUTOR.schedule(this, predictor.nextMatchingTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
+ }
+
+ public synchronized boolean cancel() {
+ canceledTask = true;
+ return future.cancel(true);
+ }
+
+ @Override
+ public Object call() throws Exception {
+ Object returnObject;
+ try {
+ if (runnable != null) {
+ runnable.run();
+ returnObject = Void.TYPE;
+ } else if (callable != null){
+ returnObject = callable.call();
+ } else {
+ throw new RejectedExecutionException("Must implement either a runnable or a callable.");
+ }
+ scheduleNextRun();
+ return returnObject;
+ } catch (RejectedExecutionException e) {
+ logger.error("I will NEVER attempt to execute this task again:" + taskName, e);
+ throw e;
+ } catch (Exception e) {
+ logger.error("I will attempt to execute this task again:" + taskName, e);
+ throw e;
+ } catch (Throwable t) {
+ logger.error("Can't recover, I will NEVER attempt to execute this task again:" + taskName, t);
+ throw t;
+ }
+ }
+ }
+
+ @Override
+ public void start(URI uri) throws Exception {
+
+ }
+
+ @Override
+ public void stop() {
+
+ }
+}
diff --git a/host/src/main/java/org/area515/resinprinter/util/cron/CronPredictor.java b/host/src/main/java/org/area515/resinprinter/util/cron/CronPredictor.java
new file mode 100644
index 000000000..f1bc42034
--- /dev/null
+++ b/host/src/main/java/org/area515/resinprinter/util/cron/CronPredictor.java
@@ -0,0 +1,335 @@
+/*
+ * cron4j - A pure Java cron-like scheduler
+ *
+ * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version
+ * 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License 2.1 for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License version 2.1 along with this program.
+ * If not, see .
+ */
+package org.area515.resinprinter.util.cron;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+/**
+ *
+ * A predictor is able to predict when a scheduling pattern will be matched.
+ *
+ *
+ * Suppose you want to know when the scheduler will execute a task scheduled
+ * with the pattern 0 3 * jan-jun,sep-dec mon-fri. You can predict the
+ * next n execution of the task using a Predictor instance:
+ *
+ *
+ *
+ * String pattern = "0 3 * jan-jun,sep-dec mon-fri";
+ * Predictor p = new Predictor(pattern);
+ * for (int i = 0; i < n; i++) {
+ * System.out.println(p.nextMatchingDate());
+ * }
+ *
+ *
+ * @author Carlo Pelliccia
+ * @since 1.1
+ */
+public class CronPredictor {
+
+ /**
+ * The scheduling pattern on which the predictor works.
+ */
+ private SchedulingPattern schedulingPattern;
+
+ /**
+ * The start time for the next prediction.
+ */
+ private long time;
+
+ /**
+ * The time zone for the prediction.
+ */
+ private TimeZone timeZone = TimeZone.getDefault();
+
+ /**
+ * It builds a predictor with the given scheduling pattern and start time.
+ *
+ * @param schedulingPattern
+ * The pattern on which the prediction will be based.
+ * @param start
+ * The start time of the prediction.
+ * @throws InvalidPatternException
+ * In the given scheduling pattern isn't valid.
+ */
+ public CronPredictor(String schedulingPattern, long start)
+ throws InvalidPatternException {
+ this.schedulingPattern = new SchedulingPattern(schedulingPattern);
+ this.time = (start / (1000 * 60)) * 1000 * 60;
+ }
+
+ /**
+ * It builds a predictor with the given scheduling pattern and start time.
+ *
+ * @param schedulingPattern
+ * The pattern on which the prediction will be based.
+ * @param start
+ * The start time of the prediction.
+ * @throws InvalidPatternException
+ * In the given scheduling pattern isn't valid.
+ */
+ public CronPredictor(String schedulingPattern, Date start)
+ throws InvalidPatternException {
+ this(schedulingPattern, start.getTime());
+ }
+
+ /**
+ * It builds a predictor with the given scheduling pattern and the current
+ * system time as the prediction start time.
+ *
+ * @param schedulingPattern
+ * The pattern on which the prediction will be based.
+ * @throws InvalidPatternException
+ * In the given scheduling pattern isn't valid.
+ */
+ public CronPredictor(String schedulingPattern) throws InvalidPatternException {
+ this(schedulingPattern, System.currentTimeMillis());
+ }
+
+ /**
+ * It builds a predictor with the given scheduling pattern and start time.
+ *
+ * @param schedulingPattern
+ * The pattern on which the prediction will be based.
+ * @param start
+ * The start time of the prediction.
+ * @since 2.0
+ */
+ public CronPredictor(SchedulingPattern schedulingPattern, long start) {
+ this.schedulingPattern = schedulingPattern;
+ this.time = (start / (1000 * 60)) * 1000 * 60;
+ }
+
+ /**
+ * It builds a predictor with the given scheduling pattern and start time.
+ *
+ * @param schedulingPattern
+ * The pattern on which the prediction will be based.
+ * @param start
+ * The start time of the prediction.
+ * @since 2.0
+ */
+ public CronPredictor(SchedulingPattern schedulingPattern, Date start) {
+ this(schedulingPattern, start.getTime());
+ }
+
+ /**
+ * It builds a predictor with the given scheduling pattern and the current
+ * system time as the prediction start time.
+ *
+ * @param schedulingPattern
+ * The pattern on which the prediction will be based.
+ * @since 2.0
+ */
+ public CronPredictor(SchedulingPattern schedulingPattern) {
+ this(schedulingPattern, System.currentTimeMillis());
+ }
+
+ /**
+ * Sets the time zone for predictions.
+ *
+ * @param timeZone
+ * The time zone for predictions.
+ * @since 2.2.5
+ */
+ public void setTimeZone(TimeZone timeZone) {
+ this.timeZone = timeZone;
+ }
+
+ /**
+ * It returns the next matching moment as a millis value.
+ *
+ * @return The next matching moment as a millis value.
+ */
+ public synchronized long nextMatchingTime() {
+ // Go a minute ahead.
+ time += 60000;
+ // Is it matching?
+ if (schedulingPattern.match(time)) {
+ return time;
+ }
+ // Go through the matcher groups.
+ int size = schedulingPattern.matcherSize;
+ long[] times = new long[size];
+ for (int k = 0; k < size; k++) {
+ // Ok, split the time!
+ GregorianCalendar c = new GregorianCalendar();
+ c.setTimeInMillis(time);
+ c.setTimeZone(timeZone);
+ int minute = c.get(Calendar.MINUTE);
+ int hour = c.get(Calendar.HOUR_OF_DAY);
+ int dayOfMonth = c.get(Calendar.DAY_OF_MONTH);
+ int month = c.get(Calendar.MONTH);
+ int year = c.get(Calendar.YEAR);
+ // Gets the matchers.
+ ValueMatcher minuteMatcher = (ValueMatcher) schedulingPattern.minuteMatchers.get(k);
+ ValueMatcher hourMatcher = (ValueMatcher) schedulingPattern.hourMatchers.get(k);
+ ValueMatcher dayOfMonthMatcher = (ValueMatcher) schedulingPattern.dayOfMonthMatchers.get(k);
+ ValueMatcher dayOfWeekMatcher = (ValueMatcher) schedulingPattern.dayOfWeekMatchers.get(k);
+ ValueMatcher monthMatcher = (ValueMatcher) schedulingPattern.monthMatchers.get(k);
+ for (;;) { // day of week
+ for (;;) { // month
+ for (;;) { // day of month
+ for (;;) { // hour
+ for (;;) { // minutes
+ if (minuteMatcher.match(minute)) {
+ break;
+ } else {
+ minute++;
+ if (minute > 59) {
+ minute = 0;
+ hour++;
+ }
+ }
+ }
+ if (hour > 23) {
+ hour = 0;
+ dayOfMonth++;
+ }
+ if (hourMatcher.match(hour)) {
+ break;
+ } else {
+ hour++;
+ minute = 0;
+ }
+ }
+ if (dayOfMonth > 31) {
+ dayOfMonth = 1;
+ month++;
+ }
+ if (month > Calendar.DECEMBER) {
+ month = Calendar.JANUARY;
+ year++;
+ }
+ if (dayOfMonthMatcher instanceof DayOfMonthValueMatcher) {
+ DayOfMonthValueMatcher aux = (DayOfMonthValueMatcher) dayOfMonthMatcher;
+ if (aux.match(dayOfMonth, month + 1, c.isLeapYear(year))) {
+ break;
+ } else {
+ dayOfMonth++;
+ hour = 0;
+ minute = 0;
+ }
+ } else if (dayOfMonthMatcher.match(dayOfMonth)) {
+ break;
+ } else {
+ dayOfMonth++;
+ hour = 0;
+ minute = 0;
+ }
+ }
+ if (monthMatcher.match(month + 1)) {
+ break;
+ } else {
+ month++;
+ dayOfMonth = 1;
+ hour = 0;
+ minute = 0;
+ }
+ }
+ // Is this ok?
+ c = new GregorianCalendar();
+ c.setTimeZone(timeZone);
+ c.set(Calendar.MINUTE, minute);
+ c.set(Calendar.HOUR_OF_DAY, hour);
+ c.set(Calendar.DAY_OF_MONTH, dayOfMonth);
+ c.set(Calendar.MONTH, month);
+ c.set(Calendar.YEAR, year);
+ // Day-of-month/month/year compatibility check.
+ int oldDayOfMonth = dayOfMonth;
+ int oldMonth = month;
+ int oldYear = year;
+ dayOfMonth = c.get(Calendar.DAY_OF_MONTH);
+ month = c.get(Calendar.MONTH);
+ year = c.get(Calendar.YEAR);
+ if (month != oldMonth || dayOfMonth != oldDayOfMonth
+ || year != oldYear) {
+ // Take another spin!
+ continue;
+ }
+ // Day of week.
+ int dayOfWeek = c.get(Calendar.DAY_OF_WEEK);
+ if (dayOfWeekMatcher.match(dayOfWeek - 1)) {
+ break;
+ } else {
+ dayOfMonth++;
+ hour = 0;
+ minute = 0;
+ if (dayOfMonth > 31) {
+ dayOfMonth = 1;
+ month++;
+ if (month > Calendar.DECEMBER) {
+ month = Calendar.JANUARY;
+ year++;
+ }
+ }
+ }
+ }
+ // Seems it matches!
+ times[k] = (c.getTimeInMillis() / (1000 * 60)) * 1000 * 60;
+ }
+ // Which one?
+ long min = Long.MAX_VALUE;
+ for (int k = 0; k < size; k++) {
+ if (times[k] < min) {
+ min = times[k];
+ }
+ }
+ // Updates the object current time value.
+ time = min;
+ // Here it is.
+ return time;
+ }
+
+ /**
+ * It returns the next matching moment as a {@link Date} object.
+ *
+ * @return The next matching moment as a {@link Date} object.
+ */
+ public synchronized Date nextMatchingDate() {
+ return new Date(nextMatchingTime());
+ }
+
+ public static void main(String[] args) {
+ Date currentDate = new Date();
+ CronPredictor cron = new CronPredictor(currentDate.getMinutes() + " * * * *", currentDate);
+ System.out.println(currentDate + "->" + cron.nextMatchingDate());
+
+ Calendar cal = Calendar.getInstance();
+ /*cal.set(2014, 6, 15, 8, 8);
+ cron = new CronPredictor("* * * 6 *", cal.getTime());
+ System.out.println(cal.getTime() + "->" + cron.nextMatchingDate());
+
+ cal.set(2014, 6, 15, 8, 8);
+ cron = new CronPredictor("* * * 7 *", cal.getTime());
+ System.out.println(cal.getTime() + "->" + cron.nextMatchingDate());
+
+ cal.set(2014, 6, 15, 8, 8);
+ cron = new CronPredictor("* * * 8 *", cal.getTime());
+ System.out.println(cal.getTime() + "->" + cron.nextMatchingDate());*/
+
+ cal.set(2014, 6, 15, 8, 8);
+ cron = new CronPredictor("* 1 * * *", cal.getTime());
+ System.out.println(cal.getTime() + "->" + cron.nextMatchingDate());
+
+ }
+}
diff --git a/host/src/main/java/org/area515/resinprinter/util/cron/DayOfMonthValueMatcher.java b/host/src/main/java/org/area515/resinprinter/util/cron/DayOfMonthValueMatcher.java
new file mode 100644
index 000000000..e040e0ed4
--- /dev/null
+++ b/host/src/main/java/org/area515/resinprinter/util/cron/DayOfMonthValueMatcher.java
@@ -0,0 +1,65 @@
+/*
+ * cron4j - A pure Java cron-like scheduler
+ *
+ * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version
+ * 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License 2.1 for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License version 2.1 along with this program.
+ * If not, see .
+ */
+package org.area515.resinprinter.util.cron;
+
+import java.util.ArrayList;
+
+/**
+ *
+ * A ValueMatcher whose rules are in a plain array of integer values. When asked
+ * to validate a value, this ValueMatcher checks if it is in the array and, if
+ * not, checks whether the last-day-of-month setting applies.
+ *
+ *
+ * @author Paul Fernley
+ */
+class DayOfMonthValueMatcher extends IntArrayValueMatcher {
+
+ private static final int[] lastDays = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+
+ /**
+ * Builds the ValueMatcher.
+ *
+ * @param integers
+ * An ArrayList of Integer elements, one for every value accepted
+ * by the matcher. The match() method will return true only if
+ * its parameter will be one of this list or the
+ * last-day-of-month setting applies.
+ */
+ public DayOfMonthValueMatcher(ArrayList integers) {
+ super(integers);
+ }
+
+ /**
+ * Returns true if the given value is included in the matcher list or the
+ * last-day-of-month setting applies.
+ */
+ public boolean match(int value, int month, boolean isLeapYear) {
+ return (super.match(value) || (value > 27 && match(32) && isLastDayOfMonth(value, month, isLeapYear)));
+ }
+
+ public boolean isLastDayOfMonth(int value, int month, boolean isLeapYear) {
+ if (isLeapYear && month == 2) {
+ return value == 29;
+ } else {
+ return value == lastDays[month - 1];
+ }
+ }
+
+}
diff --git a/host/src/main/java/org/area515/resinprinter/util/cron/IntArrayValueMatcher.java b/host/src/main/java/org/area515/resinprinter/util/cron/IntArrayValueMatcher.java
new file mode 100644
index 000000000..f9201a985
--- /dev/null
+++ b/host/src/main/java/org/area515/resinprinter/util/cron/IntArrayValueMatcher.java
@@ -0,0 +1,70 @@
+/*
+ * cron4j - A pure Java cron-like scheduler
+ *
+ * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version
+ * 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License 2.1 for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License version 2.1 along with this program.
+ * If not, see .
+ */
+package org.area515.resinprinter.util.cron;
+
+import java.util.ArrayList;
+
+/**
+ *
+ * A ValueMatcher whose rules are in a plain array of integer values. When asked
+ * to validate a value, this ValueMatcher checks if it is in the array.
+ *
+ *
+ * @author Carlo Pelliccia
+ */
+class IntArrayValueMatcher implements ValueMatcher {
+
+ /**
+ * The accepted values.
+ */
+ private int[] values;
+
+ /**
+ * Builds the ValueMatcher.
+ *
+ * @param integers
+ * An ArrayList of Integer elements, one for every value accepted
+ * by the matcher. The match() method will return true only if
+ * its parameter will be one of this list.
+ */
+ public IntArrayValueMatcher(ArrayList integers) {
+ int size = integers.size();
+ values = new int[size];
+ for (int i = 0; i < size; i++) {
+ try {
+ values[i] = ((Integer) integers.get(i)).intValue();
+ } catch (Exception e) {
+ throw new IllegalArgumentException(e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * Returns true if the given value is included in the matcher list.
+ */
+ public boolean match(int value) {
+ for (int i = 0; i < values.length; i++) {
+ if (values[i] == value) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/host/src/main/java/org/area515/resinprinter/util/cron/InvalidPatternException.java b/host/src/main/java/org/area515/resinprinter/util/cron/InvalidPatternException.java
new file mode 100644
index 000000000..a3ac4e46b
--- /dev/null
+++ b/host/src/main/java/org/area515/resinprinter/util/cron/InvalidPatternException.java
@@ -0,0 +1,46 @@
+/*
+ * cron4j - A pure Java cron-like scheduler
+ *
+ * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version
+ * 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License 2.1 for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License version 2.1 along with this program.
+ * If not, see .
+ */
+package org.area515.resinprinter.util.cron;
+
+/**
+ *
+ * This kind of exception is thrown if an invalid scheduling pattern is
+ * encountered by the scheduler.
+ *
+ *
+ * @author Carlo Pelliccia
+ */
+public class InvalidPatternException extends RuntimeException {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Package-reserved construction.
+ */
+ InvalidPatternException() {
+ }
+
+ /**
+ * Package-reserved construction.
+ */
+ InvalidPatternException(String message) {
+ super(message);
+ }
+
+}
diff --git a/host/src/main/java/org/area515/resinprinter/util/cron/SchedulingPattern.java b/host/src/main/java/org/area515/resinprinter/util/cron/SchedulingPattern.java
new file mode 100644
index 000000000..015bbcb41
--- /dev/null
+++ b/host/src/main/java/org/area515/resinprinter/util/cron/SchedulingPattern.java
@@ -0,0 +1,737 @@
+/*
+ * cron4j - A pure Java cron-like scheduler
+ *
+ * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version
+ * 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License 2.1 for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License version 2.1 along with this program.
+ * If not, see .
+ */
+package org.area515.resinprinter.util.cron;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.Iterator;
+import java.util.StringTokenizer;
+import java.util.TimeZone;
+
+/**
+ *
+ * A UNIX crontab-like pattern is a string split in five space separated parts.
+ * Each part is intented as:
+ *
+ *
+ * - Minutes sub-pattern. During which minutes of the hour
+ * should the task been launched? The values range is from 0 to 59.
+ * - Hours sub-pattern. During which hours of the day should
+ * the task been launched? The values range is from 0 to 23.
+ * - Days of month sub-pattern. During which days of the
+ * month should the task been launched? The values range is from 1 to 31. The
+ * special value L can be used to recognize the last day of month.
+ * - Months sub-pattern. During which months of the year
+ * should the task been launched? The values range is from 1 (January) to 12
+ * (December), otherwise this sub-pattern allows the aliases "jan",
+ * "feb", "mar", "apr", "may",
+ * "jun", "jul", "aug", "sep",
+ * "oct", "nov" and "dec".
+ * - Days of week sub-pattern. During which days of the week
+ * should the task been launched? The values range is from 0 (Sunday) to 6
+ * (Saturday), otherwise this sub-pattern allows the aliases "sun",
+ * "mon", "tue", "wed", "thu",
+ * "fri" and "sat".
+ *
+ *
+ * The star wildcard character is also admitted, indicating "every minute
+ * of the hour", "every hour of the day", "every day of the
+ * month", "every month of the year" and "every day of the
+ * week", according to the sub-pattern in which it is used.
+ *
+ *
+ * Once the scheduler is started, a task will be launched when the five parts in
+ * its scheduling pattern will be true at the same time.
+ *
+ *
+ * Some examples:
+ *
+ *
+ * 5 * * * *
+ * This pattern causes a task to be launched once every hour, at the begin of
+ * the fifth minute (00:05, 01:05, 02:05 etc.).
+ *
+ *
+ * * * * * *
+ * This pattern causes a task to be launched every minute.
+ *
+ *
+ * * 12 * * Mon
+ * This pattern causes a task to be launched every minute during the 12th hour
+ * of Monday.
+ *
+ *
+ * * 12 16 * Mon
+ * This pattern causes a task to be launched every minute during the 12th hour
+ * of Monday, 16th, but only if the day is the 16th of the month.
+ *
+ *
+ * Every sub-pattern can contain two or more comma separated values.
+ *
+ *
+ * 59 11 * * 1,2,3,4,5
+ * This pattern causes a task to be launched at 11:59AM on Monday, Tuesday,
+ * Wednesday, Thursday and Friday.
+ *
+ *
+ * Values intervals are admitted and defined using the minus character.
+ *
+ *
+ * 59 11 * * 1-5
+ * This pattern is equivalent to the previous one.
+ *
+ *
+ * The slash character can be used to identify step values within a range. It
+ * can be used both in the form */c and a-b/c. The
+ * subpattern is matched every c values of the range
+ * 0,maxvalue or a-b.
+ *
+ *
+ * */5 * * * *
+ * This pattern causes a task to be launched every 5 minutes (0:00, 0:05, 0:10,
+ * 0:15 and so on).
+ *
+ *
+ * 3-18/5 * * * *
+ * This pattern causes a task to be launched every 5 minutes starting from the
+ * third minute of the hour, up to the 18th (0:03, 0:08, 0:13, 0:18, 1:03, 1:08
+ * and so on).
+ *
+ *
+ * */15 9-17 * * *
+ * This pattern causes a task to be launched every 15 minutes between the 9th
+ * and 17th hour of the day (9:00, 9:15, 9:30, 9:45 and so on... note that the
+ * last execution will be at 17:45).
+ *
+ *
+ * All the fresh described syntax rules can be used together.
+ *
+ *
+ * * 12 10-16/2 * *
+ * This pattern causes a task to be launched every minute during the 12th hour
+ * of the day, but only if the day is the 10th, the 12th, the 14th or the 16th
+ * of the month.
+ *
+ *
+ * * 12 1-15,17,20-25 * *
+ * This pattern causes a task to be launched every minute during the 12th hour
+ * of the day, but the day of the month must be between the 1st and the 15th,
+ * the 20th and the 25, or at least it must be the 17th.
+ *
+ *
+ * Finally cron4j lets you combine more scheduling patterns into one, with the
+ * pipe character:
+ *
+ *
+ * 0 5 * * *|8 10 * * *|22 17 * * *
+ * This pattern causes a task to be launched every day at 05:00, 10:08 and
+ * 17:22.
+ *
+ *
+ * @author Carlo Pelliccia
+ * @since 2.0
+ */
+public class SchedulingPattern {
+
+ /**
+ * The parser for the minute values.
+ */
+ private static final ValueParser MINUTE_VALUE_PARSER = new MinuteValueParser();
+
+ /**
+ * The parser for the hour values.
+ */
+ private static final ValueParser HOUR_VALUE_PARSER = new HourValueParser();
+
+ /**
+ * The parser for the day of month values.
+ */
+ private static final ValueParser DAY_OF_MONTH_VALUE_PARSER = new DayOfMonthValueParser();
+
+ /**
+ * The parser for the month values.
+ */
+ private static final ValueParser MONTH_VALUE_PARSER = new MonthValueParser();
+
+ /**
+ * The parser for the day of week values.
+ */
+ private static final ValueParser DAY_OF_WEEK_VALUE_PARSER = new DayOfWeekValueParser();
+
+ /**
+ * Validates a string as a scheduling pattern.
+ *
+ * @param schedulingPattern
+ * The pattern to validate.
+ * @return true if the given string represents a valid scheduling pattern;
+ * false otherwise.
+ */
+ public static boolean validate(String schedulingPattern) {
+ try {
+ new SchedulingPattern(schedulingPattern);
+ } catch (InvalidPatternException e) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * The pattern as a string.
+ */
+ private String asString;
+
+ /**
+ * The ValueMatcher list for the "minute" field.
+ */
+ protected ArrayList minuteMatchers = new ArrayList();
+
+ /**
+ * The ValueMatcher list for the "hour" field.
+ */
+ protected ArrayList hourMatchers = new ArrayList();
+
+ /**
+ * The ValueMatcher list for the "day of month" field.
+ */
+ protected ArrayList dayOfMonthMatchers = new ArrayList();
+
+ /**
+ * The ValueMatcher list for the "month" field.
+ */
+ protected ArrayList monthMatchers = new ArrayList();
+
+ /**
+ * The ValueMatcher list for the "day of week" field.
+ */
+ protected ArrayList dayOfWeekMatchers = new ArrayList();
+
+ /**
+ * How many matcher groups in this pattern?
+ */
+ protected int matcherSize = 0;
+
+ /**
+ * Builds a SchedulingPattern parsing it from a string.
+ *
+ * @param pattern
+ * The pattern as a crontab-like string.
+ * @throws InvalidPatternException
+ * If the supplied string is not a valid pattern.
+ */
+ public SchedulingPattern(String pattern) throws InvalidPatternException {
+ this.asString = pattern;
+ StringTokenizer st1 = new StringTokenizer(pattern, "|");
+ if (st1.countTokens() < 1) {
+ throw new InvalidPatternException("invalid pattern: \"" + pattern + "\"");
+ }
+ while (st1.hasMoreTokens()) {
+ String localPattern = st1.nextToken();
+ StringTokenizer st2 = new StringTokenizer(localPattern, " \t");
+ if (st2.countTokens() != 5) {
+ throw new InvalidPatternException("invalid pattern: \"" + localPattern + "\"");
+ }
+ try {
+ minuteMatchers.add(buildValueMatcher(st2.nextToken(), MINUTE_VALUE_PARSER));
+ } catch (Exception e) {
+ throw new InvalidPatternException("invalid pattern \""
+ + localPattern + "\". Error parsing minutes field: "
+ + e.getMessage() + ".");
+ }
+ try {
+ hourMatchers.add(buildValueMatcher(st2.nextToken(), HOUR_VALUE_PARSER));
+ } catch (Exception e) {
+ throw new InvalidPatternException("invalid pattern \""
+ + localPattern + "\". Error parsing hours field: "
+ + e.getMessage() + ".");
+ }
+ try {
+ dayOfMonthMatchers.add(buildValueMatcher(st2.nextToken(), DAY_OF_MONTH_VALUE_PARSER));
+ } catch (Exception e) {
+ throw new InvalidPatternException("invalid pattern \""
+ + localPattern
+ + "\". Error parsing days of month field: "
+ + e.getMessage() + ".");
+ }
+ try {
+ monthMatchers.add(buildValueMatcher(st2.nextToken(), MONTH_VALUE_PARSER));
+ } catch (Exception e) {
+ throw new InvalidPatternException("invalid pattern \""
+ + localPattern + "\". Error parsing months field: "
+ + e.getMessage() + ".");
+ }
+ try {
+ dayOfWeekMatchers.add(buildValueMatcher(st2.nextToken(), DAY_OF_WEEK_VALUE_PARSER));
+ } catch (Exception e) {
+ throw new InvalidPatternException("invalid pattern \""
+ + localPattern
+ + "\". Error parsing days of week field: "
+ + e.getMessage() + ".");
+ }
+ matcherSize++;
+ }
+ }
+
+ /**
+ * A ValueMatcher utility builder.
+ *
+ * @param str
+ * The pattern part for the ValueMatcher creation.
+ * @param parser
+ * The parser used to parse the values.
+ * @return The requested ValueMatcher.
+ * @throws Exception
+ * If the supplied pattern part is not valid.
+ */
+ private ValueMatcher buildValueMatcher(String str, ValueParser parser)
+ throws Exception {
+ if (str.length() == 1 && str.equals("*")) {
+ return new AlwaysTrueValueMatcher();
+ }
+ ArrayList values = new ArrayList();
+ StringTokenizer st = new StringTokenizer(str, ",");
+ while (st.hasMoreTokens()) {
+ String element = st.nextToken();
+ ArrayList local;
+ try {
+ local = parseListElement(element, parser);
+ } catch (Exception e) {
+ throw new Exception("invalid field \"" + str
+ + "\", invalid element \"" + element + "\", "
+ + e.getMessage());
+ }
+ for (Iterator i = local.iterator(); i.hasNext();) {
+ Object value = i.next();
+ if (!values.contains(value)) {
+ values.add(value);
+ }
+ }
+ }
+ if (values.size() == 0) {
+ throw new Exception("invalid field \"" + str + "\"");
+ }
+ if (parser == DAY_OF_MONTH_VALUE_PARSER) {
+ return new DayOfMonthValueMatcher(values);
+ } else {
+ return new IntArrayValueMatcher(values);
+ }
+ }
+
+ /**
+ * Parses an element of a list of values of the pattern.
+ *
+ * @param str
+ * The element string.
+ * @param parser
+ * The parser used to parse the values.
+ * @return A list of integers representing the allowed values.
+ * @throws Exception
+ * If the supplied pattern part is not valid.
+ */
+ private ArrayList parseListElement(String str, ValueParser parser)
+ throws Exception {
+ StringTokenizer st = new StringTokenizer(str, "/");
+ int size = st.countTokens();
+ if (size < 1 || size > 2) {
+ throw new Exception("syntax error");
+ }
+ ArrayList values;
+ try {
+ values = parseRange(st.nextToken(), parser);
+ } catch (Exception e) {
+ throw new Exception("invalid range, " + e.getMessage());
+ }
+ if (size == 2) {
+ String dStr = st.nextToken();
+ int div;
+ try {
+ div = Integer.parseInt(dStr);
+ } catch (NumberFormatException e) {
+ throw new Exception("invalid divisor \"" + dStr + "\"");
+ }
+ if (div < 1) {
+ throw new Exception("non positive divisor \"" + div + "\"");
+ }
+ ArrayList values2 = new ArrayList();
+ for (int i = 0; i < values.size(); i += div) {
+ values2.add(values.get(i));
+ }
+ return values2;
+ } else {
+ return values;
+ }
+ }
+
+ /**
+ * Parses a range of values.
+ *
+ * @param str
+ * The range string.
+ * @param parser
+ * The parser used to parse the values.
+ * @return A list of integers representing the allowed values.
+ * @throws Exception
+ * If the supplied pattern part is not valid.
+ */
+ private ArrayList parseRange(String str, ValueParser parser)
+ throws Exception {
+ if (str.equals("*")) {
+ int min = parser.getMinValue();
+ int max = parser.getMaxValue();
+ ArrayList values = new ArrayList();
+ for (int i = min; i <= max; i++) {
+ values.add(new Integer(i));
+ }
+ return values;
+ }
+ StringTokenizer st = new StringTokenizer(str, "-");
+ int size = st.countTokens();
+ if (size < 1 || size > 2) {
+ throw new Exception("syntax error");
+ }
+ String v1Str = st.nextToken();
+ int v1;
+ try {
+ v1 = parser.parse(v1Str);
+ } catch (Exception e) {
+ throw new Exception("invalid value \"" + v1Str + "\", "
+ + e.getMessage());
+ }
+ if (size == 1) {
+ ArrayList values = new ArrayList();
+ values.add(new Integer(v1));
+ return values;
+ } else {
+ String v2Str = st.nextToken();
+ int v2;
+ try {
+ v2 = parser.parse(v2Str);
+ } catch (Exception e) {
+ throw new Exception("invalid value \"" + v2Str + "\", "
+ + e.getMessage());
+ }
+ ArrayList values = new ArrayList();
+ if (v1 < v2) {
+ for (int i = v1; i <= v2; i++) {
+ values.add(new Integer(i));
+ }
+ } else if (v1 > v2) {
+ int min = parser.getMinValue();
+ int max = parser.getMaxValue();
+ for (int i = v1; i <= max; i++) {
+ values.add(new Integer(i));
+ }
+ for (int i = min; i <= v2; i++) {
+ values.add(new Integer(i));
+ }
+ } else {
+ // v1 == v2
+ values.add(new Integer(v1));
+ }
+ return values;
+ }
+ }
+
+ /**
+ * This methods returns true if the given timestamp (expressed as a UNIX-era
+ * millis value) matches the pattern, according to the given time zone.
+ *
+ * @param timezone
+ * A time zone.
+ * @param millis
+ * The timestamp, as a UNIX-era millis value.
+ * @return true if the given timestamp matches the pattern.
+ */
+ public boolean match(TimeZone timezone, long millis) {
+ GregorianCalendar gc = new GregorianCalendar();
+ gc.setTimeInMillis(millis);
+ gc.setTimeZone(timezone);
+ int minute = gc.get(Calendar.MINUTE);
+ int hour = gc.get(Calendar.HOUR_OF_DAY);
+ int dayOfMonth = gc.get(Calendar.DAY_OF_MONTH);
+ int month = gc.get(Calendar.MONTH) + 1;
+ int dayOfWeek = gc.get(Calendar.DAY_OF_WEEK) - 1;
+ int year = gc.get(Calendar.YEAR);
+ for (int i = 0; i < matcherSize; i++) {
+ ValueMatcher minuteMatcher = (ValueMatcher) minuteMatchers.get(i);
+ ValueMatcher hourMatcher = (ValueMatcher) hourMatchers.get(i);
+ ValueMatcher dayOfMonthMatcher = (ValueMatcher) dayOfMonthMatchers.get(i);
+ ValueMatcher monthMatcher = (ValueMatcher) monthMatchers.get(i);
+ ValueMatcher dayOfWeekMatcher = (ValueMatcher) dayOfWeekMatchers.get(i);
+ boolean eval = minuteMatcher.match(minute)
+ && hourMatcher.match(hour)
+ && ((dayOfMonthMatcher instanceof DayOfMonthValueMatcher) ? ((DayOfMonthValueMatcher) dayOfMonthMatcher)
+ .match(dayOfMonth, month, gc.isLeapYear(year))
+ : dayOfMonthMatcher.match(dayOfMonth))
+ && monthMatcher.match(month)
+ && dayOfWeekMatcher.match(dayOfWeek);
+ if (eval) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * This methods returns true if the given timestamp (expressed as a UNIX-era
+ * millis value) matches the pattern, according to the system default time
+ * zone.
+ *
+ * @param millis
+ * The timestamp, as a UNIX-era millis value.
+ * @return true if the given timestamp matches the pattern.
+ */
+ public boolean match(long millis) {
+ return match(TimeZone.getDefault(), millis);
+ }
+
+ /**
+ * Returns the pattern as a string.
+ *
+ * @return The pattern as a string.
+ */
+ public String toString() {
+ return asString;
+ }
+
+ /**
+ * This utility method changes an alias to an int value.
+ *
+ * @param value
+ * The value.
+ * @param aliases
+ * The aliases list.
+ * @param offset
+ * The offset appplied to the aliases list indices.
+ * @return The parsed value.
+ * @throws Exception
+ * If the expressed values doesn't match any alias.
+ */
+ private static int parseAlias(String value, String[] aliases, int offset)
+ throws Exception {
+ for (int i = 0; i < aliases.length; i++) {
+ if (aliases[i].equalsIgnoreCase(value)) {
+ return offset + i;
+ }
+ }
+ throw new Exception("invalid alias \"" + value + "\"");
+ }
+
+ /**
+ * Definition for a value parser.
+ */
+ private static interface ValueParser {
+
+ /**
+ * Attempts to parse a value.
+ *
+ * @param value
+ * The value.
+ * @return The parsed value.
+ * @throws Exception
+ * If the value can't be parsed.
+ */
+ public int parse(String value) throws Exception;
+
+ /**
+ * Returns the minimum value accepred by the parser.
+ *
+ * @return The minimum value accepred by the parser.
+ */
+ public int getMinValue();
+
+ /**
+ * Returns the maximum value accepred by the parser.
+ *
+ * @return The maximum value accepred by the parser.
+ */
+ public int getMaxValue();
+
+ }
+
+ /**
+ * A simple value parser.
+ */
+ private static class SimpleValueParser implements ValueParser {
+
+ /**
+ * The minimum allowed value.
+ */
+ protected int minValue;
+
+ /**
+ * The maximum allowed value.
+ */
+ protected int maxValue;
+
+ /**
+ * Builds the value parser.
+ *
+ * @param minValue
+ * The minimum allowed value.
+ * @param maxValue
+ * The maximum allowed value.
+ */
+ public SimpleValueParser(int minValue, int maxValue) {
+ this.minValue = minValue;
+ this.maxValue = maxValue;
+ }
+
+ public int parse(String value) throws Exception {
+ int i;
+ try {
+ i = Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ throw new Exception("invalid integer value");
+ }
+ if (i < minValue || i > maxValue) {
+ throw new Exception("value out of range");
+ }
+ return i;
+ }
+
+ public int getMinValue() {
+ return minValue;
+ }
+
+ public int getMaxValue() {
+ return maxValue;
+ }
+
+ }
+
+ /**
+ * The minutes value parser.
+ */
+ private static class MinuteValueParser extends SimpleValueParser {
+
+ /**
+ * Builds the value parser.
+ */
+ public MinuteValueParser() {
+ super(0, 59);
+ }
+
+ }
+
+ /**
+ * The hours value parser.
+ */
+ private static class HourValueParser extends SimpleValueParser {
+
+ /**
+ * Builds the value parser.
+ */
+ public HourValueParser() {
+ super(0, 23);
+ }
+
+ }
+
+ /**
+ * The days of month value parser.
+ */
+ private static class DayOfMonthValueParser extends SimpleValueParser {
+
+ /**
+ * Builds the value parser.
+ */
+ public DayOfMonthValueParser() {
+ super(1, 31);
+ }
+
+ /**
+ * Added to support last-day-of-month.
+ *
+ * @param value
+ * The value to be parsed
+ * @return the integer day of the month or 32 for last day of the month
+ * @throws Exception
+ * if the input value is invalid
+ */
+ public int parse(String value) throws Exception {
+ if (value.equalsIgnoreCase("L")) {
+ return 32;
+ } else {
+ return super.parse(value);
+ }
+ }
+
+ }
+
+ /**
+ * The value parser for the months field.
+ */
+ private static class MonthValueParser extends SimpleValueParser {
+
+ /**
+ * Months aliases.
+ */
+ private static String[] ALIASES = { "jan", "feb", "mar", "apr", "may",
+ "jun", "jul", "aug", "sep", "oct", "nov", "dec" };
+
+ /**
+ * Builds the months value parser.
+ */
+ public MonthValueParser() {
+ super(1, 12);
+ }
+
+ public int parse(String value) throws Exception {
+ try {
+ // try as a simple value
+ return super.parse(value);
+ } catch (Exception e) {
+ // try as an alias
+ return parseAlias(value, ALIASES, 1);
+ }
+ }
+
+ }
+
+ /**
+ * The value parser for the months field.
+ */
+ private static class DayOfWeekValueParser extends SimpleValueParser {
+
+ /**
+ * Days of week aliases.
+ */
+ private static String[] ALIASES = { "sun", "mon", "tue", "wed", "thu", "fri", "sat" };
+
+ /**
+ * Builds the months value parser.
+ */
+ public DayOfWeekValueParser() {
+ super(0, 7);
+ }
+
+ public int parse(String value) throws Exception {
+ try {
+ // try as a simple value
+ return super.parse(value) % 7;
+ } catch (Exception e) {
+ // try as an alias
+ return parseAlias(value, ALIASES, 0);
+ }
+ }
+
+ }
+
+}
diff --git a/host/src/main/java/org/area515/resinprinter/util/cron/ValueMatcher.java b/host/src/main/java/org/area515/resinprinter/util/cron/ValueMatcher.java
new file mode 100644
index 000000000..ee6f7fc64
--- /dev/null
+++ b/host/src/main/java/org/area515/resinprinter/util/cron/ValueMatcher.java
@@ -0,0 +1,41 @@
+/*
+ * cron4j - A pure Java cron-like scheduler
+ *
+ * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version
+ * 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License 2.1 for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License version 2.1 along with this program.
+ * If not, see .
+ */
+package org.area515.resinprinter.util.cron;
+
+/**
+ *
+ * This interface describes the ValueMatcher behavior. A ValueMatcher is an
+ * object that validate an integer value against a set of rules.
+ *
+ *
+ * @author Carlo Pelliccia
+ */
+interface ValueMatcher {
+
+ /**
+ * Validate the given integer value against a set of rules.
+ *
+ * @param value
+ * The value.
+ * @return true if the given value matches the rules of the ValueMatcher,
+ * false otherwise.
+ */
+ public boolean match(int value);
+
+}
diff --git a/host/src/main/java/org/area515/util/IOUtilities.java b/host/src/main/java/org/area515/util/IOUtilities.java
index 79d8ee56d..fac6ebf84 100644
--- a/host/src/main/java/org/area515/util/IOUtilities.java
+++ b/host/src/main/java/org/area515/util/IOUtilities.java
@@ -37,6 +37,7 @@ public static enum SearchStyle {
public static class ParseState {
public int parseLocation;
public String currentLine;
+ public boolean timeout;
}
public static class ParseAction {
@@ -247,6 +248,7 @@ public static ParseState readLine(Printer printer, SerialCommunicationsPort seri
ParseState state = new ParseState();
state.currentLine = null;
state.parseLocation = parseLocation;
+ state.timeout = true;
return state;
}
diff --git a/host/src/main/java/org/area515/util/Log4jTimer.java b/host/src/main/java/org/area515/util/Log4jTimer.java
index 0e7dc8fba..ba5530d8d 100644
--- a/host/src/main/java/org/area515/util/Log4jTimer.java
+++ b/host/src/main/java/org/area515/util/Log4jTimer.java
@@ -1,20 +1,48 @@
package org.area515.util;
+import java.util.HashMap;
+import java.util.Map;
+
import org.apache.logging.log4j.ThreadContext;
public class Log4jTimer {
+ public static Map GLOBAL = new HashMap<>();
+
public static long startTimer(String timerName) {
long newTime = System.currentTimeMillis();
ThreadContext.put(timerName, newTime + "");
return newTime;
}
+ public static long startGlobalTimer(String timerName) {
+ long newTime = System.currentTimeMillis();
+ GLOBAL.put(timerName, newTime + "");
+ return newTime;
+ }
+
public static long splitTimer(String timerName) {
long newTime = System.currentTimeMillis();
- long timeTaken = newTime - Long.parseLong(ThreadContext.get(timerName));
+ String value = ThreadContext.get(timerName);
+ if (value == null) {
+ return startTimer(timerName);
+ }
+
+ long timeTaken = newTime - Long.parseLong(value);
ThreadContext.put(timerName, newTime + "");
return timeTaken;
}
+
+ public static long splitGlobalTimer(String timerName) {
+ long newTime = System.currentTimeMillis();
+ String value = GLOBAL.get(timerName);
+ if (value == null) {
+ return startGlobalTimer(timerName);
+ }
+
+ long timeTaken = newTime - Long.parseLong(value);
+ GLOBAL.put(timerName, newTime + "");
+ return timeTaken;
+ }
public static long completeTimer(String timerName) {
long newTime = System.currentTimeMillis();
@@ -27,4 +55,16 @@ public static long completeTimer(String timerName) {
ThreadContext.remove(timerName);
return timeTaken;
}
+
+ public static long completeGlobalTimer(String timerName) {
+ long newTime = System.currentTimeMillis();
+ String value = GLOBAL.get(timerName);
+ if (value == null) {
+ return -1;
+ }
+
+ long timeTaken = newTime - Long.parseLong(value);
+ GLOBAL.remove(timerName);
+ return timeTaken;
+ }
}
diff --git a/host/src/main/java/org/area515/util/TemplateEngine.java b/host/src/main/java/org/area515/util/TemplateEngine.java
index 07007ea2e..57957e2fa 100644
--- a/host/src/main/java/org/area515/util/TemplateEngine.java
+++ b/host/src/main/java/org/area515/util/TemplateEngine.java
@@ -18,11 +18,8 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
-import org.area515.resinprinter.job.AbstractPrintFileProcessor.DataAid;
-import org.area515.resinprinter.job.JobManagerException;
import org.area515.resinprinter.job.PrintJob;
import org.area515.resinprinter.printer.Printer;
-import org.area515.resinprinter.printer.SlicingProfile.TwoDimensionalSettings;
import org.area515.resinprinter.server.HostProperties;
import freemarker.cache.StringTemplateLoader;
diff --git a/host/src/main/resources/PhotonicSplash.png b/host/src/main/resources/PhotonicSplash.png
new file mode 100644
index 000000000..0754eee46
Binary files /dev/null and b/host/src/main/resources/PhotonicSplash.png differ
diff --git a/host/src/test/java/org/area515/resinprinter/display/DispManXDirectMemory.java b/host/src/test/java/org/area515/resinprinter/display/DispManXDirectMemory.java
new file mode 100644
index 000000000..f62003ea1
--- /dev/null
+++ b/host/src/test/java/org/area515/resinprinter/display/DispManXDirectMemory.java
@@ -0,0 +1,108 @@
+package org.area515.resinprinter.display;
+
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.io.IOException;
+
+import org.area515.resinprinter.display.dispmanx.ALPHA;
+import org.area515.resinprinter.display.dispmanx.DispManX;
+import org.area515.resinprinter.display.dispmanx.NativeMemoryBackedBufferedImage;
+import org.area515.resinprinter.display.dispmanx.PROTECTION;
+import org.area515.resinprinter.display.dispmanx.VC_DISPMANX_ALPHA_T;
+import org.area515.resinprinter.display.dispmanx.VC_IMAGE_TRANSFORM_T;
+import org.area515.resinprinter.display.dispmanx.VC_IMAGE_TYPE_T;
+import org.area515.resinprinter.display.dispmanx.VC_RECT_T;
+
+import com.sun.jna.ptr.IntByReference;
+
+public class DispManXDirectMemory {
+ public static void res( String str, int val ) {
+ if ( val != 0 ) {
+ System.out.printf( "%s: %08x\n", str, val );
+ } else {
+ System.out.println(str + " is 0");
+ }
+ }
+
+ //sudo vi DispManXDirectMemory.java
+ //sudo javac -cp lib/*:. DispManXDirectMemory.java
+ //sudo java -cp lib/*:. DispManXDirectMemory 2 10
+ public static void main(String[] args) throws IOException, InterruptedException {
+ DispManX dispMan = DispManX.INSTANCE;
+ System.out.println("BCM Initialized:" + dispMan.bcm_host_init());
+
+ int screen = Integer.parseInt( args[0] );
+ int time = Integer.parseInt( args[1] );
+ //IntByReference pitch = new IntByReference();
+ IntByReference width = new IntByReference();
+ IntByReference height = new IntByReference();
+ res( "get display size", dispMan.graphics_get_display_size( screen, width, height ) );
+ System.out.printf( "display %d: %d x %d\n", screen, width.getValue(), height.getValue() );
+ int display = dispMan.vc_dispmanx_display_open( screen );
+
+ NativeMemoryBackedBufferedImage data = NativeMemoryBackedBufferedImage.newInstance(width.getValue(), height.getValue());
+ int pitch = data.getPitch();
+ Graphics2D g = (Graphics2D)data.createGraphics();
+ g.drawOval(0, 0, width.getValue(), height.getValue());
+ g.setColor(Color.green);
+ g.setFont(g.getFont().deriveFont((float)30));
+ System.out.println(g.getFont());
+ g.drawString("Hello this is a test", width.getValue() / 2, 50);
+ System.out.printf( "bitmap: %d x %d pitch->%d\n", width.getValue(), height.getValue(), pitch);
+
+ VC_RECT_T.ByReference copyRect = new VC_RECT_T.ByReference();
+ VC_RECT_T.ByReference sourceRect = new VC_RECT_T.ByReference();
+ VC_RECT_T.ByReference destinationRect = new VC_RECT_T.ByReference();
+
+ res( "rect set", dispMan.vc_dispmanx_rect_set( copyRect, 0, 0, width.getValue(), height.getValue() ) );
+ //This seems to be some form of a zoom factor
+ res( "rect set", dispMan.vc_dispmanx_rect_set( sourceRect, 0, 0, width.getValue()<<16, height.getValue()<<16 ) );
+ res( "rect set", dispMan.vc_dispmanx_rect_set( destinationRect, 0, 0, width.getValue(), height.getValue() ) );
+
+ IntByReference ref = new IntByReference();
+ int resourceHandle = dispMan.vc_dispmanx_resource_create(
+ VC_IMAGE_TYPE_T.VC_IMAGE_ARGB8888.getcIndex(),
+ width.getValue(),
+ height.getValue(),
+ ref );
+ res( "resource write data", dispMan.vc_dispmanx_resource_write_data(
+ resourceHandle,
+ VC_IMAGE_TYPE_T.VC_IMAGE_ARGB8888.getcIndex(),
+ pitch,
+ data.getMemory(),
+ destinationRect )
+ );
+
+ System.out.println("copyRect:" + copyRect.width + ", " + copyRect.height);
+ System.out.println("sourceRect:" + sourceRect.width + ", " + sourceRect.height);
+ System.out.println("destinationRect:" + destinationRect.width + ", " + destinationRect.height);
+
+ int update = dispMan.vc_dispmanx_update_start( 0 );
+ VC_DISPMANX_ALPHA_T.ByReference alpha = new VC_DISPMANX_ALPHA_T.ByReference();
+ alpha.flags = ALPHA.DISPMANX_FLAGS_ALPHA_FROM_SOURCE.getFlag() | ALPHA.DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS.getFlag();
+ alpha.opacity = 255;
+
+
+ int element = dispMan.vc_dispmanx_element_add(
+ update,
+ display,
+ 2010,
+ destinationRect,
+ resourceHandle,
+ sourceRect,
+ PROTECTION.DISPMANX_PROTECTION_NONE.getcConst(),
+ alpha,
+ 0,
+ VC_IMAGE_TRANSFORM_T.VC_IMAGE_ROT0.getcConst() );
+
+ res( "submit", dispMan.vc_dispmanx_update_submit_sync( update ) );
+
+ Thread.sleep( time * 1000 );
+ update = dispMan.vc_dispmanx_update_start( 0 );
+
+ res( "element remove", dispMan.vc_dispmanx_element_remove( update, element ) );
+ res( "submit", dispMan.vc_dispmanx_update_submit_sync( update ) );
+ res( "resource delete", dispMan.vc_dispmanx_resource_delete( resourceHandle ) );
+ res( "display close", dispMan.vc_dispmanx_display_close( display ) );
+ }
+}
diff --git a/host/src/test/java/org/area515/resinprinter/display/DispManXLoadImage.java b/host/src/test/java/org/area515/resinprinter/display/DispManXLoadImage.java
new file mode 100644
index 000000000..48f91b75a
--- /dev/null
+++ b/host/src/test/java/org/area515/resinprinter/display/DispManXLoadImage.java
@@ -0,0 +1,192 @@
+package org.area515.resinprinter.display;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+
+import javax.imageio.ImageIO;
+
+import org.area515.resinprinter.display.dispmanx.ALPHA;
+import org.area515.resinprinter.display.dispmanx.DispManX;
+import org.area515.resinprinter.display.dispmanx.PROTECTION;
+import org.area515.resinprinter.display.dispmanx.SCREEN;
+import org.area515.resinprinter.display.dispmanx.VC_DISPMANX_ALPHA_T;
+import org.area515.resinprinter.display.dispmanx.VC_IMAGE_TRANSFORM_T;
+import org.area515.resinprinter.display.dispmanx.VC_IMAGE_TYPE_T;
+import org.area515.resinprinter.display.dispmanx.VC_RECT_T;
+
+import com.sun.jna.Memory;
+import com.sun.jna.ptr.IntByReference;
+
+public class DispManXLoadImage {
+ public static void displayInfo( int id, String name ) {
+ IntByReference width = new IntByReference();
+ IntByReference height = new IntByReference();
+ int res = DispManX.INSTANCE.graphics_get_display_size( id, width, height );
+ if ( res >= 0 ) {
+ System.out.printf( "\t%d %s: %dx%d\n", id, name, width.getValue(), height.getValue() );
+ } else {
+ System.out.println("graphics_get_display_size returned:" + res);
+ }
+ }
+
+ public static void usage() {
+ System.out.printf( "usage: org.area515.resinprinter.display.DispManXTest \n");
+ displayInfo( SCREEN.DISPMANX_ID_MAIN_LCD.getId(), "Main LCD" );
+ displayInfo( SCREEN.DISPMANX_ID_AUX_LCD.getId(), "AUX LCD" );
+ displayInfo( SCREEN.DISPMANX_ID_HDMI.getId(), "HDMI" );
+ displayInfo( SCREEN.DISPMANX_ID_SDTV.getId(), "SDTV" );
+ displayInfo( SCREEN.DISPMANX_ID_FORCE_LCD.getId(), "Force LCD" );
+ displayInfo( SCREEN.DISPMANX_ID_FORCE_TV.getId(), "Force TV" );
+ displayInfo( SCREEN.DISPMANX_ID_FORCE_OTHER.getId(), "Force other" );
+ System.exit( 1 );
+ }
+
+ public static void info( int screen ) {
+ IntByReference width = new IntByReference();
+ IntByReference height = new IntByReference();
+ int res = DispManX.INSTANCE.graphics_get_display_size( screen, width, height );
+ if (res >= 0) {
+ System.out.printf( "%d, %d\n", width, height );
+ } else {
+ System.out.println("graphics_get_display_size returned:" + res);
+ }
+ System.exit( 1 );
+ }
+
+ public static void res( String str, int val ) {
+ if ( val != 0 ) {
+ System.out.printf( "%s: %08x\n", str, val );
+ } else {
+ System.out.println(str + " is 0");
+ }
+ }
+
+ public static int getPitch( int x, int y ) {
+ return ((x + (y)-1) & ~((y)-1));//y*((x + y-1)/y);
+ }
+
+ public static Memory loadBitmapRGB565(String fileName, IntByReference width, IntByReference height, IntByReference pitchByRef) throws IOException {
+ int bytesPerPixel = 2;
+ BufferedImage image = ImageIO.read(new File(fileName));
+ int pitch = getPitch( bytesPerPixel * image.getWidth(), 32 );
+ pitchByRef.setValue(pitch);
+ Memory destPixels = new Memory(pitch * image.getHeight());
+ for (int y = 0; y < image.getHeight(); y++) {
+ for (int x = 0; x < image.getWidth(); x++) {
+ int rgb = image.getRGB(x, y);
+ destPixels.setShort((y*(pitch / bytesPerPixel) + x) * bytesPerPixel, (short)(((rgb & 0xf80000) >>> 8) | ((rgb & 0xfc00) >>> 5) | (rgb & 0xf8 >>> 3)));
+ }
+ }
+ width.setValue(image.getWidth());
+ height.setValue(image.getHeight());
+ return destPixels;
+ }
+
+ public static Memory loadBitmapARGB8888(String fileName, IntByReference width, IntByReference height, IntByReference pitchByRef) throws IOException {
+ int bytesPerPixel = 4;
+ BufferedImage image = ImageIO.read(new File(fileName));
+ int pitch = getPitch( bytesPerPixel * image.getWidth(), 32 );
+ pitchByRef.setValue(pitch);
+ long start = System.currentTimeMillis();
+ Memory destPixels = new Memory(pitch * image.getHeight());
+ for (int y = 0; y < image.getHeight(); y++) {
+ for (int x = 0; x < image.getWidth(); x++) {
+ destPixels.setInt((y*(pitch / bytesPerPixel) + x) * bytesPerPixel, image.getRGB(x, y));
+ }
+ }
+ System.out.println(System.currentTimeMillis() - start);
+ width.setValue(image.getWidth());
+ height.setValue(image.getHeight());
+ return destPixels;
+ }
+
+ //sudo vi DispManXLoadImage.java
+ //sudo javac -cp lib/*:. DispManXLoadImage.java
+ //sudo java -cp lib/*:. DispManXLoadImage 2 10 resourcesnew/favicon/apple-icon-144x144.png
+ public static void main(String[] args) throws IOException, InterruptedException {
+ DispManX dispMan = DispManX.INSTANCE;
+ System.out.println("BCM Initialized:" + dispMan.bcm_host_init());
+ if ( args.length < 3 ) {
+ usage();
+ return;
+ }
+
+ int screen = Integer.parseInt( args[0] );
+ /*if (screen == 2 || screen == 3 || screen == 5) {
+ IntByReference vchiHandle = new IntByReference();
+ PointerByReference connections = new PointerByReference();
+
+ dispMan.vcos_init();
+ res("vchi_initialise", dispMan.vchi_initialise(vchiHandle));
+ res("vchi_connect", dispMan.vchi_connect(null, 0, vchiHandle.getValue()));
+ res("vc_vchi_tv_init", dispMan.vc_vchi_tv_init(vchiHandle, connections.getPointer(), 1));
+ res("vc_tv_hdmi_power_on_preferred", dispMan.vc_tv_hdmi_power_on_preferred());
+ }*/
+
+ int time = Integer.parseInt( args[1] );
+ IntByReference pitch = new IntByReference();
+ IntByReference width = new IntByReference();
+ IntByReference height = new IntByReference();
+ res( "get display size", dispMan.graphics_get_display_size( screen, width, height ) );
+ System.out.printf( "display %d: %d x %d\n", screen, width.getValue(), height.getValue() );
+ int display = dispMan.vc_dispmanx_display_open( screen );
+ Memory bitmap = loadBitmapRGB565( args[2], width, height, pitch );
+ System.out.printf( "bitmap: %d x %d pitch->%d\n", width.getValue(), height.getValue(), pitch.getValue());
+
+ VC_RECT_T.ByReference copyRect = new VC_RECT_T.ByReference();
+ VC_RECT_T.ByReference sourceRect = new VC_RECT_T.ByReference();
+ VC_RECT_T.ByReference destinationRect = new VC_RECT_T.ByReference();
+
+ res( "rect set", dispMan.vc_dispmanx_rect_set( copyRect, 0, 0, width.getValue(), height.getValue() ) );
+ //This seems to be some form of a zoom factor
+ res( "rect set", dispMan.vc_dispmanx_rect_set( sourceRect, 0, 0, width.getValue()<<16, height.getValue()<<16 ) );
+ res( "rect set", dispMan.vc_dispmanx_rect_set( destinationRect, 0, 0, width.getValue(), height.getValue() ) );
+
+ IntByReference ref = new IntByReference();
+ int resourceHandle = dispMan.vc_dispmanx_resource_create(
+ VC_IMAGE_TYPE_T.VC_IMAGE_RGB565.getcIndex(),
+ width.getValue(),
+ height.getValue(),
+ ref );
+ res( "resource write data", dispMan.vc_dispmanx_resource_write_data(
+ resourceHandle,
+ VC_IMAGE_TYPE_T.VC_IMAGE_RGB565.getcIndex(),
+ pitch.getValue() ,
+ bitmap,
+ destinationRect )
+ );
+
+ System.out.println("copyRect:" + copyRect.width + ", " + copyRect.height);
+ System.out.println("sourceRect:" + sourceRect.width + ", " + sourceRect.height);
+ System.out.println("destinationRect:" + destinationRect.width + ", " + destinationRect.height);
+
+ int update = dispMan.vc_dispmanx_update_start( 0 );
+ VC_DISPMANX_ALPHA_T.ByReference alpha = new VC_DISPMANX_ALPHA_T.ByReference();
+ alpha.flags = ALPHA.DISPMANX_FLAGS_ALPHA_FROM_SOURCE.getFlag() | ALPHA.DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS.getFlag();
+ alpha.opacity = 255;
+
+
+ int element = dispMan.vc_dispmanx_element_add(
+ update,
+ display,
+ 2010,
+ destinationRect,
+ resourceHandle,
+ sourceRect,
+ PROTECTION.DISPMANX_PROTECTION_NONE.getcConst(),
+ alpha,
+ 0,
+ VC_IMAGE_TRANSFORM_T.VC_IMAGE_ROT0.getcConst() );
+
+ res( "submit", dispMan.vc_dispmanx_update_submit_sync( update ) );
+
+ Thread.sleep( time * 1000 );
+ update = dispMan.vc_dispmanx_update_start( 0 );
+
+ res( "element remove", dispMan.vc_dispmanx_element_remove( update, element ) );
+ res( "submit", dispMan.vc_dispmanx_update_submit_sync( update ) );
+ res( "resource delete", dispMan.vc_dispmanx_resource_delete( resourceHandle ) );
+ res( "display close", dispMan.vc_dispmanx_display_close( display ) );
+ }
+}
diff --git a/host/src/test/java/org/area515/resinprinter/display/DispManXTestSelfContained.java b/host/src/test/java/org/area515/resinprinter/display/DispManXTestSelfContained.java
new file mode 100644
index 000000000..13eab8614
--- /dev/null
+++ b/host/src/test/java/org/area515/resinprinter/display/DispManXTestSelfContained.java
@@ -0,0 +1,372 @@
+package org.area515.resinprinter.display;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.imageio.ImageIO;
+
+import com.sun.jna.Library;
+import com.sun.jna.Memory;
+import com.sun.jna.Native;
+import com.sun.jna.Pointer;
+import com.sun.jna.Structure;
+import com.sun.jna.ptr.IntByReference;
+
+public class DispManXTestSelfContained {
+ public static class VC_DISPMANX_ALPHA_T extends Structure {
+ public static class ByReference extends VC_DISPMANX_ALPHA_T implements Structure.ByReference {}
+
+ public int flags;
+ public int opacity;
+ public int mask;
+
+ @Override
+ protected List getFieldOrder() {
+ return Arrays.asList("flags", "opacity", "mask");
+ }
+ }
+
+ public static class VC_RECT_T extends Structure {
+ public static class ByReference extends VC_RECT_T implements Structure.ByReference {}
+ public int x;
+ public int y;
+ public int width;
+ public int height;
+
+ @Override
+ protected List getFieldOrder() {
+ return Arrays.asList("x", "y", "width", "height");
+ }
+ }
+
+ public enum ALPHA {
+ DISPMANX_FLAGS_ALPHA_FROM_SOURCE(0),
+ DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS(1),
+ DISPMANX_FLAGS_ALPHA_FIXED_NON_ZERO(2),
+ DISPMANX_FLAGS_ALPHA_FIXED_EXCEED_0X07(3),
+ DISPMANX_FLAGS_ALPHA_PREMULT(1 << 16),
+ DISPMANX_FLAGS_ALPHA_MIX(1 << 17);
+ private int flag = 0;
+
+ ALPHA(int flag) {
+ this.flag = flag;
+ }
+
+ public int getFlag() {
+ return flag;
+ }
+ }
+
+ public enum TRANSFORM {
+ TRANSFORM_HFLIP(1<<0),
+ TRANSFORM_VFLIP(1<<1),
+ TRANSFORM_TRANSPOSE (1<<2);
+ private int cConst;
+
+ TRANSFORM(int cConst) {
+ this.cConst = cConst;
+ }
+
+ public int getcConst() {
+ return cConst;
+ }
+ }
+
+ public enum VC_IMAGE_TRANSFORM_T {
+ VC_IMAGE_ROT0(0),
+ VC_IMAGE_MIRROR_ROT0(TRANSFORM.TRANSFORM_HFLIP.getcConst()),
+ VC_IMAGE_MIRROR_ROT180(TRANSFORM.TRANSFORM_VFLIP.getcConst()),
+ VC_IMAGE_ROT180(TRANSFORM.TRANSFORM_HFLIP.getcConst()|TRANSFORM.TRANSFORM_VFLIP.getcConst()),
+ VC_IMAGE_MIRROR_ROT90(TRANSFORM.TRANSFORM_TRANSPOSE.getcConst()),
+ VC_IMAGE_ROT270(TRANSFORM.TRANSFORM_TRANSPOSE.getcConst()|TRANSFORM.TRANSFORM_HFLIP.getcConst()),
+ VC_IMAGE_ROT90(TRANSFORM.TRANSFORM_TRANSPOSE.getcConst()|TRANSFORM.TRANSFORM_VFLIP.getcConst()),
+ VC_IMAGE_MIRROR_ROT270(TRANSFORM.TRANSFORM_TRANSPOSE.getcConst()|TRANSFORM.TRANSFORM_HFLIP.getcConst()|TRANSFORM.TRANSFORM_VFLIP.getcConst());
+ private int cConst;
+
+ VC_IMAGE_TRANSFORM_T(int cConst) {
+ this.cConst = cConst;
+ }
+
+ public int getcConst() {
+ return cConst;
+ }
+ }
+
+ public enum VC_IMAGE_TYPE_T {
+ VC_IMAGE_MIN(0), //bounds for error checking
+
+ VC_IMAGE_RGB565(1),
+ VC_IMAGE_1BPP(2),
+ VC_IMAGE_YUV420(3),
+ VC_IMAGE_48BPP(4),
+ VC_IMAGE_RGB888(5),
+ VC_IMAGE_8BPP(6),
+ VC_IMAGE_4BPP(7), // 4bpp palettised image
+ VC_IMAGE_3D32(8), /* A separated format of 16 colour/light shorts followed by 16 z values */
+ VC_IMAGE_3D32B(9), /* 16 colours followed by 16 z values */
+ VC_IMAGE_3D32MAT(10), /* A separated format of 16 material/colour/light shorts followed by 16 z values */
+ VC_IMAGE_RGB2X9(11), /* 32 bit format containing 18 bits of 6.6.6 RGB, 9 bits per short */
+ VC_IMAGE_RGB666(12), /* 32-bit format holding 18 bits of 6.6.6 RGB */
+ VC_IMAGE_PAL4_OBSOLETE(13), // 4bpp palettised image with embedded palette
+ VC_IMAGE_PAL8_OBSOLETE(14), // 8bpp palettised image with embedded palette
+ VC_IMAGE_RGBA32(15), /* RGB888 with an alpha byte after each pixel */ /* xxx: isn't it BEFORE each pixel? */
+ VC_IMAGE_YUV422(16), /* a line of Y (32-byte padded), a line of U (16-byte padded), and a line of V (16-byte padded) */
+ VC_IMAGE_RGBA565(17), /* RGB565 with a transparent patch */
+ VC_IMAGE_RGBA16(18), /* Compressed (4444) version of RGBA32 */
+ VC_IMAGE_YUV_UV(19), /* VCIII codec format */
+ VC_IMAGE_TF_RGBA32(20), /* VCIII T-format RGBA8888 */
+ VC_IMAGE_TF_RGBX32(21), /* VCIII T-format RGBx8888 */
+ VC_IMAGE_TF_FLOAT(22), /* VCIII T-format float */
+ VC_IMAGE_TF_RGBA16(23), /* VCIII T-format RGBA4444 */
+ VC_IMAGE_TF_RGBA5551(24), /* VCIII T-format RGB5551 */
+ VC_IMAGE_TF_RGB565(25), /* VCIII T-format RGB565 */
+ VC_IMAGE_TF_YA88(26), /* VCIII T-format 8-bit luma and 8-bit alpha */
+ VC_IMAGE_TF_BYTE(27), /* VCIII T-format 8 bit generic sample */
+ VC_IMAGE_TF_PAL8(28), /* VCIII T-format 8-bit palette */
+ VC_IMAGE_TF_PAL4(29), /* VCIII T-format 4-bit palette */
+ VC_IMAGE_TF_ETC1(30), /* VCIII T-format Ericsson Texture Compressed */
+ VC_IMAGE_BGR888(31), /* RGB888 with R & B swapped */
+ VC_IMAGE_BGR888_NP(32), /* RGB888 with R & B swapped, but with no pitch, i.e. no padding after each row of pixels */
+ VC_IMAGE_BAYER(33), /* Bayer image, extra defines which variant is being used */
+ VC_IMAGE_CODEC(34), /* General wrapper for codec images e.g. JPEG from camera */
+ VC_IMAGE_YUV_UV32(35), /* VCIII codec format */
+ VC_IMAGE_TF_Y8(36), /* VCIII T-format 8-bit luma */
+ VC_IMAGE_TF_A8(37), /* VCIII T-format 8-bit alpha */
+ VC_IMAGE_TF_SHORT(38),/* VCIII T-format 16-bit generic sample */
+ VC_IMAGE_TF_1BPP(39), /* VCIII T-format 1bpp black/white */
+ VC_IMAGE_OPENGL(40),
+ VC_IMAGE_YUV444I(41), /* VCIII-B0 HVS YUV 4:4:4 interleaved samples */
+ VC_IMAGE_YUV422PLANAR(42), /* Y, U, & V planes separately (VC_IMAGE_YUV422 has them interleaved on a per line basis) */
+ VC_IMAGE_ARGB8888(43), /* 32bpp with 8bit alpha at MS byte, with R, G, B (LS byte) */
+ VC_IMAGE_XRGB8888(44), /* 32bpp with 8bit unused at MS byte, with R, G, B (LS byte) */
+
+ VC_IMAGE_YUV422YUYV(45), /* interleaved 8 bit samples of Y, U, Y, V */
+ VC_IMAGE_YUV422YVYU(46), /* interleaved 8 bit samples of Y, V, Y, U */
+ VC_IMAGE_YUV422UYVY(47), /* interleaved 8 bit samples of U, Y, V, Y */
+ VC_IMAGE_YUV422VYUY(48), /* interleaved 8 bit samples of V, Y, U, Y */
+
+ VC_IMAGE_RGBX32(49), /* 32bpp like RGBA32 but with unused alpha */
+ VC_IMAGE_RGBX8888(50), /* 32bpp, corresponding to RGBA with unused alpha */
+ VC_IMAGE_BGRX8888(51), /* 32bpp, corresponding to BGRA with unused alpha */
+
+ VC_IMAGE_YUV420SP(52), /* Y as a plane, then UV byte interleaved in plane with with same pitch, half height */
+
+ VC_IMAGE_YUV444PLANAR(53), /* Y, U, & V planes separately 4:4:4 */
+
+ VC_IMAGE_TF_U8(54), /* T-format 8-bit U - same as TF_Y8 buf from U plane */
+ VC_IMAGE_TF_V8(55), /* T-format 8-bit U - same as TF_Y8 buf from V plane */
+
+ VC_IMAGE_MAX(56), //bounds for error checking
+ VC_IMAGE_FORCE_ENUM_16BIT(0xffff);
+
+ private int cIndex;
+
+ VC_IMAGE_TYPE_T(int cIndex) {
+ this.cIndex = cIndex;
+ }
+
+ public int getcIndex() {
+ return cIndex;
+ }
+ }
+ public interface DispManX extends Library {
+ public DispManX INSTANCE = (DispManX)Native.loadLibrary("bcm_host", DispManX.class);
+
+ public static final int DISPMANX_ID_MAIN_LCD = 0;
+ public static final int DISPMANX_ID_AUX_LCD = 1;
+ public static final int DISPMANX_ID_HDMI = 2;
+ public static final int DISPMANX_ID_SDTV = 3;
+ public static final int DISPMANX_ID_FORCE_LCD = 4;
+ public static final int DISPMANX_ID_FORCE_TV = 5;
+ public static final int DISPMANX_ID_FORCE_OTHER = 6;
+
+ public static final int DISPMANX_PROTECTION_MAX = 0x0f;
+ public static final int DISPMANX_PROTECTION_NONE = 0;
+ public static final int DISPMANX_PROTECTION_HDCP = 11;
+
+ public int bcm_host_init();
+ public int graphics_get_display_size(int screenIndex, IntByReference width, IntByReference height);
+ public int vc_dispmanx_display_open(int screenIndex);
+ public int vc_dispmanx_resource_create(int resourceType, int width, int height, IntByReference imagePointer);
+ public int vc_dispmanx_rect_set(VC_RECT_T.ByReference rectangleToCreate, int offsetX, int offsetY, int width, int height);
+ public int vc_dispmanx_update_start(int priority);//Constant of 10
+ public int vc_dispmanx_element_add(
+ int updateHandle,
+ int displayHandle,
+ int layer, // 2000
+ VC_RECT_T.ByReference destinationRectangle,
+ int sourceResourceHandle,
+ VC_RECT_T.ByReference sourceRectangle,
+ int protectionMode, // DISPMANX_PROTECTION_NONE
+ VC_DISPMANX_ALPHA_T.ByReference alpha, // { DISPMANX_FLAGS_ALPHA_FROM_SOURCE | DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS, 255, 0 };
+ int clamp, // 0
+ int imageTransformation); // VC_IMAGE_ROT0
+ public int vc_dispmanx_update_submit_sync(int updateHandle);
+ public int vc_dispmanx_resource_write_data(
+ int resourceHandle,
+ int resourceType, // VC_IMAGE_RGB565
+ int pitch,
+ Pointer imageConvertedFromBufferedImage,
+ VC_RECT_T.ByReference copyRectangle);
+ public int vc_dispmanx_element_remove(int updateHandle, int elementHandle);
+ public int vc_dispmanx_resource_delete(int resourceHandle);
+ public int vc_dispmanx_display_close(int displayHandle);
+ }
+
+ public static void displayInfo( int id, String name ) {
+ IntByReference width = new IntByReference();
+ IntByReference height = new IntByReference();
+ int res = DispManX.INSTANCE.graphics_get_display_size( id, width, height );
+ if ( res >= 0 ) {
+ System.out.printf( "\t%d %s: %dx%d\n", id, name, width.getValue(), height.getValue() );
+ } else {
+ System.out.println("graphics_get_display_size returned:" + res);
+ }
+ }
+
+ public static void usage() {
+ System.out.printf( "usage: org.area515.resinprinter.display.DispManXTest \n");
+ displayInfo( DispManX.DISPMANX_ID_MAIN_LCD, "Main LCD" );
+ displayInfo( DispManX.DISPMANX_ID_AUX_LCD, "AUX LCD" );
+ displayInfo( DispManX.DISPMANX_ID_HDMI, "HDMI" );
+ displayInfo( DispManX.DISPMANX_ID_SDTV, "SDTV" );
+ displayInfo( DispManX.DISPMANX_ID_FORCE_LCD, "Force LCD" );
+ displayInfo( DispManX.DISPMANX_ID_FORCE_TV, "Force TV" );
+ displayInfo( DispManX.DISPMANX_ID_FORCE_OTHER, "Force other" );
+ System.exit( 1 );
+ }
+
+ public static void info( int screen ) {
+ IntByReference width = new IntByReference();
+ IntByReference height = new IntByReference();
+ int res = DispManX.INSTANCE.graphics_get_display_size( screen, width, height );
+ if (res >= 0) {
+ System.out.printf( "%d, %d\n", width, height );
+ } else {
+ System.out.println("graphics_get_display_size returned:" + res);
+ }
+ System.exit( 1 );
+ }
+
+ public static void res( String str, int val ) {
+ if ( val != 0 ) {
+ System.out.printf( "%s: %08x\n", str, val );
+ } else {
+ System.out.println(str + " is 0");
+ }
+ }
+
+ public static int getPitch( int x, int y ) {
+ return ((x + (y)-1) & ~((y)-1));//y*((x + y-1)/y);
+ }
+
+ public static Memory loadBitmapRGB565(String fileName, IntByReference width, IntByReference height, IntByReference pitchByRef) throws IOException {
+ int bytesPerPixel = 2;
+ BufferedImage image = ImageIO.read(new File(fileName));
+ int pitch = getPitch( bytesPerPixel * image.getWidth(), 32 );
+ pitchByRef.setValue(pitch);
+ Memory destPixels = new Memory(pitch * image.getHeight());
+ for (int y = 0; y < image.getHeight(); y++) {
+ for (int x = 0; x < image.getWidth(); x++) {
+ int rgb = image.getRGB(x, y);
+ destPixels.setShort((y*(pitch / bytesPerPixel) + x) * bytesPerPixel, (short)(((rgb & 0xf80000) >>> 8) | ((rgb & 0xfc00) >>> 5) | (rgb & 0xf8 >>> 3)));
+ }
+ }
+ width.setValue(image.getWidth());
+ height.setValue(image.getHeight());
+ return destPixels;
+ }
+
+ public static Memory loadBitmapARGB8888(String fileName, IntByReference width, IntByReference height, IntByReference pitchByRef) throws IOException {
+ int bytesPerPixel = 4;
+ BufferedImage image = ImageIO.read(new File(fileName));
+ int pitch = getPitch( bytesPerPixel * image.getWidth(), 32 );
+ pitchByRef.setValue(pitch);
+ long start = System.currentTimeMillis();
+ Memory destPixels = new Memory(pitch * image.getHeight());
+ for (int y = 0; y < image.getHeight(); y++) {
+ for (int x = 0; x < image.getWidth(); x++) {
+ destPixels.setInt((y*(pitch / bytesPerPixel) + x) * bytesPerPixel, image.getRGB(x, y));
+ }
+ }
+ System.out.println(System.currentTimeMillis() - start);
+ width.setValue(image.getWidth());
+ height.setValue(image.getHeight());
+ return destPixels;
+ }
+
+ //sudo vi DispManXTest.java
+ //sudo javac -cp lib/*:. DispManXTest.java
+ //sudo java -cp lib/*:. DispManXTest 2 10 resourcesnew/favicon/apple-icon-144x144.png
+ public static void main(String[] args) throws IOException, InterruptedException {
+ DispManX dispMan = DispManX.INSTANCE;
+ System.out.println("Initialized:" + dispMan.bcm_host_init());
+ if ( args.length < 3 ) {
+ usage();
+ return;
+ }
+
+ int screen = Integer.parseInt( args[0] );
+ int time = Integer.parseInt( args[1] );
+ IntByReference pitch = new IntByReference();
+ IntByReference width = new IntByReference();
+ IntByReference height = new IntByReference();
+ res( "get display size", dispMan.graphics_get_display_size( screen, width, height ) );
+ System.out.printf( "display %d: %d x %d\n", screen, width.getValue(), height.getValue() );
+ int display = dispMan.vc_dispmanx_display_open( screen );
+ Memory bitmap = loadBitmapARGB8888( args[2], width, height, pitch );
+ System.out.printf( "bitmap: %d x %d pitch->%d\n", width.getValue(), height.getValue(), pitch.getValue());
+
+ IntByReference ref = new IntByReference();
+ int resourceHandle = dispMan.vc_dispmanx_resource_create( VC_IMAGE_TYPE_T.VC_IMAGE_ARGB8888.getcIndex(), width.getValue(), height.getValue(), ref );
+
+ VC_RECT_T.ByReference copyRect = new VC_RECT_T.ByReference();
+ VC_RECT_T.ByReference sourceRect = new VC_RECT_T.ByReference();
+ VC_RECT_T.ByReference destinationRect = new VC_RECT_T.ByReference();
+
+ res( "rect set", dispMan.vc_dispmanx_rect_set( copyRect, 0, 0, width.getValue(), height.getValue() ) );
+ //This seems to be some form of a zoom factor
+ res( "rect set", dispMan.vc_dispmanx_rect_set( sourceRect, 0, 0, width.getValue()<<16, height.getValue()<<16 ) );
+ res( "rect set", dispMan.vc_dispmanx_rect_set( destinationRect, 0, 0, width.getValue(), height.getValue() ) );
+
+ System.out.println("copyRect:" + copyRect.width + ", " + copyRect.height);
+ System.out.println("sourceRect:" + sourceRect.width + ", " + sourceRect.height);
+ System.out.println("destinationRect:" + destinationRect.width + ", " + destinationRect.height);
+
+ int update = dispMan.vc_dispmanx_update_start( 0 );
+ VC_DISPMANX_ALPHA_T.ByReference alpha = new VC_DISPMANX_ALPHA_T.ByReference();
+ alpha.flags = ALPHA.DISPMANX_FLAGS_ALPHA_FROM_SOURCE.getFlag() | ALPHA.DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS.getFlag();
+ alpha.opacity = 255;
+ int element = dispMan.vc_dispmanx_element_add(
+ update,
+ display,
+ 2010,
+ destinationRect,
+ resourceHandle,
+ sourceRect,
+ DispManX.DISPMANX_PROTECTION_NONE,
+ alpha,
+ 0,
+ VC_IMAGE_TRANSFORM_T.VC_IMAGE_ROT0.getcConst() );
+ res( "submit", dispMan.vc_dispmanx_update_submit_sync( update ) );
+
+ res( "resource write data", dispMan.vc_dispmanx_resource_write_data(
+ resourceHandle,
+ VC_IMAGE_TYPE_T.VC_IMAGE_ARGB8888.getcIndex(),
+ pitch.getValue() ,
+ bitmap,
+ destinationRect )
+ );
+
+ Thread.sleep( time * 1000 );
+ update = dispMan.vc_dispmanx_update_start( 0 );
+
+ res( "element remove", dispMan.vc_dispmanx_element_remove( update, element ) );
+ res( "submit", dispMan.vc_dispmanx_update_submit_sync( update ) );
+ res( "resource delete", dispMan.vc_dispmanx_resource_delete( resourceHandle ) );
+ res( "display close", dispMan.vc_dispmanx_display_close( display ) );
+ }
+}
diff --git a/host/src/test/java/org/area515/resinprinter/gcode/TestGCodeTemplating.java b/host/src/test/java/org/area515/resinprinter/gcode/TestGCodeTemplating.java
index 798d6b954..4af7baf94 100644
--- a/host/src/test/java/org/area515/resinprinter/gcode/TestGCodeTemplating.java
+++ b/host/src/test/java/org/area515/resinprinter/gcode/TestGCodeTemplating.java
@@ -17,8 +17,8 @@ public class TestGCodeTemplating {
public void testEmptyGCode() throws Exception {
AbstractPrintFileProcessor processor = Mockito.mock(AbstractPrintFileProcessor.class, Mockito.CALLS_REAL_METHODS);
PrintJob printJob = AbstractPrintFileProcessorTest.createTestPrintJob(processor);
- Assert.assertNull(printJob.getPrinter().getGCodeControl().executeGCodeWithTemplating(printJob, null));
- Assert.assertNull(printJob.getPrinter().getGCodeControl().executeGCodeWithTemplating(printJob, " "));
+ Assert.assertNull(printJob.getPrinter().getGCodeControl().executeGCodeWithTemplating(printJob, null, true));
+ Assert.assertNull(printJob.getPrinter().getGCodeControl().executeGCodeWithTemplating(printJob, " ", true));
}
@Test
@@ -51,7 +51,7 @@ public String answer(InvocationOnMock invocation) throws Throwable {
return (String)"ok";
}
});
- printJob.getPrinter().getGCodeControl().executeGCodeWithTemplating(printJob, gcodes);
+ printJob.getPrinter().getGCodeControl().executeGCodeWithTemplating(printJob, gcodes, true);
}
}
diff --git a/host/src/test/java/org/area515/resinprinter/job/AbstractPrintFileProcessorTest.java b/host/src/test/java/org/area515/resinprinter/job/AbstractPrintFileProcessorTest.java
index 6471bc33d..a3fd1abba 100644
--- a/host/src/test/java/org/area515/resinprinter/job/AbstractPrintFileProcessorTest.java
+++ b/host/src/test/java/org/area515/resinprinter/job/AbstractPrintFileProcessorTest.java
@@ -59,7 +59,7 @@ public static PrintJob createTestPrintJob(PrintFileProcessor processor) throws I
Mockito.when(slicingProfile.getDirection()).thenReturn(BuildDirection.Bottom_Up);
Mockito.when(printer.getGCodeControl()).thenReturn(gCode);
Mockito.when(slicingProfile.getgCodeLift()).thenReturn("Lift z");
- Mockito.doCallRealMethod().when(gCode).executeGCodeWithTemplating(Mockito.any(PrintJob.class), Mockito.anyString());
+ Mockito.doCallRealMethod().when(gCode).executeGCodeWithTemplating(Mockito.any(PrintJob.class), Mockito.anyString(), Mockito.anyBoolean());
Mockito.when(printer.getConfiguration().getMachineConfig()).thenReturn(machine);
Mockito.when(printer.getConfiguration().getMachineConfig().getMonitorDriverConfig()).thenReturn(monitorConfig);
return printJob;
@@ -127,7 +127,7 @@ public void unsupportedBuildAreaDoesntBreakLiftDistanceCalculator() throws Excep
DataAid aid = processor.initializeJobCacheWithDataAid(printJob);
aid.customizer.setNextStep(PrinterStep.PerformExposure);
processor.printImageAndPerformPostProcessing(aid, image);
- Mockito.verify(printJob.getPrinter().getGCodeControl(), Mockito.times(1)).executeGCodeWithTemplating(Mockito.any(PrintJob.class), Mockito.anyString());
+ Mockito.verify(printJob.getPrinter().getGCodeControl(), Mockito.times(1)).executeGCodeWithTemplating(Mockito.any(PrintJob.class), Mockito.anyString(), Mockito.anyBoolean());
}
@Test
@@ -140,7 +140,7 @@ public void getExceptionWhenWeReturnGarbageForLiftDistanceCalculator() throws Ex
try {
aid.customizer.setNextStep(PrinterStep.PerformExposure);
processor.printImageAndPerformPostProcessing(aid, image);
- Mockito.verify(printJob.getPrinter().getGCodeControl(), Mockito.times(1)).executeGCodeWithTemplating(Mockito.any(PrintJob.class), Mockito.anyString());
+ Mockito.verify(printJob.getPrinter().getGCodeControl(), Mockito.times(1)).executeGCodeWithTemplating(Mockito.any(PrintJob.class), Mockito.anyString(), true);
} catch (IllegalArgumentException e) {
Assert.assertEquals("The result of your lift distance script needs to evaluate to an instance of java.lang.Number", e.getMessage());
}
@@ -156,7 +156,7 @@ public void noNullPointerWhenWeReturnNull() throws Exception {
try {
aid.customizer.setNextStep(PrinterStep.PerformExposure);
processor.printImageAndPerformPostProcessing(aid, image);
- Mockito.verify(printJob.getPrinter().getGCodeControl(), Mockito.times(1)).executeGCodeWithTemplating(Mockito.any(PrintJob.class), Mockito.anyString());
+ Mockito.verify(printJob.getPrinter().getGCodeControl(), Mockito.times(1)).executeGCodeWithTemplating(Mockito.any(PrintJob.class), Mockito.anyString(), Mockito.anyBoolean());
} catch (IllegalArgumentException e) {
Assert.assertEquals("The result of your lift distance script needs to evaluate to an instance of java.lang.Number", e.getMessage());
}
@@ -234,6 +234,6 @@ public String answer(InvocationOnMock invocation) throws Throwable {
aid.customizer.setNextStep(PrinterStep.PerformExposure);
processor.printImageAndPerformPostProcessing(aid, image);
//The two executes are for getZLiftDistanceGCode and the life gcode itself
- Mockito.verify(printJob.getPrinter().getGCodeControl(), Mockito.times(2)).executeGCodeWithTemplating(Mockito.any(PrintJob.class), Mockito.anyString());
+ Mockito.verify(printJob.getPrinter().getGCodeControl(), Mockito.times(2)).executeGCodeWithTemplating(Mockito.any(PrintJob.class), Mockito.anyString(), Mockito.anyBoolean());
}
}
diff --git a/host/src/test/java/org/area515/resinprinter/network/LinuxNetworkManagerTest.java b/host/src/test/java/org/area515/resinprinter/network/LinuxNetworkManagerTest.java
index 81271c9f4..d225b16ba 100644
--- a/host/src/test/java/org/area515/resinprinter/network/LinuxNetworkManagerTest.java
+++ b/host/src/test/java/org/area515/resinprinter/network/LinuxNetworkManagerTest.java
@@ -13,15 +13,16 @@
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
@RunWith(PowerMockRunner.class)
@PowerMockIgnore({"javax.management.*"})
public class LinuxNetworkManagerTest {
- public static String SCAN_WIFI_DATA = "wpa_cli v1.0\nCopyright (c) 2004-2012, Jouni Malinen and contributors\n\nThis program is free software. You can distribute it and/or modify it\nunder the terms of the GNU General Public License version 2.\n\nAlternatively, this software may be distributed under the terms of the\nBSD license. See README and COPYING for more details.\n\n\n\n\nInteractive mode\n\n>OK\n>\r<3>CTRL-EVENT-SCAN-RESULTS \n>bssid / frequency / signal level / flags / ssid\n03:15:2a:0c:93:15 2437 -69 [WEP][ESS]\tSomeNetwork\n11:51:a6:71:51:55 2412 92 [WPA-PSK-TKIP+CCMP][WPA2-PSK-TKIP+CCMP][WPS][ESS]\tCenturyLink9999\nac:95:17:92:60:20 2437 26 [WPA2-PSK-CCMP][WPS][ESS]\tSomeHouse\n>";
+ public static String SCAN_WIFI_DATA = "wpa_cli v1.0\nCopyright (c) 2004-2012, Jouni Malinen and contributors\n\nThis program is free software. You can distribute it and/or modify it\nunder the terms of the GNU General Public License version 2.\n\nAlternatively, this software may be distributed under the terms of the\nBSD license. See README and COPYING for more details.\n\n\n\n\nInteractive mode\n\n>OK\n>\r<3>CTRL-EVENT-SCAN-RESULTS \n>bssid / frequency / signal level / flags / ssid\n03:15:2a:0c:93:15 2437 -69 [WEP][ESS]\tSomeNetwork\n11:51:a6:71:51:55 2412 92 [WPA-PSK-TKIP+CCMP][WPA2-PSK-TKIP+CCMP][WPS][ESS]\t\\x00\nac:95:17:92:60:20 2437 26 [WPA2-PSK-CCMP][WPS][ESS]\t☺\\u0044\\\\\\x45\\\\u0044Test\n>";
+
@Test
@PrepareForTest(IOUtilities.class)
public void getNetworks() throws IOException {
@@ -70,7 +71,8 @@ public Process answer(InvocationOnMock invocation) throws Throwable {
Assert.assertEquals(lanName, interfaces.get(0).getName());
Assert.assertEquals(3, interfaces.get(0).getWirelessNetworks().size());
Assert.assertEquals("SomeNetwork", interfaces.get(0).getWirelessNetworks().get(0).getSsid());
- Assert.assertEquals("CenturyLink9999", interfaces.get(0).getWirelessNetworks().get(1).getSsid());
- Assert.assertEquals("SomeHouse", interfaces.get(0).getWirelessNetworks().get(2).getSsid());
+ Assert.assertEquals("\u0000", interfaces.get(0).getWirelessNetworks().get(1).getSsid());
+ Assert.assertEquals(true, interfaces.get(0).getWirelessNetworks().get(1).isHidden());
+ Assert.assertEquals("\u263AD\\E\\u0044Test", interfaces.get(0).getWirelessNetworks().get(2).getSsid());
}
}
diff --git a/host/src/test/java/org/area515/resinprinter/security/keystore/RendezvousExchange.java b/host/src/test/java/org/area515/resinprinter/security/keystore/RendezvousExchange.java
index a015bc31a..a6e2ea75a 100644
--- a/host/src/test/java/org/area515/resinprinter/security/keystore/RendezvousExchange.java
+++ b/host/src/test/java/org/area515/resinprinter/security/keystore/RendezvousExchange.java
@@ -145,6 +145,10 @@ public void messageExchange() throws Exception {
Assert.assertEquals(testMessage, new String(dataFrom2));
//===================
+ //TODO: This local execution of the concurrency test failed on: 12/8/2016
+ //The I believe this occurred because there was a timeout waiting for a response from method of: org.area515.resinprinter.security.keystore.RendezvousExchange.HttpRequestRunnable
+ //Why did we timeout?
+
runOneWayConcurrencyTest(server1, user1.getUserId(), user2.getUserId());
//===================
diff --git a/host/src/test/java/org/area515/resinprinter/services/MachineServiceTest.java b/host/src/test/java/org/area515/resinprinter/services/MachineServiceTest.java
new file mode 100644
index 000000000..2facc6aa2
--- /dev/null
+++ b/host/src/test/java/org/area515/resinprinter/services/MachineServiceTest.java
@@ -0,0 +1,100 @@
+package org.area515.resinprinter.services;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.ws.rs.core.MediaType;
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Marshaller;
+import javax.xml.bind.Unmarshaller;
+
+import org.area515.resinprinter.printer.MachineConfig;
+import org.area515.resinprinter.printer.Named;
+import org.area515.resinprinter.printer.Printer;
+import org.area515.resinprinter.printer.PrinterConfiguration;
+import org.area515.resinprinter.printer.SlicingProfile;
+import org.area515.resinprinter.printer.SlicingProfile.InkConfig;
+import org.area515.resinprinter.server.ApplicationConfig;
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
+
+public class MachineServiceTest {
+ public static boolean isFound(List extends Named> named, String name) {
+ boolean fileFound = false;
+ for (Named currentConfig : named) {
+ if (currentConfig.getName().equals(name)) {
+ fileFound = true;
+ break;
+ }
+ }
+
+ return fileFound;
+ }
+
+ public Printer readConfiguration() throws JsonParseException, JsonMappingException, IOException {
+ JacksonJaxbJsonProvider provider = ApplicationConfig.buildJacksonJaxbJsonProvider();
+ Object printer = provider.readFrom(Object.class, Printer.class, null, MediaType.APPLICATION_JSON_TYPE, null, MachineServiceTest.class.getResourceAsStream("jsonToXMLTest.json"));
+ return (Printer)printer;
+ }
+
+ private void assertPrinterProperties(PrinterConfiguration config) {
+ SlicingProfile slicingProfile = config.getSlicingProfile();
+ InkConfig inkConfig = slicingProfile.getSelectedInkConfig();
+ Map,?> settings = slicingProfile.getInkConfigs().iterator().next().getPrintMaterialDetectorSettings().getSettings();
+
+ Assert.assertEquals("Firm Amber 50 Microns", inkConfig.getName());
+ Assert.assertEquals(2, settings.size());
+ Assert.assertEquals("AnotherValue", settings.get("AnotherSetting"));
+ Assert.assertEquals("GoodValue", settings.get("GoodSetting"));
+ Assert.assertEquals(10L, slicingProfile.getInkConfigs().size());
+ Assert.assertEquals(2L, (long)slicingProfile.getSelectedInkConfigIndex());
+ }
+
+ @Test
+ public void createListDeleteNewMachineConfig() throws JAXBException {
+ String configName = UUID.randomUUID() + "";
+ MachineConfig config = new MachineConfig();
+ config.setName(configName);
+ MachineService.INSTANCE.saveMachineConfiguration(config);
+ Assert.assertTrue(isFound(MachineService.INSTANCE.getMachineConfigurations(), configName));
+ MachineService.INSTANCE.deleteMachineConfiguration(configName);
+ Assert.assertFalse(isFound(MachineService.INSTANCE.getMachineConfigurations(), configName));
+ }
+
+ @Test
+ public void createListDeleteNewSlicingProfileConfig() throws JAXBException {
+ String configName = UUID.randomUUID() + "";
+ SlicingProfile config = new SlicingProfile();
+ config.setName(configName);
+ MachineService.INSTANCE.saveSlicingProfile(config);
+ Assert.assertTrue(isFound(MachineService.INSTANCE.getSlicingProfiles(), configName));
+ MachineService.INSTANCE.deleteSlicingProfile(configName);
+ Assert.assertFalse(isFound(MachineService.INSTANCE.getSlicingProfiles(), configName));
+ }
+
+ @Test
+ public void roundTripJSONToObjectToXMLToObject() throws JsonParseException, JsonMappingException, IOException, JAXBException {
+ Printer printer = readConfiguration();
+ PrinterConfiguration configuration = printer.getConfiguration();
+ assertPrinterProperties(configuration);
+
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ JAXBContext jaxbContext = JAXBContext.newInstance(PrinterConfiguration.class);
+ Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
+ jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
+ jaxbMarshaller.marshal(configuration, output);
+
+ Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
+ configuration = (PrinterConfiguration)jaxbUnmarshaller.unmarshal(new ByteArrayInputStream(output.toByteArray()));
+ assertPrinterProperties(configuration);
+ }
+}
diff --git a/host/src/test/java/org/area515/resinprinter/test/FullTestSuite.java b/host/src/test/java/org/area515/resinprinter/test/FullTestSuite.java
index 8f84ba7bf..11e458b46 100644
--- a/host/src/test/java/org/area515/resinprinter/test/FullTestSuite.java
+++ b/host/src/test/java/org/area515/resinprinter/test/FullTestSuite.java
@@ -15,6 +15,7 @@
import org.area515.resinprinter.security.KeystoreSecurityTest;
import org.area515.resinprinter.security.SerializeMessageAsJson;
import org.area515.resinprinter.security.keystore.RendezvousExchange;
+import org.area515.resinprinter.services.MachineServiceTest;
import org.area515.resinprinter.services.TestScriptAndTemplating;
import org.area515.resinprinter.slice.CheckSlicePoints;
import org.area515.resinprinter.stl.ZSlicingGeometry;
@@ -43,6 +44,7 @@
TestGCodeTemplating.class,
TestByteSession.class,
TestCustomizer.class,
+ MachineServiceTest.class,
})
public class FullTestSuite {
diff --git a/host/src/test/java/org/area515/util/IOUtilitiesTest.java b/host/src/test/java/org/area515/util/IOUtilitiesTest.java
index 3bc8ccbe2..3b84c9eb4 100644
--- a/host/src/test/java/org/area515/util/IOUtilitiesTest.java
+++ b/host/src/test/java/org/area515/util/IOUtilitiesTest.java
@@ -136,8 +136,8 @@ public void testNativeCommandCommunication() throws IOException {
List dataReturned = IOUtilities.communicateWithNativeCommand(actions, "^>|\n", true, null, "wlan0");
Assert.assertEquals("SomeNetwork", dataReturned.get(0)[4]);
- Assert.assertEquals("CenturyLink9999", dataReturned.get(1)[4]);
- Assert.assertEquals("SomeHouse", dataReturned.get(2)[4]);
+ Assert.assertEquals("\\x00", dataReturned.get(1)[4]);
+ Assert.assertEquals("☺\\u0044\\\\\\x45\\\\u0044Test", dataReturned.get(2)[4]);
}
@Test
public void inputStreamReadLineTest() throws IOException {
diff --git a/host/src/test/resources/org/area515/resinprinter/services/jsonToXMLTest.json b/host/src/test/resources/org/area515/resinprinter/services/jsonToXMLTest.json
new file mode 100644
index 000000000..b6f14688e
--- /dev/null
+++ b/host/src/test/resources/org/area515/resinprinter/services/jsonToXMLTest.json
@@ -0,0 +1,189 @@
+{
+ "configuration": {
+ "name": "LC HR",
+ "machineConfig": {
+ "name": "LC HR",
+ "FileVersion": 0,
+ "PlatformXSize": 160,
+ "PlatformYSize": 120,
+ "PlatformZSize": 200,
+ "MaxXFeedRate": 0,
+ "MaxYFeedRate": 0,
+ "MaxZFeedRate": 0,
+ "XRenderSize": 2048,
+ "YRenderSize": 1536,
+ "MotorsDriverConfig": {
+ "DriverType": "Photocentric",
+ "ComPortSettings": {
+ "PortName": "Autodetect 3d printer firmware",
+ "Speed": 115200,
+ "Databits": 8,
+ "Parity": "None",
+ "Stopbits": "One",
+ "Handshake": "None"
+ }
+ },
+ "MonitorDriverConfig": {
+ "DLP_X_Res": 2048,
+ "DLP_Y_Res": 1536,
+ "MonitorID": "DISPLAY1",
+ "OSMonitorID": "Photocentric Custom Display",
+ "DisplayCommEnabled": false,
+ "ComPortSettings": {
+ "Handshake": "None"
+ },
+ "MonitorTop": 12,
+ "MonitorLeft": 11,
+ "MonitorRight": 11,
+ "MonitorBottom": 12,
+ "UseMask": false
+ },
+ "PauseOnPrinterResponseRegEx": ".*door.*open.*"
+ },
+ "slicingProfile": {
+ "gCodeHeader": "G91;\nM17;\nG1 Z10 F50;\nG1 Z-10 F50;",
+ "gCodeFooter": "M18",
+ "gCodePreslice": null,
+ "gCodeLift": "G1 Z${ZLiftDist} F${ZLiftRate};\nG1 Z-${(ZLiftDist - LayerThickness)} F180;\nM17;\n; 1500;",
+ "gCodeShutter": null,
+ "name": "Daylight Resin Hard - Red 25 micron",
+ "zliftDistanceGCode": null,
+ "zliftSpeedGCode": null,
+ "selectedInkConfigIndex": 0,
+ "DotsPermmX": 10.3937007874,
+ "DotsPermmY": 10.3937007874,
+ "XResolution": 2048,
+ "YResolution": 1536,
+ "BlankTime": 0,
+ "PlatformTemp": 0,
+ "ExportSVG": 0,
+ "Export": false,
+ "ExportPNG": false,
+ "Direction": "Bottom_Up",
+ "LiftDistance": 5,
+ "SlideTiltValue": 0,
+ "AntiAliasing": true,
+ "UseMainLiftGCode": false,
+ "AntiAliasingValue": 10,
+ "LiftFeedRate": 50,
+ "LiftRetractRate": 0,
+ "FlipX": false,
+ "FlipY": true,
+ "ZLiftDistanceCalculator": "var minLift = 4.5;\nvar value = 8.0;\nif ($CURSLICE > $NumFirstLayers) {\nvalue = minLift + 0.0015*Math.pow($buildAreaMM,1);\n}\nvalue",
+ "ZLiftSpeedCalculator": "var value = 50.0;\nif ($CURSLICE > $NumFirstLayers) {\nvalue = 100.0 - 0.02 * Math.pow($buildAreaMM,1);\n}\nvalue",
+ "ExposureTimeCalculator": "var value = $FirstLayerTime;\nif ($CURSLICE > $NumFirstLayers) {\n\tvalue = $LayerTime\n}\nvalue",
+ "SelectedInk": "Firm Amber 50 Microns",
+ "MinTestExposure": 0,
+ "TestExposureStep": 0,
+ "InkConfig": [
+ {
+ "PercentageOfPrintMaterialConsideredEmpty": 10,
+ "PrintMaterialDetector": "org.area515.resinprinter.inkdetection.visual.VisualPrintMaterialDetector",
+ "PrintMaterialDetectorSettings": {
+ "GoodSetting": "GoodValue",
+ "AnotherSetting": "AnotherValue"
+ },
+ "Name": "Castable 50 Microns",
+ "SliceHeight": 0.05,
+ "LayerTime": 20000,
+ "FirstLayerTime": 150000,
+ "NumberofBottomLayers": 4,
+ "ResinPriceL": 45
+ },
+ {
+ "PercentageOfPrintMaterialConsideredEmpty": 10,
+ "Name": "Castable 100 Microns",
+ "SliceHeight": 0.1,
+ "LayerTime": 25000,
+ "FirstLayerTime": 200000,
+ "NumberofBottomLayers": 4,
+ "ResinPriceL": 45
+ },
+ {
+ "PercentageOfPrintMaterialConsideredEmpty": 10,
+ "Name": "Firm Amber 50 Microns",
+ "SliceHeight": 0.05,
+ "LayerTime": 16000,
+ "FirstLayerTime": 200000,
+ "NumberofBottomLayers": 4,
+ "ResinPriceL": 45
+ },
+ {
+ "PercentageOfPrintMaterialConsideredEmpty": 10,
+ "Name": "Firm Amber 100 Microns",
+ "SliceHeight": 0.1,
+ "LayerTime": 20000,
+ "FirstLayerTime": 200000,
+ "NumberofBottomLayers": 4,
+ "ResinPriceL": 45
+ },
+ {
+ "PercentageOfPrintMaterialConsideredEmpty": 10,
+ "Name": "Firm Color 50 Microns",
+ "SliceHeight": 0.05,
+ "LayerTime": 20000,
+ "FirstLayerTime": 200000,
+ "NumberofBottomLayers": 4,
+ "ResinPriceL": 45
+ },
+ {
+ "PercentageOfPrintMaterialConsideredEmpty": 10,
+ "Name": "Firm Color 100 Microns",
+ "SliceHeight": 0.1,
+ "LayerTime": 25000,
+ "FirstLayerTime": 200000,
+ "NumberofBottomLayers": 4,
+ "ResinPriceL": 45
+ },
+ {
+ "PercentageOfPrintMaterialConsideredEmpty": 10,
+ "Name": "Hard Amber 50 Microns",
+ "SliceHeight": 0.05,
+ "LayerTime": 16000,
+ "FirstLayerTime": 200000,
+ "NumberofBottomLayers": 4,
+ "ResinPriceL": 45
+ },
+ {
+ "PercentageOfPrintMaterialConsideredEmpty": 10,
+ "Name": "Hard Amber 100 Microns",
+ "SliceHeight": 0.1,
+ "LayerTime": 20000,
+ "FirstLayerTime": 200000,
+ "NumberofBottomLayers": 4,
+ "ResinPriceL": 45
+ },
+ {
+ "PercentageOfPrintMaterialConsideredEmpty": 10,
+ "Name": "Hard Color 50 Microns",
+ "SliceHeight": 0.05,
+ "LayerTime": 20000,
+ "FirstLayerTime": 200000,
+ "NumberofBottomLayers": 4,
+ "ResinPriceL": 45
+ },
+ {
+ "PercentageOfPrintMaterialConsideredEmpty": 10,
+ "Name": "Hard Color 100 Microns",
+ "SliceHeight": 0.1,
+ "LayerTime": 25000,
+ "FirstLayerTime": 200000,
+ "NumberofBottomLayers": 4,
+ "ResinPriceL": 45
+ }
+ ]
+ },
+ "MachineConfigurationName": "Photocentric 10",
+ "SlicingProfileName": "Daylight Resin Hard - Red 25 micron",
+ "AutoStart": true,
+ "Calibrated": false
+ },
+ "started": true,
+ "shutterOpen": false,
+ "displayDeviceID": "Photocentric Custom Display",
+ "currentSlicePauseTime": 0,
+ "status": "Ready",
+ "printPaused": false,
+ "cachedBulbHours": null,
+ "printInProgress": false
+ }
\ No newline at end of file