05-30-2021, 05:54 PM
So you've just gotten your brand new Rockpro64, you're looking for a clean way to stuff a PSU and 4 HDD backplane into a box that fits nicely onto your network rack. What do you do? Well, you could get the official NAS case. But that only fits 2 disks, and how are you supposed to mount that on your rack? With a shelf?? Inconceivable.
Well here's what I did:
Having bought my home network equipment off eBay, I know retired enterprise gear can be had for dirt cheap. So after some quick searching, I came across the perfect 1U case, that happened to also have a Proliant DL160 server inside it. Perfect for a weekend project.
Steps to NAS:
1. Gut the server, leaving only the PSU, backplane, and fans.
2. If the original SAS controller works, great! If not, find a Rockpro64 compatible PCIe SAS controller. I used an IBM H1110.
3. An ATX breakout board is handy for connecting to the case's power supply. But if you prefer crimping wires, you do you. As a side note, I'm using the ATX supply to directly control power to the Rockpro. The Rockpro actually has some solder points for power control, near the power buttons, which could be used to improve on my design. I'll be adding that mod myself whenever I get around to it.
4. You'll need an Arduino as well, I like using Nano's. They're super cheap and fit nicely into just about any project.
5. Shove it all into the case. It should look something like this:
6. You'll need to wire all those pieces together. I'm driving the LEDs directly from the Arduino GPIO, because I'm lazy I like to live dangerously. If you're more concerned about damaging your AVR than I am, you might want to buffer each of those LED lines with a transistor. I decided to wire the fancy buttons and blinkenlights on the front panel up to the Arduino for my NAS. You can put together a wiring harness to mate with the case connector, or be like me and shove your wires directly into the case connector. They're not going anywhere:
7. The front panel has a 2 row, 2.5mm pitch female connector for the USB connections. Hack a connector onto couple USB cables. You can then use the hacked cables as an adapter between the case USB connector and the Rockpro USB inputs. My DL160 case only supports USB 2.0, so I can't take full advantage of the Rockpro's USB interface:
8. If you don't like your NAS to sound like a jet plane taking off, take out some fans. I've left just one fan. It's keeping everything nice and cool, and runs almost silently. To maintain proper airflow, stuff some packing foam in place of the old fans:
9. You'll need to get some code onto the Arduino. This script handles power up/shutdown; fan control; and, just to look like a real enterprise grade server, blinking some lights:
10. Now you'll need something on the Rockpro side to help with fan and light control. This script uses a wake word to send commands to the Arduino over the UART console. You'll want to add this python script to systemd so it runs automagically on boot:
11. Now put it onto your rack, and make all your friends jealous of your new ultra-quiet ultra-low-power 1U server!
I promised earlier that I'd post my project if I got some useful help from this forum, so I hope I didn't disappoint!
My scripts and schematic can also be found on my github page. Feel free to send me a pull request with any suggestions.
Well here's what I did:
Having bought my home network equipment off eBay, I know retired enterprise gear can be had for dirt cheap. So after some quick searching, I came across the perfect 1U case, that happened to also have a Proliant DL160 server inside it. Perfect for a weekend project.
Steps to NAS:
1. Gut the server, leaving only the PSU, backplane, and fans.
2. If the original SAS controller works, great! If not, find a Rockpro64 compatible PCIe SAS controller. I used an IBM H1110.
3. An ATX breakout board is handy for connecting to the case's power supply. But if you prefer crimping wires, you do you. As a side note, I'm using the ATX supply to directly control power to the Rockpro. The Rockpro actually has some solder points for power control, near the power buttons, which could be used to improve on my design. I'll be adding that mod myself whenever I get around to it.
4. You'll need an Arduino as well, I like using Nano's. They're super cheap and fit nicely into just about any project.
5. Shove it all into the case. It should look something like this:
6. You'll need to wire all those pieces together. I'm driving the LEDs directly from the Arduino GPIO, because I'm lazy I like to live dangerously. If you're more concerned about damaging your AVR than I am, you might want to buffer each of those LED lines with a transistor. I decided to wire the fancy buttons and blinkenlights on the front panel up to the Arduino for my NAS. You can put together a wiring harness to mate with the case connector, or be like me and shove your wires directly into the case connector. They're not going anywhere:
7. The front panel has a 2 row, 2.5mm pitch female connector for the USB connections. Hack a connector onto couple USB cables. You can then use the hacked cables as an adapter between the case USB connector and the Rockpro USB inputs. My DL160 case only supports USB 2.0, so I can't take full advantage of the Rockpro's USB interface:
8. If you don't like your NAS to sound like a jet plane taking off, take out some fans. I've left just one fan. It's keeping everything nice and cool, and runs almost silently. To maintain proper airflow, stuff some packing foam in place of the old fans:
9. You'll need to get some code onto the Arduino. This script handles power up/shutdown; fan control; and, just to look like a real enterprise grade server, blinking some lights:
Code:
#include <Arduino.h>
#include <elapsedMillis.h>
#define PIN_UART_RX 1
#define PIN_PSU_ON 2
#define PIN_PANEL_PWR 3
#define PIN_SYS_LED_P1 4
#define PIN_NIC_LED_P 5
#define PIN_HDD_LED_P 6
#define PIN_BOARD_PWR 7
#define PIN_FAN_PWM 9
#define PIN_PWR_LED_GP_RN 12
#define PIN_PWR_LED_GN_RP 13
#define PIN_SYS_LED_G_N 19
#define PIN_SYS_LED_R_N 20
#define PIN_SYS_LED_P2 21
#define PWR_STATE_OFF 0 //PSU powered off
#define PWR_STATE_STARTUP 1 //PSU starting up, power button is still held
#define PWR_STATE_ON 2 //PSU powered up, button no longer held
#define PWR_STATE_SHUTDOWN_TMR 3 //PSU powered up, shutdown timer is counting
#define PWR_STATE_SHUTDOWN 4 //PSU shut down, button is still held
#define PWR_STATE_BOARD_OFF 5 //board shut down, PSU still on
#define BLINK_STATE_READY 0 //LED is not blinking
#define BLINK_STATE_ON 1 //LED is off
#define BLINK_STATE_OFF 2 //LED is on
#define UART_STATE_OK 0
#define UART_STATE_ERR 1
#define UART_WATCHDOG_TIMEOUT 10000
uint8_t power_state = PWR_STATE_OFF;
_Bool uart_state = UART_STATE_OK;
int blink_chance = 0;
elapsedMillis event_timer;
elapsedMillis uart_watchdog;
elapsedMillis blink_timer;
void setup() {
// Configure PWM on pin 9 (and 10) @ 25 kHz.
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 0;
TCCR1A = _BV(COM1A1)
| _BV(COM1B1)
| _BV(WGM11);
TCCR1B = _BV(WGM13)
| _BV(CS10);
ICR1 = 320;
pinMode(PIN_UART_RX, INPUT);
pinMode(PIN_PSU_ON, INPUT);
pinMode(PIN_PANEL_PWR, INPUT_PULLUP);
pinMode(PIN_SYS_LED_P1, OUTPUT);
pinMode(PIN_NIC_LED_P, OUTPUT);
pinMode(PIN_HDD_LED_P, OUTPUT);
pinMode(PIN_BOARD_PWR, INPUT);
pinMode(PIN_FAN_PWM, OUTPUT);
pinMode(PIN_PWR_LED_GP_RN, OUTPUT);
pinMode(PIN_PWR_LED_GN_RP, OUTPUT);
pinMode(PIN_SYS_LED_G_N, OUTPUT);
pinMode(PIN_SYS_LED_R_N, OUTPUT);
pinMode(PIN_SYS_LED_P2, OUTPUT);
digitalWrite(PIN_SYS_LED_P1, HIGH);
digitalWrite(PIN_NIC_LED_P, LOW);
digitalWrite(PIN_HDD_LED_P, LOW);
analogWrite(PIN_FAN_PWM, 0);
digitalWrite(PIN_PWR_LED_GP_RN, LOW);
digitalWrite(PIN_PWR_LED_GN_RP, LOW);
digitalWrite(PIN_SYS_LED_G_N, LOW);
digitalWrite(PIN_SYS_LED_R_N, HIGH);
digitalWrite(PIN_SYS_LED_P2, HIGH);
Serial.setTimeout(50);
}
void loop() {
/*********************************************
* handle system power
*********************************************/
switch(power_state)
{
case PWR_STATE_OFF:
{
if( (digitalRead(PIN_PANEL_PWR) == LOW) || (PIN_BOARD_PWR == HIGH) )
{
power_state = PWR_STATE_STARTUP;
pinMode(PIN_PSU_ON, OUTPUT);
digitalWrite(PIN_PSU_ON, LOW);
//turn on the GREEN power LED
pinMode(PIN_PWR_LED_GP_RN, OUTPUT);
pinMode(PIN_PWR_LED_GN_RP, OUTPUT);
digitalWrite(PIN_PWR_LED_GN_RP, LOW);
digitalWrite(PIN_PWR_LED_GP_RN, HIGH);
event_timer = 0;
}
} break;
case PWR_STATE_STARTUP:
{
//after button is released, wait 5 seconds for startup
if( (digitalRead(PIN_PANEL_PWR) == HIGH) && (event_timer > 5000) )
{
power_state = PWR_STATE_ON;
}
} break;
case PWR_STATE_ON:
{
if(digitalRead(PIN_PANEL_PWR) == LOW)
{
event_timer = 0;
power_state = PWR_STATE_SHUTDOWN_TMR;
}
else if(digitalRead(PIN_BOARD_PWR) == LOW)
{
event_timer = 0;
power_state = PWR_STATE_BOARD_OFF;
}
} break;
case PWR_STATE_SHUTDOWN_TMR:
{
if(digitalRead(PIN_PANEL_PWR) == LOW)
{
//button was held for 3 seconds, shut down
if( event_timer > 3000 )
{
//tri-state the power pin
power_state = PWR_STATE_SHUTDOWN;
pinMode(PIN_PSU_ON, INPUT);
//turn on the RED power LED
digitalWrite(PIN_PWR_LED_GP_RN, LOW);
digitalWrite(PIN_PWR_LED_GN_RP, HIGH);
//disconnect UART
Serial.end();
pinMode(PIN_UART_RX, INPUT);
}
}
else
{
power_state = PWR_STATE_ON;
}
} break;
case PWR_STATE_SHUTDOWN:
{
//wait until button is released
if(digitalRead(PIN_PANEL_PWR) == HIGH)
{
power_state = PWR_STATE_OFF;
}
} break;
case PWR_STATE_BOARD_OFF:
{
if(digitalRead(PIN_BOARD_PWR) == LOW)
{
//board was off for 3 seconds, shut down
if( event_timer > 3000 )
{
//tri-state the power pin
power_state = PWR_STATE_SHUTDOWN;
pinMode(PIN_PSU_ON, INPUT);
//turn on the RED power LED
digitalWrite(PIN_PWR_LED_GP_RN, LOW);
digitalWrite(PIN_PWR_LED_GN_RP, HIGH);
//disconnect UART
Serial.end();
pinMode(PIN_UART_RX, INPUT);
}
}
else
{
power_state = PWR_STATE_ON;
}
} break;
default:
break;
}
/*********************************************
* blink Eth0 LED
*********************************************/
// 100 megabit blinks fastest
#define BLINK_MAX_CHANCE 100000000
#define BLINK_MILLIS 30
static uint8_t blink_state = BLINK_STATE_READY;
if( (power_state == PWR_STATE_ON) &&
(uart_state == UART_STATE_OK) )
{
switch(blink_state)
{
case BLINK_STATE_READY:
{
// blink the eth0 LED randomly, with a frequency based
// on the network traffic
if( (blink_chance != 0) &&
( (random(0,BLINK_MAX_CHANCE) < blink_chance) ||
(random(0,2000) == 0) )
)
{
blink_state = BLINK_STATE_ON;
blink_timer = 0;
digitalWrite(PIN_NIC_LED_P, HIGH);
}
} break;
case BLINK_STATE_ON:
{
if(blink_timer > BLINK_MILLIS)
{
blink_state = BLINK_STATE_OFF;
blink_timer = 0;
digitalWrite(PIN_NIC_LED_P, LOW);
}
} break;
case BLINK_STATE_OFF:
{
if(blink_timer > (BLINK_MILLIS/2))
{
blink_state = BLINK_STATE_READY;
}
} break;
default:
{
blink_state = BLINK_STATE_READY;
blink_timer = 0;
digitalWrite(PIN_NIC_LED_P, LOW);
}
}
}
#undef BLINK_MAX_CHANCE
#undef BLINK_TICKS
/*********************************************
* handle UART commands
*********************************************/
#define UART_IDENTIFIER_IDX 0
#define UART_CMD_IDX 9
/* enable/reset UART */
// keep UART off until board is powered up
if(power_state != PWR_STATE_ON)
{
uart_watchdog = 0;
}
// no commands received in timeout period, reset UART
else if(uart_watchdog > UART_WATCHDOG_TIMEOUT)
{
Serial.end();
Serial.begin(115200);
uart_watchdog = 0;
uart_state = UART_STATE_ERR;
//set warning LED
digitalWrite(PIN_SYS_LED_G_N, HIGH);
digitalWrite(PIN_SYS_LED_R_N, LOW);
}
/* monitor the console output */
if (Serial.available() > 0) {
String command;
int argument = 0;
// monitor the serial port for the identifier string
command = Serial.readStringUntil('\n');
if(command.substring(UART_IDENTIFIER_IDX, UART_CMD_IDX)
.equals("SimonSays"))
{
// reset the watchdog timer
uart_watchdog = 0;
uart_state = UART_STATE_OK;
// system OK
digitalWrite(PIN_SYS_LED_R_N, HIGH);
digitalWrite(PIN_SYS_LED_G_N, LOW);
// parse the command
command.remove(UART_IDENTIFIER_IDX, UART_CMD_IDX);
command.trim();
int arg_idx = command.lastIndexOf(' ')+1;
if(arg_idx > 0)
{
argument = command.substring(arg_idx).toInt();
command.remove(arg_idx);
command.trim();
}
}
/* commands */
// change the fan speed
if(command.equals("SET FAN"))
{
analogWrite(PIN_FAN_PWM, argument);
}
// blink the eth0 LED
else if(command.equals("SET NET LED"))
{
blink_chance = argument;
}
}
#undef UART_IDENTIFIER_IDX
#undef UART_CMD_IDX
}
10. Now you'll need something on the Rockpro side to help with fan and light control. This script uses a wake word to send commands to the Arduino over the UART console. You'll want to add this python script to systemd so it runs automagically on boot:
Code:
#!/usr/bin/python3
import io
import time
import psutil
import os
net_traffic_prev = 0
os.system("stty -F /dev/ttyS2 ospeed 115200")
while True:
temp_file1 = open("/sys/class/thermal/thermal_zone0/temp", "r")
temp_file2 = open("/sys/class/thermal/thermal_zone1/temp", "r")
temp1 = temp_file1.readline()
temp2 = temp_file2.readline()
temp_file1.close()
temp_file2.close()
if temp1 > temp2:
hightemp = int(temp1)
else:
hightemp = int(temp2)
if hightemp < 40000:
pwm = 0
else:
pwm = int(( (hightemp-40000) / 40000 ) * 255)
net_counters = psutil.net_io_counters()
net_traffic_curr = (net_counters.bytes_sent + net_counters.bytes_recv)
net_traffic = str(net_traffic_curr - net_traffic_prev)
net_traffic_prev = net_traffic_curr
uart_out = open("/dev/ttyS2", "w")
uart_out.writelines("\nSimonSays SET FAN "+str(pwm)+"\n")
uart_out.writelines("\nSimonSays SET NET LED "+str(net_traffic)+"\n")
uart_out.close()
time.sleep(2)
11. Now put it onto your rack, and make all your friends jealous of your new ultra-quiet ultra-low-power 1U server!
I promised earlier that I'd post my project if I got some useful help from this forum, so I hope I didn't disappoint!
My scripts and schematic can also be found on my github page. Feel free to send me a pull request with any suggestions.