Данная разработка является продолжением модификации метеостанции, описанной в статье «Метеостанция на основе Ардуино». Данная модификация позволяет мониторить показания метеостанции через браузер компьютера или мобильного устройства подключенных к домашней локальной сети.
Сразу оговорюсь, как я говорил изначально я не являюсь продвинутым программистом (профиль моей работы немного иной ;-)), поэтому конструктивная критика и предложения приветствуются.
Основой данной модификации является сервер на основе микрокомпьютера Raspberry PI4, находящийся в домашней локальной сети.
Ниже представлена блок-схема метеостанции, где указанно какое ПО и языки программирования использовались для разработки.
//Приёмник. //I2C Slave 8. //Arduino Uno, датчик температуры терморезистор TTC 103 (аналог), //ЖК дисплей ST7920 , блок LoRa XL1278-SMT. #include <Wire.h> // подключаем библиотеку для работы с шиной I2C #include <SPI.h> //Библиотека для подключение внешних устройств #include <RH_RF95.h> // Библиотека для работы с блоком LoRa. #include <math.h> //Библиотека для простых мат вычислений // Подпрограмма для вычисления температуры аналогового датчика. double Tehrmister(int RawADC){ double Temp; Temp=log(((10240000/RawADC)-10000)); Temp=1/(0.001129148+(0.000234125*Temp)+(0.0000000876741*Temp*Temp*Temp)); Temp=Temp-273.15; //Кельвин в Цельсий return Temp; } #include <U8glib.h> // Подключаем библиотеку U8glib U8GLIB_ST7920_128X64_1X u8g(5); //(13, 11, 5, U8G_PIN_NONE); // настройка пинов u8g( SCK,MOSI,CS[, RST ]) RH_RF95 rf95(7, 2); // Подключаем блк LoRa 7 - выбор чипа NSS (пин на ардуино) // 2 - прерывание (пин на ардуино) // Создаём массив данных struct SEND_DATA{ int Temperature; double Pozicion; /////////////////////////////////////////////////// int Skorost; // int Skorost_max; boolean Perimetr; int Vlazhnost; int Press; int Alt; }; SEND_DATA mydata; // Переменные int Pozicion1; String Azm = ("С"); // Отоброжаемое состояние направления ветра - Азимут int TemperDom; // Отоброжение температуры в доме String Alarm; // Отоброжаемое состояние периметра охраны float switchPin = 3; // пин Вкл. охраны периметра String Alarm_sw; // Отоброжаемое сосояние включения сигнализации int temperature1; boolean znak; // Переменные для хранения состояния кнопки int lastButton = 0; int currentButton = 0; // функция для подавления дребезга int debounse(int last) { int current = analogRead(switchPin); if(last != current) { delay(5); current = analogRead(switchPin); } return current; } int piezoPin = 8; //Пищалка boolean a1 = 0; // состояние кнопки включения пищалки uint32_t myTimer1; int Sk = 0; int sk1 = 0; int sk2 = 1; int Skorost_sr; void setup() { Wire.begin(8);// инициируем подключение к шине I2C в качестве ведомого (slave) устройства, с указанием своего адреса на шине. Wire.onRequest(requestEvent); // Регистрация события. После поступления запроса от Master запускаеться функция requestEvent pinMode(switchPin, INPUT); pinMode(10, OUTPUT); Serial.begin(9600); //Открывает последовательный порт, на скорости 9600 бит/с while (!Serial); if (!rf95.init()) { //В случае не выполнения условия написать LoRa failed! u8g.firstPage(); // Всё что выводится на дисплей указывается в цикле: // u8g.firstPage(); do{ ... команды ... }while(u8g.nextPage()); do{ u8g.setFont(u8g_font_7x13); // Выбираем шрифт u8g_font_6x10 u8g.setColorIndex(1); // Выбираем цвет белый u8g.setPrintPos( 24, 35);u8g.print("LoRa failed!"); // Выводим текст "LoRa failed!" // в позиции 24х35 }while(u8g.nextPage()); delay(15000); }else{ u8g.firstPage(); // Всё что выводится на дисплей указывается в цикле: // u8g.firstPage(); do{ ... команды ... }while(u8g.nextPage()); do{ u8g.setFont(u8g_font_7x13); // Выбираем шрифт u8g_font_6x10 u8g.setColorIndex(1); // Выбираем цвет белый u8g.setPrintPos( 44, 35);u8g.print("LoRa!"); // Выводим текст "LoRa!" // в позиции 44х35 }while(u8g.nextPage()); delay(6500); }; rf95.setTxPower(1); rf95.setFrequency(433.00); rf95.setModemConfig(RH_RF95::Bw125Cr48Sf4096 ); } void loop() { currentButton = debounse(lastButton); if(lastButton <= 512 && currentButton >= 512) lastButton = currentButton; TemperDom = double(Tehrmister(analogRead(0))); if (rf95.available()) { uint8_t len = sizeof(mydata); if (rf95.recv((uint8_t*)&mydata, &len)) if (0 <= mydata.Pozicion && 11.25 > mydata.Pozicion) Azm = ("C"); if (11.25 <= mydata.Pozicion && 33.75 > mydata.Pozicion) Azm = ("CCB"); if (33.75 <= mydata.Pozicion && 56.25 > mydata.Pozicion) Azm = ("CB"); if (56.25 <= mydata.Pozicion && 78.75 > mydata.Pozicion) Azm = ("BCB"); if (78.75 <= mydata.Pozicion && 101.25 > mydata.Pozicion) Azm = ("B"); if (101.25 <= mydata.Pozicion && 123.75 > mydata.Pozicion) Azm = ("ВЮВ"); // ВЮВ if (123.75 <= mydata.Pozicion && 146.25 > mydata.Pozicion) Azm = ("ЮВ"); // ЮВ if (146.25 <= mydata.Pozicion && 168.75 > mydata.Pozicion) Azm = ("ЮЮB"); // ЮЮВ if (168.75 <= mydata.Pozicion && 191.25 > mydata.Pozicion) Azm = ("Ю"); // Ю if (191.25 <= mydata.Pozicion && 213.75 > mydata.Pozicion) Azm = ("ЮЮЗ"); // ЮЮЗ if (213.75 <= mydata.Pozicion && 236.25 > mydata.Pozicion) Azm = ("ЮЗ"); // ЮЗ if (236.25 <= mydata.Pozicion && 258.75 > mydata.Pozicion) Azm = ("ЗЮЗ"); // ЗЮЗ if (258.75 <= mydata.Pozicion && 281.25 > mydata.Pozicion) Azm = ("З"); // З if (281.25 <= mydata.Pozicion && 303.75 > mydata.Pozicion) Azm = ("ЗСЗ"); // 3C3 if (303.75 <= mydata.Pozicion && 326.25 > mydata.Pozicion) Azm = ("СЗ"); // СЗ if (326.25 <= mydata.Pozicion && 348.75 > mydata.Pozicion) Azm = ("ССЗ"); //CC3 if (348.75 <= mydata.Pozicion && 360.00 > mydata.Pozicion) Azm = ("C"); Skorost_sr = (mydata.Skorost); //Serial.print ("Температура = "); // Serial.println (mydata.Temperature); if (!mydata.Perimetr==0) Alarm = ("Авария"); else Alarm = ("Норма"); if(analogRead(switchPin) < 512){ // Кнопка нажата Alarm_sw = ("ВКЛ");a1 = 1;} else {Alarm_sw = ("ВЫКЛ");a1 = 0;} if(a1==1 && mydata.Perimetr==1) // Если нажата кнопка // и периметр Авария то { // Пищалка tone (piezoPin,1000,600); delay(600); tone (piezoPin,800,600); delay(4000); } // Выводим данные на экран. u8g.firstPage(); // Всё что выводится на дисплей указывается в цикле: // u8g.firstPage(); do{ ... команды ... }while(u8g.nextPage()); do{ u8g.setFont(rus6x12); // Выбираем шрифт u8g.setColorIndex(1); // Выбираем белый цвет // Отображение температуры u8g.drawStr( 0, 9,"Температура ул.= "); u8g.setPrintPos(98, 9); u8g.print(mydata.Temperature); // Вывод значения температуры на улице u8g.drawPixel(116,1); u8g.drawPixel(117,1); u8g.drawPixel(118,1); u8g.drawPixel(116,2); u8g.drawPixel(118,2); u8g.drawPixel(116,3); u8g.drawPixel(117,3); u8g.drawPixel(118,3); u8g.drawStr( 120, 9,"С"); // Отображение влажности воздуха u8g.drawStr( 0, 20,"Влажность = "); u8g.setPrintPos(72, 20); u8g.print(mydata.Vlazhnost); // Вывод значения влажности на улице u8g.drawStr( 90, 20,"%"); // Отображение направления ветра - Азимута u8g.drawStr( 0, 31,"Аз.= "); u8g.setPrintPos(30, 31); u8g.print(Azm); // Отображение скорости ветра u8g.drawStr( 54, 31,"Ск.в.="); u8g.setPrintPos(96, 31); u8g.print(Skorost_sr); // Вывод значения скорости ветра u8g.drawStr( 110, 31,"м/с"); // Отображение давления u8g.drawStr( 0, 42,"Давлен. ="); u8g.setPrintPos(58, 42); u8g.print(mydata.Press); // Вывод значения давления на улице u8g.drawStr( 80, 42,"мм.рт.ст"); // Отображение температуры в доме u8g.drawStr( 0, 53,"Температура дом ="); u8g.setPrintPos(105, 53); u8g.print(TemperDom); // Вывод значения температуры в доме u8g.drawPixel(118,44); u8g.drawPixel(119,44); u8g.drawPixel(120,44); u8g.drawPixel(118,45); u8g.drawPixel(120,45); u8g.drawPixel(118,46); u8g.drawPixel(119,46); u8g.drawPixel(120,46); u8g.drawStr( 122, 53,"С"); // Отоброжение состояния периметра u8g.drawStr( 0, 63,"Перим.="); u8g.setPrintPos(48, 63); u8g.print(Alarm); u8g.setPrintPos(100, 63); u8g.print(Alarm_sw); }while(u8g.nextPage()); delay(100); } } // функция, которая выполняется всякий раз, когда данные запрашиваются мастером // эта функция регистрируется как событие, см. setup () void requestEvent() { Pozicion1 = static_cast<int>(mydata.Pozicion); // Преобразование данных в целочисленное if (mydata.Temperature < 0){ temperature1 = mydata.Temperature * -1; znak = false; Wire.write (highByte (temperature1)); Wire.write (lowByte (temperature1)); } else { znak = true; Wire.write (highByte (mydata.Temperature)); Wire.write (lowByte (mydata.Temperature));} Wire.write (znak); Wire.write (highByte (Pozicion1)); Wire.write (lowByte (Pozicion1)); Wire.write (highByte (mydata.Skorost)); Wire.write (lowByte (mydata.Skorost)); Wire.write(mydata.Perimetr); Wire.write (highByte (mydata.Vlazhnost)); Wire.write (lowByte (mydata.Vlazhnost)); Wire.write (highByte (mydata.Press)); Wire.write (lowByte (mydata.Press)); Wire.write (highByte (mydata.Alt)); Wire.write (lowByte (mydata.Alt)); // как и ожидалось мастером }
Файлы проекта можно скачать с яндекс диска.
Напишите отзыв.
Блок метеостанции описан в статье «Метеостанция на основе Ардуино», поэтому здесь не рассматривается.
Блок приёмной части метеостанции (в схеме Монитор метеостанции) также описан в указанной выше статье, но в нем произведена небольшая модернизация, а именно подключение шины I2C. По данной шине передаются данные от метеостанции к серверу, по запросу от сервера. То есть блок «монитор метеостанции» является ведомым на шине I2C, соответственно мастером является сервер.
Ниже представлен измененный код на С++ для блока Arduino UNO («монитор метеостанции»).
В данном скетче добавлена дополнительная функция, которая включается после поступления запроса от ведущего устройства. В данном случае ведомое устройство имеет адрес 8.
По шине I2C в одном пакете можно передать 1 байт информации, однако метеостанция передаёт данные в словах (по 2 байта). Поэтому слова разделяются и побайтно (сначала старший байт, а затем младший байт) передаются по шине I2C.
Далее немного расскажем о совместимости шин I2C. Дело в том, что интерфейс I2C платы Ардуино имеет напряжение 5В, в то время как шина I2C Raspberry имеет напряжение 3.3В. Поэтому между портами ардуино и raspberry устанавливается блок преобразователя уровней, ниже на схеме указано подключение такого блока.
Вот и добрались до основного. Как уже упоминалось сервер собран на основе микрокомпьютера Rasberry PI 4. Операционная система, установленная на микрокомпьютере - Linux Ubuntu Server 20.04. Как установить систему рассказывалось в статье «Создаём файловый сервер на Raspberry Pi». В этой же статье рассказано как установить и настроить FTP и как подключиться к серверу через терминальную программу.
Далее нужно активировать порт I2C. Для этого нам понадобится утилита raspi-config. Вводим команду:
sudo raspi-config
Переходим в пункт Interfacing options / Advanced Options и включаем I2C.
Перезагружаем
sudo reboot
Дальнейшие действия проводим удалённо с помощью терминальной программы.
Далее устанавливаем пакет «i2c-tools» этот пакет позволяет подавать команды через терминал для работы c I2C.
sudo apt-get install -y i2c-tools
Для проверки работоспособности введём команду вывода доступных шин I2C.
sudo i2cdetect –l
Удостоверимся, что сервер видит ведомое устройство.
sudo i2cdetect -y 1
Итак, шина I2C задействована. Далее в проекте используется программа, написанная на Python. Данная программа взаимодействует с шиной I2C (передаёт запрос в ведомое устройство, а затем принимает данные), поле этого данные преобразуются и сохраняются в переменных, после этого создаётся/переписывается файл JavaScript с внесёнными данными принятыми по I2C.
Запускаем обновление репозиториев и пакетов операционной системы.
sudo apt update && apt upgrade –y
Посмотрим какой пакет Python3 установлен.
python3 -V
Но Python3 пока не может работать с I2C, поэтому поставим пакет «python3-smbus».
sudo apt-get -y install python3-smbus
Также в проекте на сервере присутствует HTML страничка, для того чтобы можно было с ней работать (загружать в браузер) нужно установить пакет «HTTP-сервер Apache».
sudo apt install apache2
Необходимо изменить настройки брандмауэра, чтобы разрешить доступ к веб-портам по умолчанию. Установка брандмауэра UFW описана в статье «Создаём файловый сервер на Raspberry Pi».
Выведем список профилей.
sudo ufw app list
Есть три профиля, доступных для Apache:
• Apache: этот профиль открывает только порт 80 (нормальный веб-трафик без шифрования)
• Apache Full: этот профиль открывает порт 80 (нормальный веб-трафик без шифрования) и порт 443 (трафик с шифрованием TLS/SSL)
• Apache Secure: этот профиль открывает только порт 443 (трафик с шифрованием TLS/SSL)
Рекомендуется применять самый ограничивающий профиль, который будет разрешать заданный трафик.
sudo ufw allow 'Apache'
Посмотрим список разрешенного трафика HTTP.
sudo ufw status
Проверим работу службы
sudo systemctl status apache2
Для проверки запустим страницу Apache. Для этого в браузере удалённого компьютера надо ввести IP адрес сервера (настройка IP адреса сервера рассматривалась в статье «Создаём файловый сервер на Raspberry Pi») или ввести команду.
hostname –I
Продолжим настройку Apache. Произведём настройку виртуального хоста
Создадим домен meteost (вы должны заменить это имя собственным доменным именем)
sudo mkdir /var/www/ html/meteost.ru/public_html
Назначим владение директорией
sudo chown -R $USER:$USER /var/www/ html/meteost.ru/public_html
Чтобы убедиться, что разрешения корректны, позволить владельцу читать, писать и запускать файлы, а группам и другим пользователям разрешить только читать и запускать файлы, вы можете ввести следующую команду
sudo chmod -R 755 /var/www/ html/meteost.ru/public_html
Далее создадим файл index.html по адресу: /var/www/html/meteost.ru/public_html
Файлы html, js и py удобнее переносить с удалённого компьютера с помощью программы FileZilla.
Можно также создать файл штатным способом.
sudo vi /var/www/html/meteost.ru/public_html/ index.html
sudo vi /var/www/html/meteost.ru/public_html/js/znach.js
Содержимое файлов рассмотрим немного позже. Пока их можно создать пустыми.
Далее создадим файл виртуального хоста. Создадим копию конфигурационного файла по умолчанию (не обязательно).
sudo cp /etc/apache2/sites-available/000-default.conf /etc/apache2/sites-available/000-default.conf.orig
Создадим новый конфигурационный файл
sudo vi /etc/apache2/sites-available/ meteost.ru.conf
Заполним файл следующим содержимым
<VirtualHost *:80>
ServerName meteost.com
ServerAlias www.meteost.com
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html/meteost.ru/public_html
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
Сохраним файл (wq).
Активируем файл.
sudo a2ensite meteost.ru.conf
Отключим сайт по умолчанию.
sudo a2dissite 000-default.conf
Проверим ошибки конфигурации.
sudo apache2ctl configtest
Перезапустим Apache.
sudo systemctl restart apache2
Теперь с удалённого компьютера локальной сети html страница доступна по IP адресу сервера.
Ну вот и закончили со встроенным ПО, далее рассмотрим файлы созданные собственно в рамках проекта.
Как уже говорилось выше файл (i2c_Data.py), написанный на Python, считывает информацию с I2C и преобразует их. Вот здесь нужно кое-что пояснить. Дело в том, что данные по шине I2C передаются по 1 байту. Поэтому первый принятый байт подвергаем побитному смещению влево на 8 бит, затем добавляем второй принятый байт посредствам логического ИЛИ. Полученное Слово сохраняем в переменную. Так последовательно принимаем все данные от метеостанции и сохраняем в переменных.
Далее файл (i2c_Data.py) создаёт файл znach.js (написанный на JavaScript) по адресу /var/www/html/meteost.ru/public_html/js, в созданный файл прописываются данные от метеостанции.
После этого шина I2C закрывается и включается пауза на 900 сек (15 мин).
Файл i2c_Data.py выполняется в бесконечном цикле.
Ниже приведено содержимое файла i2c_Data.py (его можно создать в блокноте с разрешением .py).
# Server. Master. # Получаем данные по I2C, преобразуем, расчитываем значения, записываем в файл. filename = '/var/www/html/meteost.ru/public_html/js/znach.js' # Путь к создаваемому файлу znach.js while True: from smbus import SMBus # Импортируем модуль SMBus для работы с I2C. bus = SMBus(1) # Для raspberry pi4 значение SMBus должно быть 1. from time import * # Импортируем модуль таймера. from datetime import * # Импортируем модуль системного времени i = 1 Skorost_1,Skorost_m = 0,0 while i < 50 : # Цикл для расчёта среднего и максимального значения скорости ветра. # Запрашиваем блок из устройства 8 в колличестве 14 значений данных. # Полученные данные сохраняем в переменные data(1-13). data1,data2,data2_1,data3,data4,data5,data6,data7,data8,data9,data10,data11,data12,data13 = bus.read_i2c_block_data(0x8, 0x0C, 14) # В переменных храняться данные из полуслова, в 2 переменных одно слово. # Поэтому значение 1 переменной (11111111) смещаем # на 8 бит влево (1111111100000000) и добавляем знакение 2 переменной # с помощью логического ИЛИ (1111111111111111). Temperature = data1 << 8 Temperature = Temperature | data2 znak = data2_1 Pozicion = data3 << 8 Pozicion = Pozicion | data4 Skorost = data5 << 8 Skorost = Skorost | data6 Perimetr = data7 Vlazhnost = data8 << 8 Vlazhnost = Vlazhnost | data9 Press = data10 << 8 Press = Press | data11 # Вычисляем среднюю и максимальную скорость ветра. Skorost_1 = Skorost_1 + Skorost if Skorost_m < Skorost : Skorost_m = Skorost sleep(1) # Пауза 1 сек. i+=1 Skorost_sr = int(Skorost_1 / 50) today = datetime.today() today1 = today.strftime('%X') today2 = today.strftime('%x') # Определение направления ветра по азимуту (0-360) if 0 <= Pozicion and 11 > Pozicion : Azm = 'С' if 11<= Pozicion and 34> Pozicion : Azm = 'ССВ' if 34 <= Pozicion and 56 > Pozicion : Azm = 'СВ' if 56 <= Pozicion and 79 > Pozicion : Azm = 'ВСВ' if 79 <= Pozicion and 101 > Pozicion : Azm = 'В' if 101 <= Pozicion and 124 > Pozicion : Azm = 'ВЮВ' if 124 <= Pozicion and 146 > Pozicion : Azm = 'ЮВ' if 146 <= Pozicion and 169 > Pozicion : Azm = 'ЮЮВ' if 169 <= Pozicion and 191 > Pozicion : Azm = 'Ю' if 191 <= Pozicion and 214 > Pozicion : Azm = 'ЮЮЗ' if 214 <= Pozicion and 236 > Pozicion : Azm = 'ЮЗ' if 236 <= Pozicion and 259 > Pozicion : Azm = 'ЗЮЗ' if 259 <= Pozicion and 281 > Pozicion : Azm = 'З' if 281 <= Pozicion and 304 > Pozicion : Azm = 'ЗСЗ' if 304 <= Pozicion and 326 > Pozicion : Azm = 'СЗ' if 326 <= Pozicion and 349 > Pozicion : Azm = 'ССЗ' if 349 <= Pozicion and 360 > Pozicion : Azm = 'С' # Выводим на экран параметры метеомтанции. #print('\n') #print('\tПараметры метеостанции.') #print('\n') #print('\n\tТемпература = ',Temperature,' C') #print('\n\tНаправление ветра = ',Azm) #print('\n\tСкорость ветра максимальная = ',Skorost_m,' м/с') #print('\n\tСкорость ветра средняя = ',Skorost_sr,' м/с') #print('\n\tСостояние охранного периметра (0-норма,1-авария) = ',Perimetr) #print('\n\tВлажность воздуха = ',Vlazhnost,' %') #print('\n\tДавление воздуха = ',Press,' мм.рт.ст') #print('\n\tДата и время измерения показаний метеостанции: ',today1,' ',today2) #print('\n') # Запись в файл. if znak == 0: # Определяем какой знак передан, если передано значение 0 Temperature = Temperature * -1 # знаит число отрицательное. Умножаем значение температуры на -1 Temperature1 = str(Temperature) Skorost_m1 = str(Skorost_m) Skorost_sr1 = str(Skorost_sr) Perimetr1 = str(Perimetr) Vlazhnost1 = str(Vlazhnost) Press1 = str(Press) with open(filename, 'w') as f: f.write("var temper = '" + Temperature1 + "';") f.write("\nvar napr = '" + Azm + "';") f.write("\nvar skor_m = " + Skorost_m1 + "; ") f.write("\nvar skor_s = " + Skorost_sr1 + "; ") f.write("\nvar vlash = '" + Vlazhnost1 + "'; ") f.write("\nvar davl = '" + Press1 + "'; ") f.write("\nvar vrm = '" + today1 + "'; ") f.write("\nvar vrm1 = '" + today2 + "'; ") f.write("\nvar elt = document.getElementById('temper'); ") f.write("\nelt.textContent = temper;") f.write("\nvar elv = document.getElementById('vlash');") f.write("\nelv.textContent = vlash;") f.write("\nvar eln = document.getElementById('napr'); ") f.write("\neln.textContent = napr;") f.write("\nvar els_s = document.getElementById('skor_s'); ") f.write("\nels_s.textContent = skor_s;") f.write("\nvar els_m = document.getElementById('skor_m'); ") f.write("\nels_m.textContent = skor_m;") f.write("\nvar eld = document.getElementById('davl'); ") f.write("\neld.textContent = davl; ") f.write("\nvar eld_t = document.getElementById('vrm'); ") f.write("\neld_t.textContent = vrm; ") f.write("\nvar eld_t1 = document.getElementById('vrm1'); ") f.write("\neld_t1.textContent = vrm1; ") bus.close() sleep(900)
Далее, помощью программы FileZilla (FTP менеджер), скопируем файл «i2c_Data.py» в сервер по адресу: /home/ubuntu.
Осталось запустить данный файл в фоновом режиме.
Для этого создадим сервис который будет запускать файл i2c_Data.py в фоновом режиме, а также запускаться после перезагрузки сервера. Используем для этого systemd.service.
sudo vi /etc/systemd/system/meteost.service
Содержимое файла заполним следующим:
[Unit]
Description=My meteost service
After=multi-user.target
[Service]
User=root
Group=root
Type=simple
Restart=always
ExecStart=/usr/bin/python3 /home/ubuntu/i2c_Data.py
[Install]
WantedBy=multi-user.target
ExecStart - в основном первый аргумент — это путь к python (в моем случае это python3), а второй аргумент — это путь к скрипту, который необходимо выполнить. Флаг перезапуска всегда установлен, чтобы перезапустить свою службу, если сервер будет перезапущен.
Перезагрузим демон.
sudo systemctl daemon-reload
Включим сервис, чтобы он не отключался при перезагрузке сервера.
sudo systemctl enable meteost.service
Запустим сервис.
sudo systemctl start meteost.service
Чтобы проверить статус.
sudo systemctl status meteost.service
Алгоритм файла i2c_Data.py выполняется примерно 1 мин, значит файл znach.js будет обновляться каждые 16 мин.
На этом с файлом i2c_Data.py закончим.
При вводе в браузере (внешнего компьютера) IP адреса сервера будет вызываться html страница с данными метеостанции.
Html код страницы (index.html) приведён ниже. Html код взаимодействует с JavaScript (созданный файлом i2c_Data.py), поэтому папка (js) c JavaScript (znach.js) располагается в одной папке с файлом index.html по адресу: /var/www/html/meteost.ru/public_html
Вот, пожалуй и всё, перезагружаем сервер и наслаждаемся данными метеостанции в браузере.
P.S. Кстати если кто желает наблюдать свою страничку через сеть интернет, а не только в локалке. То для этого нужно у своего интернет провайдера получить белый IP адрес (данная услуга бывает платной), затем настроить роутер и сделать проброс портов в роутере.
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Метеостанция</title> <style type="text/css"> body { width: 500px; font-family: Arial, Verdana, sans-serif; font-size: 90%; color: #666; background-color: #f8f8f8; margin: 10px auto auto auto;} h1 {margin: 0px auto 0px 130px;} table { border-spacing: 0px; margin: 0px auto 0px 18px;} th, td { padding: 5px 30px 5px 10px; border-spacing: 0px; font-size: 90%; margin: 0px;} th, td { text-align: left; background-color: #e0e9f0; border-top: 1px solid #cbd2d8; border-right: 1px solid #cbd2d8;} tr.head th { color: fff; background-color: #90b4d6; border-bottom: 2px solid #547ca0; border-right: 1px solid #749abe; border-top: 1px solid #90b4d6; text-align: center; text-shadow: -1px -1px 1px #666; letter-spacing: 0.15em;} td { text-shadow: 1px 1px 1px #fff; text-align: center;} tr.even td, tr.even th { background-color: #e8eff5;} tr.head th:first-child { border-top-left-radius: 5px;} tr.head th:last-child { border-top-right-radius: 5px;} fieldset { width: 310px; margin-top: 20px; border: 1px solid #d6d6d6; background-color: #ffffff; line-height: 1.6em;} legend { font-style: italic; color: #666666;} input[type="text"] { width: 120px; border: 1px solid #d6d6d6; padding: 2px; outline: none;} input[type="text"]:focus, input[type="text"]:hover { background-color: #d0e2f0; border: 1px solid #999;} input[type="submit"] { border: 1px solid #006633; background-color: #009966; color: #ffffff; border-radius: 5px; padding: 5px; margin-top: 10px;} input[type="submit"]:hover { border: 1px solid #006633; background-color: #00cc33; color: #ffffff; cursor: pointer;} .title { float: left; width: 160px; clear: left;} .submit { width: 310px; text-align: right;} td.lef {text-align: left;} </style> </head> <body> <h1>Метеостанция</h1> <p>В таблице отображаются параметры переданные с внешнего (уличного) блока метеостанции.</p> <table> <tr class="head"> <th>Параметр</th> <th>Значение</th> <th>Ед. изм.</th> </tr> <tr> <th>Температура на улице</th> <td id="temper"></td> <td class="lef">°С</td> </tr> <tr class="even"> <th>Влажность на улице</th> <td id="vlash"></td> <td class="lef">%</td> </tr> <tr> <th>Направление ветра</th> <td id="napr"></td> <td></td> </tr> <tr class="even"> <th>Скорость ветра (сред.)</th> <td id="skor_s"></td> <td class="lef">м/с</td> </tr> <tr class="even"> <th>Скорость ветра (макс.)</th> <td id="skor_m"></td> <td class="lef">м/с</td> </tr> <tr> <th>Атм. Давление</th> <td id="davl"></td> <td class="lef">мм.рт.ст</td> </tr> <tr> <th></th> <td></td> <td></td> </tr> <tr> <th>Дата и время измерения показаний метеостанции</th> <td id="vrm"></td> <td id="vrm1"></td> </tr> </table> <script src="js/znach.js"></script> </body> </html>