Changed system controller to accomodate more system fucntions

- Backup and restore of config folder is possible via system page
- Bumped version to 4.0.0.36
- will require cbpiui 0.0.17
This commit is contained in:
avollkopf 2021-09-21 16:20:44 +02:00
parent 2d55410d4a
commit e1e7de2924
7 changed files with 216 additions and 11 deletions

View file

@ -1 +1 @@
__version__ = "4.0.0.35" __version__ = "4.0.0.36"

View file

@ -332,7 +332,7 @@ def remove(name):
@click.command() @click.command()
@click.argument('name') @click.argument('name')
def create(name): def create(name):
'''Deactivate Plugin''' '''Create New Plugin'''
plugin_create(name) plugin_create(name)

View file

@ -1,8 +1,16 @@
import logging import logging
import os import os
import shutil
import psutil
import pathlib
import aiohttp import aiohttp
from voluptuous.schema_builder import message
from cbpi.api.dataclasses import NotificationAction, NotificationType
from cbpi.api.base import CBPiBase
from cbpi.api.config import ConfigType
from cbpi.api import *
import zipfile
import socket
class SystemController: class SystemController:
@ -17,7 +25,6 @@ class SystemController:
async def check_for_update(self, app): async def check_for_update(self, app):
pass pass
async def restart(self): async def restart(self):
logging.info("RESTART") logging.info("RESTART")
os.system('systemctl reboot') os.system('systemctl reboot')
@ -27,3 +34,135 @@ class SystemController:
logging.info("SHUTDOWN") logging.info("SHUTDOWN")
os.system('systemctl poweroff') os.system('systemctl poweroff')
pass pass
async def backupConfig(self):
output_filename = "cbpi4_config"
dir_name = pathlib.Path(os.path.join(".", 'config'))
shutil.make_archive(output_filename, 'zip', dir_name)
def allowed_file(self, filename, extension):
return '.' in filename and filename.rsplit('.', 1)[1] in set([extension])
def recursive_chown(self, path, owner, group):
for dirpath, dirnames, filenames in os.walk(path):
shutil.chown(dirpath, owner, group)
for filename in filenames:
shutil.chown(os.path.join(dirpath, filename), owner, group)
async def restoreConfig(self, data):
fileData = data['File']
filename = fileData.filename
backup_file = fileData.file
content_type = fileData.content_type
required_content=['dashboard/', 'recipes/', 'upload/', 'config.json', 'config.yaml']
if content_type == 'application/x-zip-compressed':
try:
content = backup_file.read()
if backup_file and self.allowed_file(filename, 'zip'):
self.path = os.path.join(".", "restored_config.zip")
f=open(self.path, "wb")
f.write(content)
f.close()
zip=zipfile.ZipFile(self.path)
zip_content_list = zip.namelist()
zip_content = True
for content in required_content:
try:
check = zip_content_list.index(content)
except:
zip_content = False
if zip_content == True:
output_path = pathlib.Path(os.path.join(".", 'config'))
shutil.rmtree(output_path, ignore_errors=True)
zip.extractall(output_path)
self.recursive_chown(output_path, "pi", "pi")
self.cbpi.notify("Success", "Config backup has been uploaded", NotificationType.SUCCESS)
else:
self.cbpi.notify("Error", "Wrong content type. Upload failed", NotificationType.ERROR)
except:
self.cbpi.notify("Error", "Config backup upload failed", NotificationType.ERROR)
pass
else:
self.cbpi.notify("Error", "Wrong content type. Upload failed", NotificationType.ERROR)
async def systeminfo(self):
logging.info("SYSTEMINFO")
system = ""
temp = 0
cpuload = 0
cpucount = 0
cpufreq = 0
totalmem = 0
availmem = 0
mempercent = 0
eth0IP = "N/A"
wlan0IP = "N/A"
TEMP_UNIT=self.cbpi.config.get("TEMP_UNIT", "C")
FAHRENHEIT = False if TEMP_UNIT == "C" else True
af_map = { socket.AF_INET: 'IPv4',
socket.AF_INET6: 'IPv6',
}
try:
if psutil.LINUX == True:
system = "Linux"
elif psutil.WINDOWS == True:
system = "Windows"
elif psutil.MACOS == True:
system = "MacOS"
cpuload = round(psutil.cpu_percent(interval=None),1)
cpucount = psutil.cpu_count(logical=False)
cpufreq = psutil.cpu_freq()
mem = psutil.virtual_memory()
availmem = round((int(mem.available) / (1024*1024)),1)
mempercent = round(float(mem.percent),1)
totalmem = round((int(mem.total) / (1024*1024)),1)
if system == "Linux":
try:
temps = psutil.sensors_temperatures(fahrenheit=FAHRENHEIT)
for name, entries in temps.items():
for entry in entries:
if name == "cpu_thermal":
temp = round(float(entry.current),1)
except:
pass
else:
temp = "N/A"
if system == "Linux":
try:
ethernet = psutil.net_if_addrs()
for nic, addrs in ethernet.items():
if nic == "eth0":
for addr in addrs:
if str(addr.family) == "AddressFamily.AF_INET":
if addr.address:
eth0IP = addr.address
if nic == "wlan0":
for addr in addrs:
if str(addr.family) == "AddressFamily.AF_INET":
if addr.address:
wlan0IP = addr.address
except:
pass
except:
pass
systeminfo = {'system': system,
'cpuload': cpuload,
'cpucount': cpucount,
'cpufreq': cpufreq.current,
'totalmem': totalmem,
'availmem': availmem,
'mempercent': mempercent,
'temp': temp,
'temp_unit': TEMP_UNIT,
'eth0': eth0IP,
'wlan0': wlan0IP}
return systeminfo

View file

@ -64,7 +64,7 @@ class NotificationStep(CBPiStep):
@parameters([Property.Number(label="Temp", configurable=True), @parameters([Property.Number(label="Temp", configurable=True),
Property.Sensor(label="Sensor"), Property.Sensor(label="Sensor"),
Property.Kettle(label="Kettle"), Property.Kettle(label="Kettle"),
Property.Text(label="Notification",configurable = True, description = "Text for notification hen Temp is reached"), Property.Text(label="Notification",configurable = True, description = "Text for notification when Temp is reached"),
Property.Select(label="AutoMode",options=["Yes","No"], description="Switch Kettlelogic automatically on and off -> Yes")]) Property.Select(label="AutoMode",options=["Yes","No"], description="Switch Kettlelogic automatically on and off -> Yes")])
class MashInStep(CBPiStep): class MashInStep(CBPiStep):
@ -72,11 +72,11 @@ class MashInStep(CBPiStep):
await self.next() await self.next()
async def on_timer_done(self,timer): async def on_timer_done(self,timer):
self.summary = "MashIn Temp reached. Please add Malt." self.summary = ""
await self.push_update() await self.push_update()
if self.AutoMode == True: if self.AutoMode == True:
await self.setAutoMode(False) await self.setAutoMode(False)
self.cbpi.notify(self.name, self.props.get("Notification","Target Temp reached. Please klick next to move on."), action=[NotificationAction("Next Step", self.NextStep)]) self.cbpi.notify(self.name, self.props.get("Notification","Target Temp reached. Please add malt and klick next to move on."), action=[NotificationAction("Next Step", self.NextStep)])
async def on_timer_update(self,timer, seconds): async def on_timer_update(self,timer, seconds):
await self.push_update() await self.push_update()

View file

@ -242,4 +242,4 @@ class LogHttpEndpoints:
print("JSON") print("JSON")
print(json.dumps(result, cls=ComplexEncoder)) print(json.dumps(result, cls=ComplexEncoder))
print("JSON----") print("JSON----")
return web.json_response(result, dumps=json_dumps) return web.json_response(result, dumps=json_dumps)

View file

@ -1,9 +1,13 @@
from aiohttp import web from aiohttp import web
from aiohttp import streamer
from cbpi.job.aiohttp import get_scheduler_from_app from cbpi.job.aiohttp import get_scheduler_from_app
import logging import logging
from cbpi.api import request_mapping from cbpi.api import request_mapping
from cbpi.utils import json_dumps from cbpi.utils import json_dumps
from cbpi import __version__ from cbpi import __version__
import pathlib
import os
import json
class SystemHttpEndpoints: class SystemHttpEndpoints:
@ -88,7 +92,7 @@ class SystemHttpEndpoints:
async def restart(self, request): async def restart(self, request):
""" """
--- ---
description: Restart System - Not implemented description: Restart System
tags: tags:
- System - System
responses: responses:
@ -102,7 +106,7 @@ class SystemHttpEndpoints:
async def shutdown(self, request): async def shutdown(self, request):
""" """
--- ---
description: Shutdown System - Not implemented description: Shutdown System
tags: tags:
- System - System
responses: responses:
@ -111,3 +115,64 @@ class SystemHttpEndpoints:
""" """
await self.controller.shutdown() await self.controller.shutdown()
return web.Response(text="SHUTDOWN") return web.Response(text="SHUTDOWN")
@request_mapping("/backup", method="GET", name="BackupConfig", auth_required=False)
async def backup(self, request):
"""
---
description: Zip and download Config Folder
tags:
- System
responses:
"200":
description: successful operation
content: # Response body
application/zip: # Media type
"""
await self.controller.backupConfig()
filename = "cbpi4_config.zip"
file_name = pathlib.Path(os.path.join(".", filename))
response = web.StreamResponse(
status=200,
reason='OK',
headers={'Content-Type': 'application/zip'},
)
await response.prepare(request)
with open(file_name, 'rb') as file:
for line in file.readlines():
await response.write(line)
await response.write_eof()
return response
@request_mapping("/restore", method="POST", name="RestoreConfig", auth_required=False)
async def restore(self, request):
"""
---
description: Restore Config
tags:
- System
responses:
"200":
description: successful operation
"""
logging.info("Restore Config")
data = await request.post()
logging.info("Data received")
await self.controller.restoreConfig(data)
return web.Response(status=200)
@request_mapping("/systeminfo", method="GET", name="SystemInfo", auth_required=False)
async def systeminfo(self, request):
"""
---
description: System Information
tags:
- System
responses:
"200":
description: successful operation
"""
systeminfo = await self.controller.systeminfo()
return web.json_response(data=systeminfo)

View file

@ -34,6 +34,7 @@ setup(name='cbpi',
'shortuuid==1.0.1', 'shortuuid==1.0.1',
'tabulate==0.8.7', 'tabulate==0.8.7',
'asyncio-mqtt', 'asyncio-mqtt',
'psutil',
'numpy==1.20.3', 'numpy==1.20.3',
'scipy', 'scipy',
'cbpi4ui', 'cbpi4ui',