Сигналізація для холодильника. Не життя, а «малина» c RaspberryPi 3

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



Зберемо просту систему для охорони холодильника від незаконного проникнення з фото реєстрацією та інтеграцією в інтернет через smtp. Влаштуємо у себе справжній інтернет речей на кухні!


Ця ж функція буде корисна, котрі худнуть. Система буде фотографувати порушника кухонного спокою і момент можливого правопорушення. Відкриття холодильника будемо виявляти з допомогою герконового реле, відеозйомка за допомогою звичайної веб-камери в світлі відкритого холодильника. Щоб порушник точно не пішов від відповіді, серію його фото будемо надсилати на поштову скриньку.

Короткий зміст статті
  • Апаратна частина
  • Програмна частина
  • На Groovy
  • На Java
  • Як жити далі з цими знаннями?


Апаратна частина
Геркон — будь-для охоронної системи.
Веб-камера — у мене Logitech C310, підійде будь-яка підтримувана підсистемою Video4Linux
Raspberry Pi 3 Model B — є вбудований WiFi і не потрібен USB hub.

Геркон потрібно підключити між контактом №17 та №15 — тобто між GPIO3 і +3.3 V за схемою.

Програмна частина
В 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.ua: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("з").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("з", 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)».
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.