ITСooky

IT-рецепты съедобные и не очень!

Orange PI PC Server на Armbian!

дата 07.11.2018

Откапал забытую незаконченную статью, поэтому Armbian такой старый, но в этом есть плюс! Чуть переделал и переписал, и вот сервер на Orange Pi Pc! Почему сервер? Потому что автономное — получение времени и можно напрямую подключаться, есть безопасное отключение, есть контроль температуры процессора с вентилятором и есть табло с параметрами как на взрослых серверах.

Просто на Orange Pi Pc запускаю образ от Armbian.com, чтобы не в чем себя не ограничивать собрал специальную апельсину, кстати на схеме не так страшно как на фото получилось.

Раньше выглядело так



Но я убрал макетку, обрубил кабеля и припоял их к платке, стало красивее


Стало крсивее

Вот такая схема, питание идет через microUSB разъем.

Обычно качаю образ для Orangte PI PC с ссылки https://www.armbian.com/orange-pi-pc/ он оказывается образом для PC PC PLUS ну и ладно! Но на старом ядре 3.4 серверных сборок(легких без всякого) нет, но у меня было Armbian_5.25_Orangepipcplus_Debian_jessie_default_3.4.113

Записываем на SD карту — использую «плохую» Transcend 2 Gb без указания class скорости. Архив в 7z на Ubuntu нечем открыть поэтому сначала ставим:
sudo apt-get install p7zip-full
Все кладем в нужную папку и там разархивируем
7z e Armbian_5.25_Orangepipcplus_Debian_jessie_default_3.4.113.7z
Форматируем карту SD любым способом, я делаю через Gparted. И пишем на карту, картa у меня sde1
sudo dd bs=1M if=Armbian_5.25_Orangepipcplus_Debian_jessie_default_3.4.113.img of=/dev/sde

После загрузки надо ввести имя root и пароль 1234, после этого надо сменить пароль и добавить в систему пользователя user, вот тут все описано http://docs.armbian.com/User-Guide_Getting-Started/ Еще надо пару раз перезагрузиться чтобы использовалась вся карта памяти.

Подключаемся к Wi-Fi
Да не совсем по серверному но сейчас для мобильности надо. Вставляю usb WiFi вот такой, ссылка уже не работает но название вот Мини USB адаптер wi-fi RTL 8188EUS Comfast CF-WU810N-1 adaptador беспроводная точка доступа wi-fi USB WiFi ключ adaptador WiFi USB
Это старый Armbian тут нет armbian-config так что для подключения к Wi-Fi запускаем
nmtui-connect
Даже рассказывать нечего там все понятно

Интересный глюк тут обнаружился. Wi-Fi после перезагрузки поднялся, но Armbian думал что он все еще по кабелю, хотя кабеля не было и из-за этого не работал интернет. Пришлось прибить
ifdown eth0

Безопасное отключение, относительно(по сравнению с вырыванием кабеля питания, так очень безопасное)
Проблема в том что на OrangePi(пока на всех) нет железки которая выключает питание. Но можно выключить ОС и потом дергать кабель. Чтобы понять что ОС выключилась на этом этапе я буду использовать лампочку.

Чтобы общаться с GPIO надо поставить WiringOP

cd /root
git clone https://github.com/zhaolei/WiringOP.git -b h3
cd WiringOP
chmod +x ./build
sudo ./build

Теперь включаем лампочку, идем в
cd /root/WiringOP/examples/
И создаем
vi powerled.c
Туда вставляем код

#include <stdio.h>
#include <wiringPi.h>
#include <unistd.h>
#define LED 2

int main (void)
{
  wiringPiSetup () ;
  pinMode (LED, OUTPUT) ;

for (;;)
  {
usleep(500);
if(digitalRead(LED) == 0)

  {
    digitalWrite (LED, HIGH) ;  // On
    delay (1000) ;
  }
}

  return 0 ;
}

У меня нужна лампочка оказалась на двойке. Скрипт смотрит если она выключена то включить и замереть на 1000 милисекунд, ну за чем это делать стопятцот раз в секунду, всмылсе включать, а проверять будет да.

ОСТОРОЖНО: Циклы в С грузят процессор на 100%, поэтому подтормаживаю usleep()(в милисекундах)

собираем
make ./powerled
и я копирую в root папку
cp ./powerled /root
чтобы запыскать каждый раз на загрузке идем в крон
crontab -e
добавляем эту строчку (старую убираем)

@reboot /root/powerled & > /dev/null

Cделаю апдейт это не затронет ядро
apt-get update
Ставим ACPID… почему-то сразу не стоит…
aptitude install acpid

Вот тут проявляется плюс от старого ядра 3.4 в новом acpid не работает совсем с кнопкой питания

Далее создаем файл
vi /etc/acpi/events/button_power
В него пишем

event=button/power
action=/sbin/shutdown -h now

Перезагружаем ACPID
/etc/init.d/acpid restart

Жмем на кнопку power и Опельсинка начинает выключаться это видно по консоли. Когда лампочка погаснет все службы уже выключены а ядро впало в панику, есть 10 секунд чтобы выдернуть кабель питания, если не выдернуть оно начнет грузится обратно.

К сожалению не смог сделать мигание лампочки после нажатия кнопки, какая то прострация, только одна комманда в acpid а если туда ставить ссылку на скрипт то нормально выключить одну программу для лампочки и включить другую никак не получается…

Еще про электро питание
У OrangePi идиотский разъем питания, для вроде бы дешевой модели почти проприетарный разъем хм странно, а если еще посмотреть на цену доставки которая даже в одной коробке идет за каждую штуку тооо производителей опельсинки можно назвать людьми на букву м… то есть маркетологами — продукт дешевый, мы на всем съэкономили но мы на всем зарабатываем ха! С питанием можно бороться прикрутив обычный разъем микроUSB

В феврале 17 купил таких 10 штук за 147RUR, теперь стоят 257RUR ссылку давать не будут, название 10 шт./лот Micro USB к DIP-адаптер 5pin разъем PCB конвертер. Припоял провода к GND и VBUS в опельсинке подключил к первому 5V и GND. Пока хорошо все работает от самсунговской зарядки на 2A на длинном кабеле.

Доступ к OrangePi серверу по USB кабелю
Можно использовать USB TTL конвертер но я засунул Arduino Nano внутрь, так что нужен только USB кабель. Arduino Nano брал полную китайскую копию с правильным чипом для USB, у него лучше поддержка в плане драйверов на всех ос, вот так он назывался оригинал Nano 3.0 atmega328 мини-версия FT232RL импортные чипы поддержка win7 Win8 для arduino с usb-кабель.

На Arduino Nano надо замкнуть GND и RESET и подключить к опельсинке GND и TX, RX.

Так как я на Ubuntu то заход в консоль такой, ставим
sudo apt install screen
Подключаем Arduino Nano, опельсинка должна быть включена тоже
Смотрим куда она воткнулась
dmesg | grep tty
обычно это ttyUSB0 и подключаемся к нему
sudo screen /dev/ttyUSB0 115200
Работает странновато, сначала ничего не происходит, потом или само, или после того как пару раз нажмешь enter появляется запрос пароля. В общем хороший способ подключаться к серверу без всего в поле!

Подключаем энергонезависимые часики DS3231 RTC
Если есть интернет Armbian сам ставит время, надо только свой часовой пояс выбрать
dpkg-reconfigure tzdata

Если нет интернета то время начинается с последнего до выключения. Тоесть выключили на пол часа — время отстало на пол часа.

Ставим i2c инструмент
apt-get install i2c-tools

Смотрим где появились часы
i2cdetect -y 0
показывает что он там

     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 3f 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- 57 -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --   

У меня кое что висит уже тут но я знаю что часы это 68, их надо оживить запустив

sudo echo ds1307 0x68 > /sys/class/i2c-adapter/i2c-0/new_device

Команда посмотреть время
hwclock -r -f /dev/rtc1

Команда записать в часы системное время
hwclock -w -f /dev/rtc1

Чтобы часы стартовали на загрузке и отдавали системе свое время делаем
добавляем строки в
vi /etc/rc.local
вот эти перед exit 0

sudo echo ds1307 0x68 > /sys/class/i2c-adapter/i2c-0/new_device
sudo hwclock -s -f /dev/rtc1

Автозапуск вентилятора при перегреве

Хотел это интегрировать в программу экрана но не получилось из двух разных библиотек собрать одну программу, слишком мудрено все в makefile оказалось.

Идем в
cd /root/WiringOP/examples
Делаем файл
vi coolerstart.c
Вставляем код

#include <stdio.h>
#include <wiringPi.h>
#include <unistd.h>
#define LEDR 0
#define COOLER 1
#define WARNING_TEMP 55

int main (void)
{
  int number1;

  wiringPiSetup ();
  pinMode (LEDR, OUTPUT);
  pinMode (COOLER, OUTPUT);
 for (;;)
  {
usleep(1000);
FILE *in_file;


  in_file = fopen("/sys/class/thermal/thermal_zone0/temp", "r");
  if (in_file == NULL)
    {
        printf("Can't open file for reading.\n");
    }
    else
    {
        fscanf(in_file, "%d", &number1);
        fclose(in_file);
    }
 if ( number1 <= WARNING_TEMP)
{

    digitalWrite (COOLER, HIGH);   // Off
    digitalWrite (LEDR, LOW);   // Off

}
else
{
    digitalWrite (LEDR, HIGH);  // On
    digitalWrite (COOLER, LOW);  // On
    delay (150000);               // mS

}

  }
  return 0 ;
}

Это путь к файлу с температурой в Armbian /sys/class/thermal/thermal_zone0/temp

Собираем
make coolerstart

И запускаем в фоновом режиме
./coolerstart & > /dev/null

Ксати чтобы остановить в фоне запущенное надо
pkill coolerstart
Но убивет всё что называет coolerstart*

Начинаю греть тестом sysbench он уже установлен в Armbian был
sysbench --test=cpu --cpu-max-prime=20000 --num-threads=2 run

Работает — охлаждает, до тролинга не доходит!!!

скомпелированный файл я скопировал в root директорию
cp /root/WiringOP/examples/coolerstart /root/
идем в крон
crontab -e
добавляем эту строчку (старую убираем)
@reboot /root/coolerstart & > /dev/null

и это работает

Подключаем экран lcm1602

Качаем вот эту библиотеку. Она простая по функциям но другой что-то нет. Из простоты — нельзя очистить строку только экран, нельзя вставить точное место только с начала строки.
git clone https://github.com/wargio/liblcm1602.git
cd liblcm1602
make

Экран у нас на адресе 3f это было видно в выводе i2cdetect -y 0
Редактируем
vi test.c
в это строке меняем на свое 0x37

i2c_dev = open_i2c(I2C_FILE_NAME, 0x3f);

Собираем и запускаем
make test
./test

Сработало

Теперь дальше идем в
Чтобы собирался файл с нашим именем добавляем имя server_monitoring сюда
vi Makefile
приводим строку к виду

EXAMPLES=       server_monitoring test example example2 example3

Делаем файл
vi server_monitoring
Вываливаю сразу готовый код

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include "i2c.h"
#include "lcd.h"
#include <time.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <unistd.h>
#define I2C_FILE_NAME "/dev/i2c-0"
#define TIME_TO_WAIT 2 

int main(){
int text=0;
int number1;
int number2;
clock_t last = clock();  
char txt[100];
   
// for Ip start     
 char ip_address_wlan[100];
 char ip_address_eth[100];
//for ip end


//for lcd start
 int i2c_dev;
    lcd lcd0;
    // 0x27 the address of the i2c device
    i2c_dev = open_i2c(I2C_FILE_NAME, 0x3f);
    if(i2c_dev <0){
       printf("Errore: %d\n", i2c_dev);
       return 1;
    }
lcd_init(&lcd0, i2c_dev);
    char vremya[100];
lcd_clear(&lcd0);
//for lcd end

for (;;)
  {


usleep(100);


clock_t current = clock();

//time loop start
    time_t now = time (0);
       strftime (vremya, 100, "%d%mY%y %H:%M:%S", localtime (&now));
lcd_print(&lcd0, vremya, 16,0);
//time loop end

if (current >= (last + TIME_TO_WAIT * CLOCKS_PER_SEC) && text == 0) {
int fd;
    struct ifreq ifr;    
fd = socket(AF_INET, SOCK_DGRAM, 0);
    ifr.ifr_addr.sa_family = AF_INET;
    memcpy(ifr.ifr_name, "wlan0", IFNAMSIZ-1);
    ioctl(fd, SIOCGIFADDR, &ifr);
    close(fd);
    strcpy(ip_address_wlan,inet_ntoa(((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr));
sprintf(txt, "w%s", ip_address_wlan);
lcd_print(&lcd0,"                ", 16,1);
lcd_print(&lcd0,txt, strlen(txt),1);

last = current;
text = 1;
}     

else if (current >= (last + TIME_TO_WAIT * CLOCKS_PER_SEC) && text == 1) {
int fd;
    struct ifreq ifr;    
fd = socket(AF_INET, SOCK_DGRAM, 0);
    ifr.ifr_addr.sa_family = AF_INET;
    memcpy(ifr.ifr_name, "eth0", IFNAMSIZ-1);
    ioctl(fd, SIOCGIFADDR, &ifr);
    close(fd);
   strcpy(ip_address_eth, inet_ntoa(((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr));
sprintf(txt, "e%s", ip_address_eth);
lcd_print(&lcd0,"                ", 16,1);
lcd_print(&lcd0,txt, strlen(txt),1);
last = current;
text = 2;
}
else if (current >= (last + TIME_TO_WAIT * CLOCKS_PER_SEC) && text == 2) {

FILE *in_file;
  in_file = fopen("/sys/class/thermal/thermal_zone0/temp", "r");
if (in_file == NULL)
    {
        printf("Can't open file for reading.\n");
    }
    else
    {
        fscanf(in_file, "%d", &number1);
        fclose(in_file);
    }

char cmd[]="df -BM /  | tail -1 | awk '{print $4}'" ;
FILE* apipe = popen(cmd, "r");
 
if (apipe == NULL)
    {
        printf("Can't open file for reading.\n");
    }
    else
    {
        fscanf(apipe, "%d", &number2);
pclose(apipe);
    
}





sprintf(txt, "CPU:%dC S:%dMb", number1,number2);
lcd_print(&lcd0,"                ", 16,1);
lcd_print(&lcd0,txt, strlen(txt),1);
last = current;
text = 0;
}


}
close_i2c(i2c_dev);

return 0;
}

собираем
make server_monitoring
скомпелированный файл я скопировал в root директорию
cp /root/liblcm1602/server_monitoring /root/
идем в крон
crontab -e
добавляем эту строчку (старую убираем)
@reboot /root/server_monitoringt & > /dev/null
перезагружаемся

Помогал кодить google, как всегда. Как в C взять IP взял от сюда https://www.includehelp.com/c-programs/get-ip-address-in-linux.aspx до конца не понял как это работает и как настроить под себя, и вообще есть ли там что настраивать… работает так если все интерфейсы не подключены то будет показывает хрен знает какой ip на обоих, если один подключен то показывает это ip на всех и подхватывает его на лету, если два подключены оба показывает, если один отключить все равно показывает то что было… мне норм так!

Для строки с температурой и свободным местом, тоже хотел средствами С вычислять свободное место, но это оказалось слишком сложно, но оказывает можно засунуть в С обычные консольные команды — так и сделал.

Программа есть 60% CPU, видимо это плата за время за секунды за обновление экрана 10 раз в секнду, это уже приторможенное.

Бекап и замена диска
Меняю «плохую» SD карту на «хорошую» SanDisck Ultra 16gb class 10
перед этим замеряю скорость плохой
mkdir /root/test
cd /root/test
sysbench --test=fileio --file-total-size=100Mb prepare
sysbench --test=fileio --file-total-size=100Mb --file-test-mode=rndrw --init-rng=on --max-time=300 --max-requests=0 run > test.io

результат 186.63Kb/sec

Вынимаем SD карту вставляем её в свой компьюер на Ubuntu
Смотрем где она появилась, у меня в sde
и делаем
sudo dd if=/dev/sde of=./armbian_server_itcooky.img bs=1M

По окончании вынимаем старую вставлем новую карту и делаем
sudo umount /dev/sde1
sudo mkfs.vfat -I /dev/sde
sudo dd if=./armbian_server_itcooky.img of=/dev/sde bs=1M

Ээээммм у Armbian сейчас какая-то проблема с ресайзом карт так что со старым размером пока
Прогнал тот же тест, результат 1.202Mb/sec

UPD: Переписываем контролер вентилятора и информационный экран под Node.js
Все таки, вместе все С программы отжирают 80% CPU че-то много. Я тут заметил что монстр Node-Red отжирает вообще ничего, а что если написать все что на С в Node.js. Сразу скажу что да, написал, отжирать стал в 4 раз меньше чем С, но в 6 раз больше чем Node-Red!

Сначала ставим nodejs
идем в это важно
cd /root
далее
curl -sL https://deb.nodesource.com/setup_9.x | bash -
apt-get install -y nodejs

Ставим модул который даст запускать консольные команды в node.js
npm i node-cmd
Надо еще скомпилировать программу для экрана чтобы с консоли посылать текст на экран.
liblcm1602 обновляет с очисткой экрана это приводит к морганию так что беру другу библиотек она умеет обновлять построчно, выключать экран вообще, но ставит курсор в конце — его буду убирать добавлением лишних пробелов в строку(тупо). Так же важно обновлять экран из одного места программы, а то из-за асинхронности(я так решил) если запускать команду на обновление из разных мест программы они друг друга сбивают.

cd /root
git clone https://github.com/bitbank2/LCD1602.git
cd LCD1602
make
cp main.c mainOLD.c

вставляем в main.c
vi main.c
вот это

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <lcd1602.h>

int main(int argc, char **argv)
{
int rc;
        rc = lcd1602Init(0, 0x3f);
        if (rc)
        {
                printf("Initialization failed; aborting...\n");
                return 0;
        }
int s = *(argv[2]) -'0' -1;

lcd1602SetCursor(0,s);
        lcd1602WriteString(argv[1]);
//      lcd1602Control(1,0,1); // backlight on, underline off, blink block on
//      getchar();
//      lcd1602Shutdown();
return 0;
} /* main() */

Под свой экран менял строку rc = lcd1602Init(0, 0x3f); — 0 потому что экран висит на 0 i2c
а 0x3f это его адрес

собираем
make -f make_demo
и копируем в root
cp ./demo /root/demo

Создаем
vi /root/server_monitoring.js
вставляем

var cmd=require('node-cmd');
var sec = 61;
var min = 0;
var hour = 0;
var day = 0;
var month =0;
var year = 0;
text2 = "Booting...      ";
textn = 1;
cputemp = 0;
cputempwar = 45;
cputemptime = 60;  //in 0,5 sec
cputimer = 0;

function coolercpu () {
cmd.get(
        '/bin/cat /sys/class/thermal/thermal_zone0/temp',
        function(err, data, stderr)
{ cputemp = data.replace(/[^0-9]+/, '');

if (cputemp > cputempwar) {
cmd.get(
        'gpio read 1',
        function(err, data, stderr){
  if (data == 1) {
textn = 4;
cmd.get(
`
         gpio mode 0 out
         gpio write 0 on
         gpio mode 1 out
         gpio write 1 off
`,
        function(err, data, stderr){
 cputimer= cputemptime;

});
}
;});

} else {
 cmd.get(
        'gpio read 1',
        function(err, data, stderr){
if (data == 0 && cputimer > 0) {
cputimer = cputimer - 1;
};

  if (data == 0 && cputimer <= 0) {
 cmd.get(
`
         gpio write 0 off
         gpio write 1 on 
`,
        function(err, data, stderr){
textn = 5; 
});
};});
};
});
};
setInterval(coolercpu, 500);


function ledon(){ 
    cmd.get(
        'gpio read 2',
        function(err, data, stderr){
  if (data == 0) {
            
cmd.get(
        `gpio mode 2 out
         gpio write 2 on`,
        function(err, data, stderr){
});};
});
};
setInterval(ledon, 1000);

//start screen
function screen(){
var date = new Date();
  if ( date.getSeconds() != sec ) {
sec = date.getSeconds();
sec = (date.getSeconds() <  10 ? "0" : "") + date.getSeconds();
hour = (date.getHours() < 10 ? "0" : "") + date.getHours();
min = (date.getMinutes() < 10 ? "0" : "") + date.getMinutes();
year = date.getFullYear().toString().substr(-2); 
year = (year < 10 ? "0" : "") + year;    
month = date.getMonth() + 1;
month = (month < 10 ? "0" : "") + month;
day = (date.getDate() < 10 ? "0" : "") + date.getDate();


//cmd.run('/root/example2 "' + day + month + 'Y' + year + ' ' + hour + ':' + min + ':' + sec + '" "' + text2 + '"');
cmd.run('/root/demo "' + day + month + 'Y' + year + ' ' + hour + ':' + min + ':' + sec + '" 1&&/root/demo "'+ text2 + '" 2');

};
};
setInterval(screen, 10);

function textforscreen(){
switch (textn) {
case 1:
textn = 2;
cmd.get(
'/sbin/ip addr list wlan0 |/bin/grep "inet " |/usr/bin/cut -d\' \' -f6|/usr/bin/cut -d/ -f1',
        function(err, data, stderr){
data = data.substring(0, data.length - 1); 
if(data !== null && data !== '') {
text2 = "w" + data + "          ";
} else {
textforscreen();
};
});
break;
case 2:
textn = 3;
cmd.get(
'/sbin/ip addr list eth0 |/bin/grep "inet " |/usr/bin/cut -d\' \' -f6|/usr/bin/cut -d/ -f1',
        function(err, data, stderr){
data = data.substring(0, data.length - 1);
if(data !== null && data !== '') {
text2 = "e" + data + "          ";
} else {
textforscreen();
};
});
break;
case 3:
cmd.get(
'/bin/df -BM /  | /usr/bin/tail -1 | /usr/bin/awk \'{print $4}\'',
        function(err, data, stderr){
data = data.substring(0, data.length - 1);
text2 = "CPU:" + cputemp + "C S:" + data + "     ";
textn = 1;
});
break;
case 4:
text2 = "Cooler on  " + cputemp + "C       ";
textn = 3;
break;
case 5:
text2 = "Cooler off " + cputemp + "C       ";
textn = 3;
break;
}
};

setInterval(textforscreen, 3000);

Что бы запускался сам делаем
Для этого ставим pm2
npm i pm2 -g
Запускаем
pm2 start server_monitoring.js --name Server-mon
И еще команда, теперь бот будет запускаться после перезагрузки
pm2 startup
pm2 save

Перегружаемся pm2 startup что-то не сразу срабатывает

И так работает, из плюсов снизилась загрузка процессора, из минуса заметно моргает при смене текста устранено!

Что делает программа и функции
coolercpu — берет температуру процессора консольной командой, присваивает её переменной(она потом понадобиться). Если температура больше 45 то если пин вентилятора выключен(он перевернут из-за транзистора определяется как включен) то включает, и переменной определяющий какой текст на экране показывать дает значение с 5 — там тескт Вентиялтор включен и значение температуры. А так же значение температуры очищается от мусора, что приципляется когда берешь из файла, JS пофиг на это(Си бы такую истерику закатил бы) if и так работает, но при выводе на экран какойто артефакт появляется в конце — поэтому убирают все не цифры из переменной cputemp.

ledon — это не интересно

screen — это про экран, экран обновляется раз в секунду, берет время если секунды поменялись то обновляет. Показывает дату и один из 5 текстов что надо. Время проверяется если цифры меньше 10 до добавляется нолик спереди.

textforscreen — это тексты, запускается раз в 3 секунды. 1 и 2 берут IP, к нему тоже какой то мусор цыпляется в конце, поэтому последний символ убирается. Если IP нет, то переменной определяется следующе значение и функция запускает сама себя… это же GO TO команды которой мне дико не хватает с BASIC. Благодаря чему все тексты висят на экране по 3 секунды, если бы не то из-за пустого IP предыдуший текст висел бы дольше.

Добавляем кнопки в Оrange Pi Pc на Node.Js и WiringOP
Кажется просто, и это просто, но нет. Arduino(на микроконтроллере, на ПЛИС тем более) справился бы с этим с закрытыми глазами, а для любого CPU это неподъемная задача. Опрашивать пин каждые 10 миллисекунд грузит процессор OrangePi на все 100%, поэтому буду опрашивать каждые 150… Много ядерный процессор однако не старается нагрузку кинуть на все 4 ядра, Node.js тоже не умеет запускать приложения на конкретном ядре, только в pm2 есть режим cluster но он запускает одно и тоже приложение на всех ядрах — полезно для web и всё.

Кнопка на 5 и 6 пину, у кнопок ест верх и низ — осторожно

Для Node.js есть модуль для работы с GPIO https://www.npmjs.com/package/orange-pi-gpio но если на него посмотреть он делает все тоже самое, посылает консольные команды WiringOP, это и без него можно сделать.

Вот программа просто для проверки кнопок и пальцев

const exec = require('child_process').exec;
n1 = 0;
p1 = 0;
n2 = 0;
p2 = 0;
once = 0;

if (once == 0)
{
cmd('gpio mode 5 in&&gpio mode 5 up');
cmd('gpio mode 6 in&&gpio mode 6 up');
console.log('Pins Ready');
once = 1;
};

function cmd(command) {
        return new Promise((resolve, reject)=> {
            exec(command, (error, stdout, stderr) => {
                if (error) {
                    reject(error);
                }
                resolve(stdout);
            });
});};


function button () {
cmd('gpio read 5')
.then((state)=>{
if (state == 0) {
n1 = n1 + 1;
console.log('5 count: %d', n1);
}
else { 
if (n1 > 0 ) {
p1 = p1 + 1;
if (p1 >= 3 ) {
n1 = 0;
p1 = 0;
console.log('5 none: %d', n1);
};};};});
cmd(`gpio read 6`)
.then((state)=>{
if (state == 0) {
n2 = n2 + 1;
console.log('6 count: %d', n2);
}
else {
if (n2 > 0 ) {
p2 = p2 + 1;
if (p2 >= 3 ) {
n2 = 0;
p2 = 0;
console.log('6 none: %d', n2);
};};};});
setTimeout(button, 150);
};
setTimeout(button, 10);

записываем в файл
vi button.js
и запускаем
node ./button.js

Пальцы примерно у всех одинаковые и тут можно посмотреть как они работают с кнопками в реальных условиях. Увеличивать частоту опроса кнопок можно в строке, уменьшая число
setTimeout(button, 150);
Лучше setTimeout и так способом, вместо setInterval, так каждый следующее снятие значения с кнопок начнется после окончания предыдущего. Улавливает нажатие печатает цифру, улавливает отжатие печатает ноль и то и другое должно быть четким, зависит от скорости опроса.

Cmd просто создает процесс и туда ставит консольную комманду. Результат исполнения этой функции не ответ или число а статус — статус «я тебе обещаю ответить»(Promise) поэтому чтобы востребовать ответ следующая строка then. Не совсем понял как это работает, но это все типа асинхронность, но очень синхронная — вот если бы у каждой такой функции был свой идентификатор и ожидать ответ можно было по нему… а то с виду просто синхронно все, но оно асинхронно.

Для экрана опять переписываю вывод на экран
вставляем в main.c

cd /root/LCD1602
vi main.c

вот это

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <lcd1602.h>

int main(int argc, char **argv)
{
int rc;
        rc = lcd1602Init(0, 0x3f);
        if (rc)
        {
                printf("Initialization failed; aborting...\n");
                return 0;
        }
int b = *(argv[3]) -'0';
if (b != 0) {
lcd1602SetCursor(0,0);
        lcd1602WriteString(argv[1]);
lcd1602SetCursor(0,1);
        lcd1602WriteString(argv[2]);
} else {
lcd1602Shutdown();
};
return 0;
} /* main() */

собираем
make -f make_demo
и копируем в root
cp ./demo /root/demo

И вот сам новый код с кнопками. Нажатие на левую кнопку откл/вкл экран. Нажатие правой кнопки откл/вкл ротацию нижней строки. При включении вентилятора отключается экран, все еще кажется что не хватает им вместе электричества иногда даже зависает. Добавлена еще одна инфо строка. Грузит процессор на 35%-50% меньше что удалось…

var sec = 61;
var min = 0;
var hour = 0;
var day = 0;
var month =0;
var year = 0;
text2 = "Booting...      ";
textn = 1;
cputemp = 0;
cputempwar = 45;
cputemptime = 60;  //in 0,5 sec
cputimer = 0;
backlight = 1;
screenloop = 1;
screentimer = 5;
t = 0;
n1 = 0;
p1 = 0;
n2 = 0;
p2 = 0;
const exec = require('child_process').exec;
once = 0;


function cmd(command) {
        return new Promise((resolve, reject)=> {
            exec(command, (error, stdout, stderr) => {
                if (error) {
                    reject(error);
                }
                resolve(stdout);
            });
});};


if (once == 0)
{
cmd('gpio mode 5 in&&gpio mode 5 up');
cmd('gpio mode 6 in&&gpio mode 6 up');
cmd('gpio mode 0 out&&gpio mode 1 out&&gpio mode 2 out');
once = 1;
};


function button () {

cmd('gpio read 5')
.then((state)=>{
if (state == 0) {
n1 = n1 + 1;
}
else {
if (n1 > 0 ) {
p1 = p1 + 1;
if (p1 >= 3 ) {
if (n1 >= 1&& n1 <= 10){
if (screenloop == 1)
{
screenloop = 0;
t = 0;
}
else
{
screenloop = 1;
t = screentimer;
};
};

n1 = 0;
p1 = 0;
};};};});

cmd('gpio read 6')Ощеф
.then((state)=>{
if (state == 0) {
n2 = n2 + 1;
}
else {
if (n2 > 0 ) {
p2 = p2 + 1;
if (p2 >= 3 ) {

if (n2 >= 1&& n2 <= 10){
if (backlight == 1)
{backlight = 0;}
else
{backlight = 1;};

};

n2 = 0;
p2 = 0;
};};};});

setTimeout(button, 150);

};
setTimeout(button, 1000);





function coolercpu () {
cmd ('/bin/cat /sys/class/thermal/thermal_zone0/temp')
     .then((state)=>{ 
cputemp = state.replace(/[^0-9]+/, '');

if (cputemp > cputempwar) {
cmd ('gpio read 1')
    .then((state)=>{ 
  if (state == 1) {
textn = 100;
cmd('gpio write 0 on&&gpio write 1 off');
cputimer= cputemptime;
backlight = 0;

};
});

} else {
cmd ('gpio read 1')
.then((state)=>{
if (state == 0 && cputimer > 0) {
cputimer = cputimer - 1;
};

  if (state == 0 && cputimer <= 0) {
cmd('gpio write 0 off&&gpio write 1 on');        
backlight = 1;
textn = 101; 
};});};

});
setTimeout(coolercpu, 500);
};

setTimeout(coolercpu, 500);


function ledon(){ 
  
cmd('gpio read 2')
.then((state)=>{

if (state == 0) {
cmd('gpio write 2 on');            

};});};
setInterval(ledon, 1300);

//start screen
function screen(){

if (backlight !=3) {
var date = new Date();
  if ( date.getSeconds() != sec ) {
sec = date.getSeconds();
sec = (date.getSeconds() <  10 ? "0" : "") + date.getSeconds();
hour = (date.getHours() < 10 ? "0" : "") + date.getHours();
min = (date.getMinutes() < 10 ? "0" : "") + date.getMinutes();
year = date.getFullYear().toString().substr(-2); 
year = (year < 10 ? "0" : "") + year;    
month = date.getMonth() + 1;
month = (month < 10 ? "0" : "") + month;
day = (date.getDate() < 10 ? "0" : "") + date.getDate();


//cmd.run('/root/example2 "' + day + month + 'Y' + year + ' ' + hour + ':' + min + ':' + sec + '" "' + text2 + '"');
cmd('/root/demo "' + day + month + 'Y' + year + ' ' + hour + ':' + min + ':' + sec + '" "'+ text2 + '" ' + backlight);
if (backlight == 0) { 
backlight = 3;
};

};
};
setTimeout(screen, 30);
};
setTimeout(screen, 50);

function textforscreen(){
switch (textn) {
case 1:
cmd('/sbin/ip addr list wlan0 |/bin/grep "inet " |/usr/bin/cut -d\' \' -f6|/usr/bin/cut -d/ -f1')
.then((state)=>{
status = state.substring(0, state.length - 1);
//status = state;
if(status !== null && status !== '') {
text2 = "w" + status + "          ";
if (screenloop == 1){
if (t >= screentimer)
{textn = 2;
t = 0;
} else {
t = t + 1;
};
};
} else {
textn = 2;
textforscreen();
};
});
break;
case 2:
cmd('/sbin/ip addr list eth0 |/bin/grep "inet " |/usr/bin/cut -d\' \' -f6|/usr/bin/cut -d/ -f1')
.then((state)=>{
status = state.substring(0, state.length - 1);
if(status !== null && status !== '') {
text2 = "e" + status + "          ";
if (screenloop == 1){
if (t >= screentimer)
{textn = 3;
t = 0;
} else {
t = t + 1;
};
};

} else {
textn = 3;
textforscreen();
};
});
break;
case 3:
cmd('/bin/df -BM /  | /usr/bin/tail -1 | /usr/bin/awk \'{print $4}\'')
.then((state)=>{
//status = state.substring(0, state.length - 1);
status = state;
text2 = "CPU:" + cputemp + "C S:" + status + "Mb     ";
});
if (screenloop == 1){
if (t >= screentimer)
{textn = 4;
t = 0;
} else {
t = t + 1;
};
};
break;

case 4:
cmd('/bin/cat /proc/loadavg | /usr/bin/awk \'{print $1,$2,$3}\'')
.then((state)=>{
//status = state.substring(0, state.length - 1);
status = state;
text2 = "L:" + status + "     ";
if (screenloop == 1){
if (t >= screentimer)
{textn = 1;
t = 0; 
} else {
t = t + 1;
};
};


});
break;
case 100:
text2 = "Cooler on  " + cputemp + "C       ";
textn = 3;
break;
case 101:
text2 = "Cooler off " + cputemp + "C       ";

textn = 3;

break;
}
};

setInterval(textforscreen, 750);