OrangePi R2S: Строим Linux-роутер на доступном восьмиядерном RISC-V одноплатнике с портами 2.5G
- Цена: $50 (2GB) | $77 (4GB) | $115 (8GB)
- Перейти в магазин
Друзья, привет!
Не так давно мы смотрели на одноплатный SBC-роутер Orange Pi R2S на архитектуре RISC-V. Тогда мы поверхностно окунулись в архитектуру, посмотрели, насколько это жизнеспособно в качестве домашнего сервера. В этом обзоре не будет повторов и мы не будем ходить вокруг да около, а будем тестировать устройство исключительно по его прямому назначению — в качестве DIY Linux-роутера. Забегая наперёд, я продал свой Keenetic и перешёл на модульную систему (Orange Pi R2S + Wi-Fi точка доступа). В этом обзоре протестируем его сетевые возможности, включая 2.5G порты, VPN-протоколы, USB в паре с DAS, а также попытаемся понять, это универсальное решение или нишевая история, в чём преимущества и недостатки и что интересного я смог на нём реализовать. Приглашаю ↓


ТЕХНИЧЕСКИЕ ХАРАКТЕРИСТИКИ
- Процессор: Ky X1, 8 ядер, архитектура RISC-V AI (производительность ИИ — 2.0 TOPS)
- Оперативная память: LPDDR4X: 2 ГБ / 4 ГБ / 8 ГБ
- Накопитель: eMMC: 8 ГБ
- Сетевые интерфейсы:
- 2.5G Ethernet — 2 порта
- 1000M Ethernet (гигабитный) — 2 порта
- USB:
- USB 3.0 — 1 порт
- USB 2.0 — 1 порт (может использоваться для обновления ПО и прошивки)
- Кнопки: Кнопка MASKROM — 1 шт.
- Индикаторы состояния:
- Индикатор питания — 1 шт.
- Индикатор 1000M Ethernet — 2 шт.
- Индикатор 2.5G Ethernet — 2 шт.
- Отладка (DEBUG): 3-контактный (3PIN) последовательный порт отладки, уровень напряжения 3.3 В
- Питание: Разъем Type-C, 5 В / 3 А (постоянный ток)
- Размеры платы (PCB): 79.2 * 46 * 1.6 мм
- Поддерживаемые ОС: OpenWrt, Ubuntu



Начиная этот обзор, хочу сразу отметить, что он не является инструкцией. Это именно обзор продукта — Orange Pi R2S и его возможностей.
Сама плата достаточно компактная, лёгкая и горячая в нагрузке. У меня без радиатора уходила до 90 градусов. Но стоило поставить простой рандомный пассивный радиатор на термопрокладке, температура сразу упала до комфортных 60. Стоит иметь это в виду. Также существенно нагреваются сетевые порты (особенно 2.5G). Поэтому радиаторы очень желательно поставить и на них. Планирую купить для обслуживания видеокарты жидкую термопрокладку Tputty607, заодно посажу нормально радиаторы на Orange Pi R2S.


К сожалению, порта MicroSD нет. Только проприетарный разъём для её установки, но соответствующего адаптера я в продаже не увидел. SPI нет, загрузчик U-Boot на несъёмной eMMC. Поэтому можно пойти двумя путями: или держать на eMMC только загрузчик, а систему установить на USB 3.0 носитель (производитель предусмотрел такую возможность), или пользоваться eMMC, но быть готовым к тому, что при интенсивной записи/чтении системной памяти в будущем, возможно, придётся перепаивать. В целом я посмотрел свою конфигурацию, использование вполне щадящее, настроил буферы в ОЗУ на кэш, поэтому я заморачиваться не буду, думаю, одноплатник скорее устареет, чем на нём отвалится eMMC. Но здесь каждый для себя должен сам решить. В целом пространство для манёвра есть, и это хорошо.
TTL UART работает без нареканий. Можно подключить на скорости 115200 и работать в консоли, аналогичной той, которая открывается по SSH.

Первым делом я попытался собрать свою конфигурацию на OpenWrt, но после того, как он мне гордо заявил, что одного из компонентов для WireGuard, а именно kmod-wireguard, нет ни в одном репозитории под riscv64, я поднял WireGuard на Go и, взглянув на скорости, пришёл к выводу, что пока в OpenWrt не появится полноценная поддержка данного SBC, лучше искать альтернативу. Я не расстроился, так как для R2S также есть Ubuntu 24.04, которая мне более привычна и, по моему мнению, даёт значительно больше возможностей, чем OpenWrt. Правда, больше телодвижений, но ведь один раз настроил — и потом пользуешься.
МАРШРУТИЗАЦИЯ
Linux прекрасно работает с сетью, но всё настраивается вручную. Я могу назвать себя достаточно продвинутым пользователем ПК, но при этом я не IT-шник. С опытом работы с терминалом Linux уже есть стойкое понимание многих базовых вещей, однако это всё ещё очень далеко от полного понимания многих процессов. Но это, собственно, сейчас так же нужно, как математические расчёты в уме. Нам на самом деле очень повезло: мы первые, кто является свидетелями научно-технического прогресса беспрецедентного уровня. Думаю, далеко не каждый осознаёт, насколько важные процессы сейчас происходят в этом направлении. Мы живём в информационную эру, и сейчас человечеству доступен очень важный инструмент — искусственный интеллект. Это не волшебная палочка, с ним нужно уметь правильно работать. Не всегда получается, как хочешь, иногда нужно уметь распознать, когда он не понимает, что от него хотят или свернул не туда, видеть неточности в результатах и, основываясь на этом, направлять в нужную канву. Но даже на нынешнем уровне вещи, которые он способен делать — фантастические.

У одноплатника есть 4 сетевых интерфейса: два 1G и два 2.5G. Нужно определиться, какой порт будет использоваться в WAN, остальные объединить в мост. В отличие от многих роутеров, здесь нет аппаратного сетевого ускорителя, но так как сам восьмиядерный чип Ky X1 достаточно производительный, маршрутизация на уровне SoC не является проблемой.
После создания моста нужно поднять DHCP, чтобы роутеру был присвоен статичный IP и статичная сеть (192.168.1.0/24) и, конечно, DNS, через который будут стучаться сетевые запросы. Это можно сделать из-под интерфейса, через AdGuard Home, но я с ним не подружился: у меня от его использования тупит интернет (даже без фильтрации), поэтому я пошёл путём проверенных решений. Установил пакет dnsmasq и в его конфиге (/etc/dnsmasq.conf) прописал:
# Интерфейс локальной сети
interface=br0
# Внешние DNS-серверы
server=1.1.1.1
server=1.0.0.1
server=8.8.8.8
# Не пересылать адреса без доменной части и локальные запросы на внешние DNS
domain-needed
bogus-priv
# Диапазон DHCP (от .100 до .200, время аренды 12 часов)
dhcp-range=192.168.1.100,192.168.1.200,255.255.255.0,12h
# Параметры DHCP для клиентов: Шлюз (192.168.1.1) и DNS (192.168.1.1)
dhcp-option=option:router,192.168.1.1
dhcp-option=option:dns-server,192.168.1.1
# ---------------------------------------------------------
# Статические IP-адреса клиентов (MAC, Имя, IP)
# ---------------------------------------------------------
dhcp-host=aa:bb:cc:dd:ee:ff,pc,192.168.1.2
Затем, чтобы встроенный systemd-resolved не перехватывал 53 порт (DNS), я направил символической ссылкой /etc/resolv.conf на работающий DNS:
sudo rm /etc/resolv.conf
sudo ln -s /run/systemd/resolve/resolv.conf /etc/resolv.conf
sudo systemctl restart systemd-resolved
sudo systemctl restart dnsmasq
sudo systemctl enable dnsmasq
Как это в итоге работает? У роутера IP 192.168.1.1. При подключении клиентов им даётся аренда на 12 часов из диапазона 192.168.1.100 – 192.168.1.200, после этого присваивается другой рандомный IP. Постояльцев сети можно зарегистрировать вручную в таблице (которую можно дополнять), чтобы предоставить им постоянный статический IP-адрес.
Безусловно, можно было бы так и оставить, но как человек, избалованный интерфейсом Keenetic, я захотел веб-интерфейс, чтобы можно было вносить правки в /etc/dnsmasq.conf интерактивно, из лёгкого веб-интерфейса. И ИИ сделал это для меня на базе python3-flask! Мы несколько раз меняли логику работы, принцип, включали сортировку, автоматическое определение новых клиентов, переключение политик (обычное подключение или один из WireGuard-интерфейсов), а также Wildcard DNS перенаправления. У меня на VPS-сервере есть NPMPlus, и я сделал так, чтобы все запросы доменов .local шли через него, ну и также внутренние перенаправления, например 192.168.1.1 – orangepi.lan, и этот адрес я могу вводить вместо IP, к примеру, в Samba. Сейчас у меня появилась вообще шикарная идея: отказаться от NPMPlus и добавить в скрипт 3-й блок — “Reverse Proxy” на базе Caddy, в который я буду вписывать домен, IP и порт, а Caddy будет переправлять эти порты на 80-й. Но это уже будет совсем другая история)



Вышло реально Keenetic style, хотя по мне даже лучше, так как всё это на одной странице и именно так, как я этого хотел и как я это видел у себя в голове.
Когда появляется новый клиент — его предлагает добавить на постоянной основе, можно прописать имя и задать целевой IP. Насчёт имени: предусмотрена “защита от дурака”, которая отсекает нежелательные символы и делает транслитерацию из кириллицы. После нажатия кнопки “Сохранить и применить маршруты” скрипт вносит изменения в файл dnsmasq.conf и перезагружает dnsmasq.
Выглядит это так:

Ушло, конечно, определённое количество времени на создание этого инструмента, но оно того однозначно стоило, ведь как я уже говорил — один раз настроил, а потом пользуешься.
По моему мнению, это получилось настолько хорошо, что я даже поделюсь кодом, кому понадобится — сможет адаптировать под себя:
Шаг 1. Установка Flask и создание структуры
Установите минимальные библиотеки Python:
Bash
sudo apt update
sudo apt install -y python3-flask
Создайте папку для веб-сервиса:
mkdir -p /opt/dnsmasq-web/templates
cd /opt/dnsmasq-web
Шаг 2. Создание файла скрипта app.py
Откройте app.py:
Bash
sudo nano /opt/dnsmasq-web/app.py
И вставьте следующий текст:
import json
import re
import subprocess
from flask import Flask, render_template, request, redirect, url_for, jsonify
app = Flask(__name__)
CONFIG_PATH = '/etc/dnsmasq.conf'
LEASES_PATH = '/var/lib/misc/dnsmasq.leases'
PBR_RULES_PATH = '/etc/pbr-rules.json'
CYR_TO_LAT = {
'а': 'a', 'б': 'b', 'в': 'v', 'г': 'g', 'д': 'd', 'е': 'e', 'ё': 'yo',
'ж': 'zh', 'з': 'z', 'и': 'i', 'й': 'y', 'к': 'k', 'л': 'l', 'м': 'm',
'н': 'n', 'о': 'o', 'п': 'p', 'р': 'r', 'с': 's', 'т': 't', 'у': 'u',
'ф': 'f', 'х': 'h', 'ц': 'ts', 'ч': 'ch', 'ш': 'sh', 'щ': 'sch', 'ъ': '',
'ы': 'y', 'ь': '', 'э': 'e', 'ю': 'yu', 'я': 'ya'
}
def sanitize_hostname(name):
name = name.strip().lower()
res = [CYR_TO_LAT.get(char, char) for char in name]
name = "".join(res)
name = re.sub(r'[\s_]+', '-', name)
name = re.sub(r'[^a-z0-9\-]', '', name)
return name.strip('-') if name.strip('-') else "device"
def get_pbr_modes():
try:
with open(PBR_RULES_PATH, 'r') as f:
return json.load(f)
except Exception:
return {}
def save_pbr_modes(modes):
try:
with open(PBR_RULES_PATH, 'w') as f:
json.dump(modes, f, indent=2)
except Exception as e:
print(f"Error saving PBR rules: {e}")
def apply_pbr_rule(ip, mode):
# Очищаем правила для данного IP из обеих таблиц
subprocess.run(['ip', 'rule', 'del', 'from', ip, 'table', '100'], stderr=subprocess.DEVNULL)
subprocess.run(['ip', 'rule', 'del', 'from', ip, 'table', '101'], stderr=subprocess.DEVNULL)
# Применяем нужную таблицу
if mode == 'ORACLE':
subprocess.run(['ip', 'rule', 'add', 'from', ip, 'table', '100'])
elif mode == 'WARP':
subprocess.run(['ip', 'rule', 'add', 'from', ip, 'table', '101'])
# При DIRECT (No VPN) никаких правил не нужно — трафик идёт через стандартный дефолтный шлюз системы
def read_data():
saved_hosts = []
dns_redirects = []
saved_macs = set()
pbr_modes = get_pbr_modes()
try:
with open(CONFIG_PATH, 'r') as f:
for line in f:
line = line.strip()
if line.startswith('dhcp-host='):
parts = line.replace('dhcp-host=', '').split(',')
if len(parts) >= 3:
mac = parts[0].strip().lower()
ip = parts[2].strip()
saved_hosts.append({
'mac': mac,
'name': parts[1].strip(),
'ip': ip,
'mode': pbr_modes.get(ip, 'DIRECT')
})
saved_macs.add(mac)
elif line.startswith('address=/'):
parts = line.replace('address=/', '').split('/')
if len(parts) >= 2:
# Убираем ведущую точку при чтении для чистого отображения
domain_clean = parts[0].strip().lstrip('.')
dns_redirects.append({
'domain': domain_clean,
'ip': parts[1].strip()
})
except Exception as e:
print(f"Error reading config: {e}")
leases = []
try:
with open(LEASES_PATH, 'r') as f:
for line in f:
parts = line.strip().split()
if len(parts) >= 4:
mac = parts[1].strip().lower()
ip = parts[2].strip()
hostname = parts[3].strip()
if hostname == '*':
hostname = 'new-device'
if mac not in saved_macs:
leases.append({
'mac': mac,
'ip': ip,
'name': hostname
})
except Exception as e:
print(f"Error reading leases: {e}")
return saved_hosts, dns_redirects, leases
@app.route('/')
def index():
hosts, dns_redirects, new_devices = read_data()
return render_template('index.html', hosts=hosts, dns_redirects=dns_redirects, new_devices=new_devices)
@app.route('/save', methods=['POST'])
def save():
macs = request.form.getlist('mac')
names = request.form.getlist('name')
ips = request.form.getlist('ip')
modes = request.form.getlist('mode')
domains = request.form.getlist('dns_domain')
dns_ips = request.form.getlist('dns_ip')
non_managed_lines = []
try:
with open(CONFIG_PATH, 'r') as f:
for line in f:
sline = line.strip()
if not sline.startswith('dhcp-host=') and not sline.startswith('address=/'):
non_managed_lines.append(line)
except Exception as e:
print(f"Error reading original config: {e}")
new_host_lines = []
pbr_dict = {}
for mac, name, ip, mode in zip(macs, names, ips, modes):
if mac.strip() and ip.strip():
clean_name = sanitize_hostname(name)
clean_ip = ip.strip()
new_host_lines.append(f"dhcp-host={mac.strip()},{clean_name},{clean_ip}\n")
apply_pbr_rule(clean_ip, mode)
pbr_dict[clean_ip] = mode
save_pbr_modes(pbr_dict)
new_dns_lines = []
for domain, dns_ip in zip(domains, dns_ips):
d_clean = domain.strip().lstrip('*').strip('.')
if d_clean and dns_ip.strip():
# Записываем без принудительной точки перед доменом
new_dns_lines.append(f"address=/{d_clean}/{dns_ip.strip()}\n")
with open(CONFIG_PATH, 'w') as f:
f.writelines(non_managed_lines)
if non_managed_lines and not non_managed_lines[-1].endswith('\n'):
f.write('\n')
f.write('# --- Wildcard & Specific DNS Redirects ---\n')
f.writelines(new_dns_lines)
f.write('# --- Static DHCP Hosts ---\n')
f.writelines(new_host_lines)
subprocess.run(['systemctl', 'restart', 'dnsmasq'])
return redirect(url_for('index'))
@app.route('/api/pbr', methods=['GET'])
def api_get_pbr():
ip = request.args.get('ip')
if not ip:
return jsonify({'error': 'Missing ip parameter'}), 400
pbr_modes = get_pbr_modes()
mode = pbr_modes.get(ip, 'DIRECT')
return jsonify({'ip': ip, 'mode': mode})
@app.route('/api/pbr', methods=['POST'])
def api_set_pbr():
data = request.get_json() or {}
ip = data.get('ip') or request.form.get('ip')
mode = data.get('mode') or request.form.get('mode')
if not ip or mode not in ['DIRECT', 'ORACLE', 'WARP']:
return jsonify({'error': 'Invalid ip or mode'}), 400
apply_pbr_rule(ip, mode)
pbr_modes = get_pbr_modes()
pbr_modes[ip] = mode
save_pbr_modes(pbr_modes)
return jsonify({'status': 'ok', 'ip': ip, 'mode': mode})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=3030)
И сам веб-интерфейс (index.html)
Введите код:
sudo nano /opt/dnsmasq-web/templates/index.html
Вставьте следующий текст:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>DNSMasq & Routing Manager</title>
<style>
body { font-family: system-ui, -apple-system, sans-serif; background: #121212; color: #e0e0e0; padding: 20px; }
.container { max-width: 1000px; margin: 0 auto; background: #1e1e1e; padding: 20px; border-radius: 8px; box-shadow: 0 4px 10px rgba(0,0,0,0.5); }
h2 { margin-top: 0; color: #4caf50; }
h3 { color: #2196f3; margin-top: 30px; border-bottom: 1px solid #333; padding-bottom: 5px; }
.h3-new { color: #ff9800; border: none; margin-top: 0; }
table { width: 100%; border-collapse: collapse; margin-bottom: 15px; }
th, td { padding: 8px 12px; text-align: left; border-bottom: 1px solid #333; }
th { cursor: pointer; background: #2a2a2a; user-select: none; }
th:hover { background: #333; }
input[type="text"], select { width: 95%; padding: 6px; background: #2b2b2b; border: 1px solid #444; color: #fff; border-radius: 4px; }
select { cursor: pointer; font-weight: bold; }
.btn { padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; }
.btn-save { background: #4caf50; color: white; font-size: 16px; }
.btn-add { background: #2196f3; color: white; margin-right: 10px; }
.btn-del { background: #f44336; color: white; padding: 4px 10px; }
.btn-claim { background: #ff9800; color: #121212; padding: 4px 10px; font-weight: bold; }
.actions { display: flex; justify-content: space-between; align-items: center; margin-top: 25px; }
.new-box { background: #262118; border: 1px solid #523b12; padding: 15px; border-radius: 6px; margin-top: 25px; }
.hint { font-size: 12px; color: #888; margin-top: -5px; margin-bottom: 10px; }
</style>
</head>
<body>
<div class="container">
<h2>DNSMasq & Routing Manager</h2>
<form action="/save" method="post" id="mainForm">
<!-- СЕКЦИЯ 1: DHCP HOSTS -->
<h3>1. Статические DHCP Клиенты и Маршрутизация</h3>
<table id="hostsTable">
<thead>
<tr>
<th onclick="sortTable('hostsTable', 0, false)">Имя ▲▼</th>
<th onclick="sortTable('hostsTable', 1, false)">MAC Адрес ▲▼</th>
<th onclick="sortTable('hostsTable', 2, true)">IP Адрес ▲▼</th>
<th style="width: 160px;">Маршрут</th>
<th style="width: 50px;"></th>
</tr>
</thead>
<tbody>
{% for host in hosts %}
<tr>
<td><input type="text" name="name" value="{{ host.name }}"></td>
<td><input type="text" name="mac" value="{{ host.mac }}"></td>
<td><input type="text" name="ip" value="{{ host.ip }}"></td>
<td>
<select name="mode">
<option value="DIRECT" {% if host.mode == 'DIRECT' %}selected{% endif %}>⚪ Direct (No VPN)</option>
<option value="ORACLE" {% if host.mode == 'ORACLE' %}selected{% endif %}> VPN Oracle (wg0)</option>
<option value="WARP" {% if host.mode == 'WARP' %}selected{% endif %}>⚡ WARP (wg-warp)</option>
</select>
</td>
<td><button type="button" class="btn btn-del" onclick="deleteRow(this)">X</button></td>
</tr>
{% endfor %}
</tbody>
</table>
<button type="button" class="btn btn-add" onclick="addHostRow()">+ Устройство</button>
<!-- СЕКЦИЯ 2: WILDCARD DNS -->
<h3>2. DNS Перенаправления (DNS Records / Wildcard)</h3>
<p class="hint">Укажите доменную зону (например, <code>local</code> для всех *.local) или конкретный хост (например, <code>orangepi.local</code>).</p>
<table id="dnsTable">
<thead>
<tr>
<th onclick="sortTable('dnsTable', 0, false)">Домен / Зона ▲▼</th>
<th onclick="sortTable('dnsTable', 1, true)">Целевой IP ▲▼</th>
<th style="width: 50px;"></th>
</tr>
</thead>
<tbody>
{% for dns in dns_redirects %}
<tr>
<td><input type="text" name="dns_domain" value="{{ dns.domain }}" placeholder="local или pc.local"></td>
<td><input type="text" name="dns_ip" value="{{ dns.ip }}" placeholder="192.168.1.X"></td>
<td><button type="button" class="btn btn-del" onclick="deleteRow(this)">X</button></td>
</tr>
{% endfor %}
</tbody>
</table>
<button type="button" class="btn btn-add" onclick="addDnsRow()">+ DNS Зону</button>
<!-- СЕКЦИЯ 3: НОВЫЕ УСТРОЙСТВА -->
{% if new_devices %}
<div class="new-box">
<h3 class="h3-new">Обнаружены новые устройства в сети</h3>
<table>
<thead>
<tr>
<th>Имя</th>
<th>MAC Адрес</th>
<th>Текущий IP</th>
<th>Действие</th>
</tr>
</thead>
<tbody>
{% for dev in new_devices %}
<tr>
<td>{{ dev.name }}</td>
<td><code>{{ dev.mac }}</code></td>
<td>{{ dev.ip }}</td>
<td>
<button type="button" class="btn btn-claim" onclick="claimDevice('{{ dev.name }}', '{{ dev.mac }}', '{{ dev.ip }}', this)">
+ Закрепить IP
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
<div class="actions">
<div></div>
<button type="submit" class="btn btn-save">Сохранить и применить маршруты</button>
</div>
</form>
</div>
<script>
function claimDevice(name, mac, ip, btn) {
const tbody = document.querySelectorAll('#hostsTable tbody')[0];
const tr = document.createElement('tr');
tr.innerHTML = `
<td><input type="text" name="name" value="${name}"></td>
<td><input type="text" name="mac" value="${mac}"></td>
<td><input type="text" name="ip" value="${ip}"></td>
<td>
<select name="mode">
<option value="DIRECT" selected>⚪ Direct (No VPN)</option>
<option value="ORACLE"> VPN Oracle (wg0)</option>
<option value="WARP">⚡ WARP (wg-warp)</option>
</select>
</td>
<td><button type="button" class="btn btn-del" onclick="deleteRow(this)">X</button></td>
`;
tbody.appendChild(tr);
btn.parentNode.parentNode.remove();
}
function addHostRow() {
const tbody = document.querySelectorAll('#hostsTable tbody')[0];
const tr = document.createElement('tr');
tr.innerHTML = `
<td><input type="text" name="name" placeholder="new-device"></td>
<td><input type="text" name="mac" placeholder="00:00:00:00:00:00"></td>
<td><input type="text" name="ip" placeholder="192.168.1.X"></td>
<td>
<select name="mode">
<option value="DIRECT" selected>⚪ Direct (No VPN)</option>
<option value="ORACLE"> VPN Oracle (wg0)</option>
<option value="WARP">⚡ WARP (wg-warp)</option>
</select>
</td>
<td><button type="button" class="btn btn-del" onclick="deleteRow(this)">X</button></td>
`;
tbody.appendChild(tr);
}
function addDnsRow() {
const tbody = document.querySelectorAll('#dnsTable tbody')[0];
const tr = document.createElement('tr');
tr.innerHTML = `
<td><input type="text" name="dns_domain" placeholder="local или pc.local"></td>
<td><input type="text" name="dns_ip" placeholder="192.168.1.X"></td>
<td><button type="button" class="btn btn-del" onclick="deleteRow(this)">X</button></td>
`;
tbody.appendChild(tr);
}
function deleteRow(btn) {
btn.parentNode.parentNode.remove();
}
function ipToNumArray(ip) {
return ip.split('.').map(num => parseInt(num, 10) || 0);
}
function sortTable(tableId, colIndex, isIpColumn) {
const table = document.getElementById(tableId);
let rows = Array.from(table.rows).slice(1);
let dir = table.getAttribute("data-sort-dir") === "asc" ? "desc" : "asc";
table.setAttribute("data-sort-dir", dir);
rows.sort((a, b) => {
let inputA = a.cells[colIndex].querySelector('input');
let valA = inputA ? inputA.value.trim() : a.cells[colIndex].innerText.trim();
let inputB = b.cells[colIndex].querySelector('input');
let valB = inputB ? inputB.value.trim() : b.cells[colIndex].innerText.trim();
if (isIpColumn) {
let numA = ipToNumArray(valA);
let numB = ipToNumArray(valB);
for (let i = 0; i < 4; i++) {
if (numA[i] !== numB[i]) {
return dir === "asc" ? numA[i] - numB[i] : numB[i] - numA[i];
}
}
return 0;
} else {
valA = valA.toLowerCase();
valB = valB.toLowerCase();
if (valA < valB) return dir === "asc" ? -1 : 1;
if (valA > valB) return dir === "asc" ? 1 : -1;
return 0;
}
});
rows.forEach(row => table.tBodies[0].appendChild(row));
}
</script>
</body>
</html>
Применяем изменения
Bash
sudo systemctl restart dnsmasq-web
На этом, собственно, часть маршрутизации закончена. Всё работает отлично, как и должно, и всё в рамках одного лёгкого веб-интерфейса.
ФИКС СЕТИ
Из коробки нагрузка на сеть очень сильно грузила одно ядро при относительном простое остальных. Для того чтобы это пофиксить, сделал скрипт, который отключает аппаратные offload-функции и распределяет сетевые прерывания (RPS / XPS). После этого ситуация стала заметно лучше.
sudo nano /etc/network-optimization.sh
#!/bin/bash
# Небольшая пауза при старте системы, чтобы сетевые интерфейсы успели подняться
sleep 3
# 1. Отключаем GRO/LRO/TSO/GSO для снижения нагрузки на 1-е ядро
for dev in enP1p1s0 enP2p1s0 end0 end1 br0; do
ethtool -K $dev gro off lro off tso off gso off 2>/dev/null
done
# 2. Увеличиваем таблицу RFS
echo 65536 > /proc/sys/net/core/rps_sock_flow_entries
# 3. Настраиваем RPS строго на Ядра 3-8 (маска fc = 11111100)
for dev in /sys/class/net/*/queues/rx-*/rps_cpus; do
echo "fc" > "$dev" 2>/dev/null
done
# 4. Прописываем лимит потоков на очередь
for dev in /sys/class/net/*/queues/rx-*/rps_flow_cnt; do
echo 2048 > "$dev" 2>/dev/null
done
# 5. Переносим исходящий трафик (XPS) на Ядра 3-8
for dev in /sys/class/net/*/queues/tx-*/xps_cpus; do
echo "fc" > "$dev" 2>/dev/null
done
Делаем файл исполняемым.
sudo chmod +x /etc/network-optimization.sh
Автозапуск (создание службы):
sudo nano /etc/systemd/system/network-optimization.service
[Unit]
Description=Network Stack Optimization (RPS/RFS/Offload)
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/etc/network-optimization.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.targetИ включить автозапуск службы:
sudo systemctl daemon-reload
sudo systemctl enable network-optimization.service
РЕАЛЬНАЯ ПРОПУСКНАЯ СПОСОБНОСТЬ
Роутер обладает четырьмя портами: два порта 2.5G и два порта 1G. Как я уже говорил, обработка трафика осуществляется процессором без отдельных сетевых ускорителей. Разумеется, ожидать суммарные 7G на всех портах — это утопия. У меня по большому счёту даже нечем нагрузить, запустил через 2.5G USB-адаптер iPerf, плюс Wi-Fi загрузку, плюс торрент на самом Orange Pi.

Максимум в сети, который я снял — 420 мегабайт (400 MiB) в секунду, что соответствует 3.4 гигабитам в секунду. По моему мнению, вполне неплохо. При этом загрузка на одно ядро была под 100%, на остальных — меньше половины. То есть теоретический потенциал явно выше, но здесь мы, очевидно, упираемся в сырость адаптации операционной системы под RISC-V. Возможно, когда выйдет “ванильная” OpenWrt, ситуация будет лучше. Но пока отталкиваемся от того, что есть здесь и сейчас.

USB-адаптер у меня отвратительный: покупал когда-то на распродаже, на 2.5G он явно не рассчитан, нагревается, троттлит и перезагружается. Но и на имеющейся базе в iPerf удалось снять до 1.83 гигабита в секунду.

WAN у меня гигабитный. Одноплатник его полностью раскрывает.
VPN
Сначала я поднял Xray. И здесь меня ждал не очень приятный результат.

Вспомнил, как парировал в обзоре Orange Pi Zero 3W на Allwinner A733 комментарий о том, что можно взять старое б/у железо на вторичке очень недорого, что старый x86-процессор без поддержки современных алгоритмов шифрования будет пыхтеть на 100% там, где свежие ядра Cortex-A76 с поддержкой крипто-инструкций ARMv8 даже не заметят нагрузки (Xray утилизировал всего 5% CPU на 300 Мбит/с). Здесь мы имеем архитектуру RISC-V, и она заметно пасует в криптографии. На скорости 440 мегабит в секунду Xray утилизирует процессор на 85%. Это, что называется, “почувствуй разницу”. Но если Orange Pi Zero 3W — это домашний сервер, у которого нет сетевых портов RJ45, то здесь немного другое назначение устройства, они не взаимозаменяемы в прямом смысле этого слова. Поэтому это не сравнение “кто лучше”, а просто факт.
Поднял WG. Здесь ситуация существенно лучше. На той же скорости утилизация процессора в среднем 60%, что, конечно, существенно приемлемее, чем 85%, но всё ещё далеко не 5%.

На скорости WG-соединения 600 мегабит в секунду утилизация процессора 80%. Здесь мы, очевидно, уже нащупываем потолок возможностей. Все ядра загружены относительно равномерно и существенно. К WG здесь ещё добавляется сетевая обработка, которая тоже на процессоре (600 мегабит на вход и столько же на выход).
NAS
В моём случае роутер — это ещё и NAS.
К нему по USB подключён дисковый массив DAS. Шина у Orange Pi R2S, к сожалению, не раскрывает потенциал моего DAS, она 5G, но и этого хватает для HDD-дисков с большим запасом. Внутренняя скорость чтения с NVMe составила порядка ~350 мегабайт в секунду.

В Samba мы, очевидно, упираемся в скорость сетевого интерфейса. При 1G это стабильные 115 мегабайт в секунду. При подключении 2.5G через USB-адаптер мне удалось выжать 157 мегабайт. В целом очень неплохо, для меня и такие скорости уже в новинку. Для максимальной производительности, диски должны быть с файловой системой Ext4.
ДОПОЛНИТЕЛЬНЫЕ ВОЗМОЖНОСТИ
RISC-V имеет большой потенциал, однако на текущий момент он всё ещё не раскрыт. Большинство разработчиков компилируют пакеты под архитектуры amd64-86 и arm64. Любопытства ради я попробовал скомпилировать Home Assistant на VPS-сервере (четыре ARM-ядра Ampere A1 Compute). Компиляция заняла 7 часов, однако дальше стартового окна входа работоспособность заканчивалась. Безусловно, в будущем под riscv64 будут и Home Assistant, и много чего ещё. Но сейчас можно пользоваться только тем, что есть. В частности, это qBittorrent, TorrServer MatriX, MQTT-брокер, Node-RED и, в принципе, ряд других пакетов.

Я поставил Docker и развернул Zigbee2MQTT: разработчики собирают его под архитектуру riscv64.
Также под архитектуру уже есть официальные пакеты Nextcloud и OpenClaw.
Я решил, что HA будет жить у меня на VPS, но привязка к онлайну рушит философию автономного умного дома. Как минимум базовые вещи (Zigbee-выключатели, автоматизации вроде включения и отключения бойлера в рамках ночного тарифа на электроэнергию) должны быть автономными. Попробовал Node-RED, но не подружился с ним. Казалось бы, одна логика. По сути, копирую прямо целую цепочку, но в одном сценарии она работает, а во втором не хочет. Мне такое не подходит. “ПОЭТОМУ…” ©)) Я решил, что здесь тоже нужно пойти Linux-подходом. Это ведь MQTT, он занимается тем, что читает топики и пишет в них. А уже автоматика (вроде Z2M) видит эти сообщения и реагирует. Более того, на ESP8266/ESP32 в рамках того же ESPHome MQTT прекрасно работает. С этим мне тоже помог ИИ.
Для этого нужно всего два пакета:
sudo apt install -y python3-paho-mqtt python3-schedule
Для реализации задумки, нужно создать скрипт ~/mqtt_bridge.py
import socket
import subprocess
import threading
import json
import time
import logging
from threading import Timer
from datetime import datetime
import paho.mqtt.client as mqtt
# --- НАСТРОЙКИ ПОДКЛЮЧЕНИЯ ---
MQTT_BROKER = "127.0.0.1"
MQTT_PORT = 1883
MQTT_USER = "login"
MQTT_PASS = "password"
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s')
# Global variables
corridor_timer = None
boiler_on_triggered = False
boiler_off_triggered = False
# --- ADB ---
ANDROID_TV_IP = "192.168.1.6"
tv_last_state = "UNKNOWN"
# =====================================================================
# ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ
# =====================================================================
def publish(client, topic, payload):
"""Отправка JSON или обычных команд в MQTT"""
if isinstance(payload, dict):
client.publish(topic, json.dumps(payload))
else:
client.publish(topic, str(payload))
def turn_off_corridor_light(client):
"""Таймаут: Выключение света в коридоре"""
logging.info("Автоматизация -> Таймаут коридора (90с) -> Выключение света")
publish(client, "zigbee2mqtt/Свет коридор/set", {"state": "OFF"})
def restart_corridor_timer(client):
"""Сброс и запуск таймера коридора на 90 секунд"""
global corridor_timer
if corridor_timer is not None:
corridor_timer.cancel()
corridor_timer = Timer(90.0, turn_off_corridor_light, [client])
corridor_timer.start()
# =====================================================================
# БЛОК 1: ПРЯМОЕ УПРАВЛЕНИЕ (DIRECT CONTROLS)
# (Кнопки, выключатели, пульты — реагируют сразу без сложных условий)
# =====================================================================
def handle_direct_controls(client, topic, action):
# --- ВЫКЛЮЧАТЕЛЬ ЗАЛ ---
if topic == "zigbee2mqtt/Выключатель зал":
if action == "1_single":
logging.info("Прямое управление -> Зал 1 / 1 нажатие -> Свет зал")
publish(client, "zigbee2mqtt/Свет зал/set", {"state": "TOGGLE"})
elif action == "3_double":
logging.info("Прямое управление -> Зал 3 / 2 нажатия -> AIR 25 cool")
publish(client, "esphome/45/button/ac_25c_cool/command", "PRESS")
# --- ВЫКЛЮЧАТЕЛЬ КОРИДОР ---
elif topic == "zigbee2mqtt/Выключатель коридор":
if action == "1_single":
logging.info("Прямое управление -> Коридор 1 / 1 нажатие -> Свет коридор")
publish(client, "zigbee2mqtt/Свет коридор/set", {"state": "TOGGLE"})
# =====================================================================
# БЛОК 2: АВТОМАТИЗАЦИИ (AUTOMATIONS)
# (Датчики, сценарии по времени, логика таймеров и умных событий)
# =====================================================================
def handle_automations(client, topic, payload):
# 1. Открытие входной двери -> Свет в коридоре на 1.5 мин
if topic == "zigbee2mqtt/Сенсор двери входной" and "contact" in payload:
is_open = not payload["contact"]
if is_open:
logging.info("Автоматизация -> Входная дверь открыта -> Свет в коридоре на 90 сек")
publish(client, "zigbee2mqtt/Свет коридор/set", {"state": "ON"})
restart_corridor_timer(client)
def check_scheduled_automations(client):
"""Автоматизации по строгому времени (бойлер, ночные режимы)"""
global boiler_on_triggered, boiler_off_triggered
now = datetime.now()
# --- Бойлер: Включение в 00:00:00 ---
if now.hour == 0 and now.minute == 0 and now.second == 0:
if not boiler_on_triggered:
logging.info("Автоматизация времени -> Ночной тариф (00:00) -> Включение бойлера")
publish(client, "zigbee2mqtt/Бойлер/set", {"state": "ON"})
boiler_on_triggered = True
else:
boiler_on_triggered = False
# --- Бойлер: Выключение в 06:50:00 ---
if now.hour == 6 and now.minute == 50 and now.second == 0:
if not boiler_off_triggered:
logging.info("Автоматизация времени -> Конец ночного тарифа (06:50) -> Выключение бойлера")
publish(client, "zigbee2mqtt/Бойлер/set", {"state": "OFF"})
boiler_off_triggered = True
else:
boiler_off_triggered = False
# =====================================================================
# МОДУЛЬ ANDROID TV (ADB Мониторинг)
# =====================================================================
def get_android_tv_state(ip):
"""Проверка питания экрана Android TV через ADB"""
try:
# Быстрый коннект
subprocess.run(f"adb connect {ip}:5555", shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=2)
# Запрос состояния экрана
cmd = f"adb -s {ip}:5555 shell dumpsys power | grep 'mHoldingDisplaySuspendBlocker'"
result = subprocess.check_output(cmd, shell=True, timeout=2).decode('utf-8')
if "true" in result:
return "ON"
else:
return "OFF"
except Exception:
return "OFFLINE"
def monitor_android_tv(client):
"""Фоновый цикл слежения за ТВ"""
global tv_last_state
while True:
current_state = get_android_tv_state(ANDROID_TV_IP)
if current_state != tv_last_state and current_state != "OFFLINE":
logging.info(f"Android TV изменил статус: {tv_last_state} -> {current_state}")
if current_state == "ON":
logging.info("Автоматизация ADB -> Включение 'ТВ Кухня'")
publish(client, "zigbee2mqtt/ТВ Кухня/set", {"state": "ON"})
elif current_state == "OFF":
logging.info("Автоматизация ADB -> Выключение 'ТВ Кухня'")
publish(client, "zigbee2mqtt/ТВ Кухня/set", {"state": "OFF"})
tv_last_state = current_state
time.sleep(4) # Опрос каждые 4 секунды
# =====================================================================
# ЯДРО СИСТЕМЫ (MQTT CLIENT LOOP)
# =====================================================================
def on_connect(client, userdata, flags, rc):
if rc == 0:
logging.info("Соединение с MQTT Брокером установлено успешно!")
topics = [
"zigbee2mqtt/Выключатель зал",
"zigbee2mqtt/Выключатель коридор",
"zigbee2mqtt/Сенсор двери входной"
]
for t in topics:
client.subscribe(t)
else:
logging.error(f"Ошибка подключения, код: {rc}")
def on_message(client, userdata, msg):
try:
topic = msg.topic
payload = json.loads(msg.payload.decode('utf-8'))
# Разводка событий по блокам
if "action" in payload:
handle_direct_controls(client, topic, payload["action"])
handle_automations(client, topic, payload)
except Exception as e:
logging.error(f"Ошибка обработки пакета: {e}")
# Инициализация и запуск
client = mqtt.Client()
client.username_pw_set(MQTT_USER, MQTT_PASS)
client.on_connect = on_connect
client.on_message = on_message
client.connect(MQTT_BROKER, MQTT_PORT, 60)
client.loop_start()
# Запуск фонового отслеживания Android TV
threading.Thread(target=monitor_android_tv, args=(client,), daemon=True).start()
try:
while True:
check_scheduled_automations(client)
time.sleep(0.5)
except KeyboardInterrupt:
logging.info("Остановка службы...")
client.loop_stop()
client.disconnect()В итоге это работает даже лучше, чем я ожидал! У меня всегда такие автоматизации жили на Home Assistant, в зависимости от железа между нажатием и срабатыванием были задержки разной продолжительности (обычно доли секунды). Субъективно, при такой реализации задержки самые низкие из всех, что были у меня раньше, и всё работает абсолютно автономно. На кухне у меня стоит необычный телевизор. Это сочетание Android TV бокса и моего старого 24’ монитора. Работает это замечательно, но есть одно «но» — включение и выключение. У монитора нет автоотключения, и при отключении ТВ-бокса он продолжает показывать пустой экран. У меня была ADB-интеграция с автоматизацией в HA. Если ТВ-бокс включается, то включается Zigbee-розетка, питающая монитор, и наоборот. На Linux это реализуется тоже просто, достаточно установить пакет ADB: (apt install adb). В итоге получилось достаточно неплохо. Для сложных автоматизаций (и тех, которые требуют доступа в интернет) у меня продолжает крутиться HA на VPS, но вопрос управления локальными устройствами закрыт полностью.


Что касается qBittorrent, у меня помимо основного подключения поднято два WG. Один с VPN от Cloudflare WARP (для обхода фильтрации провайдера), а второй — мой мост к VPS. Соответственно, мне хочется, чтобы торрент подключался и к WARP, и к основному подключению, следовательно, резонно оставить “все подключения”. Но при этом если мой IP от достаточно хорошего и бесплатного VPS (Oracle cloud) засветится на какой-то раздаче, то с большой долей вероятности я могу забыть об этом VPS. Этого допустить нельзя ни при каких обстоятельствах. Но так как это Linux, это решается всего двумя командами:
sudo iptables -A OUTPUT -o wg0 -m owner --uid-owner qbittorrent -j DROP
sudo netfilter-persistent save

Официальный TorrServer Matrix от YouROK тоже собирается под архитектуру riscv64.
P.S. “И только на третий день вождь Зоркий Глаз заметил, что у тюрьмы нет четвёртой стены”.
У меня роутер с 2.5G входом и ПК с 2.5G адаптером. Как я подключил? Правильно, 2.5G в 2.5G, а гигабитный WAN — в гигабитный порт. Вроде бы логично. Да вот только в роутер подавать 2.5G смысла нет вообще никакого. Полосу 80+80 я не использую (так как на 36–48 каналах сидят ВСЕ соседские роутеры, только меня не хватает в этой компании), следовательно, моя максимальная пропускная способность — 1.2G (номинальная, на клиентах Wi-Fi 6), которая по факту значительно ниже гигабита. Но порты 2.5G, в отличие от 1G, имеют отдельные, независимые чипы, каждый на своей PCIe линии. И конечно, абсолютно очевидно, что WAN-порт, имеющий самую интенсивную нагрузку, должен находиться на таком порте. И действительно, когда я загружаю WAN-порт гигабитной скоростью, ядро не стучится в 100%, оно загружено на 80%–90%, а скорость не просаживается до 700 мегабит, а отдаёт честный интернет-канал.
ВЫВОДЫ
Почему же я решил перейти со своего Keenetic Hopper 3810?
Всё очень просто. Несмотря на то, что Keenetic — это прямо очень крутое решение «включил в розетку и пользуешься», с офигенным веб-интерфейсом и кучей плюшек (свой внешний DNS-прокси, интуитивно понятные настройки, много пакетов и расширений, активная поддержка, свежие прошивки и т.д.), он не покрывал конкретно мои потребности. Двухъядерный процессор на архитектуре MIPS и 256 МБ ОЗУ были очевидным узким горлышком для меня. WireGuard давал скорости в районе 70–80 мегабит в секунду, USB пропускал через себя максимум 90 мегабайт в секунду, а торрент-клиент не выдавал более 6–7 мегабайт в секунду. На Orange Pi я получил 600 мегабит в WireGuard, USB со скоростью ~350 мегабайт в секунду (но дальше упирается в потолок сетевого интерфейса). Если это гигабит, то стабильные 115 мегабайт в секунду, если 2.5G, то лично мне удалось выжать на USB-адаптере 165 мегабайт в секунду, на нативном или PCIe должно быть интереснее. Ну и торрент, который по скорости упирается только в пропускную способность моего интернета и сидов (отдающих).
Резонный вопрос — а как же Wi-Fi? Я купил на Taobao Wi-Fi 7 роутер Xiaomi BE3600 с 2.5G портом. С доставкой вышло около $28. По Wi-Fi он пушка (европейские радиочастотные нормы строже, чем таковые в Китае, США и ряде других стран), в 2.4G я даже выставил среднюю мощность.
Интерфейс полностью на китайском, но он-то мне и не нужен. Выставил режим точки доступа, прописал SSID, пароли, каналы — и работает. В итоге я получил модульный роутер (радио и “мозги” — отдельно друг от друга), который покрывает с лихвой мои потребности и имеет большой потенциал для увеличения своих возможностей в будущем. Готовый роутер с подобной производительностью, безусловно, стоил бы существенно дороже.
Но является ли такое решение универсальным? Абсолютно очевидно, что нет. Это вариант для энтузиастов, которые чётко знают, что они делают и зачем. “Танцев с бубном”, абсолютно очевидно, не избежать, и, думаю, все это понимают. Это не та история, где включил в розетку — и всё работает (хотя с выходом ванильной OpenWrt, думаю, ситуация будет намного дружелюбнее ко всем пользователям, но пока нет ни сроков, ни гарантий).
Касаемо версий — есть 2 ГБ, 4 ГБ и 8 ГБ ОЗУ. У меня в полностью рабочем состоянии потребление ОЗУ не выходит за 700 мегабайт. То есть для рабочего сетевого сценария использования 2 ГБ версии хватит с запасом. 4 ГБ — тем, кто планирует пользоваться Nextcloud, OpenClaw или рассчитывает на потенциал RISC-V в будущем (Home Assistant и т.д.). В версии 8 ГБ, по правде говоря, смысла не вижу. На сегодняшний день я просто не вижу сценариев, которые способны этот объём утилизировать (хотя это не означает, что таких сценариев нет, в теории я это допускаю).
Если оценивать Orange Pi R2S и такое решение в целом, то можно выделить:
Слабые стороны:
- Нет слота MicroSD и отдельного SPI с U-Boot загрузчиком.
- USB 3.0 с пропускной способностью 5G, а не 10G.
- Выпуск операционных систем, а также пакетов под архитектуру всё ещё проходит с большим скрипом.
- Процессор значительно уступает ARMv8 (Allwinner A733 в Orange Pi Zero 3W) в плане криптографии, который щёлкает Xray как орехи.
- Далеко не весь потенциал на текущий момент раскрыт.
Нейтральные стороны (имеющие как плюсы, так и минусы, а также особенности):
- Linux. С одной стороны, это полностью ручные и долгие настройки. Нужно быть готовым потратить время, допускать ошибки и их устранять, сталкиваться с тем, что получается не так, как хочется, провести время в диалоге с ИИ. Конечно, всё-таки необходимы базовые знания и понимание процессов. С другой стороны — это полная свобода и максимальная кастомизация. Как я показал в обзоре, можно реализовать свои желания в соответствии со своим видением, а не видением вендора. И всё это нужно в основном для первоначальной настройки, после которой остаётся только наслаждаться пользованием.
- DIY-решение. В отличие от готового роутера, здесь нет красивого и стильного корпуса. Но, с другой стороны, можно напечатать такой, как вам хочется.
- Нагрев. Чип, как и сетевые контроллеры 2.5G, достаточно горячие. Из коробки плата “голая”, поэтому нужно обязательно позаботиться о радиаторах.
- Нужна отдельная точка доступа, но её можно выбрать под свой бюджет и под свои потребности.
- Обработка трафика — на процессоре. Хотя процессор достаточно производительный, чтобы с этим справиться.
- RISC-V, в отличие от ARM и x86 — открытая архитектура, освобождённая от монополии крупных производителей. Любой производитель чипов может собрать себе чип на своих инструкциях. Это делает архитектуру достаточно привлекательной, с большим потенциалом. Но она достаточно молодая и развивается не так быстро, как хотелось бы.
Сильные стороны:
- Цена решения. За такие характеристики и конечный результат, по моему мнению, она вполне привлекательная. Готовые роутеры с такими возможностями стоят существенно дороже, чем Orange Pi R2S + Wi-Fi точка доступа.
- Два 2.5G интерфейса. Причём реализованы они на двух отдельных чипах Realtek RTL8125BG, каждый “сидит” на своей линии PCIe x1.
- Хорошая производительность при оптимальном энергопотреблении.
- Два USB-порта: USB 3.0 и USB 2.0.
В целом я остался очень доволен такими переменами и для себя считаю, что время и нервы на настройку были потрачены не зря.
Продукт на официальном сайте Orange Pi:
http://www.orangepi.org/html/hardWare/computerAndMicrocontrollers/details/Orange-Pi-R2S.html
Я прикладываю максимум усилий и много времени, чтобы мои обзоры были полезными и интересными. Если этот обзор вам понравился — поддержите его плюсом, мне будет очень приятно. А ещё — это отличная мотивация для меня писать ещё больше и качественнее для вас.
Всем хорошего настроения, позитива, добра и мира!
| +345 |
22973
188
|
Соответственно 142,39€, 164,99€ и 203,39€
А так за 50-115 железяка интересная, прямо очень. Узкое роутерное применение, тут практически всё что надо.
Судя по всему это у вас для расширения собственного кругозора, но уровень погружения для не IT-шника даже слишком глубокий. Даже интересно какой ваш основной профиль…
Да, если честно то было интересно всё настраивать (это скорее хобби, причём конкретно в Linux погрузился не то, чтобы очень давно. Благодаря ИИ наверное). Хотя если взять чисто экономическую сторону вопроса, то если взять условный Xiaomi BE3600 за $28 (а можно взять и какой-нибудь Redmi AC2100 ещё дешевле, тоже дальнобойный но WIFI 5, хотя на деле разницы практически не будет с WIFI 6) и даже если предположить, что апельсинку мне не дали на обзор, а я бы купил её за свои ($50 за 2G, сейчас нет в наличии, но я точно видел), то это как раз цена моего проданного Keenetic. Но если сравнить по производительности и возможностям, то между этим сетом и Keenetic (KN-3810) — пропасть. Ну и несколько вечеров настройки конечно))
p.s. минус поставил не я.