Demoka M203 Arduino Kaffeemühlen Timer die Zweite

Nachdem ich die Mühle nach ihrem Umbau ein paar Tage im Einsatz hatte, bin ich zu dem Entschluss gekommen, dass der Timer eine Feineinstellung ohne Rechner benötigt. Zudem brauche ich die verschiedenen Betriebsmodi (doppelte Füllung, Abbruch des Mahlvorganges) nicht, die über verschiedene Knopfdrücke ausgelöst wurden. Sie sind dem WAF-Faktor nicht unbedingt zuträglich und irgendwie finde ich die Funktionen überflüssig. Falls ich sie wieder brauchen sollte, kann ich sie ja über den USP-Port schnell
wieder einfügen.
Für die Feineinstellung habe ich in der Bastelkiste noch ein 5K Poti aus Metall gefunden, das ich oberhalb des An/Aus Schalters anbringen möchte. Dazu muss natürlich erstmal die Mühle geöffnet werden, um das Poti zu montieren und die Kabel an den Arduino zu löten:
DSC01233 DSC01235

Was hier noch etwas wild aussieht, bekommt mit ein paar Kabelbindern und etwas Schrumpfschlauch schon gleich ein professionelleres Aussehen:DSC01242

Den Schaltplan aus dem letzten Post habe ich etwas angepasst und neu gezeichnet. Zudem habe ich noch den Pull-Down Widerstand am Taster eingezeichnet, den ich beim letzten Schaltplan vergessen hatte einzuzeichnen.
KaffeeMuehle

Den Code habe ich angepasst und vor allen Dingen die Button API rausgenommen, da sie doch einen gewissen Overhead hat und nicht wirklich gebraucht wird:


const int grinderPin = 4;
// debug with arduino led
// const int grinderPin = 13;
const int buttonPin = A1;
const int potiPin = A2;</code>

int buttonState = LOW;
int isGrinding = LOW;

unsigned long stopTime = 0;

// Grind Time in Millis
int grindTimeShort= 6000;
int grindTimeLong= 13000;

void grindOnce(int timeOffset) {
Serial.println("grindOnce() called with grindingTime:");
Serial.println(grindTimeShort + timeOffset);
stopTime = millis() + grindTimeShort + timeOffset;
isGrinding = HIGH;
digitalWrite(grinderPin, HIGH);
}

// read PotiData
int l;
int getPotiValue() {
int v = analogRead(potiPin) ; // 0-1023

// double the value to archive 2000ms difference in grinding time
Serial.println("poti Value:");
Serial.println(v);
return v*2;
}

void setup()
{
//debug
Serial.begin(9600);

// init grinderPin
pinMode(grinderPin, OUTPUT);
digitalWrite(grinderPin, LOW);
}

void loop()
{
// keep watching the grinderStart button:
int buttonReading = digitalRead(buttonPin);

// only grind if the new button state is HIGH
if (buttonReading == HIGH && isGrinding == LOW) {
Serial.println("Start grinding");
grindOnce(getPotiValue());
}

// stop the grinder if nessesary
if(millis() >= stopTime && isGrinding == HIGH){
Serial.println("Stop grinding");
isGrinding = LOW;
digitalWrite(grinderPin, LOW);
}
}

Noch ein Knopf aus Messing gedreht (gibt es natürlich auch zu kaufen..), auf das Poti montiert und fertig..
DSC01259

 

 

 

Demoka Kaffeemühle M203 Lärmschutz und Arduino Timer Umbau

Seit einiger Zeit wohnt bei mir eine Siebträgerkaffeemaschine, zu der auch eine Demoka M203 Mühle gehört.
Die Mühle mahlt sehr gut, hat aber ein paar Nachteile. Unter anderem ist sie recht laut, es landet beim Mahlen immer etwas Kaffee neben dem Siebträger und sie hat keinen Timer. In einem ersten Schritt habe ich mich deshalb um eine Dämmung und das Problem des danebenfallenden Kaffees gekümmert. Zum Dämmen habe ich die Maschine aufgeschraubt und die Maschine innen mit 8mm starken Moosgummi gedämmt:
DSC01166
Das Moosgummi habe ich einfach zurechtgeschnitten und in die Gehäusebleche eingelegt. Beim hinteren Blech muss man darauf achten, das man unten in der Mitte ein Stück ausschneidet, damit sich das Lüfterrad des Motors noch frei bewegen kann. Die Lärmentwicklung hat sich dadurch auf ein sehr erträgliches Mass reduziert, vor allem ist der unangenehme “Kreischton” weg.

Das Danebenfallen des Kaffees habe ich durch Kürzen des Einschaltknopfes und Entfernen der Siebträgerauflage behoben. Der Siebträger lässt durch das Blech nicht genau genug unter dem Kaffeeauslass positionieren, so dass der Kaffee gerade bei halbgefülltem Siebträger immer danebenfiel. Im Gegensatz zu den Lösungen mit Auffangbehältern wird das Problem behoben und der optische Eindruck der Mühle bleibt erhalten.
DSC01162
Das Blech habe ich einfach gelöst, indem ich die beiden Nieten mit einem 3,5mm Bohrer aufgebohrt habe. Die Löcher habe ich dann wieder mit zwei 4mm Blindnieten geschlossen. Der Knopf des Schalters steht etwas zu weit vor, was zum einen etwas “wackelig” aussieht und zum anderen ebenfalls verhindert, das man den Siebträger mittig unter den Kaffeeauslass halten kann. Den Knopf habe ich einfach abgezogen und den Teil, der in den Schalter gesteckt wird, einfach mit einem scharfen Messer um ca. 2mm gekürzt. Beim Wiederaufstecken habe ich den Schalterstift mit dem Messer festgehalten, damit der Schalter blockiert ist und ich den Knopf richtig festdrücken kann

DSC01165DSC01164

 

 

 

 

 

 

 

 

Mit diesen einfachen Massnahmen kann man den Siebträger so halten, dass so gut wie nichts mehr daneben fällt. Das macht die Demoka meines Erachtens zu einer sehr brauchbaren, preiswerte Mühle. Leider muss man jedoch die Kaffeemenge immer noch per Augenmass bestimmen, da sie ja keinen Timer hat. Also musste ich auch hier etwas tun.. Ausserdem ist eine rein analoge Mühle ja nichts für einen Informatiker. In meinem Bastelfundus befand sich noch ein kleiner Arduino nano (ca. 10€ bei ebah) der für solche einfachen Timeraufgaben fast schon etwas zu mächtig ist, aber dafür natürlich alle Möglichkeiten offen lässt. Und er war halt  gerade in der Bastelkiste 😉 Zunächst mal ist in der Mühle wenig  Platz zum Unterbringen der Bauteile, wie man auf dem nachfolgenden Bild vlt. erkennen kann:

DSC01168

Ich wollte gerne alles in der Mühle haben, um den optischen Gesamteindruck zu erhalten und nicht irgendetwas Unnötiges in der Küche rumstehen zu haben. Als Netzteil habe ich deshalb ein altes elektronisches Ladegerät (z.b.: Handy, Kamera etc.) benutzt. Diese sind meist klein, kosten nichts und passen gut zum Arduino. Zudem habe ich noch einen Netzschalter in das Gehäuse eingebaut und USB Anschluss nach aussen geführt. um den Arduino programmieren zu können. Auf einen Poti zum Einstellen der Laufzeit und eine Anzeige habe ich verzichtet. Der Arduino ist per Notebook schnell programmiert und ich wollte den Aufwand gering halten. (Inzwischen geändert, siehe hier) Den Netzschalter habe ich hinten rechts in das Gehäuse eingesetzt, die USB Dose (Weibchen) ebenfalls. Als USB Dose habe ich einfach eine alte USB Verlängerung benutzt, die ich mit 2 Komponentenkleber in das Gehäuseblech eingeklebt habe:
DSC01189

Wie man auf dem Bild erkennen kann, schalte ich den Motor mit einem mechanischen Relais. Ein elektronisches geht auch und braucht keine Zusatzschaltung, um vom Arduino angesteuert zu werden, ist aber viel teurer und war leider in meiner Bastelkiste nicht vorhanden.. Ich habe also ein Omron G2R-2 mit 5A Schaltstrom verwendet, das ich mit einem Transistor 2N2222 und einer Diode 1N4007 als Prellschutz für den Arduino ansteuere.  Hier der Schaltplan:
DSC01224

Das Ganze habe ich auf einem Steckbrett vertestet und dann “inline” zusammengelötet.
DSC01171 DSC01187
und mit reichlich Schrumpfschlauch möglichst platzsparend zusammengepackt:
DSC01196DSC01200

 

Die ganze Elektrik habe ich mit Kabelbindern zusammen an der Innenseite der Platte befestigt, die auch den Schalter trägt (im Bild ist es noch an der Platte).
DSC01205

Anschliessend die Gehäuseteile wieder angebaut und den Arduino programmiert:
DSC01207DSC01208

Hier das Programm für den Arduino:

#include OneButton.h

OneButton button(A1, true);
const int grinderPin = 4;
unsigned long stopTime = 0;

// Grind Time in Millis
int grindTimeShort= 7000;
int grindTimeLong= 13000;

void oneClick() {
stopTime = millis() + grindTimeShort;
digitalWrite(grinderPin, HIGH);
}

void doubleClick() {
stopTime = millis() + grindTimeLong;
digitalWrite(grinderPin, HIGH);
}

void buttonPress() {
digitalWrite(grinderPin, LOW);
}

void setup()
{
// init grinderPin
pinMode(grinderPin, OUTPUT);
digitalWrite(grinderPin, LOW);

// link the doubleclick function to be called on a doubleclick event.
button.attachClick(oneClick);
button.attachDoubleClick(doubleClick);
button.attachPress(buttonPress);
}

void loop()
{
// keep watching the grinderStart button:
button.tick();

// stop the grinder if nessesary
if(millis() >= stopTime){
digitalWrite(grinderPin, LOW);
}
}

Ich habe der Einfachheit halber die Onebutton Bibliothek von Matthias Hertel verwendet. Wird der Knopf einmal gedrückt, wird der Kaffee für einen 1fach Siebträger, bei einem doppelten Drücken für einen 2fach Siebträger gemahlen. Dadurch können unterschiedliche Zeiten und Kaffeemengen eingestellt werden. Längeres Drücken beendet den Mahlvorgang vorzeitig. Ich brauche nun mit dem Siebträger den Knopf nur kurz anzutippen und die Mühle mahlt 7 Gramm, während ich das herabfallende Mehl durch Bewegen optimal im Siebträger verteilen kann.
Das Ganze funktioniert bisher einwandfrei, jedoch habe ich den Doppelklick und den Buttonhold auskommentiert. Eventuell rüste ich noch einen Poti nach, bisher musste ich die Mahldauer jedoch noch nicht so oft umstellen, als dass mich das Anschliessen des Notebooks gestört hätte. Alles in allem ein sehr lohnenswerter Umbau, der die Mühle sehr flexibel und millisekundengenau macht. Ausserdem finde ich den USB Anschluss an der Mühle cool 🙂

Update: Inzwischen habe das ganze nochmal geändert, hier gehts weiter.

Custom Loginpage with SpringSecurity 3, JSF2 and Facelets

These days I tried to integrate a custom login page into my Web Application. I found a lot of hints in the Web
using a a plain form tag for the Login page but nothing was working and I always got a errors like “No navigation case match for viewId action j_spring_security_check and outcome…” I didn’t get it work by using the j_security_check mechanism, therefore I used a custom bean and controller.

Here is the solution:

my web.xml:


       <context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>
            /WEB-INF/applicationContext.xml,
            /WEB-INF/applicationsContext-Security.xml
        </param-value>
	</context-param>

	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	<filter>
		<filter-name>springSecurityFilterChain</filter-name>
		<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>springSecurityFilterChain</filter-name>
		<url-pattern>/*</url-pattern>
		<dispatcher>FORWARD</dispatcher>
   		<dispatcher>REQUEST</dispatcher>
	</filter-mapping>

the applicationContext.xml:


        <bean scope="session" id="loginBean" class="com.XXX.security.LoginBean">
		<aop:scoped-proxy />
	</bean>
	<bean scope="singleton" id="loginController"
		class="com.XXX.security.LoginController">
		<property name="loginBean" ref="loginBean" />
		<property name="authenticationManager" ref="authenticationManager" />
	</bean>

the applicationContext-Security.xml:


	<sec:http auto-config='true' use-expressions="true">

		<sec:intercept-url pattern="/xhtml/loggedout.xhtml" access="permitAll" />
		<sec:intercept-url pattern="/xhtml/timeout.xhtml" access="permitAll" />
		<sec:intercept-url pattern="/xhtml/login.xhtml" access="permitAll" />
		<sec:intercept-url pattern="/xhtml/**" access="isAuthenticated()" />
			
		<sec:form-login login-page="/xhtml/login.xhtml"  />
			
	</sec:http>

	<!-- AuthenticationManager - add your User-Service-->
	<authentication-manager alias="authenticationManager">
		<authentication-provider user-service-ref="XXXXXX">
			<password-encoder hash="md5" />
		</authentication-provider>
	</authentication-manager>

the Form: /xhtml/login.xhtml

		
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
	xmlns:h="http://java.sun.com/jsf/html"
	xmlns:my="http://www.XXXX.com/my/facelets"
	xmlns:f="http://java.sun.com/jsf/core"
	xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:security="http://www.springframework.org/security/facelets/tags">


<my:myMainTemplate title="Login" class="mainContent" >
      
       <h:form id="loginForm" prependId="false">
        	
            <h:panelGrid columns="2">  
                <h:outputLabel for="j_username" value="User: " />  
                <h:inputText id="j_username" value="#{loginBean.userName}" required="true" requiredMessage="Username !"/>  
                <h:outputLabel for="j_password" value="Password: " />  
                <h:inputSecret id="j_password" value="#{loginBean.password}" required="true" requiredMessage="Passwort !"/>  
				<h:commandButton id="submitButton" value="Start" action="#{loginController.login}" />  
            </h:panelGrid>  
        </h:form>  
</my:myMainTemplate>
	
</html>

my beans – loginBean.java:


package com.XXXX.security;

import java.io.Serializable;
public class LoginBean  implements Serializable{

	private static final long serialVersionUID = 6175255537226165236L;

	private String userName;
	private String password;
	
	
	public void reset(){
		userName = "";
		password = "";
	}
		
	
... getter/setter...

}

and loginController.java


package com.XXXX.security;

import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;


public class LoginController {
	
	private static final Log LOG = LogFactory.getLog(LoginController.class);

        // beans used by this controller, injected by spring
	private LoginBean loginBean;
	private AuthenticationManager authenticationManager;
	
        /**
	 * the login action called by the view
	 * @return
	 */
	public String login() {
		try{
		LOG.info("Login started for User with Name: "+getLoginBean().getUserName());
		
		 // check if userdata is given 
		 if (getLoginBean().getUserName() == null || getLoginBean().getPassword() == null) {
	            FacesMessage facesMsg = new FacesMessage(
	            FacesMessage.SEVERITY_ERROR, "Error", "login.failed" );
	            FacesContext.getCurrentInstance().addMessage(null, facesMsg);
	            LOG.info("Login not started because userName or Password is empty: "+getLoginBean().getUserName());
	            return null;
	        }
	       
		 // authenticate afainst spring security
		 Authentication request = new UsernamePasswordAuthenticationToken(
				 getLoginBean().getUserName(), getLoginBean().getPassword());            
	            
	        Authentication result = authenticationManager.authenticate(request);
	        SecurityContextHolder.getContext().setAuthentication(result);
	 
	    } catch (AuthenticationException e) {
	    	LOG.info("Login failed: " + e.getMessage());
	        FacesMessage facesMsg = new FacesMessage(
	            FacesMessage.SEVERITY_ERROR, "Error", "login.failed") ;
	        FacesContext.getCurrentInstance().addMessage(null, facesMsg);
	            
	        return null;
	    }
	    return "success";
		
	}

	
	/**
	 * @return the loginBean
	 */
	public LoginBean getLoginBean() {
		return loginBean;
	}


	/**
	 * @param loginBean the loginBean to set
	 */
	public void setLoginBean(LoginBean loginBean) {
		this.loginBean = loginBean;
	}

	/**
	 * @return the authenticationManager
	 */
	public AuthenticationManager getAuthenticationManager() {
		return authenticationManager;
	}

	/**
	 * @param authenticationManager the authenticationManager to set
	 */
	public void setAuthenticationManager(AuthenticationManager authenticationManager) {
		this.authenticationManager = authenticationManager;
	}
}

and Last but not least, add navigationRules to your
faces-config.xml:


<navigation-rule>
  <from-view-id>/xhtml/login.xhtml</from-view-id>
    <navigation-case>
      <from-outcome>success</from-outcome>
	 <to-view-id>/xhtml/main/main.xhtml</to-view-id>
   </navigation-case>
 </navigation-rule>