Merge pull request #97 from PiBrewing/development

Merge changes from Development
This commit is contained in:
Alexander Vollkopf 2023-03-10 07:01:42 +01:00 committed by GitHub
commit 10fa86b72c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 167 additions and 25 deletions

View file

@ -9,13 +9,16 @@ on:
pull_request: pull_request:
env: env:
image-name: ghcr.io/${{ github.repository_owner }}/craftbeerpi4 IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/craftbeerpi4
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
packages: write
name: Builds the source distribution package name: Builds the source distribution package
steps: steps:
- name: Checkout source - name: Checkout source
uses: actions/checkout@v2 uses: actions/checkout@v2
@ -54,8 +57,11 @@ jobs:
id: prep id: prep
run: | run: |
IMAGE_NAME_LOWERCASE=${IMAGE_NAME,,}
echo "Using image name $IMAGE_NAME_LOWERCASE"
PUBLISH_IMAGE=false PUBLISH_IMAGE=false
TAGS="${{ env.image-name }}:dev" TAGS="$IMAGE_NAME_LOWERCASE:dev"
# Define the image that will be used as a cached image # Define the image that will be used as a cached image
# to speed up the build process # to speed up the build process
@ -65,18 +71,21 @@ jobs:
# when building master/main use :latest tag and the version number # when building master/main use :latest tag and the version number
# from the cbpi/__init__.py file # from the cbpi/__init__.py file
VERSION=$(grep -o -E "(([0-9]{1,2}[.]?){2,3}[0-9]+)" cbpi/__init__.py) VERSION=$(grep -o -E "(([0-9]{1,2}[.]?){2,3}[0-9]+)" cbpi/__init__.py)
LATEST_IMAGE=${{ env.image-name }}:latest LATEST_IMAGE=$IMAGE_NAME_LOWERCASE:latest
BUILD_CACHE_IMAGE_NAME=${LATEST_IMAGE} BUILD_CACHE_IMAGE_NAME=${LATEST_IMAGE}
TAGS="${LATEST_IMAGE},${{ env.image-name }}:v${VERSION}" TAGS="${LATEST_IMAGE},$IMAGE_NAME_LOWERCASE:v${VERSION}"
PUBLISH_IMAGE=true PUBLISH_IMAGE="true"
elif [[ $GITHUB_REF_NAME == development ]]; then elif [[ $GITHUB_REF_NAME == development ]]; then
PUBLISH_IMAGE=true PUBLISH_IMAGE="true"
fi fi
# Set output parameters.
echo ::set-output name=tags::${TAGS} echo "tags: $TAGS"
echo ::set-output name=publish_image::${PUBLISH_IMAGE} echo "publish_image: $PUBLISH_IMAGE"
echo ::set-output name=build_cache_image_name::${BUILD_CACHE_IMAGE_NAME} echo "cache_name: $BUILD_CACHE_IMAGE_NAME"
echo "tags=$TAGS" >> $GITHUB_OUTPUT
echo "publish_image=$PUBLISH_IMAGE" >> $GITHUB_OUTPUT
echo "cache_name=$BUILD_CACHE_IMAGE_NAME" >> $GITHUB_OUTPUT
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@master uses: docker/setup-qemu-action@master
@ -88,23 +97,23 @@ jobs:
uses: docker/setup-buildx-action@master uses: docker/setup-buildx-action@master
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v1 uses: docker/login-action@v2
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build - name: Build
uses: docker/build-push-action@v2 uses: docker/build-push-action@v4
with: with:
builder: ${{ steps.buildx.outputs.name }} builder: ${{ steps.buildx.outputs.name }}
context: . context: .
file: ./Dockerfile file: ./Dockerfile
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
target: deploy target: deploy
push: ${{ steps.prep.outputs.publish_image }} push: ${{ steps.prep.outputs.publish_image == 'true' }}
tags: ${{ steps.prep.outputs.tags }} tags: ${{ steps.prep.outputs.tags }}
cache-from: type=registry,ref=${{ steps.prep.outputs.build_cache_image_name }} cache-from: type=registry,ref=${{ steps.prep.outputs.cache_name }}
cache-to: type=inline cache-to: type=inline
labels: | labels: |
org.opencontainers.image.title=${{ github.event.repository.name }} org.opencontainers.image.title=${{ github.event.repository.name }}

View file

@ -1,3 +1,3 @@
__version__ = "4.1.5" __version__ = "4.1.6"
__codename__ = "Groundhog Day" __codename__ = "Groundhog Day"

View file

@ -64,6 +64,10 @@ class Actor:
def to_dict(self): def to_dict(self):
return dict(id=self.id, name=self.name, type=self.type, props=self.props.to_dict(), state=self.instance.get_state(), power=self.power) return dict(id=self.id, name=self.name, type=self.type, props=self.props.to_dict(), state=self.instance.get_state(), power=self.power)
class DataType(Enum):
VALUE="value"
DATETIME="datetime"
STRING="string"
@dataclass @dataclass
class Sensor: class Sensor:
@ -73,6 +77,7 @@ class Sensor:
state: bool = False state: bool = False
type: str = None type: str = None
instance: str = None instance: str = None
datatype: DataType = DataType.VALUE
def __str__(self): def __str__(self):
return "name={} props={}, state={}".format(self.name, self.props, self.state) return "name={} props={}, state={}".format(self.name, self.props, self.state)

View file

@ -2,6 +2,7 @@ import asyncio
import logging import logging
from abc import abstractmethod, ABCMeta from abc import abstractmethod, ABCMeta
from cbpi.api.extension import CBPiExtension from cbpi.api.extension import CBPiExtension
from cbpi.api.dataclasses import DataType
from cbpi.api.base import CBPiBase from cbpi.api.base import CBPiBase
@ -16,6 +17,7 @@ class CBPiSensor(CBPiBase, metaclass=ABCMeta):
self.data_logger = None self.data_logger = None
self.state = False self.state = False
self.running = False self.running = False
self.datatype=DataType.VALUE
def init(self): def init(self):
pass pass
@ -33,13 +35,14 @@ class CBPiSensor(CBPiBase, metaclass=ABCMeta):
pass pass
def push_update(self, value, mqtt = True): def push_update(self, value, mqtt = True):
try: try:
self.cbpi.ws.send(dict(topic="sensorstate", id=self.id, value=value)) self.cbpi.ws.send(dict(topic="sensorstate", id=self.id, value=value, datatype=self.datatype.value))
if mqtt: if mqtt:
self.cbpi.push_update("cbpi/sensordata/{}".format(self.id), dict(id=self.id, value=value), retain=True) self.cbpi.push_update("cbpi/sensordata/{}".format(self.id), dict(id=self.id, value=value, datatype=self.datatype.value), retain=True)
# self.cbpi.push_update("cbpi/sensor/{}/udpate".format(self.id), dict(id=self.id, value=value), retain=True) # self.cbpi.push_update("cbpi/sensor/{}/udpate".format(self.id), dict(id=self.id, value=value), retain=True)
except: except:
logging.error("Failed to push sensor update") logging.error("Failed to push sensor update for sensor {}".format(self.id))
async def start(self): async def start(self):
pass pass

View file

@ -158,11 +158,14 @@ class LogController:
dateparse = lambda dates: [datetime.datetime.strptime(d, '%Y-%m-%d %H:%M:%S') for d in dates] dateparse = lambda dates: [datetime.datetime.strptime(d, '%Y-%m-%d %H:%M:%S') for d in dates]
result = dict() result = dict()
for id in ids: for id in ids:
all_filenames = glob.glob(os.path.join(self.logsFolderPath,f"sensor_{id}.log*")) try:
df = pd.concat([pd.read_csv(f, parse_dates=['DateTime'], date_parser=dateparse, index_col='DateTime', names=['DateTime', 'Values'], header=None) for f in all_filenames]) all_filenames = glob.glob(os.path.join(self.logsFolderPath,f"sensor_{id}.log*"))
df = df.resample('60s').max() df = pd.concat([pd.read_csv(f, parse_dates=['DateTime'], date_parser=dateparse, index_col='DateTime', names=['DateTime', 'Values'], header=None) for f in all_filenames])
df = df.dropna() df = df.resample('60s').max()
result[id] = {"time": df.index.astype(str).tolist(), "value":df.Values.tolist()} df = df.dropna()
result[id] = {"time": df.index.astype(str).tolist(), "value":df.Values.tolist()}
except:
pass
return result return result

View file

@ -4,7 +4,8 @@ import random
import logging import logging
from cbpi.api import * from cbpi.api import *
from cbpi.api.base import CBPiBase from cbpi.api.base import CBPiBase
from cbpi.api.dataclasses import Kettle, Props, Fermenter from cbpi.api.dataclasses import Kettle, Props, Fermenter, DataType
import time
@parameters([]) @parameters([])
class CustomSensor(CBPiSensor): class CustomSensor(CBPiSensor):

View file

@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
from aiohttp import web
import logging
import asyncio
from cbpi.api import *
from cbpi.api import base
from time import strftime, gmtime
from cbpi.api.timer import Timer
from cbpi.api.dataclasses import DataType
from cbpi.api.dataclasses import NotificationAction, NotificationType
logger = logging.getLogger(__name__)
@parameters([])
class AlarmTimer(CBPiSensor):
def __init__(self, cbpi, id, props):
super(AlarmTimer, self).__init__(cbpi, id, props)
self.value = "00:00:00"
self.datatype=DataType.STRING
self.timer = None
self.time=0
self.stopped=False
self.sensor=self.get_sensor(self.id)
@action(key="Set Timer", parameters=[Property.Number(label="time", description="Time in Minutes", configurable=True)])
async def set(self, time = 0,**kwargs):
self.stopped=False
self.time = float(time)
self.value=self.calculate_time(self.time)
if self.timer is not None:
await self.timer.stop()
self.timer = Timer(int(self.time * 60), on_update=self.on_timer_update, on_done=self.on_timer_done)
await self.timer.stop()
self.timer.is_running = False
logging.info("Set Timer")
@action(key="Start Timer", parameters=[])
async def start(self , **kwargs):
if self.timer is None:
self.timer = Timer(int(self.time * 60), on_update=self.on_timer_update, on_done=self.on_timer_done)
if self.timer.is_running is not True:
self.timer.start()
self.stopped=False
self.timer.is_running = True
else:
self.cbpi.notify(self.sensor.name,'Timer is already running', NotificationType.WARNING)
@action(key="Stop Timer", parameters=[])
async def stop(self , **kwargs):
self.stopped=False
await self.timer.stop()
self.timer.is_running = False
logging.info("Stop Timer")
@action(key="Reset Timer", parameters=[])
async def Reset(self , **kwargs):
self.stopped=False
if self.timer is not None:
await self.timer.stop()
self.value=self.calculate_time(self.time)
self.timer = Timer(int(self.time * 60), on_update=self.on_timer_update, on_done=self.on_timer_done)
await self.timer.stop()
self.timer.is_running = False
logging.info("Reset Timer")
async def on_timer_done(self, timer):
#self.value = "Stopped"
if self.stopped is True:
self.cbpi.notify(self.sensor.name,'Timer done', NotificationType.SUCCESS)
self.timer.is_running = False
pass
async def on_timer_update(self, timer, seconds):
self.stopped=True
self.value = Timer.format_time(seconds)
async def run(self):
while self.running is True:
self.push_update(self.value)
await asyncio.sleep(1)
pass
def get_state(self):
return dict(value=self.value)
def calculate_time(self, time):
return strftime("%H:%M:%S", gmtime(time*60))
def setup(cbpi):
cbpi.plugin.register("AlarmTimer", AlarmTimer)
pass

View file

@ -0,0 +1,3 @@
name: timer
version: 4
active: true

View file

@ -4,6 +4,7 @@ from cbpi.utils.utils import json_dumps
from cbpi.api import request_mapping from cbpi.api import request_mapping
import os import os
import json import json
import logging
class LogHttpEndpoints: class LogHttpEndpoints:
def __init__(self,cbpi): def __init__(self,cbpi):
@ -189,7 +190,8 @@ class LogHttpEndpoints:
description: successful operation. description: successful operation.
""" """
data = await request.json() data = await request.json()
return web.json_response(await self.cbpi.log.get_data2(data), dumps=json_dumps) values = await self.cbpi.log.get_data2(data)
return web.json_response(values, dumps=json_dumps)
@request_mapping(path="/{name}", method="DELETE", auth_required=False) @request_mapping(path="/{name}", method="DELETE", auth_required=False)

View file

@ -45,6 +45,22 @@
"type": "string", "type": "string",
"value": "Some New Brewery Name" "value": "Some New Brewery Name"
}, },
"BoilAutoTimer": {
"description": "Start Boil timer automatically if Temp does not change for 5 Minutes and is above 95C/203F",
"name": "BoilAutoTimer",
"options": [
{
"label": "Yes",
"value": "Yes"
},
{
"label": "No",
"value": "No"
}
],
"type": "select",
"value": "No"
},
"BoilKettle": { "BoilKettle": {
"description": "Define Kettle that is used for Boil, Whirlpool and Cooldown. If not selected, MASH_TUN will be used", "description": "Define Kettle that is used for Boil, Whirlpool and Cooldown. If not selected, MASH_TUN will be used",
"name": "BoilKettle", "name": "BoilKettle",
@ -107,6 +123,13 @@
"type": "select", "type": "select",
"value": "No" "value": "No"
}, },
"INFLUXDBMEASUREMENT": {
"description": "Name of the measurement in your INFLUXDB database (default: measurement)",
"name": "INFLUXDBMEASUREMENT",
"options": null,
"type": "string",
"value": "measurement"
},
"INFLUXDBNAME": { "INFLUXDBNAME": {
"description": "Name of your influxdb database name (If INFLUXDBCLOUD set to Yes use bucket of your influxdb cloud database)", "description": "Name of your influxdb database name (If INFLUXDBCLOUD set to Yes use bucket of your influxdb cloud database)",
"name": "INFLUXDBNAME", "name": "INFLUXDBNAME",