Skip to content

API и примеры интеграции PRIMO core

REST API Endpoints

Аутентификация

Вход в систему:

POST /login
Content-Type: application/x-www-form-urlencoded

login=admin&password=your_password

Выход из системы:

GET /logout

Получение конфигураций

Получение конфигурации освещения:

GET /get_config?conf=light

Ответ:

{
    "conf": "light",
    "time_start": "08:00",
    "time_stop": "22:00", 
    "imitation_dawn": "n",
    "imitation_dawn_time": "30",
    "dimmer_max": "10000"
}

Получение конфигурации питания:

GET /get_config?conf=nutrient

Ответ:

{
    "conf": "nutrient",
    "relay_ec_time": "5",
    "relay_ph_time": "5",
    "limit_ph": "6.8",
    "limit_ec": "1150",
    "mixing_time": "5",
    "await": "1"
}

Другие доступные конфигурации: - conf=light_mode - режим работы освещения - conf=nutrient_mode - режим работы питания - conf=solution - конфигурация подачи раствора - conf=solution_mode - режим подачи раствора - conf=climat - конфигурация климата - conf=climat_mode - режим климата

Установка конфигураций

Изменение настроек освещения:

GET /set_config?conf=light&time_start=09:00&time_stop=21:00&imitation_dawn=y&imitation_dawn_time=60

Изменение настроек питания:

GET /set_config?conf=nutrient&limit_ph=6.5&limit_ec=1200&relay_ph_time=3

Активация/деактивация режимов:

GET /set_config?conf=light_mode&active=yes
GET /set_config?conf=nutrient_mode&active=no

Управление реле

Включение конкретного реле:

GET /relay_on/1

Выключение конкретного реле:

GET /relay_off/1

Выключение всех реле:

GET /relay_all_off/

Получение статуса реле:

GET /relay_status/1

Получение статуса канала:

GET /channel_status/1

Системная информация

Получение системной конфигурации:

GET /sys_config/

Перезагрузка системы:

GET /sys_reboot/

MQTT API

Подключение к MQTT брокеру

Python пример:

import paho.mqtt.client as mqtt
import json

def on_connect(client, userdata, flags, rc):
    print(f"Подключено к MQTT брокеру с кодом {rc}")
    # Подписка на топики
    client.subscribe("/sensor/+")
    client.subscribe("/relays/state")

def on_message(client, userdata, msg):
    topic = msg.topic
    payload = msg.payload.decode()
    print(f"Получено: {topic} -> {payload}")

client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message

client.connect("raspberry-pi-ip", 1883, 60)
client.loop_forever()

JavaScript пример (Node.js):

const mqtt = require('mqtt');
const client = mqtt.connect('mqtt://raspberry-pi-ip:1883');

client.on('connect', function () {
    console.log('Подключено к MQTT брокеру');
    client.subscribe('/sensor/+');
    client.subscribe('/relays/state');
});

client.on('message', function (topic, message) {
    console.log(`Получено: ${topic} -> ${message.toString()}`);
});

Управление устройствами через MQTT

Включение реле:

import paho.mqtt.publish as publish
import json

# Включение реле №1
message = json.dumps({"pin": 1, "state": "on"})
publish.single("/set/gpio/", message, hostname="raspberry-pi-ip")

# Выключение реле №1  
message = json.dumps({"pin": 1, "state": "off"})
publish.single("/set/gpio/", message, hostname="raspberry-pi-ip")

Управление ЦАП/диммером:

# Установка яркости на канале 1 (значение 0-10000)
message = json.dumps({"channel": 1, "value": 5000})
publish.single("/set/dac/", message, hostname="raspberry-pi-ip")

Получение данных сенсоров

Подписка на данные сенсоров:

import paho.mqtt.subscribe as subscribe

def get_temperature():
    msg = subscribe.simple("/sensor/temp", hostname="raspberry-pi-ip")
    return float(msg.payload.decode())

def get_humidity():
    msg = subscribe.simple("/sensor/humidity", hostname="raspberry-pi-ip")
    return float(msg.payload.decode())

def get_ph():
    msg = subscribe.simple("/nutrient/sensor/ph", hostname="raspberry-pi-ip")
    return float(msg.payload.decode())

def get_ec():
    msg = subscribe.simple("/nutrient/sensor/ec", hostname="raspberry-pi-ip")
    return float(msg.payload.decode())

# Использование
temperature = get_temperature()
humidity = get_humidity()
ph = get_ph()
ec = get_ec()

print(f"Температура: {temperature}°C")
print(f"Влажность: {humidity}%") 
print(f"pH: {ph}")
print(f"EC: {ec} µS/cm")

Примеры интеграции

1. Мониторинг системы с Telegram Bot

import telepot
import time
import requests
import json
from telepot.loop import MessageLoop

# Токен Telegram бота
TOKEN = 'YOUR_BOT_TOKEN'
CHAT_ID = 'YOUR_CHAT_ID'
PRIMO_IP = 'raspberry-pi-ip'

bot = telepot.Bot(TOKEN)

def get_system_status():
    try:
        response = requests.get(f'http://{PRIMO_IP}/sys_config/')
        return response.json()
    except:
        return None

def get_sensor_data():
    # Здесь можно добавить получение данных через MQTT
    return {
        'temperature': 25.5,
        'humidity': 65,
        'ph': 6.2,
        'ec': 1150
    }

def handle_command(msg):
    chat_id = msg['chat']['id']
    command = msg['text']

    if command == '/status':
        status = get_system_status()
        sensors = get_sensor_data()

        message = f"""
🌱 Статус PRIMO core:
🌡️ Температура: {sensors['temperature']}°C
💧 Влажность: {sensors['humidity']}%
⚡ pH: {sensors['ph']}
🧪 EC: {sensors['ec']} µS/cm
        """
        bot.sendMessage(chat_id, message)

    elif command == '/lights_on':
        requests.get(f'http://{PRIMO_IP}/set_config?conf=light_mode&active=yes')
        bot.sendMessage(chat_id, "💡 Освещение включено")

    elif command == '/lights_off':
        requests.get(f'http://{PRIMO_IP}/set_config?conf=light_mode&active=no')
        bot.sendMessage(chat_id, "💡 Освещение выключено")

MessageLoop(bot, handle_command).run_as_thread()

# Автоматические уведомления каждые 6 часов
while True:
    sensors = get_sensor_data()

    # Проверка критических значений
    if sensors['temperature'] > 30:
        bot.sendMessage(CHAT_ID, "🚨 ВНИМАНИЕ: Высокая температура!")

    if sensors['ph'] < 5.0 or sensors['ph'] > 7.5:
        bot.sendMessage(CHAT_ID, f"🚨 ВНИМАНИЕ: pH вне нормы ({sensors['ph']})")

    time.sleep(21600)  # 6 часов

2. Веб-дашборд на Flask

from flask import Flask, render_template, jsonify
import requests
import paho.mqtt.subscribe as subscribe

app = Flask(__name__)
PRIMO_IP = 'raspberry-pi-ip'

@app.route('/')
def dashboard():
    return render_template('dashboard.html')

@app.route('/api/sensors')
def get_sensors():
    try:
        # Получение данных через MQTT
        temp_msg = subscribe.simple("/sensor/temp", hostname=PRIMO_IP)
        hum_msg = subscribe.simple("/sensor/humidity", hostname=PRIMO_IP)
        ph_msg = subscribe.simple("/nutrient/sensor/ph", hostname=PRIMO_IP)
        ec_msg = subscribe.simple("/nutrient/sensor/ec", hostname=PRIMO_IP)

        return jsonify({
            'temperature': float(temp_msg.payload.decode()),
            'humidity': float(hum_msg.payload.decode()),
            'ph': float(ph_msg.payload.decode()),
            'ec': float(ec_msg.payload.decode()),
            'timestamp': time.time()
        })
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/relays')
def get_relays():
    try:
        response = requests.get(f'http://{PRIMO_IP}/relay_status/1')
        # Получить статус всех реле
        relays = []
        for i in range(1, 9):
            response = requests.get(f'http://{PRIMO_IP}/relay_status/{i}')
            relays.append({
                'id': i,
                'status': response.text.strip() == '1'
            })
        return jsonify(relays)
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/control/relay/<int:relay_id>/<action>')
def control_relay(relay_id, action):
    try:
        if action == 'on':
            requests.get(f'http://{PRIMO_IP}/relay_on/{relay_id}')
        elif action == 'off':
            requests.get(f'http://{PRIMO_IP}/relay_off/{relay_id}')
        return jsonify({'success': True})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0', port=5000)

HTML шаблон (dashboard.html):

<!DOCTYPE html>
<html>
<head>
    <title>PRIMO Dashboard</title>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <style>
        .sensor-card {
            border: 1px solid #ddd;
            padding: 20px;
            margin: 10px;
            border-radius: 8px;
            display: inline-block;
            width: 200px;
        }
        .relay-control {
            margin: 10px;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 5px;
        }
    </style>
</head>
<body>
    <h1>🌱 PRIMO Dashboard</h1>

    <div id="sensors">
        <div class="sensor-card">
            <h3>Температура</h3>
            <span id="temperature">--</span>°C
        </div>
        <div class="sensor-card">
            <h3>Влажность</h3>
            <span id="humidity">--</span>%
        </div>
        <div class="sensor-card">
            <h3>pH</h3>
            <span id="ph">--</span>
        </div>
        <div class="sensor-card">
            <h3>EC</h3>
            <span id="ec">--</span> µS/cm
        </div>
    </div>

    <h2>Управление реле</h2>
    <div id="relays"></div>

    <canvas id="sensorChart" width="800" height="400"></canvas>

    <script>
        let sensorData = [];
        let chart;

        function updateSensors() {
            fetch('/api/sensors')
                .then(response => response.json())
                .then(data => {
                    document.getElementById('temperature').textContent = data.temperature.toFixed(1);
                    document.getElementById('humidity').textContent = data.humidity.toFixed(1);
                    document.getElementById('ph').textContent = data.ph.toFixed(2);
                    document.getElementById('ec').textContent = data.ec.toFixed(0);

                    // Добавить данные в график
                    sensorData.push({
                        time: new Date(),
                        temperature: data.temperature,
                        humidity: data.humidity,
                        ph: data.ph
                    });

                    updateChart();
                });
        }

        function updateRelays() {
            fetch('/api/relays')
                .then(response => response.json())
                .then(relays => {
                    const container = document.getElementById('relays');
                    container.innerHTML = '';

                    relays.forEach(relay => {
                        const div = document.createElement('div');
                        div.className = 'relay-control';
                        div.innerHTML = `
                            <span>Реле ${relay.id}: ${relay.status ? 'ВКЛ' : 'ВЫКЛ'}</span>
                            <button onclick="controlRelay(${relay.id}, 'on')">ВКЛ</button>
                            <button onclick="controlRelay(${relay.id}, 'off')">ВЫКЛ</button>
                        `;
                        container.appendChild(div);
                    });
                });
        }

        function controlRelay(relayId, action) {
            fetch(`/api/control/relay/${relayId}/${action}`)
                .then(() => updateRelays());
        }

        function initChart() {
            const ctx = document.getElementById('sensorChart').getContext('2d');
            chart = new Chart(ctx, {
                type: 'line',
                data: {
                    labels: [],
                    datasets: [{
                        label: 'Температура',
                        data: [],
                        borderColor: 'rgb(255, 99, 132)',
                        tension: 0.1
                    }, {
                        label: 'Влажность',
                        data: [],
                        borderColor: 'rgb(54, 162, 235)',
                        tension: 0.1
                    }]
                },
                options: {
                    responsive: true,
                    scales: {
                        x: {
                            type: 'time',
                            time: {
                                displayFormats: {
                                    minute: 'HH:mm'
                                }
                            }
                        }
                    }
                }
            });
        }

        function updateChart() {
            if (!chart || sensorData.length === 0) return;

            // Оставляем только последние 50 точек
            if (sensorData.length > 50) {
                sensorData = sensorData.slice(-50);
            }

            chart.data.labels = sensorData.map(d => d.time);
            chart.data.datasets[0].data = sensorData.map(d => d.temperature);
            chart.data.datasets[1].data = sensorData.map(d => d.humidity);
            chart.update();
        }

        // Инициализация
        initChart();
        updateSensors();
        updateRelays();

        // Обновление каждые 5 секунд
        setInterval(updateSensors, 5000);
        setInterval(updateRelays, 10000);
    </script>
</body>
</html>

3. Интеграция с Home Assistant

configuration.yaml:

# MQTT сенсоры PRIMO core
mqtt:
  sensor:
    - name: "PRIMO Temperature"
      state_topic: "/sensor/temp"
      unit_of_measurement: "°C"
      device_class: temperature

    - name: "PRIMO Humidity"
      state_topic: "/sensor/humidity"  
      unit_of_measurement: "%"
      device_class: humidity

    - name: "PRIMO pH"
      state_topic: "/nutrient/sensor/ph"

    - name: "PRIMO EC"
      state_topic: "/nutrient/sensor/ec"
      unit_of_measurement: "µS/cm"

  switch:
    - name: "PRIMO Lights"
      command_topic: "/set/gpio/"
      payload_on: '{"pin": 1, "state": "on"}'
      payload_off: '{"pin": 1, "state": "off"}'

    - name: "PRIMO Pump"
      command_topic: "/set/gpio/"
      payload_on: '{"pin": 2, "state": "on"}'
      payload_off: '{"pin": 2, "state": "off"}'

# REST команды
rest_command:
  primo_lights_on:
    url: "http://raspberry-pi-ip/relay_on/1"

  primo_lights_off:
    url: "http://raspberry-pi-ip/relay_off/1"

  primo_emergency_stop:
    url: "http://raspberry-pi-ip/relay_all_off/"

# Автоматизации
automation:
  - alias: "PRIMO High Temperature Alert"
    trigger:
      platform: numeric_state
      entity_id: sensor.primo_temperature
      above: 30
    action:
      service: notify.mobile_app
      data:
        message: "🚨 Высокая температура в теплице: {{ states('sensor.primo_temperature') }}°C"

  - alias: "PRIMO pH Alert"
    trigger:
      platform: numeric_state
      entity_id: sensor.primo_ph
      below: 5.0
    action:
      service: notify.mobile_app
      data:
        message: "🚨 Низкий pH в системе: {{ states('sensor.primo_ph') }}"

4. Мобильное приложение (React Native)

import React, { useState, useEffect } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, Alert } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';

const PRIMO_IP = 'http://raspberry-pi-ip';

const App = () => {
  const [sensors, setSensors] = useState({});
  const [relays, setRelays] = useState([]);

  useEffect(() => {
    const interval = setInterval(() => {
      fetchSensors();
      fetchRelays();
    }, 5000);

    return () => clearInterval(interval);
  }, []);

  const fetchSensors = async () => {
    try {
      // Здесь должна быть реализация получения данных через MQTT или REST API
      // Для примера используем моковые данные
      setSensors({
        temperature: 25.5,
        humidity: 65,
        ph: 6.2,
        ec: 1150
      });
    } catch (error) {
      Alert.alert('Ошибка', 'Не удалось получить данные сенсоров');
    }
  };

  const fetchRelays = async () => {
    try {
      const response = await fetch(`${PRIMO_IP}/api/relays`);
      const data = await response.json();
      setRelays(data);
    } catch (error) {
      console.log('Ошибка получения состояния реле:', error);
    }
  };

  const toggleRelay = async (relayId, currentState) => {
    try {
      const action = currentState ? 'off' : 'on';
      await fetch(`${PRIMO_IP}/relay_${action}/${relayId}`);
      fetchRelays(); // Обновить состояние
    } catch (error) {
      Alert.alert('Ошибка', 'Не удалось управлять реле');
    }
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>🌱 PRIMO Control</Text>

      <View style={styles.sensorsContainer}>
        <View style={styles.sensorCard}>
          <Text style={styles.sensorLabel}>Температура</Text>
          <Text style={styles.sensorValue}>{sensors.temperature}°C</Text>
        </View>

        <View style={styles.sensorCard}>
          <Text style={styles.sensorLabel}>Влажность</Text>
          <Text style={styles.sensorValue}>{sensors.humidity}%</Text>
        </View>

        <View style={styles.sensorCard}>
          <Text style={styles.sensorLabel}>pH</Text>
          <Text style={styles.sensorValue}>{sensors.ph}</Text>
        </View>

        <View style={styles.sensorCard}>
          <Text style={styles.sensorLabel}>EC</Text>
          <Text style={styles.sensorValue}>{sensors.ec}</Text>
        </View>
      </View>

      <Text style={styles.subtitle}>Управление реле</Text>

      {relays.map(relay => (
        <TouchableOpacity
          key={relay.id}
          style={[styles.relayButton, relay.status && styles.relayButtonActive]}
          onPress={() => toggleRelay(relay.id, relay.status)}
        >
          <Text style={styles.relayButtonText}>
            Реле {relay.id}: {relay.status ? 'ВКЛ' : 'ВЫКЛ'}
          </Text>
        </TouchableOpacity>
      ))}

      <TouchableOpacity
        style={styles.emergencyButton}
        onPress={() => {
          Alert.alert(
            'Аварийное отключение',
            'Отключить все реле?',
            [
              { text: 'Отмена', style: 'cancel' },
              {
                text: 'Отключить',
                style: 'destructive',
                onPress: () => fetch(`${PRIMO_IP}/relay_all_off/`)
              }
            ]
          );
        }}
      >
        <Text style={styles.emergencyButtonText}>🚨 АВАРИЙНОЕ ОТКЛЮЧЕНИЕ</Text>
      </TouchableOpacity>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: '#f5f5f5'
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    textAlign: 'center',
    marginBottom: 20
  },
  subtitle: {
    fontSize: 18,
    fontWeight: 'bold',
    marginTop: 20,
    marginBottom: 10
  },
  sensorsContainer: {
    flexDirection: 'row',
    flexWrap: 'wrap',
    justifyContent: 'space-between'
  },
  sensorCard: {
    backgroundColor: 'white',
    padding: 15,
    borderRadius: 8,
    width: '48%',
    marginBottom: 10,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
    elevation: 3
  },
  sensorLabel: {
    fontSize: 14,
    color: '#666',
    marginBottom: 5
  },
  sensorValue: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#333'
  },
  relayButton: {
    backgroundColor: '#ddd',
    padding: 15,
    borderRadius: 8,
    marginBottom: 10
  },
  relayButtonActive: {
    backgroundColor: '#4CAF50'
  },
  relayButtonText: {
    textAlign: 'center',
    fontWeight: 'bold'
  },
  emergencyButton: {
    backgroundColor: '#f44336',
    padding: 15,
    borderRadius: 8,
    marginTop: 20
  },
  emergencyButtonText: {
    color: 'white',
    textAlign: 'center',
    fontWeight: 'bold',
    fontSize: 16
  }
});

export default App;

Webhooks и уведомления

Настройка Webhook для критических событий

# webhook_handler.py
from flask import Flask, request
import requests
import json

app = Flask(__name__)

DISCORD_WEBHOOK_URL = "YOUR_DISCORD_WEBHOOK_URL"
SLACK_WEBHOOK_URL = "YOUR_SLACK_WEBHOOK_URL"

@app.route('/webhook/alert', methods=['POST'])
def handle_alert():
    data = request.json

    message = f"""
🚨 Критическое событие в PRIMO core:
- Тип: {data.get('type', 'Unknown')}
- Значение: {data.get('value', 'N/A')}
- Время: {data.get('timestamp', 'N/A')}
- Описание: {data.get('description', 'No description')}
    """

    # Отправка в Discord
    discord_payload = {
        "content": message,
        "username": "PRIMO Alert Bot"
    }
    requests.post(DISCORD_WEBHOOK_URL, json=discord_payload)

    # Отправка в Slack
    slack_payload = {
        "text": message,
        "username": "PRIMO Alert Bot",
        "icon_emoji": ":warning:"
    }
    requests.post(SLACK_WEBHOOK_URL, json=slack_payload)

    return {"status": "success"}

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080)

Мониторинг и алерты

# alert_system.py
import time
import requests
import paho.mqtt.subscribe as subscribe

WEBHOOK_URL = "http://your-server.com:8080/webhook/alert"
PRIMO_IP = "raspberry-pi-ip"

def check_critical_values():
    while True:
        try:
            # Получение данных сенсоров
            temp_msg = subscribe.simple("/sensor/temp", hostname=PRIMO_IP)
            hum_msg = subscribe.simple("/sensor/humidity", hostname=PRIMO_IP)
            ph_msg = subscribe.simple("/nutrient/sensor/ph", hostname=PRIMO_IP)
            ec_msg = subscribe.simple("/nutrient/sensor/ec", hostname=PRIMO_IP)

            temperature = float(temp_msg.payload.decode())
            humidity = float(hum_msg.payload.decode())
            ph = float(ph_msg.payload.decode())
            ec = float(ec_msg.payload.decode())

            # Проверка критических значений
            if temperature > 35:
                send_alert("high_temperature", temperature, 
                          f"Критически высокая температура: {temperature}°C")

            if temperature < 15:
                send_alert("low_temperature", temperature,
                          f"Критически низкая температура: {temperature}°C")

            if ph < 4.5 or ph > 8.0:
                send_alert("ph_critical", ph,
                          f"Критический уровень pH: {ph}")

            if ec > 2000:
                send_alert("ec_high", ec,
                          f"Слишком высокий EC: {ec} µS/cm")

        except Exception as e:
            send_alert("system_error", str(e), 
                      f"Ошибка мониторинга: {e}")

        time.sleep(60)  # Проверка каждую минуту

def send_alert(alert_type, value, description):
    payload = {
        "type": alert_type,
        "value": value,
        "description": description,
        "timestamp": time.strftime("%Y-%m-%d %H:%M:%S")
    }

    try:
        requests.post(WEBHOOK_URL, json=payload)
    except Exception as e:
        print(f"Ошибка отправки уведомления: {e}")

if __name__ == '__main__':
    check_critical_values()

API документация и примеры интеграции PRIMO core v1.3 Создано: 2024