Commits

Sebastian Bub committed deb0084

RandomAccessFileSimulation added and some formatting

Comments (0)

Files changed (15)

   a port on for a defined period of time, it can be done with a single request). 
 * You can set a simulation mode for testing your client.
 * Setting multiple ports in one requests are set one after another, but the code
-  is optimized and nothing unnecessary is done in between (it takes about 2-5ms on an idle Raspberry Pi to set all 17 ports, a simple 'find /' in the background will slow it down to 10-15ms).
+  is optimized and nothing unnecessary is done in between (it takes about 2-5ms on an idle Raspberry Pi to set all 17 ports, some artificial load (e.g.'find /' in the background) will slow it down to 10-15ms).
 * Cronjobs (exact to the second) for output ports (http://quartz-scheduler.org/documentation/quartz-2.1.x/tutorials/crontrigger). Output ports can be set conditionally and you have a simple but powerful semaphore mechanism.
 
 ### Planned Features
 
-* Sunset and sunrise aware cronjobs, e.g. for lights (see http://lexikon.astronomie.info/zeitgleichung/)
 * More status information via web about configuration.
 
 ### Possible Unplaned Features
 
 * Bit sequences (especially with AUTO.TOGGLE.TIME) for serial output or to control a servo
 * Some kind of PLC (Programmable Logic Controller)
+* Sunset and sunrise aware cronjobs, e.g. for lights (see http://lexikon.astronomie.info/zeitgleichung/). You can use a photoresistor and analog input ports instead.
 * Make your suggestions via email
 
 
 ## Project Status
 
-The project has just started and the author uses it by himself mainly for
-simple output (manually and with cronjobs). If I had to give it a release number, I would say it is a 0.81 release. Bug reports and feature requests are welcome.
+The project has started and I use it by myself mainly for
+simple output (manually and with cronjobs). If I had to give it a release number, I would say it is a 0.85 release. Bug reports and feature requests are welcome.
 
 
 ## Getting Started
 
     It will return {"g1":0,"g0":1} (and you may check the log).
     
-12. Stop the servlet container `sudo ./stop.sh`. Make sure that there are no old Winstone processes with old configurations running! You can check that with `ps -ef| grep java`.
+12. Stop the servlet container `sudo ./stop.sh`. Make sure that there are no old Winstone processes running with old configurations! You can check that with `ps -ef| grep java`.
 
 13. To enable cronjobs, you need to copy cron.conf.MAY_BE_CHANGED to cron.conf and configure it
 
 ## Going Productive
 
 * Make sure you **disable simulate mode**.
-* Turn down logging after testing in log4j.properties.
+* Turn down logging after testing in log4j.properties (this is important for performance on your Raspberry Pi).
 * A simple init.d script is also provided in src/main/resources/init.d/gpio-winstone which can be installed with the follwing commands:
 
 ```
 
 A simple but powerful circuit can be found at [raspberrypi-spy.co.uk](http://www.raspberrypi-spy.co.uk/2012/08/reading-analogue-sensors-with-one-gpio-pin/) which makes analog inputs simple to read and handle in software.
 
-Please also read comments in gpio.conf.
+**Please also read comments in gpio.conf about the internal processing.**
 
-Although it is an analog input port, you must explicitly start reading the value (IN) because it may be slow (and blocks your port and your http request). The last read value is saved internally and can be used afterwards with conditions (initial value is -1). Keep in mind that reading may be slow. With manual control, your response may be slow (since reading blocks the port). With automatic control, make sure that the read process has finished or you may check against a previous value.
+Although it is an analog input port, you must explicitly start reading the value (IN) because it may be slow (and blocks your port and your http request). The last read value is saved internally and can be used afterwards with conditions (initial and error value is -1). Keep in mind that reading may be slow. With manual control, your response may be slow (since reading blocks the port). With automatic control, make sure that the read process has finished or you may check against a previous value.
 
 #### Example:
 
 
 `0 */15 16-21 ? * * :: analogSensor2ti=IN`
 
-`0 */15 16-21 ? * * :: analogSensor2ti>10000&lamp1out=1`
+`0 */15 16-21 ? * * :: analogSensor2ti!=-1&analogSensor2ti>10000&lamp1out=1`
 
-Given your analogSensor2ti takes 10 seconds to be read at maximum, the following example is better:
+Given your analogSensor2ti takes 1200 milliseconds to be read at maximum, the following example is better:
 
 `0 */15 16-21 ? * * :: analogSensor2ti=IN`
 
-`15 */15 16-21 ? * * :: analogSensor2ti>10000&lamp1out=1`
+`3 */15 16-21 ? * * :: analogSensor2ti!=-1&analogSensor2ti>10000&lamp1out=1`
+
+You can also do it all at once:
+
+`0 */15 16-21 ? * * :: analogSensor2ti=IN&analogSensor2ti!=-1&analogSensor2ti>10000&lamp1out=1`
 
 #### Please Note
 

gpio.conf.MUST_BE_CHANGED

 #       2. It is set to the value of DEFAULT.STATE (required).
 #       3. Wait for the time specified in AUTO.TOGGLE (required).
 #       4. It is reconfigured to be an input port.
-#       5. It is read (in a while loop as fast as possible (max is ti.max.counter),
-#          until a value of (required) not-DEFAULT.STATE is returned.
-#       6. The counter of the while loop is returned (which can be checked with conditions).
+#       5. It is read (in a while loop as fast as possible until a value of (required) not-DEFAULT.STATE is returned.
+#       6. The counter of the while loop is returned (which can be checked with conditions afterwards) or -1
+#          if ti.max.counter is reached.
 #       7. It stays an input port until next read.
-# - choose a name which does not get you in trouble addressing it
-#   as a http parameter (umlaut, spaces), e.g. sensor1
+# - choose a name which does not get you in trouble addressing it as a http parameter (umlaut, spaces), e.g. sensor1
 # - if you do not set the default state, it is defined by the OS (default state
 #   is required on auto toggle and TI direction)
-# - auto toggle interval is specified in ms (requires default state),
-#   e.g. if you set a gpio, it will automatically flip to default state
-#   after specified time. auto toggle must be smaller than block time
-# - block time is specified in ms, e.g. you can not set the gpio again during
-#   this interval (ensure that user does not "click multiple times")
-# - choose an appropriate block time for every TI port (or unpredictable things
-#   may happen depeding on your hardware)
-# - only comment out those ports that you need (as they are set up)
-#   on startup (they are exported in sysfs)
+# - auto toggle interval is specified in ms (requires default state), e.g. if you set a gpio, it will automatically
+#   flip to default state after specified time. auto toggle must be smaller than block time
+# - block time is specified in ms, e.g. you can not set the gpio again during  this interval (ensure that user does
+#   not "click multiple times")
+# - choose an appropriate block time for every TI port (or unpredictable things may happen depending on your hardware)
+# - only comment out those ports that you need (as they are set up) on startup (they are exported in sysfs)
 
 # you must set simulate to false for the real world
 simulate.gpios=true
 
-# if direction TI, max looping (it is about 2500ms)
+# if direction TI, no of max reads in loop; defaults to 100000 (it is about 2500ms)
 ti.max.counter=100000
 
 #GPIO.0 = P1-03

src/main/java/de/derbub/rpigpio/SimpleCommand.java

             log.debug("read in " + param);
             Matcher m = CONDITION_OR_ASSIGN_PATTERN.matcher(param);
             m.matches();
-            if(m.groupCount() != 3){
+            if (m.groupCount() != 3) {
                 log.warn("ignoring command _" + param + "_ Regexp does not match.");
                 continue;
             }

src/main/java/de/derbub/rpigpio/SingleContext.java

 
         String tiMaxCounterString = configProperties.getProperty(CONFIG_TI_MAX_COUNTER_KEY);
         int tiMaxCounter = 100000;
-        try{
+        try {
             tiMaxCounter = Integer.parseInt(tiMaxCounterString);
             log.debug(CONFIG_TI_MAX_COUNTER_KEY + " is set to " + tiMaxCounter);
-        } catch(NumberFormatException e){
+        } catch (NumberFormatException e) {
             log.error(CONFIG_TI_MAX_COUNTER_KEY + " is not a number. Using default (" + tiMaxCounter + ").");
         }
 

src/main/java/de/derbub/rpigpio/cron/CronConfigLine.java

 package de.derbub.rpigpio.cron;
 
 import de.derbub.rpigpio.SimpleCommand;
-import de.derbub.rpigpio.SingleContext;
 import org.apache.log4j.Logger;
 
 import java.util.ArrayList;
 
-/** Helper class which takes a crontab line, checks validity and has
- *  some access helpers
+/**
+ * Helper class which takes a crontab line, checks validity and has
+ * some access helpers
  *
  * @author sb
  */
 
     /**
      * constructor is doing the parsing already
-     * 
+     *
      * @param line String one config line
      */
     public CronConfigLine(String line) {
                 String tmpCommand = lineParts[1];
 
                 if (null != tmpSchedule && tmpSchedule.trim().length() > 0
-                        && null != tmpCommand && tmpCommand.trim().length() > 0) {
+                    && null != tmpCommand && tmpCommand.trim().length() > 0) {
                     schedule = tmpSchedule.trim();
                     command = tmpCommand.trim();
                     commandList = SimpleCommand.createCommandList(command);
 
     /**
      * checks if line looks valid (actually it has a "::" in the middle)
-     *
+     * <p/>
      * comment is false, too
      *
      * @return boolean if it is a valid line
      */
     public boolean isValid() {
         return (null != schedule && schedule.length() > 0
-                && null != command && command.length() > 0);
+            && null != command && command.length() > 0);
     }
 
     public String getSchedule() {

src/main/java/de/derbub/rpigpio/cron/CronjobAdapter.java

 import static org.quartz.JobBuilder.newJob;
 import static org.quartz.TriggerBuilder.newTrigger;
 
-/** 
+/**
  * Class handles config read and creation/cleanup of quartz scheduler
  *
  * @author sb
                 scheduler = StdSchedulerFactory.getDefaultScheduler();
                 scheduler.start();
 
-                for (CronConfigLine ccl : cronLines)
-                {
+                for (CronConfigLine ccl : cronLines) {
                     JobDetail job = newJob(GPIOOutJob.class).build();
                     job.getJobDataMap().put(GPIOOutJob.COMMAND_KEY, ccl);
                     CronTrigger trigger = newTrigger().withSchedule(cronSchedule(ccl.getSchedule())).build();

src/main/java/de/derbub/rpigpio/gpio/ConfiguredGpio.java

     private long blockTime = NO_DEFAULT_TIME;
 
     public ConfiguredGpio(GpioPin gpioPin, Direction direction, String name,
-            String defaultState, String autoToggleTime, String blockTime) {
+                          String defaultState, String autoToggleTime, String blockTime) {
         this.gpioPin = gpioPin;
         this.direction = direction;
         this.userdefinedName = name;
         // auto toggle requires defaultState and blockTime > autoToggleTime
         setAutoToggleTime(autoToggleTime);
 
-        if(Direction.TI.equals(direction)){
+        if (Direction.TI.equals(direction)) {
             // autoToggleTime and blockTime and defaultState are required
-            if(ConfiguredGpio.NO_DEFAULT_TIME == this.autoToggleTime
+            if (ConfiguredGpio.NO_DEFAULT_TIME == this.autoToggleTime
                 || ConfiguredGpio.NO_DEFAULT_TIME == this.blockTime
                 || ConfiguredGpio.NO_STATE == this.defaultState) {
                 this.direction = Direction.IN;
     @Override
     public String toString() {
         return "[" + gpioPin + ":" + direction + ":" + userdefinedName
-                + (NO_STATE == getDefaultState() ? "" : " default:" + getDefaultState())
-                + (NO_DEFAULT_TIME == getAutoToogleTime() ? "" : " toggle:" + getAutoToogleTime())
-                + (NO_DEFAULT_TIME == getBlockTime() ? "" : " block:" + getBlockTime())
-                + "]";
+            + (NO_STATE == getDefaultState() ? "" : " default:" + getDefaultState())
+            + (NO_DEFAULT_TIME == getAutoToogleTime() ? "" : " toggle:" + getAutoToogleTime())
+            + (NO_DEFAULT_TIME == getBlockTime() ? "" : " block:" + getBlockTime())
+            + "]";
     }
 }

src/main/java/de/derbub/rpigpio/gpio/Direction.java

     IN("in"), OUT("out"), TI("ti");
     private String directionString;
 
-    public static final String CONDITIONAL_INPUT="CIN";
+    public static final String CONDITIONAL_INPUT = "CIN";
 
     private Direction(String directionString) {
         this.directionString = directionString;

src/main/java/de/derbub/rpigpio/gpio/GpioOutAutoToggleThread.java

             absSleepTime = tmpSleepTime;
             try {
                 currentDeltaSleepTime -= sysfsProcessingTime; // remove some time for processing here
-                if(currentDeltaSleepTime > 0) {
-                    log.debug("run() sleep for " +currentDeltaSleepTime +" ms");
+                if (currentDeltaSleepTime > 0) {
+                    log.debug("run() sleep for " + currentDeltaSleepTime + " ms");
                     sleep(currentDeltaSleepTime);
                 } else {
                     log.debug("run() not sleeping already late by " + currentDeltaSleepTime + " ms");
                 log.error("run() interrupted (shutdown in progress?). Thread is stopped!");
                 break;
             }
-            long before=System.currentTimeMillis();
+            long before = System.currentTimeMillis();
             // after delta time, find notable
             Map<ConfiguredGpio, String> outGpioValueMap = new HashMap<ConfiguredGpio, String>();
             for (ConfiguredGpio gpio : gpioList) {
             // remove notable from list and handle them
             gpioList.removeAll(outGpioValueMap.keySet());
             GpioSysFsInterface.getInitializedInstance().writeGpios(outGpioValueMap, true);
-            sysfsProcessingTime = System.currentTimeMillis()-before;
+            sysfsProcessingTime = System.currentTimeMillis() - before;
         }
         log.debug("run() finished");
     }

src/main/java/de/derbub/rpigpio/gpio/GpioSysFsInterface.java

      * methods creates a singleton
      *
      * @param configuredNameGpioMapParam Map with the configured ports
-     * @param tiMaxCounterParam max counter for read loop for direction TI
+     * @param tiMaxCounterParam          max counter for read loop for direction TI
      * @return a single instance of GpioSysFsInterface or throws a RuntimeException if called twice
      */
     public static GpioSysFsInterface getInstance(Map<String, ConfiguredGpio> configuredNameGpioMapParam, int tiMaxCounterParam) {
             try {
                 aGpioFile = FileHandlingFactory.getFile(SYSFS_GPIO_FILE_PREFIX + gpio.getGpioPin().getNumber() + SYSFS_GPIO_VALUE_FILE_SUFFIX);
                 if (aGpioFile.exists()) {
-                    // TODO use FileHandlingFactory to create RandomAccessFile for simulation
-                    aGpioRandomAccessFile = new RandomAccessFile(aGpioFile, "r");
+                    aGpioRandomAccessFile = FileHandlingFactory.getRandomAccessFile(aGpioFile, "r");
                     long before = System.currentTimeMillis();
                     for (int i = 0; i < tiMaxCounter; i++) {
                         aGpioRandomAccessFile.seek(0);
                         }
                     }
                     if (tiMaxCounter == counter) {
-                        counter = 0;
+                        counter = ConfiguredGpio.NO_STATE;
                         log.warn("Max reached (" + tiMaxCounter + "). Check your configuration or hardware: Setting value to "
                             + counter + " (within " + (System.currentTimeMillis() - before) + "ms) for "
                             + gpio + (FileHandlingFactory.isSimulation() ? " (SIMULATION)" : ""));

src/main/java/de/derbub/rpigpio/gpio/files/FileHandlingFactory.java

     public static void setSimulateGpios(Boolean b) {
         SIMULATE_GPIOS = b;
     }
-    
-    public static boolean isSimulation(){
+
+    public static boolean isSimulation() {
         return SIMULATE_GPIOS;
     }
 
             return new FileReader(f);
         }
     }
+
+    public static RandomAccessFile getRandomAccessFile(File f, String mode) throws FileNotFoundException {
+        if (SIMULATE_GPIOS) {
+            log.info("Return FileReaderSimulation for " + f);
+            return new RandomAccessFileSimulation(f, mode);
+        } else {
+            log.debug("Return FileReader for " + f);
+            return new RandomAccessFile(f, mode);
+        }
+    }
+
 }

src/main/java/de/derbub/rpigpio/gpio/files/FileSimulation.java

 
 /**
  * a File mock for simulation
- *
+ * <p/>
  * it acts as if a lot is possible
  *
  * @author sb

src/main/java/de/derbub/rpigpio/gpio/files/FileWriterSimulation.java

  *
  * @author sb
  */
-public class FileWriterSimulation extends FileWriter{
+public class FileWriterSimulation extends FileWriter {
 
     public FileWriterSimulation(FileDescriptor fd) {
         super(fd);
     @Override
     public void write(String string) throws IOException {
     }
-    
+
 }

src/main/java/de/derbub/rpigpio/gpio/files/RandomAccessFileSimulation.java

+/*
+ *
+ * Copyright 2012 der-bub.de
+ * http://www.der-bub.de
+ * Author: Sebastian Bub (sebastian@der-bub.de)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package de.derbub.rpigpio.gpio.files;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+/**
+ * a RandomAccessFile mock for simulation
+ * <p/>
+ * minimum is implemented to simulate TI direction
+ *
+ * @author sb
+ */
+public class RandomAccessFileSimulation extends RandomAccessFile {
+
+    public RandomAccessFileSimulation(String name, String mode) throws FileNotFoundException {
+        super(name, mode);
+        throw new RuntimeException("Not yet implemented");
+    }
+
+    public RandomAccessFileSimulation(File file, String mode) throws FileNotFoundException {
+        super(new FileSimulation("/bin/sh"), "r");
+    }
+
+    @Override
+    public int read() throws IOException {
+        return 49;
+    }
+
+    @Override
+    public int read(byte[] b, int off, int len) throws IOException {
+        throw new RuntimeException("Not yet implemented");
+    }
+
+    @Override
+    public int read(byte[] b) throws IOException {
+        throw new RuntimeException("Not yet implemented");
+    }
+
+    @Override
+    public int skipBytes(int n) throws IOException {
+        throw new RuntimeException("Not yet implemented");
+    }
+
+    @Override
+    public void write(int b) throws IOException {
+        throw new RuntimeException("Not yet implemented");
+    }
+
+    @Override
+    public void write(byte[] b) throws IOException {
+        throw new RuntimeException("Not yet implemented");
+    }
+
+    @Override
+    public void write(byte[] b, int off, int len) throws IOException {
+        throw new RuntimeException("Not yet implemented");
+    }
+
+    @Override
+    public long getFilePointer() throws IOException {
+        throw new RuntimeException("Not yet implemented");
+    }
+
+    @Override
+    public void seek(long pos) throws IOException {
+        // do nothing on purpose
+    }
+
+    @Override
+    public long length() throws IOException {
+        throw new RuntimeException("Not yet implemented");
+    }
+
+    @Override
+    public void setLength(long newLength) throws IOException {
+        throw new RuntimeException("Not yet implemented");
+    }
+
+    @Override
+    public void close() throws IOException {
+        super.close();
+    }
+}

src/main/java/de/derbub/rpigpio/web/GpioServlet.java

      * will send you the state of each requested and known pin as json, or -1 if
      * you tried to set an input pin or tried to read an output pin or -2 if pin
      * is within blocked time
-     *
+     * <p/>
      * e.g. {"g0":0,"g1":1,"g4":-1}
      *
-     * @param req HttpServletRequest
+     * @param req  HttpServletRequest
      * @param resp HttpServletResponse
      * @throws javax.servlet.ServletException
      * @throws java.io.IOException
      */
     @Override
     public void doGet(HttpServletRequest req, HttpServletResponse resp)
-            throws ServletException, IOException {
+        throws ServletException, IOException {
         log.info("Get request for " + req.getRequestURI()
-                + "?" + req.getQueryString() + " from " + req.getRemoteAddr());
+            + "?" + req.getQueryString() + " from " + req.getRemoteAddr());
 
         // set/get hardware values
         ArrayList<SimpleCommand> commandArray = SimpleCommand.createCommandList(req.getQueryString());
 
     @Override
     public void doPost(HttpServletRequest req, HttpServletResponse resp)
-            throws ServletException, IOException {
+        throws ServletException, IOException {
         log.debug("Handle post as get");
         this.doGet(req, resp);
     }