Docker Images für den PI-2

Da ich verschiedene Dinge auf dem PI ausprobieren möchte, aber schon ein einigermaßen stabiles System habe, bot es sich an, neue Dinge zunächst in Docker-Containern auszuprobieren, da diese isoliert vom System ablaufen.

Im aktuellen Fall wollte ich ein Java-Programm testen, aber nicht Java-8 (inkl. dessen Abhängigkeiten) auf mein bestehendes System packen.

Also zunächst (und hoffentlich als einziges) Docker installieren:

sudo apt-get install docker.io

Danach das nächst beste Docker-Image (gefunden auf https://hub.docker.com/) ziehen:

docker pull dordoka/rpi-java8

Dies kann eine Weile dauern…

Anschließend einmal ausprobieren:

sudo docker run dordoka/rpi-java8 java -version

Wenn nun etwas in der Art erscheint, hat man sein Java im Docker-Container:

java version "1.8.0_33"
Java(TM) SE Runtime Environment (build 1.8.0_33-b05)
Java HotSpot(TM) Client VM (build 25.33-b05, mixed mode)

Wenn man aber gerade eh dabei ist sich eine Umgebung einzurichten,
kann man es nicht schaden sich auch noch einen Container zu bauen, in dem maven3 vorhanden ist (aufbauend von dem eben genutzten java-container).

Also ein neues Arbeitsverzeichnis einrichten:

mkdir docker-container
cd docker-container
mkdir rpi-maven
cd rpi-maven

In diesem Ordner wird nun eine neue Datei mit dem Namen „Dockerfile“ angelegt und folgender Inhalt kopiert:

FROM dordoka/rpi-java8

ENV MAVEN_VERSION 3.3.9

RUN apt-get update && apt-get install -y curl
RUN curl -fsSL https://archive.apache.org/dist/maven/maven-3/$MAVEN_VERSION/binaries/apache-maven-$MAVEN_VERSION-bin.tar.gz | tar xzf - -C /usr/share \
  && mv /usr/share/apache-maven-$MAVEN_VERSION /usr/share/maven \
  && ln -s /usr/share/maven/bin/mvn /usr/bin/mvn

ENV MAVEN_HOME /usr/share/maven

VOLUME /root/.m2

CMD ["mvn"]

Mit diesem Rezept kann man nun ein eigenes Docker-Image bauen:

sudo docker build -t rpi-maven .

Auch das dauert eine Weile. Konkret wird hier curl installiert.
Danach (mittels curl) das gewünschte Maven heruntergeladen (hier: 3.3.9) und einige verschiebe und verlinke Operationen durchgeführt.

Wem das zu lange Dauert, kann auch einfach das fertige Image vom dockerhub nehmen (qnerd/rpi-maven).

Nun sollte man ein Docker-Container haben, in dem Maven verfügbar ist:

sudo docker run rpi-maven mvn -version

Beziehungsweise, wenn man das Image nicht selbst gebaut hat:

sudo docker run qnerd/rpi-maven mvn -version

Nun sollte folgende Ausgabe erscheinen:

 
Apache Maven 3.3.9 (bb52d8502b132ec0a5a3f4c09453c07478323dc5; 2015-11-10T16:41:47+00:00)
Maven home: /usr/share/maven
Java version: 1.8.0_33, vendor: Oracle Corporation
Java home: /usr/lib/jvm/java-8-oracle/jre
Default locale: en_US, platform encoding: ANSI_X3.4-1968
OS name: "linux", version: "4.1.13-v7+", arch: "arm", family: "unix"



Das ist ganz praktisch, da ich nun mein eigentliches Java Programm nicht mehr irgendwo runterladen muss, sondern es direkt aus den Quellen bauen kann.

Also auch hier wieder ein neue Container:

cd ..
mkdir hue-bridge
cd hue-bridge
vi Dockerfile

Im folgenden Rezept installiere ich noch eben git, clone das Repo mit dem zu testenden Programm und baue es mit maven.
Danach sorge ich dafür, dass der Port 8080 von außen verfügbar ist und mache noch etwas umständliches rumgehacke um die aktuelle IP-Adresse zu bestimmen (okay, das meiste davon ist von irgendwo geklaut):

FROM rpi-maven

RUN apt-get install -y git
RUN git clone https://github.com/armzilla/amazon-echo-ha-bridge.git
RUN cd amazon-echo-ha-bridge && mvn install

EXPOSE 8080

CMD ["bash", "-c", "java -jar amazon-echo-ha-bridge/target/amazon-echo-bridge-*.jar --upnp.config.address=$(ip route get 8.8.8.8 | egrep -o '[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\s*$')"]

Und wieder bauen lassen:

sudo docker build -t rpi-hue-bridge-sim .

Jetzt wird es Zeit das eigentliche Programm zu starten:

sudo docker run --net="host" rpi-hue-bridge-sim

Beziehungsweise:

sudo docker run --net="host" qnerd/amazon-echo-ha-bridge

Durch die Angabe von --net="host" wird der Docker-Container nicht virtuell in einem privaten Netz mit eigener IP gestartet, sondern verhält sich so, als würde alles innerhalb des Containers direkt auf dem Host ausgeführt. Das macht es einfacher auf den gestarteten Tomcat zuzugreifen.

Der Start sieht wie folgt aus:

pi@raspberrypi:~/hue-bridge $ sudo docker run --net="host" rpi-hue-bridge-sim

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.2.6.RELEASE)

2015-12-13 20:06:45.291  INFO 1 --- [           main] com.armzilla.ha.SpringbootEntry          : Starting SpringbootEntry v0.2.1 on raspberrypi with PID 1 (/data/amazon-echo-ha-bridge/target/amazon-echo-bridge-0.2.1.jar started by root in /data)
2015-12-13 20:06:45.714  INFO 1 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@1d15089: startup date [Sun Dec 13 20:06:45 UTC 2015]; root of context hierarchy
2015-12-13 20:06:54.192  INFO 1 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Overriding bean definition for bean 'beanNameViewResolver': replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter.class]]
2015-12-13 20:06:58.727  INFO 1 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.scheduling.annotation.SchedulingConfiguration' of type [class org.springframework.scheduling.annotation.SchedulingConfiguration$$EnhancerBySpringCGLIB$$26d4532] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2015-12-13 20:07:02.862  INFO 1 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
2015-12-13 20:07:04.395  INFO 1 --- [           main] o.apache.catalina.core.StandardService   : Starting service Tomcat
2015-12-13 20:07:04.404  INFO 1 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.0.26
2015-12-13 20:07:05.705  INFO 1 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2015-12-13 20:07:05.707  INFO 1 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 20015 ms
2015-12-13 20:07:14.242  INFO 1 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean        : Mapping servlet: 'dispatcherServlet' to [/]
2015-12-13 20:07:14.300  INFO 1 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'metricFilter' to: [/*]
2015-12-13 20:07:14.303  INFO 1 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'characterEncodingFilter' to: [/*]
2015-12-13 20:07:14.305  INFO 1 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2015-12-13 20:07:14.306  INFO 1 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'webRequestLoggingFilter' to: [/*]
2015-12-13 20:07:14.308  INFO 1 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'springBootCorsFilter' to: [/*]
2015-12-13 20:07:14.309  INFO 1 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'applicationContextIdFilter' to: [/*]
2015-12-13 20:07:17.177  INFO 1 --- [           main] org.elasticsearch.node                   : [Mondo] version[1.3.2], pid[1], build[dee175d/2014-08-13T14:29:30Z]
2015-12-13 20:07:17.180  INFO 1 --- [           main] org.elasticsearch.node                   : [Mondo] initializing ...
2015-12-13 20:07:17.219  INFO 1 --- [           main] org.elasticsearch.plugins                : [Mondo] loaded [], sites []
2015-12-13 20:07:34.985  INFO 1 --- [           main] org.elasticsearch.node                   : [Mondo] initialized
2015-12-13 20:07:34.986  INFO 1 --- [           main] org.elasticsearch.node                   : [Mondo] starting ...
2015-12-13 20:07:35.037  INFO 1 --- [           main] org.elasticsearch.transport              : [Mondo] bound_address {local[1]}, publish_address {local[1]}
2015-12-13 20:07:35.115  INFO 1 --- [           main] org.elasticsearch.discovery              : [Mondo] elasticsearch/WwlYnRlVRsugFcnj37AL3g
2015-12-13 20:07:35.137  INFO 1 --- [pdateTask][T#1]] org.elasticsearch.cluster.service        : [Mondo] new_master [Mondo][WwlYnRlVRsugFcnj37AL3g][raspberrypi][local[1]]{local=true}, reason: local-disco-initial_connect(master)
2015-12-13 20:07:35.351  INFO 1 --- [           main] org.elasticsearch.node                   : [Mondo] started
2015-12-13 20:07:36.743  INFO 1 --- [pdateTask][T#1]] org.elasticsearch.gateway                : [Mondo] recovered [0] indices into cluster_state
2015-12-13 20:07:39.658  INFO 1 --- [pdateTask][T#1]] org.elasticsearch.cluster.metadata       : [Mondo] [device] creating index, cause [api], shards [1]/[0], mappings []
2015-12-13 20:07:42.427  INFO 1 --- [pdateTask][T#1]] org.elasticsearch.cluster.metadata       : [Mondo] [device] create_mapping [devicedescriptor]
2015-12-13 20:07:48.230  INFO 1 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@1d15089: startup date [Sun Dec 13 20:06:45 UTC 2015]; root of context hierarchy
2015-12-13 20:07:48.972  INFO 1 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/devices/{lightId}],methods=[GET],produces=[application/json]}" onto public org.springframework.http.ResponseEntity<com.armzilla.ha.dao.DeviceDescriptor> com.armzilla.ha.devicemanagmeent.DeviceResource.findByDevicId(java.lang.String)
2015-12-13 20:07:48.976  INFO 1 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/devices/{lightId}],methods=[DELETE],produces=[application/json]}" onto public org.springframework.http.ResponseEntity<java.lang.String> com.armzilla.ha.devicemanagmeent.DeviceResource.deleteDeviceById(java.lang.String)
2015-12-13 20:07:48.978  INFO 1 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/devices],methods=[POST],produces=[application/json]}" onto public org.springframework.http.ResponseEntity<com.armzilla.ha.dao.DeviceDescriptor> com.armzilla.ha.devicemanagmeent.DeviceResource.createDevice(com.armzilla.ha.api.Device)
2015-12-13 20:07:48.980  INFO 1 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/devices/{lightId}],methods=[PUT],produces=[application/json]}" onto public org.springframework.http.ResponseEntity<com.armzilla.ha.dao.DeviceDescriptor> com.armzilla.ha.devicemanagmeent.DeviceResource.updateDevice(java.lang.String,com.armzilla.ha.api.Device)
2015-12-13 20:07:48.982  INFO 1 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/devices],methods=[GET],produces=[application/json]}" onto public org.springframework.http.ResponseEntity<java.util.List<com.armzilla.ha.dao.DeviceDescriptor>> com.armzilla.ha.devicemanagmeent.DeviceResource.findAllDevices()
2015-12-13 20:07:48.987  INFO 1 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/upnp/{deviceId}/setup.xml],methods=[GET],produces=[application/xml]}" onto public org.springframework.http.ResponseEntity<java.lang.String> com.armzilla.ha.upnp.UpnpSettingsResource.getUpnpConfiguration(java.lang.String,javax.servlet.http.HttpServletRequest)
2015-12-13 20:07:48.996  INFO 1 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/{userId}/lights],methods=[GET],produces=[application/json]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.String>> com.armzilla.ha.hue.HueMulator.getUpnpConfiguration(java.lang.String,javax.servlet.http.HttpServletRequest)
2015-12-13 20:07:48.998  INFO 1 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/{userId}/lights/{lightId}],methods=[GET],produces=[application/json]}" onto public org.springframework.http.ResponseEntity<com.armzilla.ha.api.hue.DeviceResponse> com.armzilla.ha.hue.HueMulator.getLigth(java.lang.String,java.lang.String,javax.servlet.http.HttpServletRequest)
2015-12-13 20:07:49.000  INFO 1 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/{userId}/lights/{lightId}/state],methods=[PUT]}" onto public org.springframework.http.ResponseEntity<java.lang.String> com.armzilla.ha.hue.HueMulator.stateChange(java.lang.String,java.lang.String,javax.servlet.http.HttpServletRequest,java.lang.String)
2015-12-13 20:07:49.002  INFO 1 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/*],methods=[POST],produces=[application/json]}" onto public org.springframework.http.ResponseEntity<java.lang.String> com.armzilla.ha.hue.HueMulator.postAPI(javax.servlet.http.HttpServletRequest)
2015-12-13 20:07:49.004  INFO 1 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/{userId}],methods=[GET],produces=[application/json]}" onto public org.springframework.http.ResponseEntity<com.armzilla.ha.api.hue.HueApiResponse> com.armzilla.ha.hue.HueMulator.getApi(java.lang.String,javax.servlet.http.HttpServletRequest)
2015-12-13 20:07:49.017  INFO 1 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest)
2015-12-13 20:07:49.020  INFO 1 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2015-12-13 20:07:49.451  INFO 1 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2015-12-13 20:07:49.452  INFO 1 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2015-12-13 20:07:50.016  INFO 1 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2015-12-13 20:07:54.574  INFO 1 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/env/{name:.*}],methods=[GET]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EnvironmentMvcEndpoint.value(java.lang.String)
2015-12-13 20:07:54.575  INFO 1 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/env],methods=[GET]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2015-12-13 20:07:54.579  INFO 1 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/metrics/{name:.*}],methods=[GET]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint.value(java.lang.String)
2015-12-13 20:07:54.580  INFO 1 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/metrics],methods=[GET]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2015-12-13 20:07:54.583  INFO 1 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/dump],methods=[GET]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2015-12-13 20:07:54.587  INFO 1 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/autoconfig],methods=[GET]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2015-12-13 20:07:54.589  INFO 1 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/beans],methods=[GET]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2015-12-13 20:07:54.592  INFO 1 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/health]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint.invoke(java.security.Principal)
2015-12-13 20:07:54.595  INFO 1 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/mappings],methods=[GET]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2015-12-13 20:07:54.598  INFO 1 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/trace],methods=[GET]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2015-12-13 20:07:54.600  INFO 1 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/configprops],methods=[GET]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2015-12-13 20:07:54.603  INFO 1 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/info],methods=[GET]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2015-12-13 20:07:55.070  INFO 1 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2015-12-13 20:07:55.131  INFO 1 --- [           main] o.s.c.support.DefaultLifecycleProcessor  : Starting beans in phase 0
2015-12-13 20:07:55.862  INFO 1 --- [pool-1-thread-1] com.armzilla.ha.upnp.UpnpListener        : Starting UPNP Discovery Listener
2015-12-13 20:07:56.449  INFO 1 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2015-12-13 20:07:56.459  INFO 1 --- [           main] com.armzilla.ha.SpringbootEntry          : Started SpringbootEntry in 73.894 seconds (JVM running for 77.281)
2015-12-13 20:08:00.959  INFO 1 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
2015-12-13 20:08:00.960  INFO 1 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2015-12-13 20:08:01.267  INFO 1 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 305 ms
2015-12-13 20:14:26.643  INFO 1 --- [pdateTask][T#1]] org.elasticsearch.cluster.metadata       : [Mondo] [device] update_mapping [devicedescriptor] (dynamic)

Und wie erwartet bekomme ich eine UI wenn ich folgende URL von außen aufrufe:
http://raspberrypi:8080/configurator.html

Dieser Dienst soll übrigens eine Phillips Hue Bridge simulieren können, was für mein Mi-Light Projekt (vgl. anderen Artikel) interessant werden könnte, da ich diese auch gerne über die Hue-Apps steuern möchte.
Aber dies ist eine andere Geschichte... 😉

Advertisements

Mi Light direkt mit RF24 steuern

Verkabeln:

Quelle: http://nathan.chantrell.net/downloads/arduino/tinytx/tinytx_stripboard_ds18b20.png
Quelle: http://torsten-traenkner.de/wissen/smarthome/openmilight.php
Leitung Kabelfarbe Pin beim Raspberry Pi 2 Pin beim Funkchip nRF24L01+
3,3 Volt rot 1 2
GND schwarz 6 1
MOSI blau 19 6
MISO braun 21 7
CE gelb 22 3
SCLK violett 23 5
CSN orange 24 4

* aktuelles raspbian (jessie) image erstellen.
* apt-get install git
* mkdir openmilight
* cd openmilight
* git clone https://github.com/TMRh20/RF24
* cd RF24
* make all
* sudo make install
* cd ..
* wget http://torsten-traenkner.de/wissen/smarthome/openmilight_raspberry_pi.tgz
* tar xzvf openmilight_raspberry_pi.tgz
* cd openmilight
* vi openmili.cpp

/**
 * On a Raspberry Pi 2 compile with:
 *
 * g++ -Ofast -mfpu=vfp -mfloat-abi=hard -march=armv7-a -mtune=arm1176jzf-s -I/usr/local/include -L/usr/local/lib -lrf24-bcm PL1167_nRF24.cpp MiLightRadio.cpp openmili.cpp -o openmilight
 *
 * for receiver mode run with:
 * sudo ./openmilight
 *
 * for sender mode run with:
 * sudo ./openmilight "B0 F2 EA 6D B0 02 f0"
 */

#include <cstdlib>
#include <iostream>
#include <string.h>

using namespace std;

#include <RF24/RF24.h>

#include "PL1167_nRF24.h"
#include "MiLightRadio.h"

RF24 radio(RPI_V2_GPIO_P1_22, RPI_V2_GPIO_P1_24, BCM2835_SPI_SPEED_1MHZ);

PL1167_nRF24 prf(radio);
MiLightRadio mlr(prf);

void setup()
{

/*
  Serial.begin(115200);
  printf_begin();
  delay(1000);
  Serial.println("# OpenMiLight Receiver/Transmitter starting");
*/

  mlr.begin();
}


static int dupesPrinted = 0;
static bool receiving = false;
static bool escaped = false;
static uint8_t outgoingPacket[7];
static uint8_t outgoingPacketPos = 0;

static uint8_t nibble;

static enum {
  IDLE,
  HAVE_NIBBLE,
  COMPLETE,
} state;

void loop(const char* newPacketBytes)
{
  if (receiving) {
    if (mlr.available()) {
      printf("\n");
      uint8_t packet[7];
      size_t packet_length = sizeof(packet);
      mlr.read(packet, packet_length);

      for (int i = 0; i < packet_length; i++) {
        printf("%02X ", packet[i]);
      }
    }

    int dupesReceived = mlr.dupesReceived();
    for (; dupesPrinted < dupesReceived; dupesPrinted++) {
      printf(".");
    }
  } else {

    string line = "";
    
    const char* packetBytes = newPacketBytes;

    memset(outgoingPacket, 0, 7);

    // convert input into hex
    int index = 0;
    for (int counter = 0; *packetBytes; ++packetBytes) {
      int n = 0;
      if (*packetBytes >= 'a' && *packetBytes <= 'f') {
        n = *packetBytes - 'a' + 10;
      } else if (*packetBytes >= 'A' && *packetBytes <= 'F') {
        n = *packetBytes - 'A' + 10;
      } else if (*packetBytes >= '0' && *packetBytes <= '9') {
        n = *packetBytes - '0';
      } else if (*packetBytes == ' ') {
        index++;
      } else {
        cout << "cannot decode" << endl;
        exit(1);
      }
      outgoingPacket[index] = outgoingPacket[index] * 16 + (unsigned long) n;
    }

    printf("sending packet\n");
    for (int i = 0; i < 7; i++) {
      printf("%02X ", outgoingPacket[i]);
    }
    printf("\n");

    for (int index = 0; index < 1; index++) {
    }



    mlr.write(outgoingPacket, sizeof(outgoingPacket));
    delay(10);
    mlr.resend();
    delay(10);
    outgoingPacket[6] += 8;
  }



}

int
main(int argc, char** arguments)
{
  setup();

  char* packetBytes = 0;

  if (argc < 2) {
    receiving = true;
    printf("in listening mode\n");

  } else {
    receiving = false;
    packetBytes = arguments[1];
  }

  //while (true) {
        loop("B8 F2 EA 00 00 03 27");
	loop("B0 F2 EA 00 00 13 28");
	loop("B0 F2 EA 00 00 13 29");
	loop("B0 F2 EA 00 00 13 2A");
	loop("B0 F2 EA 00 00 13 2B");
	loop("B0 F2 EA 00 00 13 2C");
	loop("B0 F2 EA 00 00 13 2D");
	loop("B0 F2 EA 00 00 13 2E");
	loop("B0 F2 EA 00 00 13 2F");
  //}

  return 0;
}

* g++ -Ofast -mfpu=vfp -mfloat-abi=hard -march=armv7-a -mtune=arm1176jzf-s -I/usr/local/include -L/usr/local/lib -lrf24-bcm PL1167_nRF24.cpp MiLightRadio.cpp openmili.cpp -o openmilight
* mv openmilight sync
* sudo ./sync
* vi openmili.cpp

[...]
int
main(int argc, char** arguments)
{
  setup();

  char* packetBytes = 0;

  if (argc < 2) {
    receiving = true;
    printf("in listening mode\n");

  } else {
    receiving = false;
    packetBytes = arguments[1];
  }

  //while (true) {
       
    loop(packetBytes);
  //}

  return 0;
}
[...]

* g++ -Ofast -mfpu=vfp -mfloat-abi=hard -march=armv7-a -mtune=arm1176jzf-s -I/usr/local/include -L/usr/local/lib -lrf24-bcm PL1167_nRF24.cpp MiLightRadio.cpp openmili.cpp -o openmilight
* AN: sudo ./openmilight „B0 F2 EA 00 00 01 F0“
* AUS: sudo ./openmilight „B0 F2 EA 00 00 02 F1“
* DISCO: sudo ./openmilight „B0 F2 EA 00 00 0D F2“
* WEISS: sudo ./openmilight „B0 F2 EA 00 00 11 F3“

* Codes

Broadcast remote command over the Air:
Register07: Channel 9 2411MHz (0x01 0x09)
Preamble = ON (0xAAAAAA) 3 bytes
SyncBytes = ON (0x147A, 0x258B) 2 bytes
Trailer = ON (0x05) 4 bits little endian (1010)
Length = (0x07) 1 byte
CommandType = 0xB0 (color, white) or 0xB8 (group/all on/off) 1 byte
RemoteID = (2 bytes) (this is securely stored into the bulb during remote-bulb sync at power on, max of 4 per bulb, syncing a 5th RemoteID drops the first one that is stored in that bulb. When clearing the bulb it removes all 4 remote ids from the bulb)
Color = (0x00 to 0xFF) See colour chart above. 1 byte
Brightness = (0x00 to 0xFF) See brightness chart above.  1 byte
Command = (0x01 to 0x1A) See command list below. 1 byte
Checksum = See checksum calc function code below. 2 Bytes
Repeat again using Channel 40 2442MHz (Set Register 7: 0x01 0x28)
Repeat again using Channel 71 2473MHz (Set Register 7: 0x01 0x47)
Repeat again 5 to 40 times to ensure the bulbs receive the command. If sent a lower number of times, increase the time between sends.

Available Commands
0x01, // All ON
0x02, // All OFF
0x03, // Group 1 ON
0x04, // Group 1 OFF
0x05, // Group 2 ON
0x06, // Group 2 OFF
0x07, // Group 3 ON
0x08, // Group 3 OFF
0x09, // Group 4 ON
0x0A, // Group 4 OFF
0x0B, // Disco Speed Increase
0x0C, // Disco Speed Decrease
0x0D, // Disco Mode
0x11, // Set Color White - All Groups
0x13, // Set Color White - Group 1
0x15, // Set Color White - Group 2
0x17, // Set Color White - Group 3
0x19, // Set Color White - Group 4
0x12, // Night Mode - All Groups
0x14, // Night Mode - Group 1
0x16, // Night Mode - Group 2
0x18, // Night Mode - Group 3
0x1A  // Night Mode - Group 4

//Thanks to Henryk and Erantimus for providing details and checksum code.
//Calculate Checksum - Returns 2 bytes.
uint16 calc_crc(uint8 data[], data_length = 0x08) {
  uint16 state = 0;
  for (size i = 0; i < data_length; i++) {
    uint8 byte = data[i];
    for (int j = 0; j < 8; j++) {
      if ((byte ^ state) & 0x01) {
        state = (state >> 1) ^ 0x8408;
      } else {
        state = state >> 1;
      }
      byte = byte >> 1;
    }
  }
  return state;
}

Sensornetzwerk mit RFM12B

Diese Anleitung basiert auf den TinyTx-Sensoren (http://nathan.chantrell.net/tinytx-wireless-sensor/).

Als Funkmodul kommt das o.g. RFM12B-Modul zum Einsatz, welches auf einer Frequenz von 868 Mhz funkt.
Als Prozessor wird ein AtTiny84 genutzt.
Bisher habe ich nur DHT-22 Sensoren im Einsatz, welche Temperatur und Luftfeuchtigkeit messen können.

Zusammengesetzt werden die Komponenten wie folgt:

Quelle: https://i1.wp.com/nathan.chantrell.net/downloads/arduino/tinytx/tinytx_stripboard_ds18b20.png
Quelle: http://nathan.chantrell.net/downloads/arduino/tinytx/tinytx_stripboard_ds18b20.png

Anschließend kann die Firmware geflashed werden.

#include "DHT.h"

#include <JeeLib.h> // https://github.com/jcw/jeelib

ISR( WDT_vect) {
	Sleepy::watchdogEvent();
} // interrupt handler for JeeLabs Sleepy power saving

#define myNodeID 17 // RF12 node ID in the range 1-30
#define network 211 // RF12 Network group
#define freq RF12_868MHZ // Frequency of RFM12B module
//#define USE_ACK // Enable ACKs, comment out to disable
#define RETRY_PERIOD 5 // How soon to retry (in seconds) if ACK didn't come in
#define RETRY_LIMIT 5 // Maximum number of times to retry
#define ACK_TIME 10 // Number of milliseconds to wait for an ack
#define DHT22_PIN 10 // DHT sensor is connected on D10/ATtiny pin 13
#define DHT22_POWER 9 // DHT Power pin is connected on D9/ATtiny pin 12
#define DHTTYPE DHT22 // This is the type of DHT Sensor (Change it to DHT11 if you're using that model)

DHT myDHT22(DHT22_PIN, DHTTYPE); // Initialize DHT object

//######################
//Data Structure to be sent
//######################
typedef struct {
	int humidity; // Humidity reading
	int supplyV; // Supply voltage
	int temp; // Temperature reading
} Payload;

Payload tinytx;
// Wait a few milliseconds for proper ACK
#ifdef USE_ACK
	static byte waitForAck() {
	MilliTimer ackTimer;
	while (!ackTimer.poll(ACK_TIME)) {
		if (rf12_recvDone() &amp;&amp; rf12_crc == 0 &amp;&amp;
				rf12_hdr == (RF12_HDR_DST | RF12_HDR_CTL | myNodeID))
		return 1;
	}
		return 0;
	}
#endif

//--------------------------------------------------------------------------------------------------
// Send payload data via RF
//-------------------------------------------------------------------------------------------------
static void rfwrite() {
#ifdef USE_ACK
		for (byte i = 0; i <= RETRY_LIMIT; ++i) { // tx and wait for ack up to RETRY_LIMIT times
			rf12_sleep(-1);// Wake up RF module
			while (!rf12_canSend())
				rf12_recvDone();
			rf12_sendStart(RF12_HDR_ACK, &amp;tinytx, sizeof tinytx);
			rf12_sendWait(2);// Wait for RF to finish sending while in standby mode
			byte acked = waitForAck();// Wait for ACK
			rf12_sleep(0);// Put RF module to sleep
			if (acked) {return;} // Return if ACK received
			Sleepy::loseSomeTime(RETRY_PERIOD * 1000);// If no ack received wait and try again
		}
#else
		rf12_sleep(-1); // Wake up RF module
		while (!rf12_canSend())
			rf12_recvDone();
		rf12_sendStart(0, &tinytx, sizeof tinytx);
		rf12_sendWait(2); // Wait for RF to finish sending while in standby mode
		rf12_sleep(0); // Put RF module to sleep
		return;
#endif
}
//--------------------------------------------------------------------------------------------------
// Read current supply voltage
//--------------------------------------------------------------------------------------------------
long readVcc() {
		bitClear(PRR, PRADC);
		ADCSRA |= bit(ADEN); // Enable the ADC
		long result;
// Read 1.1V reference against Vcc
#if defined(__AVR_ATtiny84__)
		ADMUX = _BV(MUX5) | _BV(MUX0); // For ATtiny84
#else
		ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); // For ATmega328
#endif
		delay(2); // Wait for Vref to settle
		ADCSRA |= _BV(ADSC); // Convert
		while (bit_is_set(ADCSRA, ADSC))
			;
		result = ADCL;
		result |= ADCH & lt;
		<
		8;
		result = 1126400L / result; // Back-calculate Vcc in mV
		ADCSRA &= ~ bit(ADEN); bitSet(PRR, PRADC); // Disable the ADC to save power
		return result;
}

void setup() {
//Serial.begin(57600);
		rf12_initialize(myNodeID, freq, network); // Initialize RFM12 with settings defined above
		rf12_sleep(0); // Put the RFM12 to sleep
		pinMode(DHT22_POWER, OUTPUT); // set power pin for DHT to output
		PRR = bit(PRTIM1); // only keep timer 0 going
		ADCSRA &= ~ bit(ADEN); bitSet(PRR, PRADC); // Disable the ADC to save power

}
void loop() {
		digitalWrite(DHT22_POWER, HIGH); // turn DHT sensor on
		//Sleepy::loseSomeTime(2000); // Sensor requires minimum 2s warm-up after power-on.
		delay(2000);

		tinytx.temp = (myDHT22.readTemperature() * 100); // Get temperature reading and convert to integer, reversed at receiving end
		tinytx.humidity = (myDHT22.readHumidity() * 100); // Get humidity reading and convert to integer, reversed at receiving end
		tinytx.supplyV = readVcc(); // Get supply voltage
				/*
				 Serial.print(tinytx.humidity);
				 Serial.print(",\t");
				 Serial.print(tinytx.temp);
				 Serial.print(",\t");
				 Serial.println(tinytx.supplyV );
				 delay(10);
				 */
		rfwrite(); // Send data via RF

		digitalWrite(DHT22_POWER, LOW); // turn DS18B20 off
		Sleepy::loseSomeTime(60000); //JeeLabs power save function: enter low power mode for 60 seconds (valid range 16-65000 ms)
		Sleepy::loseSomeTime(60000);
		Sleepy::loseSomeTime(60000);
		Sleepy::loseSomeTime(60000);
		Sleepy::loseSomeTime(60000);
}

Als network kann man auch eine andere Zahl als 211 nehmen.
Die myNodeID muss für jeden Sensor geändert werden und muss für das gesamte Netzwerk eindeutig sein. Damit werden die einzelnen Sensoren später identifiziert.

Damit der Sketch kompiliert werden kann, muss die JeeLib sowie die DHT Bibliothek für die Arduino IDE verfügbar sein.
Diese müssen also zuvor runtergeladen und in das Libraries Verzeichnis kopiert werden (bei mir ist das: /usr/share/arduino/libraries).

Um den Sketch auf den AtTiny zu bekommen benötigt die Arduino IDE die Beschreibung des Chips. Diese kann man hier herunterlanden: https://code.google.com/p/arduino-tiny/downloads/list
Anschließend muss die Zip-Datei im arduino/hardware-Verzeichnis entpackt werden.
Nun stehen in der IDE unter Tools–>Board drei neue AtTiny84 Einträge, wovon die 8Mhz Variante genutzt werden sollte.

Zur Übertragung der Firmware auf den Chip gibt es mehrere Möglichkeiten. Entweder nutzt man direkt die entsprechende Hardware (Programmer) oder man verwendet dafür einen Arduino.
Letzteres geht relativ einfach:
Man steckt den Arduino über das USB-Kabel an den Rechner. Läd das Sktech „Beispiele–>Adruino-ISP“ und läd ihn auf den Arduino.
Jetzt funktioniert der Arduino genauso wie ein Programmer.
Damit dieser den AtTiny84 Programmieren kann, muss man die Pins wie folgt verbinden:

Arduino ATtiny84
D13 Pin 9
D12 Pin 8
D11 Pin 7
D10 Pin 4
3.3/5V Pin 1
GND Pin 14

In der IDE stellt man als Programmer jetzt noch den Arduino ein: Tools–>Programmer–>Arduino as ISP

Als nächstes sollte man auf den AtTiny den Arduino Bootloader brennen.
Das geht ebenfalls über das Menü: Tools–>Programmer–>Bootloader installieren.

Und schon hat man seinen AtTiny zu einem Arduino umprogrammiert.

Damit hat man alle Vorbereitungen getroffen um das eigentliche Sketch auf den AtTiny zu flashen. Hierbei ist es wichtig, dass man jetzt nicht einfach, wie sonst von der IDE gewohnt, auf „upload“ klickt. Würde man dies tun, läd man den Sketch nämlich auf seinen Arduino (der ja eigentlich als Programmer gedacht war). Stattdessen geht man auf Datei->Upload mit Programmer.
Dadurch weiss die IDE, dass sie nicht den Arduino selbst flaschen soll, sondern dem Arduino nur sagen soll, dass dieser den AtTiny84 falshen soll.

Nun kann man den Chip in seinen Sockel auf der Platine stecken und den Strom anschalten. Ab jetzt sendet der Sensor alle 5min. Temperatur, Luftfeuchtigkeit, Aktuelle Voltzahl der Batterien und seine ID.
(Anmerkung: Zu Testzwecken sollte man das Senden auf alle 10 Sekunden reduzieren, bis man sicher ist, dass alles so läuft wie man möchte).

Diese Schritte wiederholt man jetzt so oft, wie man Sensoren haben möchte.

Was jetzt noch fehlt ist ein passender Empfänger. Auf der TinyTx-Seite steht eine Beschreibung, wie man mit einem AtTiny und einem RFM12B Chip einen Empfänger bauen kann, die über eine serielle Schnittstelle von einem PC/Raspberry Pi ausgelesen werden kann.

Ich habe mich dafür entschieden auf ein solches Empfangsmodul zu verzichten und habe statdessen ein RFM12B Modul direkt an die GPIO-Pins des PIs angeschlossen.

Dazu habe ich das folgende Kernelmodul verwendet: https://github.com/gkaindl/rfm12b-linux

Damit kann man über folgende Pinbelegung direkt losfunken:

Raspberry Pi RFM12B RFM69CW
P1/17 VDD (+3.3V) VDD (+3.3V)
P1/19 SDI MOSI
P1/21 SDO MISO
P1/22 nIRQ DIO0
P1/23 SCK SCK
P1/25 GND GND
P1/26 nSEL NSS

Folgende Punkte sollten durchgeführt werden, um das Modul zu kompilieren:

  • To install the kernel sources for your running kernel, please use rpi-source. Follow the instructions carefully. Alternatively, you can install kernel sources manually by using a script such as this: https://gist.github.com/azbesthu/3893319 – However, if your kernel is recent enough, rpi-source is highly preferred!
  • Open the rfm12b_config.h file, read it carefully and edit the settings to your liking. Be especially sure to correctly choose the board you are building for.
  • Clone this repository and run make
  • Make sure that the RFM12B/RFM69CW board is connected
  • Load the kernel driver for the SPI interface: sudo modprobe spi-bcm2708
  • Load the driver module: sudo insmod rfm12b.ko.
  • Check the output of dmesg and look into /dev to make sure that the driver has been loaded successfully.

Ich hatte zunächst einige Schwierigkeiten die aktuellen Quellen des Kernels vom Raspberry Pi zu bekommen, hatte aber mit folgendem Skript Erfolg: https://gist.github.com/azbesthu/3893319
Angeblich ist das aber mittlerweile nicht mehr notwendig bzw. geht komfortabler…

Anschließend kann man über die Test-Tools schauen, ob man bereits etwas empfängt:

cd rfm12b-linux/examples/bin
sudo ./rfm12b_read

Mit diesen Beispielen kann man sich auch relativ schnell eigene Tools schreiben.

Mit folgendem Code habe ich die empfangenen Pakete etwas aufbereitet:

/* rfm12b-linux: linux kernel driver for the rfm12(b) RF module by HopeRF
 * Copyright (C) 2013 Georg Kaindl
 *
 * This file is part of rfm12b-linux.
 *
 * rfm12b-linux is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * rfm12b-linux 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with rfm12b-linux. If not, see <http://www.gnu.org/licenses/>.
 */

/*
 DOCUMENTATION:

 This is a trivial example that will open the rfm12b device and
 continously read() from it in an endless loop. The individual
 bytes of the received packet will then be written to stdout.

 On the receiving side, the driver will block on read() until a
 packet is received. A subsequent read will then return the entire
 packet, provided your userspace buffer is large enough (if your
 buffer is too small, the rest of the bytes in the packet will be
 lost forever). Since the maximum packet length is 66 bytes (to
 ensure compatibility with the JeeNode Arduino library), you should
 provide a buffer at least of this size. The return value of read()
 behaves as expected, and if it's a positive number, it's the length
 of the packet that has been received.

 Press Ctrl+C (e.g. send a SIGINT) to quit the program. It will
 then print the driver statistics.

 You can use this example as a receiver to the rfm12b_write.c example
 or as receiver for the rfm12b_send Arduino example. It's also useful
 to test or debug your own sending code.
 */

#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <sys/select.h>

#include "../common/common.h"
#include "../../rfm12b_config.h"
#include "../../rfm12b_ioctl.h"
#include "../../rfm12b_jeenode.h"

#define JEENODE_ID 12

#define RF12_MAX_RLEN 128
#define RF12_MAX_SLEN 66

static volatile int running;

void sig_handler(int signum) {
	signal(signum, SIG_IGN);
	running = 0;
}

int set_nonblock_fd(int fd) {
	int opts;

	opts = fcntl(fd, F_GETFL);
	if (opts >= 0) {

		opts = (opts | O_NONBLOCK);
		if (fcntl(fd, F_SETFL, opts) >= 0) {
			return 0;
		}
	}

	return -1;
}

int main(int argc, char** argv) {

	int rfm12_fd, len, i, nfds, ipos, jee_id = JEENODE_ID, band_id, group_id,
			bit_rate, ioctl_err, has_ack, has_ctl, is_dst, jee_addr, jee_len,
			send_ack;
	char* devname, ibuf[RF12_MAX_SLEN + 1], obuf[RF12_MAX_RLEN + 1], c;
	fd_set fds;

	if (set_nonblock_fd(STDIN_FILENO)) {
		printf("\nfailed to set non-blocking I/O on stdin: %s.\n\n",
				strerror(errno));
		return -1;
	}

	devname = RF12_TESTS_DEV;

	rfm12_fd = open(RF12_TESTS_DEV, O_RDWR);
	if (rfm12_fd < 0) {
		printf("\nfailed to open %s: %s.\n\n", devname, strerror(errno));
		return rfm12_fd;
	} else {
		if (set_nonblock_fd(rfm12_fd)) {
			printf("\nfailed to set non-blocking I/O on %s: %s.\n\n", devname,
					strerror(errno));
			return -1;
		}

	}

	fflush(stdout);
	signal(SIGINT, sig_handler);
	signal(SIGTERM, sig_handler);

	ioctl_err = 0;
	ioctl_err |= ioctl(rfm12_fd, RFM12B_IOCTL_GET_GROUP_ID, &group_id);
	ioctl_err |= ioctl(rfm12_fd, RFM12B_IOCTL_GET_BAND_ID, &band_id);
	ioctl_err |= ioctl(rfm12_fd, RFM12B_IOCTL_GET_BIT_RATE, &bit_rate);

// we also want to send ACK packets automatically
	send_ack = 1;
	ioctl_err |= ioctl(rfm12_fd, RFM12B_IOCTL_SET_JEEMODE_AUTOACK, &send_ack);

	if (ioctl_err) {
		printf("\nioctl() error: %s.\n", strerror(errno));
		return -1;
	}

// activate jeenode-compatible mode by giving this module a jeenode id
	if (ioctl(rfm12_fd, RFM12B_IOCTL_SET_JEE_ID, &jee_id)) {
		printf("\nioctl() error while setting jeenode id: %s.\n",
				strerror(errno));
		return -1;
	}

	running = 1;
	ipos = 2;
	while (running) {
		FD_ZERO(&fds);
		FD_SET(STDIN_FILENO, &fds);
		FD_SET(rfm12_fd, &fds);

		nfds = select(rfm12_fd + 1, &fds, NULL, NULL, NULL);
		if (nfds < 0 && running) {
			printf("\nan error happened during select: %s.\n\n",
					strerror(errno));
			return -1;
		} else if (nfds > 0) {
			if (FD_ISSET(rfm12_fd, &fds)) {
				len = read(rfm12_fd, obuf, RF12_MAX_RLEN);

				if (len < 0) {
					printf("\nerror while receiving: %s.\n\n", strerror(errno));
					return -1;
				} else if (len > 0) {
					obuf[len] = '\0';
					has_ack = (RFM12B_JEE_HDR_ACK_BIT & obuf[0]) ? 1 : 0;
					has_ctl = (RFM12B_JEE_HDR_CTL_BIT & obuf[0]) ? 1 : 0;
					is_dst = (RFM12B_JEE_HDR_DST_BIT & obuf[0]) ? 1 : 0;
					jee_addr = RFM12B_JEE_ID_FROM_HDR(obuf[0]);
					jee_len = obuf[1];

					printf("%d ", jee_addr);
					for (i = 2; i < len; i = i + 2) {
						int number = obuf[i] | obuf[i + 1] << 8;
						float formatted;
						if (i == 4) {
							formatted = (float) number / 1000;
						} else {
							formatted = (float) number / 100;
						}
						printf("%.2f ", formatted);
					}
					printf("\n");
					fflush(stdout);

//printf(RECV_COLOR "" STOP_COLOR " %s\n",
// has_ctl, has_ack, is_dst, jee_addr, jee_len, &obuf[2]);

// in jeenode-compatible mode, the driver sends ACK packets automatically by default,
// so we're just writing out here what the driver does internally. you do
// not need to manually send an ACK if one is requested, though!
					if ((obuf[0] & RFM12B_JEE_HDR_ACK_BIT)
							&& !(obuf[0] & RFM12B_JEE_HDR_CTL_BIT)) {
						if (obuf[0] & RFM12B_JEE_HDR_DST_BIT) {
// if this was only sent to us, an ACK is sent as broadcast
// printf(SEND_COLOR "\n" STOP_COLOR,
// jee_id);
						} else {
// if this was a broadcast, the ACK is sent directly to the source node.
//printf(SEND_COLOR "\n" STOP_COLOR,
// RFM12B_JEE_ID_FROM_HDR(obuf[0]));
						}
					}
				}
			}
		}
	}

	close(rfm12_fd);

//printf("\n\n%lu packet(s) received.\n\n", pkt_cnt);

	return 0;
}

Mit einem kleinen Batchskript kann man nun bei jedem Eintreffen eines Pakets ein Statusupdate an Openhab schicken:

while read eingabe
do
   nodeId="unknown"
   COUNTER=0
   for word in $eingabe
   do
#       bc -l <<< "scale=2;$word/100"
        case $COUNTER in
                [0]* )
#                       echo $word
                        case $word in
                                "16")
                                        nodeId="Bad" ;;
                                "17")
                                        nodeId="Wohnzimmer" ;;
                                "18")
                                        nodeId="Schlafzimmer" ;;
                        esac ;;
                [1]* )
                        item="Sensor_Humidity_"$nodeId ;;
                [2]* )
                        item="Sensor_Battery_"$nodeId ;;
                [3]* )
                        item="Sensor_Temperature_"$nodeId ;;
        esac
        if [ $COUNTER -gt 0 ]; then
#         echo $item
         curl --header "Content-Type: text/plain" --request PUT --data "$word" http://192.168.1.6:8080/rest/items/$item/state
        fi
        let COUNTER=COUNTER+1
   done
done < "${1:-/dev/stdin}"

Jetzt piped man das Kompilierte Programm noch gegen das Bachskript:

sudo ./myRead | ./send.sh

In Openhab müssen nun entsprechende Sensoren konfiguriert werden:

Group Sensor
Group Sensor_Temp
Group Sensor_Hum
Group Sensor_Bat
Number Sensor_Temperature_Bad "Bad Temperatur [%.1f °C]" (Sensor,Sensor_Temp)
Number Sensor_Humidity_Bad "Bad Luftfeuchte [%.1f %%]" (Sensor,Sensor_Hum)
Number Sensor_Battery_Bad "Bad Batterie [%.2f V]" (Sensor,Sensor_Bat)
Number Sensor_Temperature_Wohnzimmer "WZ Temperatur [%.1f °C]" (Sensor,Sensor_Temp)
Number Sensor_Humidity_Wohnzimmer "WZ Luftfeuchte [%.1f %%]" (Sensor,Sensor_Hum)
Number Sensor_Battery_Wohnzimmer "WZ Batterie [%.2f V]" (Sensor,Sensor_Bat)
Number Sensor_Temperature_Schlafzimmer "Schlaf Temperatur [%.1f °C]" (Sensor,Sensor_Temp)
Number Sensor_Humidity_Schlafzimmer "Schlaf Luftfeuchte [%.1f %%]" (Sensor,Sensor_Hum)
Number Sensor_Battery_Schlafzimmer "Schlaf Batterie [%.2f V]" (Sensor,Sensor_Bat)