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