diff --git a/pkg/oled/espresso_pb2.py b/pkg/oled/espresso_pb2.py new file mode 100644 index 0000000..fb24d67 --- /dev/null +++ b/pkg/oled/espresso_pb2.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: espresso.proto +"""Generated protocol buffer code.""" +from google.protobuf.internal import builder as _builder +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0e\x65spresso.proto\x12\nespressopb\x1a\x1fgoogle/protobuf/timestamp.proto\"S\n\x11TemperatureSample\x12\r\n\x05value\x18\x01 \x01(\x02\x12/\n\x0bobserved_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"D\n\x12TemperatureHistory\x12.\n\x07samples\x18\x01 \x03(\x0b\x32\x1d.espressopb.TemperatureSample\"\x1a\n\x18TemperatureStreamRequest\"\x87\x01\n\x19TemperatureStreamResponse\x12\x31\n\x07history\x18\x01 \x01(\x0b\x32\x1e.espressopb.TemperatureHistoryH\x00\x12/\n\x06sample\x18\x02 \x01(\x0b\x32\x1d.espressopb.TemperatureSampleH\x00\x42\x06\n\x04\x64\x61ta\"\x19\n\x17GetConfigurationRequest\"q\n\rConfiguration\x12\x13\n\x0btemperature\x18\x01 \x01(\x02\x12\t\n\x01p\x18\x02 \x01(\x02\x12\t\n\x01i\x18\x03 \x01(\x02\x12\t\n\x01\x64\x18\x04 \x01(\x02\x12*\n\x06set_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp2\x8c\x02\n\x08\x45spresso\x12\x62\n\x11\x42oilerTemperature\x12$.espressopb.TemperatureStreamRequest\x1a%.espressopb.TemperatureStreamResponse0\x01\x12R\n\x10GetConfiguration\x12#.espressopb.GetConfigurationRequest\x1a\x19.espressopb.Configuration\x12H\n\x10SetConfiguration\x12\x19.espressopb.Configuration\x1a\x19.espressopb.Configurationb\x06proto3') + +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'espresso_pb2', globals()) +if _descriptor._USE_C_DESCRIPTORS == False: + + DESCRIPTOR._options = None + _TEMPERATURESAMPLE._serialized_start=63 + _TEMPERATURESAMPLE._serialized_end=146 + _TEMPERATUREHISTORY._serialized_start=148 + _TEMPERATUREHISTORY._serialized_end=216 + _TEMPERATURESTREAMREQUEST._serialized_start=218 + _TEMPERATURESTREAMREQUEST._serialized_end=244 + _TEMPERATURESTREAMRESPONSE._serialized_start=247 + _TEMPERATURESTREAMRESPONSE._serialized_end=382 + _GETCONFIGURATIONREQUEST._serialized_start=384 + _GETCONFIGURATIONREQUEST._serialized_end=409 + _CONFIGURATION._serialized_start=411 + _CONFIGURATION._serialized_end=524 + _ESPRESSO._serialized_start=527 + _ESPRESSO._serialized_end=795 +# @@protoc_insertion_point(module_scope) diff --git a/pkg/oled/espresso_pb2_grpc.py b/pkg/oled/espresso_pb2_grpc.py new file mode 100644 index 0000000..edf1a2a --- /dev/null +++ b/pkg/oled/espresso_pb2_grpc.py @@ -0,0 +1,132 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +import espresso_pb2 as espresso__pb2 + + +class EspressoStub(object): + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.BoilerTemperature = channel.unary_stream( + '/espressopb.Espresso/BoilerTemperature', + request_serializer=espresso__pb2.TemperatureStreamRequest.SerializeToString, + response_deserializer=espresso__pb2.TemperatureStreamResponse.FromString, + ) + self.GetConfiguration = channel.unary_unary( + '/espressopb.Espresso/GetConfiguration', + request_serializer=espresso__pb2.GetConfigurationRequest.SerializeToString, + response_deserializer=espresso__pb2.Configuration.FromString, + ) + self.SetConfiguration = channel.unary_unary( + '/espressopb.Espresso/SetConfiguration', + request_serializer=espresso__pb2.Configuration.SerializeToString, + response_deserializer=espresso__pb2.Configuration.FromString, + ) + + +class EspressoServicer(object): + """Missing associated documentation comment in .proto file.""" + + def BoilerTemperature(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetConfiguration(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetConfiguration(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_EspressoServicer_to_server(servicer, server): + rpc_method_handlers = { + 'BoilerTemperature': grpc.unary_stream_rpc_method_handler( + servicer.BoilerTemperature, + request_deserializer=espresso__pb2.TemperatureStreamRequest.FromString, + response_serializer=espresso__pb2.TemperatureStreamResponse.SerializeToString, + ), + 'GetConfiguration': grpc.unary_unary_rpc_method_handler( + servicer.GetConfiguration, + request_deserializer=espresso__pb2.GetConfigurationRequest.FromString, + response_serializer=espresso__pb2.Configuration.SerializeToString, + ), + 'SetConfiguration': grpc.unary_unary_rpc_method_handler( + servicer.SetConfiguration, + request_deserializer=espresso__pb2.Configuration.FromString, + response_serializer=espresso__pb2.Configuration.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'espressopb.Espresso', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class Espresso(object): + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def BoilerTemperature(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_stream(request, target, '/espressopb.Espresso/BoilerTemperature', + espresso__pb2.TemperatureStreamRequest.SerializeToString, + espresso__pb2.TemperatureStreamResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def GetConfiguration(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/espressopb.Espresso/GetConfiguration', + espresso__pb2.GetConfigurationRequest.SerializeToString, + espresso__pb2.Configuration.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def SetConfiguration(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/espressopb.Espresso/SetConfiguration', + espresso__pb2.Configuration.SerializeToString, + espresso__pb2.Configuration.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/pkg/oled/grpc_client_micro_oled.py b/pkg/oled/grpc_client_micro_oled.py new file mode 100644 index 0000000..dbc7b54 --- /dev/null +++ b/pkg/oled/grpc_client_micro_oled.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +""" +We are using Sparkfun micro OLED breakout (Qwiic), article LCD-14532 + +Prereqs: +* all packages need to be installed including luma +* I2C is enabled on port 1 +""" +import datetime +import board +import digitalio +from PIL import Image, ImageDraw, ImageFont +from luma.core.interface.serial import i2c +from luma.core.render import canvas +from luma.oled.device import ssd1306 +import grpc +import espresso_pb2 +import espresso_pb2_grpc +import requests +import os +import subprocess +import sys, time + +# Use for I2C. +serial = i2c(port=1, address=0x3D) +device = ssd1306(serial, width=64, height=48, rotate=2) +font = ImageFont.truetype("LiberationSerif-Bold.ttf", size=15) +aux_font = ImageFont.truetype("LiberationSerif-Bold.ttf", size=10) + +def print_to_oled( temp,error ): + t_now = datetime.datetime.now(datetime.timezone.utc).strftime("%d-%m/%H:%M:%S") + (tfont_width, tfont_height) = font.getsize(t_now) + (font_width, font_height) = font.getsize(str(temp)) + error_str = "Delta T="+str(error)+"°C" + (error_font_width, error_font_height) = font.getsize(error_str) + with canvas(device) as draw: + draw.rectangle(device.bounding_box, outline="white", fill="black") + draw.text((device.width // 2 - font_width // 2, + device.height // 2 - font_height // 2), + temp, + font=font, + fill="white", + ) + draw.text((device.width // 2 - tfont_width // 3.1, + device.height // 10 - tfont_height // 10), + t_now, + font=aux_font, + fill="green", + ) + draw.text((device.width // 2 - error_font_width // 3.1, + device.height // 2 - error_font_height // -1.4), + error_str, + font=aux_font, + fill="white",) + +def run(): + with grpc.insecure_channel('localhost:8080') as channel: + stub = espresso_pb2_grpc.EspressoStub(channel) + #TODO: Initialize the PID values once here to P=1, I=4, D=8 + + response_iter_temp = stub.BoilerTemperature(espresso_pb2.TemperatureStreamRequest()) + for response in response_iter_temp: + print(response.sample.value) + response_setT = stub.GetConfiguration(espresso_pb2.GetConfigurationRequest()) + print(str(response_setT.temperature)) + error = response_setT.temperature - response.sample.value + print_to_oled(str(float(f'{response.sample.value:.1f}'))+"°C",error) + #TODO: display an image of steaming cup if error is < 5°C +run() diff --git a/systemd/espresso.service b/systemd/espresso.service new file mode 100644 index 0000000..34d958f --- /dev/null +++ b/systemd/espresso.service @@ -0,0 +1,12 @@ +[Unit] +Description=Espresso PID service +After=multi-user.target + +[Service] +Restart=on-failure +RestartSec=5s +User=pi +ExecStart=/bin/bash --login -c "/usr/bin/espresso --boiler-therm-clk-pin 23 --boiler-therm-cs-pin 27 --boiler-therm-miso-pin 22 --verbose" +[Install] +WantedBy=multi-user.target + diff --git a/systemd/micro_oled.service b/systemd/micro_oled.service new file mode 100644 index 0000000..311cff1 --- /dev/null +++ b/systemd/micro_oled.service @@ -0,0 +1,13 @@ +[Unit] +Description=Sparkfun micro oled service +After=multi-user.target espresso.service + +[Service] +Restart=on-failure +RestartSec=5s +User=pi +ExecStart=/usr/bin/python3 /home/pi/espresso-controller/pkg/espressopb/grpc_client_micro_oled.py + +[Install] +WantedBy=multi-user.target + diff --git a/systemd/readme.md b/systemd/readme.md new file mode 100644 index 0000000..9dda9ea --- /dev/null +++ b/systemd/readme.md @@ -0,0 +1,6 @@ +# Copy the .service files to your /lib/systemd/system/ +## type into terminal +### `sudo systemctl daemon-reload` +### `sudo systemctl enable espresso.service` +### `sudo systemctl enable micro_oled.servic` +# Reboot