Automatische Klappe in Action

Post: automatische Hühnerstallklappe

No Comments

automatische Hühnerstallklappe mit AVR ATMega32

Morgens mit den Hühnern aufstehen um selbige aus dem Stall zu lassen, der Alptraum aller Langschläfer.
Sowas muss sich doch automatisieren lassen, oder?

Version 1 – für Nicht-Informatiker:

Wir kaufen eine kommerzielle Lösung, Kosten ca. 150 Euro, geschätzte Montagedauer ca. 3 Stunden.

Version 2 – die Informatiker Lösung:

“Kaufen? Viel zu teuer! Dass kann ich doch selber bauen!”
Man nehme:

  • AVR Evaluation Board (Pollin) ca. 15 Euro
  • AVR ATMega32 ca. 4 Euro
  • Netzteil 9V (Pollin) ca. 5 Euro
  • Spindelmotor (Ebay) ca. 20 Euro
  • Motortreiberplatine (L298) (Ebay) 8 Euro
  • diverse Kleinteile (Fotodiode, Kabel, etc.) 10 Euro
  • Atmel AVR Studio
  • PonyProg
  • avrlib
  • AVR C-Compiler
  • Firmware Entwicklungskosten … unbezahlbar :)

Als Basis habe ich ein ATMEL Evaluations-Board von Pollin mit einem AVR ATMega32 in meiner Bastelkiste gefunden. Mein Jtag ICE CLone hat leider den Dienst verweigert und war einfach nicht zum debuggen zu bewegen, warum weiss ich auch nicht.
Zum Glück hatte das Board einen ISP Anschluss via RS232 und so konnte ich die Firmware mittels PonyProg auf den Chip bringen.

Hardware

Zum Öffnen der Hühnerklappe braucht man natürlich einen Motor, aber die Klappe mittels einer Schnur die auf- und abgewickelt wird zu öffnen oder zu schliessen schien mir etwas fehleranfällig und kompliziert im mechanischen Aufbau. Auch andere Selbstbauten haben mich nicht so richtig überzeugt. Nicht dass es dann bei der Frage “Wieviele Hühner habt ihr eigentlich?” heisst: “20 … und ein paar zerquetschte!”
Ein Spindelmotor der die Klappe rauf und runter dreht sollte wesentlich einfacher im Aufbau und störsicherer im Betrieb sein.
Ebay sei dank konnte ich ein günstiges Exemplar für unter 20 Euro incl. Versand erstehen.

Um den Motor zu steuern braucht man eine eine entsprechende Schaltung, in Fachkreisen auch H-Brücke genannt. So was kann man mit einem IC, ein paar Widerständen und Schutzdioden ganz einfach selber zusammenbauen. Da ich allerdings als ‘Softi’ von Hardware relativ wenig (bis überhaupt keine) Ahnung habe ist es günstiger und zeitsparender eine fertige Lösung zu benutzen. Für 8 Euro gibts eine industriell gefertigte Platine mit einem L298 Motortreiber der bis 46V und 4A vertragen kann. Dass sollte für eine kleine Hühnerklappe vorerst genügen und bietet noch ausreichend Reserven falls die Tierchen mal grösser werden.
Für die Erkennung ob die Klappe offen bzw. geschlossen ist sind zwei Reedrelais nötig. Ein Magnet der an der Klappe angebracht ist schaltet die Kontakte.

Zur Unterscheidung von Tag/Nacht bzw. Hell/Dunkel braucht es noch eine Art Lichtsensor. Sowas gibt es zwar auch als Bausatz, aber da sich der Mikroprozessor eh die meiste Zeit langweilen wird kann man ja auch den ADC (Analog-Digital-Converter) mittels einer Fotodiode verwenden.
Eine Fotodiode oder auch LDR ist ein lichtabhängiger Widerstand, d.h. je dunkler es ist desto mehr Widerstand hat die Diode bzw. je heller desto weniger. Dadurch fällt mehr oder weniger Spannung an diesem Widerstand ab. Mehr elektrotechnische Grundlagen habe ich auch nicht.
Ich weiss halt nur … es funkioniert. Die “Schaltung” ist minimalistisch:
5V — LDR — ADC0-Pin — 10k Widerstand — Masse
Am ADC des Microprozessors kann man dann die Spannung die am ADC0-Pin anliegt messen. Je heller desto mehr Spannung, je dunkler desto weniger.

Die Hardware Seite ist damit erledigt. Aber die Intelligenz der Steuerung liegt ja in der Software.
Deshalb ist Softwareentwicklung ja auch so teuer! ;)

Hardware

Fertiger Aufbau im ‘Labor’
Aufbau im 'Labor'

Software

Jetzt kommt der spassige Teil!

Einer von vielen Versuchen:
System states

Helligkeitssensor
Um Ungenauigkeiten die z.B. durch vorbeifahrende Autos und deren Scheinwerfer verursacht werden können zu vermeiden benutze ich den Mittelwert aus n Samples. Bei einer genügend grossen Anzahl von Samples sollten sich Messungenauigkeiten somit einigermassen “glätten” lassen.
Ein Sample besteht aus 3 Messungen, jedes Sample wird in einen Ringbuffer gefüttert, aus diesem wird der Mittelwert zur Helligkeitsbestimmung ermittelt.
Mit einem intelligenten Algorithmus könnten man dass natürlich wesentlich effizienter und einfacher erledigen, allerdings will ich nur eine Klappe für einen Hühnerstall steuern … und keinen Flugzeugträger. ;)
Die Min/Max Grenzwerte für Tag und Nacht sind im EEPROM abgelegt um den Sensor einfacher kalibrierbar zu machen ohne jedesmal die Firmware neu kompilieren zu müssen.

Der Zustandsautomat für die Helligkeitsmessung hat 4 Zustände:

UNKNOWN: nicht genügend Werte
DAY: wenn es Tag ist
DUSKDAWN: Übergang von Tag <-> Nacht
NIGHT: wenn es Nacht ist

Sensoren bzw. Taster
Das Board hat 3 Taster von denen ich 2 als “Auf” und “Ab” Schalter verwenden, damit man die Klappe auch manuell steuern kann.
Das Erkennen der Klappenposition wird von den Reedkontakten am oberen bzw. unteren Ende der Öffnung erledigt. Ist der obere Kontakt geschlossen ist die Klappe geöffnet, ist der untere geschlossen ist die Klappe zu.
Der Zustand der Sensoren, Taster gedrückt bzw. nicht gedrückt und Endschalter geschlossen bzw nicht geschlossen wird über digitale IO Ports gelesen.
Um korrekte Signale zu bekommen muss auch hier eine Entprellung vorgenommen werden. Die Taster des Evaluation Boards haben zwar eine hardwaremässige Entprellung, aber die beiden Reedrelais werden gegen Masse geschaltet. Ist einfacher im Aufbau da ich die internen Pullup Widerstände benutzen kann und zudem die gängigere Variante.

Man kann seitenweise Forendiskussionen nachlesen bzgl. ‘der einzig wahre und richtige’ Entprell-Algorithmus.
Für meine Zwecke sollte diese Quick&Dirty Lösung reichen.
Der Zustand des jeweiligen PortPins wird in einem 10ms Interrupt in ein State-Byte geshiftet. Besitzt das Byte den Wert 0xff bzw. 0×00 wechselt der Zustand des Schalters, ansonsten bleibt der letzte Zustand gültig.
D.h. nach 80ms kann ein Wechsel erkannt werden. Sollte schnell genug sein.

	sensor->state = (sensor->state<<1);
 	if(pin) {
		sensor->state |= 0x01;
	} else {
		sensor->state &= 0xfe;
	}
 
	// state changed?
	if(sensor->state==0xff) {
		ptast->key = ON;
	} else if(sensor->state==0x00) {
		ptast->key = OFF;
	} else {
                // keep old state
	}

Die Statemachine für die Klappe hat folgende Zustände:

DOOR_MOVING: (kein Schalter geschlossen)
DOOR_OPEN: Klappe ist offen (Schalter oben geschlossen)
DOOR_CLOSE: Klappe ist geschlossen (Schalter unten geschlossen)
DOOR_ERROR: Fehlerzustand

ISR und main

Timer1 Interrupt ‘weckt’ den ATMEGA aus dem SleepMode auf.
Hier wird nur ein Flag gesetzt, welches in der Haupt-Schleife ausgewertet wird.

ISR(TIMER1_OVF_vect) // timer_overflow
{
	// restart counter
	TCNT1H = TCNT1H_VALUE;
	TCNT1L = TCNT1L_VALUE;
	wokeup = TRUE;
}

Der Timer0 Interrupt liest im 10ms Intervall die Taster und Magnetkontakte ein.

ISR(TIMER0_OVF_vect) // timer_overflow
{
	// restart counter
	TCNT0 = TCNT0_VALUE;
 
	readInput(&sensor[BUTTON_UP], PIND &  (1 << KEY_BUTTON_UP));
	readInput(&sensor[BUTTON_DOWN], PIND & (1 << KEY_BUTTON_DOWN));
	readInput(&sensor[LIMITSWITCH_UP], PINC & (1<< KEY_SWITCH_UP));
	readInput(&sensor[LIMITSWITCH_DOWN], PINC & (1 << KEY_SWITCH_DOWN));
	readInput(&sensor[BUTTON_3], PIND & (1 << KEY_BUTTON_3));
 
        round++;
	if(getTasterInit()==TRUE) {
		checkDoorStatus();
	} else {
	    if(round==8) {
		setTasterInit(TRUE);
	    }
        }
	round=round%8;
}

In der Main Routine des Programm wird durch die System Statemachine ‘geloopt’.

int main()
{
    // HW intitialisierung, Timer Interrupts, UART, Ports, etc.
   ...
	printf("hello chicken!\n");
	// read config from eeprom
	eeprom_read_block(&configdata,0,sizeof(configdata));
 
	if(configdata.duskdawnmin==DEFAULT &&
		configdata.duskdawnmax == DEFAULT) {
		configdata.duskdawnmin = DUSKDAWNMIN;
		configdata.duskdawnmax = DUSKDAWNMAX;
		printf("no eeprom data ... using default\n");
	}
	printf("DUSKDAWNMIN %i\n",configdata.duskdawnmin);
	printf("DUSKDAWNMAX %i\n",configdata.duskdawnmax);
 
	setEngineStatus(ENGINE_STOP);
	disableEngine();
	LED2_ON;
	sei();
	checkSensors();
 
        // try to set system state on startup
        if(getDoorStatus()==DOOR_CLOSE) {
                systemstatus = SYSTEM_CLOSE;
        } else if(getDoorStatus()==DOOR_OPEN) {
                 systemstatus = SYSTEM_OPEN;      
        } else {
                // door not open/close ... set init state
                systemstatus = SYSTEM_INIT;
        }
 
	// --- main loop ---
 
	for (;;) {
	switch(systemstatus) {
		case  SYSTEM_INIT:
                        // user action requested
			if(getPinStatus(BUTTON_UP)==ON) {
				systemstatus = SYSTEM_START_OPENING;
			}
			if(getPinStatus(BUTTON_DOWN)==ON) {
				systemstatus = SYSTEM_START_CLOSING;
			}
		break;
 		case  SYSTEM_START_OPENING:
			checkSensors();
			setEngineStatus(ENGINE_MOVE_UP);
			enableEngine();
			systemstatus = SYSTEM_OPENING;
			enableErrorTimer();
		break;
 		case  SYSTEM_START_CLOSING:
			checkSensors();
			setEngineStatus(ENGINE_MOVE_DOWN);
			enableEngine();
			systemstatus = SYSTEM_CLOSING;
			enableErrorTimer();
		break;
 		case  SYSTEM_CLOSING:
			// check for timeout
			checkError(&systemstatus);
                        // door closed
			if(getDoorStatus()==DOOR_CLOSE) {
				disableEngine();
				setEngineStatus(ENGINE_STOP);
				disableErrorTimer();
				systemstatus = SYSTEM_CLOSE;
			}
		break;
		case  SYSTEM_OPENING:
			// check for timeout
			checkError(&systemstatus);
                        // door opened
			if(getDoorStatus()==DOOR_OPEN) {
				disableEngine();
				setEngineStatus(ENGINE_STOP);
				disableErrorTimer();
				systemstatus = SYSTEM_OPEN;
			}
		break;
 		case  SYSTEM_OPEN:
			// if woke up from tmr1 ... do adc
			checkADC();
			checkSensors();
			if(getPinStatus(BUTTON_DOWN)==ON || 
				getLightStatus()==NIGHT) {
				systemstatus = SYSTEM_START_CLOSING;
			} else {
				// goto sleep
				goSleep();
			}
		break;
 		case  SYSTEM_CLOSE:
			// if woke up from tmr1 ... do adc
			checkADC();
			checkSensors();
			if(getPinStatus(BUTTON_UP)==ON || 
				getLightStatus()==DAY) {
				systemstatus = SYSTEM_START_OPENING;
			} else {
				// goto sleep
				goSleep();
			}
		break;
 		case SYSTEM_ERROR:
				disableEngine();
				setEngineStatus(ENGINE_STOP);
				// flash error led
		break;
		default:
		break;
        }
	return 0;
}

Wir alle sollten Strom sparen – deshalb benutze ich den IDLE Sleepmode des AVR im SYSTEM_CLOSE und SYSTEM_OPEN State, da sich das Programm in 99% der Zeit in diesen Zuständen befinden wird. Angeblich sinkt die Stromaufnahme im IDLE Mode auf 0,3mA was ca. 70% Ersparnis gegenüber dem ACTIVE Mode bedeutet. Da eine Lichtmessung pro Minute meiner Meinung nach völlig ausreichend ist spricht nichts dagegen den Controller schlafen zu legen wenn er eh gerade nichts zu tun hat.

Kunde beim präsentieren der Rechnung

Die ‘Kundschaft’ ist noch etwas skeptisch, aber der Beta-Test läuft!

Andere Leute bauen auch gute Hühnerklappen

Nachdem ich mit der Installation fertig war habe ich mal bei YouTube nach automatischen Hühnerklappen gesucht. Hier sind meine 3 Favoriten:

Automatic Chicken Coop Door Opener Project
Die Verriegelung (gegen Waschbären) finde ich ausgeklügelt. Ausserdem wird ein Akkuschrauber als Motor benutzt … wenn der Mann mal kein Informatiker ist!

Automatic Timed Chicken Coop Door
Hier ist der mechanische Aufbau sehr interessant. Ich glaube da wird ein Scheibenwischermotor benutzt.

Automatic Chicken Door Opener
Absolut coole Konstruktion! Ich verstehe es zwar nicht, finde es aber trotzdem gut! :)

3 Comments

Communication between C# .NET and Java via ActiveMQ messaging – Part 3

The Java server side – ‘same same … but different!’

For C# client example see Part 2.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:amq="http://activemq.apache.org/schema/core"
	xmlns:jms="http://www.springframework.org/schema/jms"
	xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core-5.3.0.xsd
http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-2.5.xsd">
 
   ...
 
<!--  define broker & connectors -->
  <amq:broker persistent="false" useJmx="false">
    <amq:transportConnectors>
      <amq:transportConnector uri="tcp://localhost:61616" />
    </amq:transportConnectors>
  </amq:broker>
 
<!--  define connection factory -->
  <amq:connectionFactory id="jmsConnectionFactory" brokerURL="vm://localhost" />
  <amq:queue id="test.queue" physicalName="test.queue" />
 
     <!-- A cached connection factory to wrap the default factory -->
  <bean id="cachedConnectionFactory"
      class="org.springframework.jms.connection.CachingConnectionFactory"
      p:targetConnectionFactory-ref="jmsConnectionFactory"
      p:sessionCacheSize="10" />
 
 
  <jms:listener-container connection-factory="cachedConnectionFactory" destination-type="queue">
    <jms:listener destination="test.queue" ref="MessageReceiverListener" />
  </jms:listener-container>
 
  <bean id="ExampleMessageHandler" class="it.koller.interopexample.handlers.ExampleMessageHandler" />
  <bean id="ExampleMessageConverter" class="it.koller.interopexample.converter.ExampleMessageConverter"/>
 
<bean id="MessageReceiverListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
 <property name="defaultListenerMethod" value="handleObject"/>
   <property name="delegate" ref="ExampleMessageHandler" />
   <property name="messageConverter" ref="ExampleMessageConverter" />
</bean>
 
   ...
 
</beans>

We also need an ExampleMessageConverter as we have it at the C# client side.

package it.koller.interopexample.converter;
 
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.Session;
 
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.jms.support.converter.MessageConversionException;
import org.springframework.jms.support.converter.MessageConverter;
 
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import it.koller.interopexample.ExampleMessage;
 
public class ExampleMessageConverter implements MessageConverter {
 
	protected Log logger = LogFactory.getLog(SoCamMessageConverter.class);
 
	@Override
	public Object fromMessage(Message mes) throws JMSException,
			MessageConversionException {
		if(logger.isDebugEnabled())
			logger.debug("fromMessage()");
        ExampleMessage examplemsg = new ExampleMessage();
        BASE64Decoder decoder = new BASE64Decoder();
        MapMessage mm = (MapMessage)mes;
        if examplemsg == null)
        {
            throw new MessageConversionException("ExampleMessageConverter can not convert object of type " +
            		mes.getJMSType());
        }
        try
        {
            examplemsg.setMessagetext(mm.getString("messageText"));
 
            if(mm.getString("pictureBytes")!=null)
            	examplemsg.setPictureBytes(decoder.decodeBuffer(mm.getString("pictureBytes")));
 
    		if(logger.isDebugEnabled())
    			logger.debug("got Examplemessage: "+examplemsg);
            return tradeRequest;
 
         } catch (Exception e)
         {
            throw new MessageConversionException("Could not convert ExampleMessage", e);
         }
	}
 
	@Override
	public Message toMessage(Object mesobj, Session session) throws JMSException,
			MessageConversionException {
		if(logger.isDebugEnabled())
			logger.debug("toMessage()");
        ExampleMessage examplemsg = (ExampleMessage) mesobj;
        BASE64Encoder encoder = new sun.misc.BASE64Encoder();
        if examplemsg == null)
        {
            throw new MessageConversionException("ExampleMessageConverter can not convert object of type " +
            		mesobj.getClass());
        }
        try
        {
            MapMessage mm = session.createMapMessage();
            mm.setString("messageText", examplemsg.getMessagetext());
            if(examplemsg.getPictureBytes()!=null)
            	mm.setString("pictureBytes", encoder.encode(examplemsg.getPictureBytes()));
            return mm;
 
         }
        catch (Exception e)
         {
            throw new MessageConversionException("Could not convert ExampleMessage to message", e);
         }
	}
 
}

To receive messages from the test.queue a listener and listener-container have to be configured.
The listener-container needs a connection factory and the required destination-type (topic or queue) set.

 
   ...
 
  <jms:listener-container connection-factory="cachedConnectionFactory" destination-type="queue">
    <jms:listener destination="test.queue" ref="MessageReceiverListener" />
  </jms:listener-container>
 
   ...

The listener class has to be a MessageListener, which means it has to implement the according interface.

public interface MessageListener {
 
        public void onMessage(Message message);
}

I use a default MessageListenerAdapter provided by the Spring framework and inject my custom MessageConverter as well as a MessageHandler.

 
   ...
 
<bean id="MessageReceiverListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
 <property name="defaultListenerMethod" value="handleObject"/>
   <property name="delegate" ref="ExampleMessageHandler" />
   <property name="messageConverter" ref="ExampleMessageConverter" />
</bean>
 
   ...

The received Message is passed on to the toObject method of the ExampleMessageConverter, which extracts the data from the MapMessage and returns a ExampleMessage object.
The ExampleMessage is then handed over to the ExampleMessageHandler and can be processed by the server application.

package it.koller.interopexample.receiver;
import org.apache.log4j.Logger;
 
public class ExampleMessageHandler {
 
	private Logger logger = Logger.getLogger(ExampleMessageHandler.class);
 
	public void handleObject(Object mesobj)
 
		if(logger.isDebugEnabled())
			logger.debug("ExampleMessageHandler handleObject "+mesobj);
 
		...
                // do something ;)
	}
 
}

The weak point in this (very) simple approach is the MessageConverter. Both are coded manually and therefore a possible source of error.
To generate the POJO/PONO and the converter from the examplemessage.xsd would provide much more safety and convenience.
Commercial message provider support this feature, i guess.

Please feel free to leave any comments!

, , , , ,

1 Comment

Communication between C# .NET and Java via ActiveMQ messaging – Part 2

The C# client side.

In Part 1 we defined and generated the ExampleMessage POJO/PONO.

We have to configure the logging part to enable the debug/info output getting Spring.NET to work.

   ...
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 
  <configSections>
    <sectionGroup name="common">
      <section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging" />
    </sectionGroup>
    <sectionGroup name="spring">
      <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core"/>
      <section name="parsers" type="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core"/>
      <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core"/>
    </sectionGroup>
  </configSections>
 
 
  <common>
    <logging>
      <factoryAdapter type="Common.Logging.Simple.ConsoleOutLoggerFactoryAdapter, Common.Logging">
        <arg key="level" value="DEBUG" />
        <arg key="showLogName" value="true" />
        <arg key="showDataTime" value="true" />
        <arg key="dateTimeFormat" value="yyyy/MM/dd HH:mm:ss:fff" />
      </factoryAdapter>
    </logging>
  </common>
 
  <spring>
 
   ...
 
    <objects>
      <import resource="objects.xml"/>
    </objects>
 
  </spring>
 
</configuration>

objects.xml looks like:

 
<objects xmlns="http://www.springframework.net"
  xmlns:nms="http://www.springframework.net/nms">
 
<object id="ActiveMqConnectionFactory" type="Apache.NMS.ActiveMQ.ConnectionFactory, Apache.NMS.ActiveMQ">
    <constructor-arg index="0" value="tcp://localhost:61616"/>
  </object>
 
  <!-- use caching connection factory  -->
  <object id="ConnectionFactory" type="Spring.Messaging.Nms.Connections.CachingConnectionFactory, Spring.Messaging.Nms">
    <constructor-arg index="0" ref="ActiveMqConnectionFactory" />
    <property name="SessionCacheSize" value="10"/>
  </object>
 
  <!-- message converter for hashmap-message <=> examplemessage -->
  <object id="ExampleMessageConverter" type="ClientApp.ExampleMessageConverter"/>
 
  <!-- template for automatic conversion -->
  <object id="NmsTemplate" type="Spring.Messaging.Nms.Core.NmsTemplate">
    <property name="ConnectionFactory" ref="ConnectionFactory" />
    <property name="DefaultDestinationName" value="test.queue" />
    <property name="SessionAcknowledgeMode" value="AutoAcknowledge" />
    <property name="MessageConverter" ref="ExampleMessageConverter" />
  </object>
 
  <!-- service ;) to send  -->
  <object name="ExempleService" type="ClientApp.ExampleService">
    <property name="NmsTemplate"  ref="NmsTemplate"/>
  </object>
 
   ...
 
</objects>

Lets go top down.

 
<objects xmlns="http://www.springframework.net"
  xmlns:nms="http://www.springframework.net/nms">
 
   ...
 
  <!-- service ;) to send  -->
  <object name="ExempleService" type="ClientApp.ExampleService">
    <property name="NmsTemplate"  ref="NmsTemplate"/>
  </object>
 
 
   ...
 
</objects>

To provide the message sending functionality to the client application lets create a simple “service”.

using System;
using System.Collections.Generic;
using System.Text;
using Spring.Messaging.Nms.Core;
using Common.Logging;
 
namespace ClientApp
{
    public class ExampleService : NmsGatewaySupport
    {
        private ILog logger = LogManager.GetLogger(typeof(ExampleService ));
 
        public ExampleService()
        {
            logger.Info("ExampleService created");
        }
 
        public void send(ExampleMessage mes)
        {
            if(logger.IsDebugEnabled)
                 logger.Debug("sending ExampleMessage "+mes);
            NmsTemplate.ConvertAndSend(mes);
        }
    }
}

The implementation of ExampleService inherits from Spring.Messaging.Nms.Core.NmsGatewaySupport which is a helper class providing easy access to a NmsTemplate.
The NmsTemplate is similar to JmsTemplate from the Java Spring world.
We have to set some properties such as a ConnectionFactory for NmsConnctions and a destination to specify which Topic/Queue the message should be sent to.

<objects xmlns="http://www.springframework.net"
  xmlns:nms="http://www.springframework.net/nms">
 
   ...
 
  <!-- template for message sending-->
  <object id="NmsTemplate" type="Spring.Messaging.Nms.Core.NmsTemplate">
    <property name="ConnectionFactory" ref="ConnectionFactory" />
    <property name="DefaultDestinationName" value="test.queue" />
    <property name="SessionAcknowledgeMode" value="AutoAcknowledge" />
   ...
  </object>
 
 
   ...
 
</objects>

JMS specifies TextMessages, MapMessages and ObjectMessages. I decided to use a MapMessage for exchanging data beween client and server.
NMS tries to follow the JMS standard, but there is no de facto standard messaging API in .NET
To enable convertion between ExampleMessage object and MapMessage object we make use of the MessageConverter interface.

namespace Spring.Messaging.Nms.Support.Converter
{
   public interface IMessageConverter
   {
      public Object FromMessage(IMessage mes);
      public IMessage ToMessage(Object mesobj, ISession session);
   }
}

The ExampleMessageConverter functionality is very simple. The ToMessage code has to take care of putting all the transmit data from the ExampleMessage to the MapMessage.
The FromMessage do it vice versa. I assumed there is a SetBytes method like in JMS MapMessage interface.
Thought wrong! Did i overlook something maybe? Nevermind!
A conversion from a byte array to a Base64 encoded string is also quite easy.

using System;
using System.Collections.Generic;
using System.Text;
using Spring.Messaging.Nms.Support.Converter;
using Apache.NMS;
using Apache.NMS.ActiveMQ;
using Common.Logging;
 
namespace ClientApp
{
 
    public class ExampleMessageConverter : IMessageConverter
    {
        ILog logger = LogManager.GetLogger(typeof(ExampleMessageConverter));
 
	// called to convert a JMS/NMS message to a object
        public Object FromMessage(IMessage mes)
        {
 
            logger.Info("fromMessage()");
            ExampleMessage examplemsg= new ExampleMessage();
            IMapMessage mm = (IMapMessage)mes;
            if (examplemsg == null)
            {
                throw new MessageConversionException("ExampleMessageConverter can not convert object of type " +
                        mes.GetType());
            }
            try
            {
                examplemsg.Messagetext = mm.Body.GetString("messageText");
                if(mm.Body.GetString("pictureBytes")!=null)
                    examplemsg.PictureBytes = System.Convert.FromBase64String(mm.Body.GetString("pictureBytes"));
 
                logger.Info("got ExampleMessage: " + examplemsg);
                return examplemsg;
            }
            catch (Exception e)
            {
                throw new MessageConversionException("Could not convert ExampleMessage to message", e);
            }
        }
 
	// called to convert a object to a JMS/NMS message
        public IMessage ToMessage(Object mesobj, ISession session)
        {
            logger.Info("toMessage()");
            ExampleMessage examplemsg = (ExampleMessage)mesobj;
            if (examplemsg == null)
            {
                throw new MessageConversionException("ExampleMessageConverter can not convert object of type " +
                        mesobj.GetType());
            }
            try
            {
                IMapMessage mm = session.CreateMapMessage();
                mm.Body.SetString("messageText", examplemsg.Messagetext);
                if(tradeRequest.PictureBytes!=null)
                    mm.Body.SetString("pictureBytes", System.Convert.ToBase64String(examplemsg.PictureBytes));
                else
                    mm.Body.SetString("pictureBytes",null);
                return mm;
 
            }
            catch (Exception e)
            {
                throw new MessageConversionException("Could not convert ExampleMessage to message", e);
            }
        }
    }
}

Now we add the ExampleMessageConverter to the NmsTemplate configuration.
Whenever a ExampleMessage is passed via the ConvertAndSend method, all the hard work is done by the NmsTemlate.
We do not have to care about connections, sessions, message conversion and all that stuff.
As long as we did the correct configuration ;)

<objects xmlns="http://www.springframework.net"
  xmlns:nms="http://www.springframework.net/nms">
 
   ...
 
  <!-- message converter for mapmessage <=> examplemessage -->
  <object id="ExampleMessageConverter" type="ClientApp.ExampleMessageConverter"/>
 
  <!-- template for automatic conversion -->
  <object id="NmsTemplate" type="Spring.Messaging.Nms.Core.NmsTemplate">
   ...
    <property name="MessageConverter" ref="ExampleMessageConverter" />
  </object>
 
 
   ...
 
</objects>

Receiving example will be shown in
Part 3.

Please feel free to leave any comments!

, , , , ,

2 Comments

Communication between C# .NET and Java via ActiveMQ messaging – Part 1

Cross-language communication between a C# .NET client and a Java server application.
A (very) simple approach.

JMS is widely used, and has become a de-facto communication standard. It is also pretty easy to use and allows asynchronous processing of messages. A popular open source messaging provider is ActiveMQ. It has a very big advantage, because there is also a C# implementation available, which is part of the messaging in Spring.NET.
If your are familiar with JMS and Spring it’s very simple to understand how to use NMS and Spring.NET.

For this communication example i used Spring 3.0.0 Release, ActiveMq NMS 1.1.0 and Spring.NET 1.3.0 GA.

First step:
We need a POJO/PONO object as data container. A language independed approach is to use XML for schema description. A more improved version would be to generate the neccessary MessageConverters also, but I will drop some words about this later.

 
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
   <xs:element name="ExampleMessage">
      <xs:complexType>
         <xs:sequence>
            <xs:element name="Messagetext" type="xs:string"/>
            <xs:element name="ImageData" type="xs:hexBinary"/>
         </xs:sequence>
      </xs:complexType>
   </xs:element>
</xs:schema>

Our ExampleMessage encloses two fields. A string variable for the message text and a byte-array for image data.
With this XML definition its easy to generate code (C#,Java) for both application parts.

In the Microsoft world there is a command line tool named xsd.exe, which is part of the Windows SDK.

xsd.exe /c examplemessage.xsd

The output:

using System.Xml.Serialization;
 
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.3038")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=false)]
public partial class ExampleMessage {
 
    private string messagetextField;
 
    private byte[] imageDataField;
 
    ///
    public string Messagetext {
        get {
            return this.messagetextField;
        }
        set {
            this.messagetextField = value;
        }
    }
 
  ...    
 
}

jaxb can be used for the Java code generation.

xjc.bat -p it.koller.interopexample examplemessage.xsd
package it.koller.interopexample;
 
public class ExampleMessage
{
     private String messagetext;
     private byte[] imagedata;
 
     ...
 
    /* getter &amp; setter methods for var access */
     public String getMessagetext()
     {
           return messagetext;
     }
 
     public void setName(String messagetext)
     {
          this.messagetext = messagetext;
     }
 
     ...
 
}

For C# client example see Part 2.

Please feel free to leave any comments!

, , , , ,

3 Comments

Hello blog!

 
<? print("Hello, blog!"); ?>

:-)

No Comments