Запылилась за месяц у меня на полке Raspberry Pi 3 со встроенным Wi-Fi. Ресурсов процессора и объема памяти уже достаточно для запуска ресурсоемких программ. Как же быстро разработать и запустить на ней свою программу состоящую всего из одного небольшого файла с отправкой фото на почту и веб сервером мониторинга?

Соберем простую систему для охраны холодильника от незаконного проникновения с фото регистрацией и интеграцией в интернет через smtp. Устроим у себя настоящий интернет вещей на кухне!
Эта же функция будет полезна худеющим. Система будет фотографировать нарушителя кухонного спокойствия и момент возможного правонарушения. Открытие холодильника будем обнаруживать с помощью герконового реле, видеосъемка с помощью обычной веб камеры в свете открытого холодильника. Чтобы нарушитель точно не ушел от ответа, серию его фото будем отправлять на почтовый ящик.
Геркон — любой для охранной системы.
Веб-камера — у меня Logitech C310, подойдет любая поддерживаемая подсистемой Video4Linux
Raspberry Pi 3 Model B — есть встроенный WiFi и не нужен USB hub.
Геркон нужно подключить между контактом №17 и №15 — т.е. между GPIO3 и +3.3V по схеме.
В open source фреймворке Apache Camel и его компонентах Rhiot для JVM сделали многое, чтобы найти применение пылящемуся в шкафу одноплатному компьютеру. Достаточно, используя его язык для описания конфигурации, скомпоновать из готовых компонент «маршрут» сигналов/данных в системе и Camel превратит его в приложение.
В прошлой своей статье про разработку для интернета вещей в JVM я обещал пример для Camel и сегодня сдерживаю обещание! Идея этого проекта навеяна примером «Intruder detection with Raspberry-Pi». Только геркон доступнее и программно работать с ним так же как и с обычной кнопкой — не нужно никакого протокола I2C.
С помощью RouteBuilder создаем маршрут. Источники и приемники данных в camel описываются в виде URL и для каждого компонента/протокола описание формата любого компонента сможете прочитать на странице.
camelContext.start() инициализирует компоненты и запускает маршрут. Реагировать на размыкание контакта геркона очень просто:

addEventNotifier() позволяет нам перехватывать события маршрута. Мы будем реагировать на запуск и остановку маршрута и отправлять сообщение о статусе сигнализации на почтовый ящик.
Если ваша почта не на сервере mail.ru, то найдите smtp хост, порт для вашей почты и внесите их вместо «smtps://smtp.mail.ru:465».
Пробовал также искать лицо на фото в том же маршруте Camel, но даже Raspberry PI 3 model B подтормаживает на этой задаче.
Фрагмент кода запускает веб консоль hawt.io для мониторинга и управления приложением:
Если же функциональности почти двухсот компонентов вам окажется мало, то разработать свой новый компонент для Apache Camel достаточно легко. Недавно делал это в проекте camel-gcode для отправки команд станку ЧПУ под управлением LinuxCNC из программы в JVM.
AlarmSystem.groovy
Чтобы сделать то же самое на java понадобилось больше букв, файлов и конечно Reflection API.
Для сборки нужен:
Сборка linux Raspbian на SD карте уже чудесным образом содержит Java 8 от Oracle. Настройте подключение к интернет по WiFi или подключите патчкордом и сконфигурируйте доступ к интернет по ethernet сети через RJ-45 разьем на плате.
Так что вся установка будет состоять из простых команд:
И запуска программы:
Или вы можете просто внести свои логин и пароль в скрипт, чтобы не светить их в истории команд:
Сразу после запуска скрипта маршрут «GPIO read» ждет сигнала с геркона и запущен, а второй маршрут «RaspberryPI Alarm» с вебкамерой — на паузе.
Эта консоль мониторинга доступна по адресу http:// АДРЕС_МАЛИНЫ :10090/hawtio/
Java версию нужно собрать с помощью maven. Или же можно пойти на хитрость и запустить Java программу как скрипт с динамическим разрешением зависимостей следующим образом:
Про то как работает java-as-script-1.0.jar и что еще можно делать с его помощью будет отдельная статья.
Apache Camel оказался отличным инструментом для быстрого прототипирования, так как есть много готовых компонент для различной периферии, интернет-сервисов. Хоть его обычно и используют в enterprise приложениях для интеграции, но даже на современных одноплатных компьютерах и в решениях для «интернета вещей» он даст фору другим подходам для разработки систем. Просто «распробуйте» его и он вам понравится, особенно вместе с Groovy!
Проект доступен в github репозитарии alarm-system и засветился на официальном сайте Apache Camel в разделе «Camel and the IoT (Internet of Things)».

Соберем простую систему для охраны холодильника от незаконного проникновения с фото регистрацией и интеграцией в интернет через smtp. Устроим у себя настоящий интернет вещей на кухне!
Эта же функция будет полезна худеющим. Система будет фотографировать нарушителя кухонного спокойствия и момент возможного правонарушения. Открытие холодильника будем обнаруживать с помощью герконового реле, видеосъемка с помощью обычной веб камеры в свете открытого холодильника. Чтобы нарушитель точно не ушел от ответа, серию его фото будем отправлять на почтовый ящик.
Краткое содержание статьи
- Аппаратная часть
- Программная часть
- На Groovy
- На Java
- Как жить дальше с этими знаниями?
Аппаратная часть
Геркон — любой для охранной системы.
Веб-камера — у меня Logitech C310, подойдет любая поддерживаемая подсистемой Video4Linux
Raspberry Pi 3 Model B — есть встроенный WiFi и не нужен USB hub.
Геркон нужно подключить между контактом №17 и №15 — т.е. между GPIO3 и +3.3V по схеме.
Программная часть
В open source фреймворке Apache Camel и его компонентах Rhiot для JVM сделали многое, чтобы найти применение пылящемуся в шкафу одноплатному компьютеру. Достаточно, используя его язык для описания конфигурации, скомпоновать из готовых компонент «маршрут» сигналов/данных в системе и Camel превратит его в приложение.
В прошлой своей статье про разработку для интернета вещей в JVM я обещал пример для Camel и сегодня сдерживаю обещание! Идея этого проекта навеяна примером «Intruder detection with Raspberry-Pi». Только геркон доступнее и программно работать с ним так же как и с обычной кнопкой — не нужно никакого протокола I2C.
С помощью RouteBuilder создаем маршрут. Источники и приемники данных в camel описываются в виде URL и для каждого компонента/протокола описание формата любого компонента сможете прочитать на странице.
- controlbus — это компонент для управления маршрутами. В нашем случае с помощью него запускаем и останавливаем фотосъемку.
- pi4j-gpio — использует библиотеку pi4j для получения сигналов с GPIO «малины».
- webcam — получает кадр с веб камеры через интервалы времени, определенные компонентом-таймером.
- smtps — передача сообщения электронной почты.
camelContext.start() инициализирует компоненты и запускает маршрут. Реагировать на размыкание контакта геркона очень просто:

Визуализация реакции на геркон в hawt.io

Маршрут же фото регистрации с отправкой снимка на почту в hawt.io

addEventNotifier() позволяет нам перехватывать события маршрута. Мы будем реагировать на запуск и остановку маршрута и отправлять сообщение о статусе сигнализации на почтовый ящик.
Если ваша почта не на сервере mail.ru, то найдите smtp хост, порт для вашей почты и внесите их вместо «smtps://smtp.mail.ru:465».
Пробовал также искать лицо на фото в том же маршруте Camel, но даже Raspberry PI 3 model B подтормаживает на этой задаче.
Фрагмент кода запускает веб консоль hawt.io для мониторинга и управления приложением:
MavenClassLoader.usingCentralRepo() .forMavenCoordinates('io.hawt:hawtio-app:2.0.0').loadClass('io.hawt.app.App') Thread.currentThread().setContextClassLoader(hawtIoConsole.getClassLoader()) hawtIoConsole.main('--port','10090')
Если же функциональности почти двухсот компонентов вам окажется мало, то разработать свой новый компонент для Apache Camel достаточно легко. Недавно делал это в проекте camel-gcode для отправки команд станку ЧПУ под управлением LinuxCNC из программы в JVM.
На Groovy
AlarmSystem.groovy
@Grab('org.apache.camel:camel-groovy:2.18.0') @Grab('org.apache.camel:camel-core:2.18.0') @Grab('org.apache.camel:camel-mail:2.18.0') @Grab('io.rhiot:camel-webcam:0.1.4') @Grab('io.rhiot:camel-pi4j:0.1.4') @Grab('org.slf4j:slf4j-simple:1.6.6') import org.apache.camel.builder.RouteBuilder import org.apache.camel.impl.DefaultAttachment import org.apache.camel.impl.DefaultCamelContext import org.apache.camel.management.event.CamelContextStartedEvent import org.apache.camel.management.event.CamelContextStoppedEvent import org.apache.camel.support.EventNotifierSupport import javax.mail.util.ByteArrayDataSource import com.github.igorsuhorukov.smreed.dropship.MavenClassLoader def login = System.properties['login'] def password = System.properties['password'] def camelContext = new DefaultCamelContext() camelContext.setName('Alarm system') def mailEndpoint = camelContext.getEndpoint("smtps://smtp.mail.ru:465?username=${login}&password=${password}&contentType=text/html&debugMode=true") camelContext.addRoutes(new RouteBuilder() { def void configure() { from('pi4j-gpio://3?mode=DIGITAL_INPUT&pullResistance=PULL_DOWN').routeId('GPIO read') .choice() .when(header('CamelPi4jPinState').isEqualTo("LOW")) .to("controlbus:route?routeId=RaspberryPI Alarm&action=resume") .otherwise() .to("controlbus:route?routeId=RaspberryPI Alarm&action=suspend"); from("timer://capture_image?delay=200&period=5000") .routeId('RaspberryPI Alarm') .to("webcam:spycam?resolution=HD720") .setHeader('to').constant(login) .setHeader('from').constant(login) .setHeader('subject').constant('alarm image') .process{ def attachment = new DefaultAttachment(new ByteArrayDataSource(it.in.body, 'image/jpeg')); it.in.setBody("<html><head></head><body><img src=\"cid:alarm-image.jpeg\" /> ${new Date()}</body></html>"); attachment.addHeader("Content-ID", '<alarm-image.jpeg>'); it.in.addAttachmentObject("alarm-image.jpeg", attachment); //set CL to avoid javax.activation.UnsupportedDataTypeException: no object DCH for MIME type multipart/mixed Thread.currentThread().setContextClassLoader( getClass().getClassLoader() ); } .to(mailEndpoint) } }) registerLifecycleActions(camelContext, mailEndpoint, login) camelContext.start() def hawtIoConsole = MavenClassLoader.usingCentralRepo() .forMavenCoordinates('io.hawt:hawtio-app:2.0.0').loadClass('io.hawt.app.App') Thread.currentThread().setContextClassLoader(hawtIoConsole.getClassLoader()) hawtIoConsole.main('--port','10090') void registerLifecycleActions(camelContext, mailEndpoint, login) { camelContext.getManagementStrategy().addEventNotifier(new EventNotifierSupport() { boolean isEnabled(EventObject event) { return event instanceof CamelContextStartedEvent | event instanceof CamelContextStoppedEvent } void notify(EventObject event) throws Exception { def status = event instanceof CamelContextStartedEvent ? 'up' : 'down' if ('up' == status){ def suspendEndpoint = camelContext.getEndpoint("controlbus:route?routeId=RaspberryPI Alarm&action=suspend") suspendEndpoint.createProducer().process(suspendEndpoint.createExchange()) } def message = mailEndpoint.createExchange(); message.in.setHeader('to', login) message.in.setHeader('from', login) message.in.setHeader('subject', "Alarm system is ${status}") message.in.setBody("System is ${status}: ${new Date()}"); mailEndpoint.createProducer().process(message) } }) addShutdownHook { camelContext.stop() } }
На Java
Чтобы сделать то же самое на java понадобилось больше букв, файлов и конечно Reflection API.
Класс com.github.igorsuhorukov.alarmsys.AlarmSystem:
AlarmSystem.java
package com.github.igorsuhorukov.alarmsys; //dependency:mvn:/com.github.igor-suhorukov:mvn-classloader:1.8 //dependency:mvn:/org.apache.camel:camel-core:2.18.0 //dependency:mvn:/org.apache.camel:camel-mail:2.18.0 //dependency:mvn:/io.rhiot:camel-webcam:0.1.4 //dependency:mvn:/io.rhiot:camel-pi4j:0.1.4 //dependency:mvn:/org.slf4j:slf4j-simple:1.6.6 import com.github.igorsuhorukov.smreed.dropship.MavenClassLoader; import org.apache.camel.Endpoint; import org.apache.camel.Exchange; import org.apache.camel.Processor; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.impl.DefaultAttachment; import org.apache.camel.impl.DefaultCamelContext; import org.apache.camel.management.event.CamelContextStartedEvent; import org.apache.camel.management.event.CamelContextStoppedEvent; import org.apache.camel.support.EventNotifierSupport; import javax.mail.util.ByteArrayDataSource; import java.lang.reflect.Method; import java.util.Date; import java.util.EventObject; class AlarmSystem { public static void main(String[] args) throws Exception{ String login = System.getProperty("login"); String password = System.getProperty("password"); DefaultCamelContext camelContext = new DefaultCamelContext(); camelContext.setName("Alarm system"); Endpoint mailEndpoint = camelContext.getEndpoint(String.format("smtps://smtp.mail.ru:465?username=%s&password=%s&contentType=text/html&debugMode=true", login, password)); camelContext.addRoutes(new RouteBuilder() { @Override public void configure() throws Exception { from("pi4j-gpio://3?mode=DIGITAL_INPUT&pullResistance=PULL_DOWN").routeId("GPIO read") .choice() .when(header("CamelPi4jPinState").isEqualTo("LOW")) .to("controlbus:route?routeId=RaspberryPI Alarm&action=resume") .otherwise() .to("controlbus:route?routeId=RaspberryPI Alarm&action=suspend"); from("timer://capture_image?delay=200&period=5000") .routeId("RaspberryPI Alarm") .to("webcam:spycam?resolution=HD720") .setHeader("to").constant(login) .setHeader("from").constant(login) .setHeader("subject").constant("alarm image") .process(new Processor() { @Override public void process(Exchange it) throws Exception { DefaultAttachment attachment = new DefaultAttachment(new ByteArrayDataSource(it.getIn().getBody(byte[].class), "image/jpeg")); it.getIn().setBody(String.format("<html><head></head><body><img src=\"cid:alarm-image.jpeg\" /> %s</body></html>", new Date())); attachment.addHeader("Content-ID", "<alarm-image.jpeg>"); it.getIn().addAttachmentObject("alarm-image.jpeg", attachment); //set CL to avoid javax.activation.UnsupportedDataTypeException: no object DCH for MIME type multipart/mixed Thread.currentThread().setContextClassLoader( getClass().getClassLoader() ); } }).to(mailEndpoint); } }); registerLifecycleActions(camelContext, mailEndpoint, login); camelContext.start(); Class<?> hawtIoConsole = MavenClassLoader.usingCentralRepo() .forMavenCoordinates("io.hawt:hawtio-app:2.0.0").loadClass("io.hawt.app.App"); Thread.currentThread().setContextClassLoader(hawtIoConsole.getClassLoader()); Method main = hawtIoConsole.getMethod("main", String[].class); main.setAccessible(true); main.invoke(null, (Object) new String[]{"--port","10090"}); } private static void registerLifecycleActions(final DefaultCamelContext camelContext, final Endpoint mailEndpoint, final String login) { camelContext.getManagementStrategy().addEventNotifier(new EventNotifierSupport() { public boolean isEnabled(EventObject event) { return event instanceof CamelContextStartedEvent | event instanceof CamelContextStoppedEvent; } public void notify(EventObject event) throws Exception { String status = event instanceof CamelContextStartedEvent ? "up" : "down"; if ("up".equals(status)){ Endpoint suspendEndpoint = camelContext.getEndpoint("controlbus:route?routeId=RaspberryPI Alarm&action=suspend"); suspendEndpoint.createProducer().process(suspendEndpoint.createExchange()); } Exchange message = mailEndpoint.createExchange(); message.getIn().setHeader("to", login); message.getIn().setHeader("from", login); message.getIn().setHeader("subject", "Alarm system is "+status); message.getIn().setBody("System is "+status+": "+new Date()); mailEndpoint.createProducer().process(message); } }); Runtime.getRuntime().addShutdownHook(new Thread(){ @Override public void run(){ try { camelContext.stop(); } catch (Exception e) { System.exit(-1); } } }); } }
Для сборки нужен:
pom.xml c зависимостями проекта
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.github.igor-suhorukov</groupId> <artifactId>alarm-system</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>com.github.igor-suhorukov</groupId> <artifactId>mvn-classloader</artifactId> <version>1.8</version> </dependency> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-core</artifactId> <version>2.18.0</version> </dependency> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-mail</artifactId> <version>2.18.0</version> </dependency> <dependency> <groupId>io.rhiot</groupId> <artifactId>camel-webcam</artifactId> <version>0.1.4</version> </dependency> <dependency> <groupId>io.rhiot</groupId> <artifactId>camel-pi4j</artifactId> <version>0.1.4</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>1.6.6</version> </dependency> </dependencies> </project>
Запускаем результат на Raspberry Pi 3 Model B
Сборка linux Raspbian на SD карте уже чудесным образом содержит Java 8 от Oracle. Настройте подключение к интернет по WiFi или подключите патчкордом и сконфигурируйте доступ к интернет по ethernet сети через RJ-45 разьем на плате.
Так что вся установка будет состоять из простых команд:
wget https://repo1.maven.org/maven2/com/github/igor-suhorukov/groovy-grape-aether/2.4.5.4/groovy-grape-aether-2.4.5.4.jar wget https://raw.githubusercontent.com/igor-suhorukov/alarm-system/master/AlarmSystem.groovy
И запуска программы:
java -Dlogin=...ВАША_ПОЧТА...@mail.ru -Dpassword=******* -jar groovy-grape-aether-2.4.5.4.jar AlarmSystem.groovy
Или вы можете просто внести свои логин и пароль в скрипт, чтобы не светить их в истории команд:
def login = ... def password = ...
Сразу после запуска скрипта маршрут «GPIO read» ждет сигнала с геркона и запущен, а второй маршрут «RaspberryPI Alarm» с вебкамерой — на паузе.
На это можно посмотреть в веб консоли...

Еще можно отложить jconsole в сторону. Ведь потоки и метрики jvm можно смотреть в hawt.io



Эта консоль мониторинга доступна по адресу http:// АДРЕС_МАЛИНЫ :10090/hawtio/
Java версию нужно собрать с помощью maven. Или же можно пойти на хитрость и запустить Java программу как скрипт с динамическим разрешением зависимостей следующим образом:
java -Dlogin=...YOUR_EMAIL...@mail.ru -Dpassword=******* -DscriptPath=https://raw.githubusercontent.com/igor-suhorukov/alarm-system/master/src/main/java/com/github/igorsuhorukov/alarmsys/AlarmSystem.java -jar java-as-script-1.0.jar
Про то как работает java-as-script-1.0.jar и что еще можно делать с его помощью будет отдельная статья.
Как жить дальше с этими знаниями?
Apache Camel оказался отличным инструментом для быстрого прототипирования, так как есть много готовых компонент для различной периферии, интернет-сервисов. Хоть его обычно и используют в enterprise приложениях для интеграции, но даже на современных одноплатных компьютерах и в решениях для «интернета вещей» он даст фору другим подходам для разработки систем. Просто «распробуйте» его и он вам понравится, особенно вместе с Groovy!
Проект доступен в github репозитарии alarm-system и засветился на официальном сайте Apache Camel в разделе «Camel and the IoT (Internet of Things)».