Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
c00d755
install grott proxy server
andrewfg Aug 12, 2025
4dbe6e1
typo
andrewfg Aug 12, 2025
83a96dd
use curly braces for string concat
andrewfg Aug 12, 2025
d59d468
download extension python file
andrewfg Aug 12, 2025
ec38803
main menu entries; use username; and determine ip address
andrewfg Aug 13, 2025
5fdbb8c
add DRAFT bats file
andrewfg Aug 13, 2025
c5ce9b1
fix code check suggestion
andrewfg Aug 13, 2025
9d51563
add timestamp; modify .bats
andrewfg Aug 13, 2025
b786fca
tweak test
andrewfg Aug 13, 2025
c2b3233
service start/stop tweaks
andrewfg Aug 13, 2025
d65606f
wip debugging
andrewfg Aug 13, 2025
4b82839
fix global python dependencies
Aug 13, 2025
3b44e9c
add comments
Aug 13, 2025
15396ca
add doc; increase menu size
Aug 13, 2025
43b60b1
refactor based on reviewer suggestions
Aug 14, 2025
6ee008c
change line endings
Aug 14, 2025
f05ce3e
wip
Aug 14, 2025
c851048
refactoring
Aug 14, 2025
cb8c55b
tweak menu sizes
Aug 14, 2025
58c8b7f
use ip address again
Aug 14, 2025
2fc37c8
fix shellcheck error
Aug 15, 2025
833342f
whiptail tweaks
Aug 15, 2025
49e7252
comments cosmetics
Aug 15, 2025
1661e3e
do unattended setup if grottSetupEnabled is true
Aug 16, 2025
43c1d74
fix style check error
Aug 16, 2025
b7cb4c8
fix bats test
Aug 16, 2025
c3b2fa7
fix bats second try
Aug 16, 2025
0c3e52f
prettify logging
Aug 16, 2025
64f3502
tweak to trigger a rebuild
Aug 16, 2025
12624bc
further cosmetics on sequencing and logging
Aug 16, 2025
4d44029
adopt reviewer suggestions
Aug 19, 2025
390cc6c
shorten menu texts
Aug 19, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/bats-action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
paths:
- 'tests/Dockerfile.*-BATS'
- 'functions/*.bats'
- 'functions/grott-install.bash'
Comment thread
andrewfg marked this conversation as resolved.
Outdated
- 'functions/habapp.bash'
- 'functions/helpers.bash'
- 'functions/influxdb+grafana.bash'
Expand All @@ -18,6 +19,7 @@ on:
paths:
- 'tests/Dockerfile.*-BATS'
- 'functions/*.bats'
- 'functions/grott-install.bash'
- 'functions/habapp.bash'
- 'functions/helpers.bash'
- 'functions/influxdb+grafana.bash'
Expand Down
3 changes: 2 additions & 1 deletion docs/openhabian.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,12 +254,13 @@ Each of these are included as a part of `openhabian-config` menu option 20.
- [OWServer](https://owfs.org) - 1-Wire system of Dallas/Maxim
- [FIND](https://www.internalpositioning.com/) - Framework for Internal Navigation and Discovery
- Mi Flora MQTT daemon
- Grott Proxy server for [Growatt binding](https://www.openhab.org/addons/bindings/growatt/#grott-application-installation-and-setup)

## First boot configuration

Many settings are configurable prior to the first boot of openHABian by changing the key value pairs in the `/boot/openhabian.conf` file on the SD card once you have flashed the initial image onto it.

Please note that - in case you use a Windows system for writing the SD card - the `/boot/` partition will be mounted to a drive named `bootfs`. So, e.g. if this drive has the letter `D:`, `/boot/openhabian.conf` will be found as `D:\openhabian.conf`.
Please note that - in case you use a Windows system for writing the SD card - the `/boot/` partition will be mounted to a drive named `bootfs`. So, e.g. if this drive has the letter `D:`, `/boot/openhabian.conf` will be found as `D:\openhabian.conf`.

The openHABian configuration file uses key value pairs, essentially a list of `option=value` settings in a plain text file.
All supported options are already in the file but unused options and optional components are commented out by default.
Expand Down
152 changes: 152 additions & 0 deletions functions/grott-install.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
#!/usr/bin/env bash
Comment thread
andrewfg marked this conversation as resolved.
Outdated

## Install the Grott proxy server on the current system
##
## install_grott(String install|remove)
##
install_grott() {
# Fail on errors
set -e
Comment thread
andrewfg marked this conversation as resolved.
Outdated

# Validate install type argument
if [ "$#" -lt 1 ]; then
echo "Error: Please supply install type."
echo "Usage: $0 <install|remove>"
exit 1
elif [[ "$1" != "install" && "$1" != "remove" ]]; then
echo "Error: Invalid install type '$1'. Use 'install' or 'remove'."
exit 1
fi

local INSTALL_TYPE="$1"
local USERNAME=${username:-openhabian}
local INSTALL_DIR="/home/${USERNAME}/grott"
local SERVICE_FILE="/etc/systemd/system/grott.service"

if [[ $INSTALL_TYPE == "install" ]]; then
# Get default IPv4 address
local ipAddress
ipAddress="$(ip route get 8.8.8.8 | awk '{print $7}' | xargs)"
Comment thread
andrewfg marked this conversation as resolved.
Outdated
if ! [[ "$ipAddress" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Error: Invalid IP address format: $ipAddress"
exit 1
fi
local EXT_URL="http://${ipAddress}:8080/growatt"

echo "$(timestamp) [openHABian] Installing Grott Proxy with extension URL: $EXT_URL "

# Update system and install dependencies. NOTE: paho-mqtt is a required dependency (even if disabled)
sudo apt update
Comment thread
andrewfg marked this conversation as resolved.
Outdated
sudo apt install -y python3 python3-pip python3-paho-mqtt python3-requests
Comment thread
andrewfg marked this conversation as resolved.
Outdated

# Prepare installation directory
sudo -u "$USERNAME" mkdir -p -m 755 "$INSTALL_DIR"
Comment thread
andrewfg marked this conversation as resolved.
Outdated
if [ ! -d "$INSTALL_DIR" ]; then
echo "Error: Installation directory $INSTALL_DIR does not exist."
exit 1
fi

# Download Grott Python files to installation directory
download_grott_files "$INSTALL_DIR" || {
echo "Failed to download Grott files. Exiting."
return 1
}

# Create grott.service systemd file from template
if ! sed -e "s|%INSTALL_DIR|$INSTALL_DIR|g" \
Comment thread
andrewfg marked this conversation as resolved.
Outdated
-e "s|%USERNAME|$USERNAME|g" \
"${BASEDIR:-/opt/openhabian}/includes/grott.service" > "$SERVICE_FILE"; then
echo "FAILED (sed substitution)"
return 1
fi

# Create grott.ini file from template
if ! sed -e "s|%EXT_URL|$EXT_URL|g" \
Comment thread
mstormi marked this conversation as resolved.
Outdated
"${BASEDIR:-/opt/openhabian}/includes/grott.ini" > "${INSTALL_DIR}/grott.ini"; then
echo "FAILED (sed substitution)"
return 1
fi

# Enable and start service
sudo systemctl enable grott --now
sudo systemctl daemon-reload
Comment thread
andrewfg marked this conversation as resolved.
Outdated

if [[ -n "$INTERACTIVE" ]]; then
whiptail --title "Grott Proxy installed" --msgbox "We installed Grott Proxy on your system." 7 80
fi

return 0

elif [[ $INSTALL_TYPE == "remove" ]]; then
echo "$(timestamp) [openHABian] Removing Grott Proxy... "

# Stop and disable systemd service
if sudo systemctl is-active --quiet grott; then
sudo systemctl stop grott
fi

if sudo systemctl is-enabled --quiet grott; then
sudo systemctl disable grott
Comment thread
andrewfg marked this conversation as resolved.
Outdated
fi

# Remove systemd service file
if [ -f "$SERVICE_FILE" ]; then
sudo rm "$SERVICE_FILE"
sudo systemctl daemon-reload
fi

# Remove installation directory
if [ -d "$INSTALL_DIR" ]; then
sudo rm -rf "$INSTALL_DIR"
Comment thread
andrewfg marked this conversation as resolved.
Outdated
echo "Removed $INSTALL_DIR"
else
echo "Directory $INSTALL_DIR not found."
fi

if [[ -n "$INTERACTIVE" ]]; then
whiptail --title "Grott Proxy removed" --msgbox "We removed Grott Proxy from your system." 7 80
fi

return 0
fi
}

## Download the Grott Proxy Python files
##
## download_grott_files(String install_directory)
##
download_grott_files() {
local INSTALL_DIR="$1"
local GROTT_FILE_URL="https://raw.githubusercontent.com/johanmeijer/grott/master"
local GROTT_PY_FILES=(
"grott.py"
"grottconf.py"
"grottdata.py"
"grottproxy.py"
"grottserver.py"
"grottsniffer.py"
)
local GROTT_EXT_PY_FILE="grottext.py"
local file url

echo "Installing files to $INSTALL_DIR "

# Download Grott main program Python files
for file in "${GROTT_PY_FILES[@]}"; do
url="${GROTT_FILE_URL}/${file}"
echo "Downloading $file"
curl -fsSL "$url" -o "${INSTALL_DIR}/${file}" || {
echo "Failed to download $file"
return 1
}
done

# Download Grott extension Python file
file="$GROTT_EXT_PY_FILE"
url="${GROTT_FILE_URL}/examples/Extensions/${file}"
echo "Downloading $file"
curl -fsSL "$url" -o "${INSTALL_DIR}/${file}" || {
echo "Failed to download $file"
return 1
}
}
34 changes: 34 additions & 0 deletions functions/grott-install.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/usr/bin/env bats

load 'grott-install.bash'
load 'helpers.bash'

setup_file() {
export BASEDIR="${BATS_TEST_DIRNAME}/.."
}

teardown_file() {
unset BASEDIR
# Stop grott service
systemctl kill grott || true
}

## Confirm Grott Proxy install completes without error; and confirm Grott service is running
@test "destructive-grott_install" {
## Confirm Grott Proxy install completes without error
echo -e "# ${COL_CYAN}$(timestamp) [openHABian] Grott Proxy installation starting...${COL_DEF}" >&3
run install_grott "install" 3>&-
if [ "$status" -ne 0 ]; then echo "$output" >&3; fi
[ "$status" -eq 0 ]
echo -e "# ${COL_GREEN}$(timestamp) [openHABian] Grott Proxy installation successful.${COL_DEF}" >&3

# Await Grott service
sleep 5s

## Confirm Grott service is running
echo -e "# ${COL_CYAN}$(timestamp) [openHABian] Checking if Grott Proxy service is running...${COL_DEF}" >&3
run systemctl is-active --quiet grott
if [ "$status" -ne 0 ]; then echo "$output" >&3; fi
[ "$status" -eq 0 ]
echo -e "# ${COL_GREEN}$(timestamp) [openHABian] Grott Proxy service is running.${COL_DEF}" >&3
}
6 changes: 5 additions & 1 deletion functions/menu.bash
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ show_main_menu() {
esac

elif [[ "$choice" == "20"* ]]; then
choice2=$(whiptail --title "openHABian Configuration Tool — $(get_git_revision)" --menu "Optional Components" 24 118 16 --cancel-button Back --ok-button Execute \
choice2=$(whiptail --title "openHABian Configuration Tool — $(get_git_revision)" --menu "Optional Components" 33 118 25 --cancel-button Back --ok-button Execute \
Comment thread
andrewfg marked this conversation as resolved.
Outdated
"21 | Log Viewer" "[DEPRECATED] openHAB Log Viewer webapp (frontail)" \
" | Add log to viewer" "[DEPRECATED] Add a custom log to openHAB Log Viewer (frontail)" \
" | Remove log from viewer" "[DEPRECATED] Remove a custom log from openHAB Log Viewer (frontail)" \
Expand All @@ -133,6 +133,8 @@ show_main_menu() {
" | Setup EVCC" "Setup EVCC from command line (German only)" \
"2E | Install ESPHome dashboard" "Deploy ESPHome dashboard" \
" | Remove ESPHome dashboard" "Uninstall ESPHome dashboard" \
"2F | Install Grott" "Install Grott Proxy server (for Growatt binding)" \
" | Remove Grott" "Uninstall Grott Proxy server (for Growatt binding)" \
3>&1 1>&2 2>&3)
RET=$?
if [ $RET -eq 1 ] || [ $RET -eq 255 ]; then return 0; fi
Expand Down Expand Up @@ -160,6 +162,8 @@ show_main_menu() {
*Setup\ EVCC*) setup_evcc;;
2E\ *) install_esphomedashboard "install";;
*Remove\ ESPHome\ dashboard*) install_esphomedashboard "remove";;
2F\ *) install_grott "install";;
*Remove\ Grott*) install_grott "remove";;
"") return 0 ;;
*) whiptail --msgbox "An unsupported option was selected (probably a programming error):\\n \"$choice2\"" 8 80 ;;
esac
Expand Down
107 changes: 107 additions & 0 deletions includes/grott.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Specify grott monitor configuration
# Defaults are described
# Remove # and update the value to enable the setting
# Last updated: 2022-11-04
# Version 2.7.0

[Generic]
# Specify verbose for extended messaging
verbose = True

# Specify minrecl for debugging purposes only (default = 100)
#minrecl = 100

# Specify mode (sniff or proxy)(> 2.1.0 proxy is default)
mode = proxy

# Specify port and IP address to listen to (only proxy), default port 5279, 0.0.0.0 ==> own ip address
ip = default
port = 5279

# To blocks commands from outside (to channge inverter and shine devices settings) specify blockcmd = True,
# Specify noipf = True if you still want be able to set the destination ip addres from growatt server (advice
# only to use this for a short time)
#blockcmd = True
#noipf = True

# Time = auto/server parameter enable/disable date/time retrieval from data record (server), default is
# auto: grott decides which time is used (data record if valid otherwise Server)
# If time = server Grott server time is alwas used
#time = auto

# Sendbuf = True / False parameter to enable / disable sending historical (buffered) data. Default is sendbuf = True.
#sendbuf = True

# Compat is True and valoffset needs to be set if offset / growatt protocol has been changed.
compat = False
#valueoffset = 6

# Specify inverter id (not necessary in version >2.1.0 if compat = false!)
#inverterid = KUM0CLU03Y
# Specify the type of the inverter (default/sph/spf/max)
invtype = sph

# Decrypt is False if growatt communication is not encrypted (older inverters), (not necessary in version
# >2.1.0 if compat = false!)
#decrypt = True

[Growatt]
# Server name/IP address and port of Growatt server
# specify only if the IP address of server.growatt.com is changed
# The address as of Nov 2022 is 47.91.67.66

ip = server.growatt.com
#ip = 47.91.67.66
#port = 5279

[MQTT]
# Mqtt parameters definitions
# Be aware nomqtt = True means no MQTT processing will be done!!!!!!

nomqtt = True
#ip = localhost
#port = 1883
#topic= energy/growatt
#auth = False
#user = grott
#password = growatt2020

[PVOutput]
# PVOutput parameters definitions

pvoutput = False
#apikey = yourapikey
# Data upload limit (in minutes)
#pvuplimit = 5
# Use this if you have one inverter
#systemid = 12345

# Use this if you have multiple inverters
#pvinverters = 2
#systemid1 = 12345
#inverterid1 = inverter1
#systemid2 = 67890
#inverterid2 = inverter2

#systemid99 = 99999
#inverterid99 = inverter99

[influx]
# Influxdb parameters definitions

#influx = False
#influx2 = False
#dbname = grottdb
#ip = localhost
#port = 8086
#user = grott
#password = growatt2020
#token = "influx_token"
#org = "grottorg"
#bucket = "grottdb"

[extension]
# grott extension parameters definitions
extension = True
extname = grottext
extvar = {"url": "%EXT_URL"}
12 changes: 12 additions & 0 deletions includes/grott.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[Unit]
Description=Grott Proxy Service
After=network.target

[Service]
ExecStart=/usr/bin/python3 -u %INSTALL_DIR/grott.py -v
WorkingDirectory=%INSTALL_DIR
Restart=on-failure
User=%USERNAME

[Install]
WantedBy=multi-user.target