Skip to content

Commit c84db62

Browse files
authored
Merge pull request #773 from Paraphraser/20240721-esphome-master
2024-07-21 Adds ESPHome - master branch - PR 1 of 2
2 parents 2f2b27d + a890000 commit c84db62

15 files changed

+532
-0
lines changed

Diff for: .templates/esphome/88-tty-iotstack-esphome.rules

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Assumptions:
2+
#
3+
# 1. The ESPhome container is running with the container-name "esphome".
4+
#
5+
# 2. The service definition for the ESPhome container includes:
6+
#
7+
# device_cgroup_rules:
8+
# - 'c 188:* rw'
9+
#
10+
# This clause permits the container to access any device with a major
11+
# number 188, which captures most USB-to-serial adapters that are
12+
# found on ESP32 dev boards or equivalent off-board adapters such as
13+
# those made by Future Technology Devices International (FTDI) and
14+
# Silicon Laboratories Incorporated. The major number 188 also shows
15+
# up in the UDEV rules below.
16+
#
17+
# 3. The ESP device to be managed is mounted and/or unmounted WHILE the
18+
# container is running. In other words, all bets are off if the host
19+
# system reboots or the container starts while the USB device is
20+
# connected. You will likely need to unplug/replug the device to
21+
# get the container back in sync.
22+
#
23+
# The rules do NOT check if the container is running and do NOT check
24+
# for errors. All that will happen is errors in the system log.
25+
#
26+
# Removing ESPhome from your stack does NOT remove this rules file. It
27+
# does not matter whether you accomplish removal by editing your compose
28+
# file or via the IOTstack menu, this rule will be left in place and it
29+
# will generate an error every time it fires in response to insertion
30+
# or removal of a matching USB device.
31+
#
32+
# It is perfectly safe to remove this rules file yourself:
33+
#
34+
# sudo rm /etc/udev/rules.d/88-tty-iotstack-esphome.rules
35+
#
36+
# That's all you have to do. UDEV is dynamic and, despite what you read
37+
# on the web, does NOT have to be restarted or reloaded.
38+
39+
# Upon insertion of a matching USB device, mount the same device inside the container
40+
ACTION=="add", \
41+
SUBSYSTEM=="tty", ENV{MAJOR}=="188", \
42+
RUN+="/usr/bin/docker exec esphome mknod %E{DEVNAME} c %M %m"
43+
44+
# Upon removal of a matching USB device, remove the same device inside the container
45+
ACTION=="remove", \
46+
SUBSYSTEM=="tty", ENV{MAJOR}=="188", \
47+
RUN+="/usr/bin/docker exec esphome rm -f %E{DEVNAME}"

Diff for: .templates/esphome/build.py

+175
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
#!/usr/bin/python3
2+
# -*- coding: utf-8 -*-
3+
4+
issues = {} # Returned issues dict
5+
buildHooks = {} # Options, and others hooks
6+
haltOnErrors = True
7+
8+
import os
9+
import sys
10+
11+
global templatesDirectory
12+
global currentServiceName # Name of the current service
13+
global generateRandomString
14+
15+
from deps.consts import templatesDirectory
16+
from deps.common_functions import generateRandomString
17+
18+
19+
# Main wrapper function. Required to make local vars work correctly
20+
def main():
21+
22+
global toRun # Switch for which function to run when executed
23+
global buildHooks # Where to place the options menu result
24+
global issues # Returned issues dict
25+
global haltOnErrors # Turn on to allow erroring
26+
27+
# runtime vars
28+
portConflicts = []
29+
30+
# This lets the menu know whether to put " >> Options " or not
31+
# This function is REQUIRED.
32+
def checkForOptionsHook():
33+
try:
34+
buildHooks["options"] = callable(runOptionsMenu)
35+
except:
36+
buildHooks["options"] = False
37+
return buildHooks
38+
return buildHooks
39+
40+
# This function is REQUIRED.
41+
def checkForPreBuildHook():
42+
try:
43+
buildHooks["preBuildHook"] = callable(preBuild)
44+
except:
45+
buildHooks["preBuildHook"] = False
46+
return buildHooks
47+
return buildHooks
48+
49+
# This function is REQUIRED.
50+
def checkForPostBuildHook():
51+
try:
52+
buildHooks["postBuildHook"] = callable(postBuild)
53+
except:
54+
buildHooks["postBuildHook"] = False
55+
return buildHooks
56+
return buildHooks
57+
58+
# This function is REQUIRED.
59+
def checkForRunChecksHook():
60+
try:
61+
buildHooks["runChecksHook"] = callable(runChecks)
62+
except:
63+
buildHooks["runChecksHook"] = False
64+
return buildHooks
65+
return buildHooks
66+
67+
# This service will not check anything unless this is set
68+
# This function is optional, and will run each time the menu is rendered
69+
def runChecks():
70+
checkForIssues()
71+
return []
72+
73+
# This function is optional, and will run after the docker-compose.yml file is written to disk.
74+
def postBuild():
75+
return True
76+
77+
# This function is optional, and will run just before the build docker-compose.yml code.
78+
def preBuild():
79+
return True
80+
81+
# #####################################
82+
# Supporting functions below
83+
# #####################################
84+
85+
def doCustomSetup() :
86+
87+
import os
88+
import re
89+
import subprocess
90+
from os.path import exists
91+
92+
def copyUdevRulesFile(templates,rules) :
93+
94+
# the expected location of the rules file in the template is the absolute path ...
95+
SOURCE_PATH = templates + '/' + currentServiceName + '/' + rules
96+
97+
# the rules file should be installed at the following absolute path...
98+
TARGET_PATH = '/etc/udev/rules.d' + '/' + rules
99+
100+
# does the target already exist?
101+
if not exists(TARGET_PATH) :
102+
103+
# no! does the source path exist?
104+
if exists(SOURCE_PATH) :
105+
106+
# yes! we should copy the source to the target
107+
subprocess.call(['sudo', 'cp', SOURCE_PATH, TARGET_PATH])
108+
109+
# sudo cp sets root ownership but not necessarily correct mode
110+
subprocess.call(['sudo', 'chmod', '644', TARGET_PATH])
111+
112+
def setEnvironment (path, key, value) :
113+
114+
# assume the variable should be written
115+
shouldWrite = True
116+
117+
# does the target file already exist?
118+
if exists(path) :
119+
120+
# yes! open the file so we can search it
121+
env_file = open(path, 'r+')
122+
123+
# prepare to read by lines
124+
env_data = env_file.readlines()
125+
126+
# we are searching for...
127+
expression = '^' + key + '='
128+
129+
# search by line
130+
for line in env_data:
131+
if re.search(expression, line) :
132+
shouldWrite = False
133+
break
134+
135+
else :
136+
137+
# no! create the file
138+
env_file = open(path, 'w')
139+
140+
# should the variable be written?
141+
if shouldWrite :
142+
print(key + '=' + value, file=env_file)
143+
144+
# done with the environment file
145+
env_file.close()
146+
147+
copyUdevRulesFile(
148+
os.path.realpath(templatesDirectory),
149+
'88-tty-iotstack-' + currentServiceName + '.rules'
150+
)
151+
152+
# the environment file is located at ...
153+
DOT_ENV_PATH = os.path.realpath('.') + '/.env'
154+
155+
# check/set environment variables
156+
setEnvironment(DOT_ENV_PATH,'ESPHOME_USERNAME',currentServiceName)
157+
setEnvironment(DOT_ENV_PATH,'ESPHOME_PASSWORD',generateRandomString())
158+
159+
160+
def checkForIssues():
161+
doCustomSetup() # done here because is called least-frequently
162+
return True
163+
164+
if haltOnErrors:
165+
eval(toRun)()
166+
else:
167+
try:
168+
eval(toRun)()
169+
except:
170+
pass
171+
172+
if currentServiceName == 'esphome':
173+
main()
174+
else:
175+
print("Error. '{}' Tried to run 'plex' config".format(currentServiceName))

Diff for: .templates/esphome/service.yml

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
esphome:
2+
container_name: esphome
3+
image: esphome/esphome
4+
restart: unless-stopped
5+
environment:
6+
- TZ=${TZ:-Etc/UTC}
7+
- USERNAME=${ESPHOME_USERNAME:-esphome}
8+
- PASSWORD=${ESPHOME_PASSWORD:?eg echo ESPHOME_PASSWORD=ChangeMe >>~/IOTstack/.env}
9+
network_mode: host
10+
x-ports:
11+
- "6052:6052"
12+
volumes:
13+
- ./volumes/esphome/config:/config
14+
device_cgroup_rules:
15+
- 'c 188:* rw'

0 commit comments

Comments
 (0)