Raspberri PI: прием и передача сигналов на частоте 433mhz

На даче у меня управление батареями построено на реле Sonoff R3 DIY, которые включаются-выключаются при помощи WiFi. Это было моей ошибкой — управление получилось не очень стабильным. Реле имеют свойство периодически «отваливаться», из-за того что сигнал от роутера слабоват и весь дом покрывает с трудом. Решил освоить управление реле при помощи приёмопередатчиков 433mhz. Приём-передача по слухам гораздо стабильнее. Да и пультики-брелки можно будет прикрутить таким образом для управления уличным освещением. Приятно будет — подъезжаешь вечером на машине и шарахаться в темноте не нужно — с пульта включил свет. Плюс бонусом хочу на первый этаж поставить arduino с приёмником, и часть функционала с raspberry перекинуть на неё, т.к. тупо пинов уже не хватает к малинке.

» Читать далее

Dart: секунды в строку вида hh:mm:ss

Задача: преобразовать и отобразить секунды в строку часы-минуты-секунды

Решение:

  String TimeLeft(diff){
    var timestamp = diff.floor();
    var hours = (timestamp / 60 / 60).floor();
    var minutes = ((timestamp / 60) - (hours * 60)).floor();
    var seconds = timestamp % 60;
    if (seconds<0){
      seconds=0;hours=0;minutes=0;
    }
    String hs=hours.toString();
    hs=hs.length>1?hs:"0"+hs;
    String ms=minutes.toString();
    ms=ms.length>1?ms:"0"+hs;
    String ss=seconds.toString();
    ss=ss.length>1?ss:"0"+ss;
    return "$hs:$ms:$ss";
  }

Бонусом тоже самое на javascript:

function TimeLeft(diff){    
    var timestamp = Math.floor(diff);
    var hours = Math.floor(timestamp / 60 / 60);
    var minutes = Math.floor(timestamp / 60) - (hours * 60);
    var seconds = timestamp % 60;        
    if (seconds<0){seconds=0;hours=0;minutes=0;} 
    h=("0"+hours).slice(-2);
    m=("0"+minutes).slice(-2);
    s=("0"+seconds).slice(-2);
};

Защита сайта сертификатом p12

Задача: обеспечить вход на сайт только при наличии действующего сертификата формата p12.

Решение:

Инструкция создана на основе нескольких источников, в т.ч. этого и этого, но с внесением поправок что год уже конец 2022, и появились некоторые нюансы, что старые инструкции не работают по умолчанию для новых дистрибутивов.

Сначала создадим корневой сертификат сервера и необходимые для дальнейшей работы файлы и папки. Для этого создадим в любой папке файл root_cer.sh с содержимым вида:

mkdir db                # БД сертификатов
mkdir certs             # ключ и сертификат сервера
mkdir db/certs          # пользовательские сертификаты
mkdir db/newcerts       # новые сертификаты
mkdir p12               # сертификаты для передачи клиентам (p12)
touch db/index.txt
echo "01" > db/serial
openssl req -new -newkey rsa:2048 -nodes -keyout certs/server.key -x509 -days 1024 \
          -subj /C=RU/ST=Vol/L=Vol/O=LTD\ ElkiPalki/OU=Sale/CN=zz/emailAddress=wefwerfwe@gmail.com \
          -out certs/server.crt

, где:

rsa:2048- длина ключа, -days — сколько дней действителен сертификат, -x509 — создаём самоподписаный сертификат, /C — страна, /ST — область, /L — город, /O — организация, /OU — отдел,

Результатом работы будут два файла в папке certs: servert.crt (сертификат) и serverk.key (закрытый ключ). Данные закрытого ключа можно посмотреть при помощи:

openssl rsa -noout -text -in server.key  
openssl x509 -noout -text -in server.crt           

В файле db/serial записывается текущий серийный номер
подписываемого сертификата в шестнадцатиричном формате.

В файл db/index.txt сохраняются данные о подписываемых сертификатах.

Далее необходимо создать файл с настройками для генерации пользовательских сертификатов ca.config:

 [ ca ]
        default_ca             = CA_CLIENT       # При подписи сертификатов  использовать секцию CA_CLIENT

        [ CA_CLIENT ]
        dir                    = ./db            # Каталог для служебных файлов
        certs                  = $dir/certs      # Каталог для сертификатов
        new_certs_dir          = $dir/newcerts   # Каталог для новых сертификатов

        database               = $dir/index.txt  # Файл с базой данных
        # подписанных сертификатов
        serial                 = $dir/serial     # Файл содержащий серийный номер
        # сертификата
        # (в шестнадцатиричном формате)
        certificate            = ./certs/server.crt        # Файл сертификата CA
        private_key            = ./certs/server.key        # Файл закрытого ключа CA

        default_days           = 365             # Срок действия подписываемого
        # сертификата
        default_crl_days       = 7               # Срок действия CRL (см. $4)
        default_md             = sha256          # Алгоритм подписи

        policy                 = policy_anything # Название секции с описанием политики в отношении данных сертификата

        [ policy_anything ]
        countryName            = optional        # Код страны - не обязателен
        stateOrProvinceName    = optional        # ......
        localityName           = optional        # ......
        organizationName       = optional        # ......
        organizationalUnitName = optional        # ......
        commonName             = supplied        # ...... - обязателен
        emailAddress           = optional        # ......

Для автоматизации генерации клиентских сертификатов можно собрать небольшой bash скрипт:

#!/bin/bash
NO_ARGS=0
BASE_DIR=""
P12_DIR="$BASE_DIR/p12"
CA_CFG="$BASE_DIR/ca.config"            # Конфигурационный файл для подписи
CERTS="$BASE_DIR/db/certs"              # Каталог для хранения сертификатов
CA_FILE="$BASE_DIR/certs/server.crt"    # Доверенный сертификат (Им подписывам)

usage () {
  echo "Скрипт basename $0 предназначен для создания клиентских SSL сертификатов."
  echo ""
  echo "Использование: basename $0 email name password"
}

if [ $# -eq "$NO_ARGS" ]  # Сценарий вызван без аргументов?
then
  usage                   # Если запущен без "аргуменотов" - вывести справку
  exit $E_OPTERROR        # и выйти с кодом ошибки
fi

EMAIL=$1
echo "Email:$EMAIL"
NAME=$2
echo "Name:$NAME"
PASSWORD=$3
echo "Password:$PASSWORD"

# создаём запрос на клиентский сертификат
openssl req -new -newkey rsa:2048 -nodes -keyout "$CERTS/$NAME.key" \
        -subj /C=RU/CN=usr/emailAddress="$EMAIL" \
        -out "$CERT/$NAME.csr"

# подписываем запрос на сертификат при помощи доверенного сертификата
openssl ca -config "$CA_CFG" -in "$CERT/$NAME.csr" -out "$CERT/$NAME.crt" -batch

# создаём файл p12 для передачи клиенту
openssl pkcs12 -export -in "$CERT/$NAME.crt" -inkey "$CERTS/$NAME.key" \
        -certfile "$CA_FILE" -out "$P12_DIR/$NAME.p12" -passout pass:"$PASSWORD"

На входе скрипта необходимо задать email, имя пользователя без пробелов и пароль на установку сертификата. На выходе будет запись в БД сертификатов и непосредственно сам контейнер p12 в папке p12 для передачи клиенту.

И ещё один полезный скрипт, для автоматизации отзыва сертификатов revote.sh:

#!/bin/bash
NO_ARGS=0
BASE_DIR=""
CA_CFG="$BASE_DIR/ca.config"
CERTS="$BASE_DIR/db/newcerts"
usage () {
  echo "Скрипт basename $0 предназначен для отзыва клиентских SSL сертификатов."
  echo ""
  echo "Использование: basename $0 serial"
}
if [ $# -eq "$NO_ARGS" ]  # Сценарий вызван без аргументов?
then
  usage                   # Если запущен без "аргуменотов" - вывести справку
  exit $E_OPTERROR        # и выйти с кодом ошибки
fi

SERIAL=$1
echo "Отзываю сертификат: $CERTS/$SERIAL.pem"
openssl ca -config "$CA_CFG" -revoke "$CERTS/$SERIAL.pem"
# составим список отозваных в файл revoked.crl
openssl ca -gencrl -config "$CA_CFG" -out revoked.crl

Посмотреть список отозваных сертификатов можно при помощи команды:

openssl crl -in revoked.crl -text -noout

Осталось только настроить apache для того что бы он пускал на сайт, только при наличии сертификата, добавив:

SSLCACertificateFile /path/to/server.crt
SSLVerifyClient require

Flutter: Хранение данных приложения

На этом месте должна была быть статья по работу Flutter с SQLite, но не сложилось. Не умеет Flutter без плясок с бубном работать с SQLite одновременно на всех целевых платформах сразу: WIndows/Android/IOS. Самый популярный пакет sqflite — работает только на Android/IOS, остальные, задекларированные как рабочие под Windows, по оной так запустить мне и не удалось, с учётом что у меня ограниченные права Windows. Потому, стал смотреть в сторону пакета Hive — работающего на всех целевых платформах, т.к. написан изначально на языке Dart.

Hive -это быстрая локальная база для Flutter, Dart работающая на основе ключ-значение.

Установка: добавить в зависимости pubsec.yaml:

dependencies:
  hive: ^2.2.3

Для удобства можно написать маленький класс для работы с ключами-значениями:

import 'dart:io';
import 'package:hive/hive.dart';

class THave {
  String bname="";
  void Init(String basename)  {
    var path = Directory.current.path;
    Hive.init(path);
    bname=basename;
  }
  void Add(String key,var value) async {
    var box = await Hive.openBox(bname);
    await box.put(key, value);
  }
  void Delete(String key) async {
    var box = await Hive.openBox(bname);
    await box.delete(key);
  }
  dynamic  Get(String key) async  {
    var box =  await Hive.openBox(bname);
    return (box.get(key));
  }
  Future <Iterable> GetKeys() async {
    var box = await Hive.openBox(bname);
    return await box.keys;
  }
}

И теперь например можно при новом запуске приложения «вспоминать» авторизацию:

main.dart:

void main() {
  THave have=THave();
  have.Init("auth");
  have.Get("UserId").then((UserId){
    if (UserId!=null){
      globals.UserId=UserId;
    };
    have.Get("UserName").then((UserName){
      if (UserId!=null){
        globals.UserName=UserName;
        globals.is_login=true;
      };
      runApp(const MyApp());
    });
  });
}

login.dart:

                      onPressed: (){
                        http.post(Uri.parse('https://erfwe.ru/1.php'),body: {'login':_controller1.text,'password':_controller2.text}).then((response) {
                          print("Response status: ${response.statusCode}");
                          print("Response body: ${response.body}");
                          Map<String, dynamic> user = jsonDecode(response.body);
                          setState(() {
                            globals.UserName = user["UserName"];
                            globals.is_login = true;
                            globals.UserId = user["UserId"];
                            THave have=THave();
                            have.Init("auth");
                            have.Add("UserName", globals.UserName);
                            have.Add("UserId", globals.UserId);
                          });
                        }).catchError((error){
                          print("Error: $error");
                        });
                      },

Причем нужно отметить свойства сохранения и извлечения ключ-значение: при сохранении мы не ждем результата записи. При чтении — соответственно необходимо дождаться.

1 7 8 9 10 11 71