Java на Raspberry Pi

«Пи» в названии Raspberry Pi относится к Python, но как Java-разработчик я люблю знать и экспериментировать с различными фреймворками Java, которые я также использую на работе. Spring является основным, и я хотел разработать приложение для проверки концепции, которое предоставляет REST API для хранения и извлечения данных датчиков с помощью серверной части базы данных на Raspberry Pi.

Это отрывок из примера приложения из моей книги Начало работы с Java на Raspberry Pi, в которой вы можете найти гораздо больше информации и примеров о Java, Raspberry Pi, Pi4J

Код

Исходники этого приложения доступны в репозитории GitHub JavaOnRaspberryPi, который содержит все примеры, ссылки, схемы и т. Д. Из моей книги.

База данных H2, которую мы будем использовать, представляет собой полноценное Java-решение, которое не требует дополнительной установки, но является полностью частью самого приложения.

Существует множество «дискуссий» о том, следует ли использовать H2 только для тестирования или можно использовать его в продакшене. В последние годы он превратился в очень стабильное решение для базы данных, которое, на мой взгляд, является очень хорошим выбором для встраиваемых проектов.

JPA - это спецификация постоянства, используемая для взаимодействия с базой данных. Это помогает вам определить, какие объекты (сущности) могут храниться в базе данных, в какой структуре (таблицах).

Spring Initializr

Благодаря веб-сайту Spring Initializr мы можем легко начать работу с проектом Spring Boot. Выберите все параметры для самого последнего приложения Java и добавьте

  • Весенняя паутина
  • Spring Data JPA
  • Остальные репозитории
  • База данных H2

Это создаст для вас загрузку ZIP, содержащего полный проект Maven, который вы можете открыть в своей среде IDE и расширить.

Создание сущностей базы данных

Основное преимущество JPA заключается в том, что мы можем определять структуру нашей базы данных в коде. Нам не нужно создавать и определять таблицы; код позаботится об этом.

Это модель, которую мы хотим использовать в нашем примере:

  • Датчик
  • Неограниченное количество измерений на датчик

На схеме это выглядит так:

Этого можно добиться, добавив в наш проект два класса в «entity» пакета (каталога). Сначала мы создаем сенсор под названием «SensorEntity.java». Как видите, этот код содержит множество «аннотаций» (начинающихся с @), которые помогут фреймворку сопоставить код с базой данных.

/**
 * Maps a database entity from the table SENSORS to a Java object.
 * The ID is marked as the unique identifier.
 */
@Entity
@Table(name = "SENSORS", 
    uniqueConstraints={@UniqueConstraint(
        name="UN_SENSOR_ID", 
        columnNames={"ID"})})
public class SensorEntity {
    /**
     * Auto-generated identifier to have a unique key for
     * this sensor.
     */
    @Id
    @GeneratedValue
    private Long id;

    /**
     * Name of the sensor, a required value.
     */
    @Column(nullable = false)
    private String name;

    /**
     * Relationship between the sensor and a list of measurements.
     */
    @OneToMany(
            mappedBy = "sensorEntity",
            cascade = {CascadeType.MERGE},
            fetch = FetchType.LAZY
    )
    private Set<MeasurementEntity> measurements = new HashSet<>();

    ...
}

Таким же образом мы добавляем класс MeasurementEntity.java.

/**
 * Maps a database entity from the table MEASUREMENTS to a Java
 * object. The ID is marked as the unique identifier.
 */
@Entity
@Table(name = "MEASUREMENTS", 
    uniqueConstraints={@UniqueConstraint(
        name="UN_MEASUREMENT_ID", 
        columnNames={"ID"})})
public class MeasurementEntity {

    /**
     * Auto-generated identifier to have a unique key for 
     * this sensor.
     */
    @Id
    @GeneratedValue
    private Long id;

    /**
     * Relationship between the measurement and its sensor.
     */
    @ManyToOne
    @JoinColumn(
        name = "SENSOR_ID", 
        nullable = false, 
        foreignKey = @ForeignKey(name="FK_MEASUREMENT_SENSOR"))
    private SensorEntity sensorEntity;

    /**
     * Timestamp of the measurement.
     */
    @Column(nullable = false)
    private long timestamp;

    /**
     * Key for the type of measurement, e.g. "temperature", 
     * "distance"...
     */
    @Column(nullable = false)
    private String key;

    /**
     * Value of the measurement
     */
    @Column(nullable = false)
    private double value;

    ...
}

Эти два класса - все, что нам нужно для определения таблиц базы данных и того, как данные должны храниться!

Хранение данных в базе данных

Мы сделаем это с помощью двух классов-репозиториев и минимального кода! Давайте снова начнем с датчиков и создадим пакет «репозиторий» и файл «SensorRepository.java». Расширяя «JpaRepository», мы получаем большую часть CRUD-функций (создание, чтение, обновление и удаление) бесплатно, и нам нужно только определить дополнительные методы, которые нам нужны. Используя те же имена параметров, что и значения в объектах, Spring выполнит эту функцию за нас!

@Repository
public interface SensorRepository extends   
       JpaRepository<SensorEntity, Long> {
    Page<SensorEntity> findAll(Pageable pageable);

    List<SensorEntity> findAllByName(String name);

    SensorEntity findById(@Param("id") long id);
}

«MeasurementRepository.java» еще меньше:

@Repository
public interface MeasurementRepository extends 
    JpaRepository<MeasurementEntity, Long>{
    Page<MeasurementEntity> findAll(Pageable pageable);
}

Добавление REST-сервисов

Теперь давайте представим функциональность нашей базы данных с помощью REST-сервисов, чтобы любой мог читать и записывать данные из базы данных и в нее!

Создайте пакет «ресурс» и файл «SensorResource.java». Здесь мы определяем три службы, которые будут доступны по URL-адресу:

  • GET localhost: 8080 / sensor: все датчики из базы данных
  • GET localhost: 8080 / sensor / id: один датчик из базы данных
  • POST localhost: 8080 / sensor: добавьте датчик с заданным именем, после проверки, что это имя еще не используется

Все это делается через созданный нами ранее SensorRepository:

@RestController
public class SensorResource {
    @Autowired
    private SensorRepository sensorRepository;

    @GetMapping("/sensor")
    public List<SensorEntity> retrieveAllSensors() {
        return sensorRepository.findAll();
    }

    @GetMapping("/sensor/{id}")
    public SensorEntity retrieveSensor(@RequestParam long id) {
        return sensorRepository.findById(id);
    }

    @PostMapping("/sensor")
    public ResponseEntity createSensor(@RequestParam String name) {
        List<SensorEntity> sensorEntities = sensorRepository
            .findAllByName(name);

        if (sensorEntities.size() > 0) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                .body("There is already a sensor with the name: " 
                        + name);
        }

        SensorEntity sensorEntity = new SensorEntity(name);
        sensorRepository.save(sensorEntity);
        return ResponseEntity.ok(sensorEntity);
    }
}

Нам нужно сделать то же самое для измерений, чтобы предоставить эти URL-адреса:

  • GET localhost: 8080 / измерение: все измерения из базы данных
  • POST localhost: 8080 / измерение: добавьте измерение для данного идентификатора датчика, ключа и значения после проверки, что данный идентификатор датчика определен в базе данных:
@RestController
public class MeasurementResource {
    @Autowired
    private SensorRepository sensorRepository;

    @Autowired
    private MeasurementRepository measurementRepository;

    @GetMapping("/measurement")
    public List<MeasurementEntity> retrieveAllMeasurements() {
        return measurementRepository.findAll();
    }

    @PostMapping("/measurement")
    public ResponseEntity createMeasurement(
        @RequestParam long sensorId, 
        @RequestParam String key, 
        @RequestParam double value) {

        SensorEntity sensorEntity = sensorRepository
            .findById(sensorId);

        if (sensorEntity == null) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                .body("No sensor defined with the ID: " + sensorId);
        }

        MeasurementEntity measurementEntity = new MeasurementEntity(
            sensorEntity, System.currentTimeMillis(), key, value);
        measurementRepository.save(measurementEntity);

        return ResponseEntity.ok().build();
    }
}

Тестирование на ПК

Давай попробуем! Нажмите Выполнить для основной функции в JavaSpringRestDbApplication.java и перейдите по адресу http: // localhost: 8080 / swagger-ui.html. Используя интерфейс Swagger, мы можем создать несколько датчиков, щелкнув POST / sensor и Попробуйте. Вы можете увидеть созданные записи, попробовав GET для / sensor и / sensor / {id}, а также перейдя непосредственно к http: // localhost: 8080 / sensor. Посмотрите на снимки экрана примеры сохраненных данных.

Проверка данных в H2-консоли

Если вы добавите дополнительную настройку в «src› main ›resources› application.properties », произойдет нечто волшебное. Совершенно неожиданно в наше приложение добавлен полноценный браузер баз данных!

spring.h2.console.enabled=true

Перезапустите приложение, чтобы применить новый параметр. Поскольку H2 по умолчанию хранит все в памяти, наши тестовые данные исчезли, и нам нужно сначала сгенерировать их снова с помощью Swagger, как описано ранее. Теперь перейдите к http: // localhost: 8080 / h2-console и убедитесь, что jdbc: h2: mem: testdb используется как URL-адрес JDBC. На скриншотах ниже вы можете увидеть структуру базы данных, как мы определили ее в нашем коде для обеих таблиц, и проверить данные.

Конфигурация для работы на Raspberry Pi

Наше приложение работает на ПК, давайте сейчас подготовим Raspberry Pi. Spring будет искать файл application.properties в каталоге config при запуске и, если он будет найден, будет использовать его вместо того, который включен в jar. Давайте воспользуемся этой функцией, чтобы перенастроить приложение для использования файловой базы данных вместо оперативной памяти, чтобы данные не терялись при перезапуске.

Мы создадим каталог для приложения и добавим такие свойства:

$ mkdir /home/pi/dbapp $ cd /home/pi/dbapp $ mkdir config $ nano config/application.properties spring.datasource.url=jdbc:h2:file:/home/pi/dbapp/spring-boot-h2-db spring.datasource.username=sa spring.datasource.password= spring.datasource.driver-class-name=org.h2.Driver spring.jpa.hibernate.ddl-auto=update

Соберите приложение на своем ПК в банку с «mvn clean package» и скопируйте «java-spring-rest-db-0.0.1-SNAPSHOT.jar» из «целевого» каталога в свой Pi в «/ home / pi». / dbapp ».

На этом снимке экрана вы также видите созданный файл базы данных «spring-boot-h2-db.mv.db», как определено в файле свойств. При добавлении данных через Swagger вы увидите, что размер этого файла увеличивается.

Запуск приложения дает ожидаемый результат, аналогичный результату на ПК:

$ java -jar java-spring-rest-db-0.0.1-SNAPSHOT.jar

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

Starting JavaSpringRestDbApplication v0.0.1-SNAPSHOT on raspberrypi with PID 4557 
    (/home/pi/dbapp/java-spring-rest-db-0.0.1-SNAPSHOT.jar
    started by pi in /home/pi/dbapp)
No active profile set, falling back to default profiles: default
Bootstrapping Spring Data repositories in DEFAULT mode.
Finished Spring Data repository scanning in 465ms. Found 2 repository interfaces.
...
Tomcat started on port(s): 8080 (http) with context path ''
Started JavaSpringRestDbApplication in 63.416 seconds (JVM running for 67.637)

Большая разница в этой последней строке по сравнению с моим ПК для разработки:

Started JavaSpringRestDbApplication in 5.452 seconds (JVM running for 7.338)

Действительно, вам нужно принять во внимание, что это приложение требует более длительного времени запуска на Pi. Но как только он запустится, вы сможете получить доступ к Swagger и REST-сервисам с любого ПК в вашей сети по IP-адресу вашего Pi, в моем случае 192.168.0.223.

Заключение

Это просто базовый пример с двумя таблицами, но он показывает, как быстро и с минимальным кодом можно создать приложение базы данных. REST-сервисы доступны для всех ваших устройств, которые могут подключаться к Raspberry Pi через сеть, поэтому они могут хранить данные в одном месте и / или читать эти данные.

Данные JSON, предоставляемые REST-сервисами, могут использоваться всеми видами устройств или приложений для визуализации результатов. На сайте Elektor вы можете найти хороший пример того, как микроконтроллер делает это для отображения данных JSON на экране ».

Запуск Java и Spring на Raspberry Pi - лишь один пример универсальности этой небольшой, недорогой, но очень мощной машины! Чтобы получить больше удовольствия от #JavaOnRaspberryPi, посмотрите мою книгу Начало работы с Java на Raspberry Pi.

Первоначально опубликовано на https://webtechie.be.