mirror of
https://github.com/house-of-abbey/GarminHomeAssistant.git
synced 2025-06-16 03:18:35 +00:00
Compare commits
26 Commits
Author | SHA1 | Date | |
---|---|---|---|
6b8892aee8 | |||
fce353dc38 | |||
647f92e9c6 | |||
c2bd0dfa40 | |||
22f9fa6901 | |||
cf7be0bcd9 | |||
6a2a70ba0b | |||
f58fe2d26d | |||
812b601f86 | |||
b726b8b6ac | |||
abfddd22e2 | |||
5794d67058 | |||
a6b7c27bf8 | |||
c57d6c18cf | |||
ae346da3f2 | |||
ff47e2c76c | |||
f9efb7b9b8 | |||
f5e68da346 | |||
e05799bbbc | |||
34bead8b67 | |||
33bc12d779 | |||
28a2616021 | |||
2ccd2bfbff | |||
c57324f7ad | |||
3c7d9e8b41 | |||
ba8812a6ab |
35
.github/workflows/translate.yml
vendored
Normal file
35
.github/workflows/translate.yml
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
name: Translate
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- "resources/strings/strings.xml"
|
||||
- "translate.py"
|
||||
- "resources-*/strings/corrections.xml"
|
||||
|
||||
jobs:
|
||||
translate:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
# Give the default GITHUB_TOKEN write permission to commit and push the
|
||||
# added or changed files to the repository.
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4.7.1
|
||||
|
||||
- run: |
|
||||
pip install beautifulsoup4
|
||||
pip install deep-translator
|
||||
pip install lxml
|
||||
|
||||
- run: python translate.py
|
||||
|
||||
# Commit all changed files back to the repository
|
||||
- uses: stefanzweifel/git-auto-commit-action@v5
|
||||
with:
|
||||
push_options: '--force'
|
@ -2,10 +2,6 @@
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
},
|
||||
{
|
||||
"name": "abc",
|
||||
"path": "../blah/abc"
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
|
22
README.md
22
README.md
@ -34,7 +34,8 @@ Example schema as shown in the images:
|
||||
{
|
||||
"entity": "script.food_on_table",
|
||||
"name": "Food is Ready!",
|
||||
"type": "tap"
|
||||
"type": "tap",
|
||||
"service" : "script.turn_on"
|
||||
},
|
||||
{
|
||||
"entity": "light.bedside_light_switch",
|
||||
@ -79,6 +80,12 @@ Example schema as shown in the images:
|
||||
"name": "Garage Door Check",
|
||||
"type": "toggle"
|
||||
},
|
||||
{
|
||||
"entity": "automation.turn_off_usb_chargers",
|
||||
"name": "Turn off USBs",
|
||||
"type": "tap",
|
||||
"service" : "automation.trigger"
|
||||
},
|
||||
{
|
||||
"entity": "scene.tv_light",
|
||||
"name": "TV Lights Scene",
|
||||
@ -99,6 +106,18 @@ The example above illustrates how to configure:
|
||||
* Service invocation, e.g. Scene setting, (tap)
|
||||
* A sub-menu to open (tap)
|
||||
|
||||
The example JSON shows an example usage of each of these Home Assistance entity types. Presently, an automation is the only one that can be either a 'tap' or a 'toggle'.
|
||||
|
||||
| HA Type | Tap | Toggle |
|
||||
|------------|:---:|:------:|
|
||||
| Switch | N | Y |
|
||||
| Light | N | Y |
|
||||
| Automation | Y | Y |
|
||||
| Script | Y | N |
|
||||
| Scene | Y | N |
|
||||
|
||||
NB. All 'tap' items must specify a 'service' tag.
|
||||
|
||||
Possible future extensions might include specifying the alternative texts to use instead of "On" and "Off", e.g. "Locked" and "Unlocked" (but wouldn't having locks operated from your watch be a security concern ;-))
|
||||
|
||||
The [schema](https://raw.githubusercontent.com/house-of-abbey/GarminHomeAssistant/main/config.schema.json) is checked by using a URL directly back to this GitHub source repository, so you do not need to install that file. You can just copy & paste your entity names from the YAML configuration files used to configure Home Assistant. With a submenu, there's a difference between "title" and "name". The "name" goes on the menu item, and the "title" at the head of the submenu. If your dashboard definition fails to meet the schema, the application will simply drop items with the wrong field names without warning.
|
||||
@ -150,3 +169,4 @@ When you change the JSON file defining your dashboard, you must exit the applica
|
||||
| 1.0 | Initial release for 26 devices. |
|
||||
| 1.1 | Updated for 54 more devices, 80 in total. Scene support. Added vibrate acknowledgement for tap-based menu items. Falls back to a custom visual confirmation in the absence of 'toast' and vibrate support. Bug fix for large menus needing status updates. |
|
||||
| 1.2 | Do not crash on zero items to update. Report unreachable URLs. Verify API URL does not have a trailing slash '/'. Increased HTTP response diagnosis. Reduced minimum API Level required from 3.3.0 to 3.1.0 to allow more device "part numbers" to be satisfied. |
|
||||
| 1.3 | Tap for scripts was working in emulation but not on some phones. Decision is to make the 'service' field in the JSON compulsory for 'tap' menu items. This is a breaking change, but for many might be a fix for something not working correctly. Improve language support, we can now accept language corrections and prevent the automated translation of strings from clobbering manually refined entries. Thank you to two new contributors. |
|
@ -26,7 +26,7 @@
|
||||
"type": { "const": "tap" },
|
||||
"service": { "$ref": "#/$defs/entity" }
|
||||
},
|
||||
"required": ["entity", "name", "type"],
|
||||
"required": ["entity", "name", "type", "service"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"menu": {
|
||||
|
@ -25,7 +25,7 @@
|
||||
<string id="MenuItemTap">Tryk på</string>
|
||||
<string id="MenuItemMenu">Menu</string>
|
||||
<string id="NoInternet">Ingen internetforbindelse</string>
|
||||
<string id="NoMenu">Menuhentningsfejl</string>
|
||||
<string id="NoMenu">Fejl ved menuhentning</string>
|
||||
<string id="NoAPIKey">Ingen API-nøgle i applikationsindstillingerne</string>
|
||||
<string id="NoApiUrl">Ingen API-URL i applikationsindstillingerne</string>
|
||||
<string id="NoConfigUrl">Ingen konfigurations-URL i applikationsindstillingerne</string>
|
||||
|
25
resources-deu/strings/corrections.xml
Normal file
25
resources-deu/strings/corrections.xml
Normal file
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
|
||||
Distributed under MIT Licence
|
||||
See https://github.com/house-of-abbey/GarminHomeAssistant/blob/main/LICENSE.
|
||||
|
||||
|
||||
GarminHomeAssistant is a Garmin IQ application written in Monkey C and routinely
|
||||
tested on a Venu 2 device. The source code is provided at:
|
||||
https://github.com/house-of-abbey/GarminHomeAssistant.
|
||||
|
||||
P A Abbey & J D Abbey, 14 November 2023
|
||||
|
||||
-->
|
||||
|
||||
<!--
|
||||
Corrections for the German language
|
||||
Korrekturen für die deutsche Sprache
|
||||
-->
|
||||
|
||||
<strings>
|
||||
<string id="MenuItemTap">Antippen</string>
|
||||
<string id="MenuItemMenu">Menü</string>
|
||||
<string id="UnhandledHttpErr">Die HTTP-Anfrage gab folgenden Fehlercode zurück = </string>
|
||||
</strings>
|
@ -22,8 +22,8 @@
|
||||
<string id="AppName">HomeAssistant</string>
|
||||
<string id="MenuItemOn">An</string>
|
||||
<string id="MenuItemOff">Aus</string>
|
||||
<string id="MenuItemTap">Klopfen</string>
|
||||
<string id="MenuItemMenu">Speisekarte</string>
|
||||
<string id="MenuItemTap">Antippen</string>
|
||||
<string id="MenuItemMenu">Menü</string>
|
||||
<string id="NoInternet">Keine Internetverbindung</string>
|
||||
<string id="NoMenu">Fehler beim Abrufen des Menüs</string>
|
||||
<string id="NoAPIKey">Kein API-Schlüssel in den Anwendungseinstellungen</string>
|
||||
@ -32,6 +32,6 @@
|
||||
<string id="ApiFlood">API-Aufrufe zu schnell. Bitte verlangsamen Sie Ihre Anfragen.</string>
|
||||
<string id="ApiUrlNotFound">URL nicht gefunden. Möglicher API-URL-Fehler in den Einstellungen.</string>
|
||||
<string id="ConfigUrlNotFound">URL nicht gefunden. Möglicher Konfigurations-URL-Fehler in den Einstellungen.</string>
|
||||
<string id="UnhandledHttpErr">Die HTTP-Anfrage hat den Fehlercode = zurückgegeben</string>
|
||||
<string id="UnhandledHttpErr">Die HTTP-Anfrage gab folgenden Fehlercode zurück = </string>
|
||||
<string id="TrailingSlashErr">Die API-URL darf keinen abschließenden Schrägstrich „/“ enthalten.</string>
|
||||
</strings>
|
@ -15,7 +15,7 @@
|
||||
|
||||
<!--
|
||||
Generated by Google Translate: English to Estonian
|
||||
Loodud Google'i tõlke abil inglise keelest
|
||||
Inglise keelest loodud Google'i tõlke abil
|
||||
-->
|
||||
|
||||
<strings>
|
||||
|
25
resources-fre/strings/corrections.xml
Normal file
25
resources-fre/strings/corrections.xml
Normal file
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
|
||||
Distributed under MIT Licence
|
||||
See https://github.com/house-of-abbey/GarminHomeAssistant/blob/main/LICENSE.
|
||||
|
||||
|
||||
GarminHomeAssistant is a Garmin IQ application written in Monkey C and routinely
|
||||
tested on a Venu 2 device. The source code is provided at:
|
||||
https://github.com/house-of-abbey/GarminHomeAssistant.
|
||||
|
||||
P A Abbey & J D Abbey, 14 November 2023
|
||||
|
||||
-->
|
||||
|
||||
<!--
|
||||
Corrections for the French language
|
||||
Corrections pour la langue française
|
||||
-->
|
||||
|
||||
<strings>
|
||||
<string id="MenuItemOn">Activé</string>
|
||||
<string id="MenuItemTap">Clic</string>
|
||||
<string id="ApiFlood">Appels API trop rapide. Veuillez signaler cette erreur avec les détails de l'appareil.</string>
|
||||
</strings>
|
@ -20,16 +20,16 @@
|
||||
|
||||
<strings>
|
||||
<string id="AppName">HomeAssistant</string>
|
||||
<string id="MenuItemOn">Sur</string>
|
||||
<string id="MenuItemOn">Activé</string>
|
||||
<string id="MenuItemOff">Désactivé</string>
|
||||
<string id="MenuItemTap">Robinet</string>
|
||||
<string id="MenuItemTap">Clic</string>
|
||||
<string id="MenuItemMenu">Menu</string>
|
||||
<string id="NoInternet">Pas de connexion Internet</string>
|
||||
<string id="NoMenu">Erreur de récupération du menu</string>
|
||||
<string id="NoAPIKey">Pas de clé API dans les paramètres de l'application</string>
|
||||
<string id="NoApiUrl">Aucune URL API dans les paramètres de l'application</string>
|
||||
<string id="NoConfigUrl">Aucune URL de configuration dans les paramètres de l'application</string>
|
||||
<string id="ApiFlood">Appels API trop rapides. Veuillez ralentir vos demandes.</string>
|
||||
<string id="ApiFlood">Appels API trop rapide. Veuillez signaler cette erreur avec les détails de l'appareil.</string>
|
||||
<string id="ApiUrlNotFound">URL introuvable. Erreur potentielle d'URL d'API dans les paramètres.</string>
|
||||
<string id="ConfigUrlNotFound">URL introuvable. Erreur potentielle d'URL de configuration dans les paramètres.</string>
|
||||
<string id="UnhandledHttpErr">La requête HTTP a renvoyé un code d'erreur =</string>
|
||||
|
@ -33,5 +33,5 @@
|
||||
<string id="ApiUrlNotFound">URL을 찾을 수 없습니다. 설정에 잠재적인 API URL 오류가 있습니다.</string>
|
||||
<string id="ConfigUrlNotFound">URL을 찾을 수 없습니다. 설정에 잠재적인 구성 URL 오류가 있습니다.</string>
|
||||
<string id="UnhandledHttpErr">HTTP 요청이 오류 코드를 반환했습니다 =</string>
|
||||
<string id="TrailingSlashErr">API URL에는 후행 슬래시 '/'가 있어서는 안 됩니다.</string>
|
||||
<string id="TrailingSlashErr">API URL에는 후행 슬래시 '/'가 없어야 합니다.</string>
|
||||
</strings>
|
@ -33,5 +33,5 @@
|
||||
<string id="ApiUrlNotFound">URL bulunamadı. Ayarlarda olası API URL hatası.</string>
|
||||
<string id="ConfigUrlNotFound">URL bulunamadı. Ayarlarda Olası Yapılandırma URL'si hatası.</string>
|
||||
<string id="UnhandledHttpErr">HTTP isteği hata kodunu döndürdü =</string>
|
||||
<string id="TrailingSlashErr">API URL'sinin sonunda '/' eğik çizgi olmamalıdır</string>
|
||||
<string id="TrailingSlashErr">API URL'sinin sonunda eğik çizgi '/' olmamalıdır</string>
|
||||
</strings>
|
@ -15,7 +15,7 @@
|
||||
|
||||
<!--
|
||||
Generated by Google Translate: English to Ukrainian
|
||||
Створено Google Translate з англійської
|
||||
Згенеровано Google Translate з англійської
|
||||
-->
|
||||
|
||||
<strings>
|
||||
|
@ -77,36 +77,27 @@ class HomeAssistantMenuItem extends WatchUi.MenuItem {
|
||||
}
|
||||
WatchUi.pushView(new ErrorView(strApiUrlNotFound), new ErrorDelegate(), WatchUi.SLIDE_UP);
|
||||
} else if (responseCode == 200) {
|
||||
var d = data as Lang.Array;
|
||||
if (Globals.scDebug) {
|
||||
System.println("HomeAssistantMenuItem onReturnExecScript(): Service executed.");
|
||||
}
|
||||
var d = data as Lang.Array;
|
||||
var toast = "Executed";
|
||||
for(var i = 0; i < d.size(); i++) {
|
||||
if ((d[i].get("entity_id") as Lang.String).equals(mIdentifier)) {
|
||||
if (Globals.scDebug) {
|
||||
System.println("HomeAssistantMenuItem onReturnExecScript(): Correct script executed.");
|
||||
}
|
||||
if (WatchUi has :showToast) {
|
||||
WatchUi.showToast(
|
||||
(d[i].get("attributes") as Lang.Dictionary).get("friendly_name") as Lang.String,
|
||||
null
|
||||
);
|
||||
}
|
||||
if (Attention has :vibrate) {
|
||||
Attention.vibrate([
|
||||
new Attention.VibeProfile(50, 100), // On for 100ms
|
||||
new Attention.VibeProfile( 0, 100), // Off for 100ms
|
||||
new Attention.VibeProfile(50, 100) // On for 100ms
|
||||
]);
|
||||
}
|
||||
if (!(WatchUi has :showToast) && !(Attention has :vibrate)) {
|
||||
new Alert({
|
||||
:timeout => Globals.scAlertTimeout,
|
||||
:font => Graphics.FONT_MEDIUM,
|
||||
:text => (d[i].get("attributes") as Lang.Dictionary).get("friendly_name") as Lang.String,
|
||||
:fgcolor => Graphics.COLOR_WHITE,
|
||||
:bgcolor => Graphics.COLOR_BLACK
|
||||
}).pushView(WatchUi.SLIDE_IMMEDIATE);
|
||||
}
|
||||
toast = (d[i].get("attributes") as Lang.Dictionary).get("friendly_name") as Lang.String;
|
||||
}
|
||||
}
|
||||
if (WatchUi has :showToast) {
|
||||
WatchUi.showToast(toast, null);
|
||||
} else {
|
||||
new Alert({
|
||||
:timeout => Globals.scAlertTimeout,
|
||||
:font => Graphics.FONT_MEDIUM,
|
||||
:text => toast,
|
||||
:fgcolor => Graphics.COLOR_WHITE,
|
||||
:bgcolor => Graphics.COLOR_BLACK
|
||||
}).pushView(WatchUi.SLIDE_IMMEDIATE);
|
||||
}
|
||||
} else {
|
||||
if (Globals.scDebug) {
|
||||
System.println("HomeAssistantMenuItem onReturnExecScript(): Unhandled HTTP response code = " + responseCode);
|
||||
@ -129,7 +120,7 @@ class HomeAssistantMenuItem extends WatchUi.MenuItem {
|
||||
// ERROR: venu: Cannot find symbol ':substring' on type 'PolyType<Null or $.Toybox.Lang.Object>'.
|
||||
var id = mIdentifier as Lang.String;
|
||||
if (mService == null) {
|
||||
var url = (Properties.getValue("api_url") as Lang.String) + "/services/" + id.substring(0, id.find(".")) + "/" + id.substring(id.find(".")+1, id.length());
|
||||
var url = (Properties.getValue("api_url") as Lang.String) + "/services/" + id.substring(0, id.find(".")) + "/" + id.substring(id.find(".")+1, null);
|
||||
if (Globals.scDebug) {
|
||||
System.println("HomeAssistantMenuItem execScript() URL=" + url);
|
||||
System.println("HomeAssistantMenuItem execScript() mIdentifier=" + mIdentifier);
|
||||
@ -144,7 +135,7 @@ class HomeAssistantMenuItem extends WatchUi.MenuItem {
|
||||
var url = (Properties.getValue("api_url") as Lang.String) + "/services/" + mService.substring(0, mService.find(".")) + "/" + mService.substring(mService.find(".")+1, null);
|
||||
if (Globals.scDebug) {
|
||||
System.println("HomeAssistantMenuItem execScript() URL=" + url);
|
||||
System.println("HomeAssistantMenuItem execScript() mIdentifier=" + mIdentifier);
|
||||
System.println("HomeAssistantMenuItem execScript() mService=" + mService);
|
||||
}
|
||||
Communications.makeWebRequest(
|
||||
url,
|
||||
@ -155,6 +146,13 @@ class HomeAssistantMenuItem extends WatchUi.MenuItem {
|
||||
method(:onReturnExecScript)
|
||||
);
|
||||
}
|
||||
if (Attention has :vibrate) {
|
||||
Attention.vibrate([
|
||||
new Attention.VibeProfile(50, 100), // On for 100ms
|
||||
new Attention.VibeProfile( 0, 100), // Off for 100ms
|
||||
new Attention.VibeProfile(50, 100) // On for 100ms
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
if (Globals.scDebug) {
|
||||
System.println("HomeAssistantMenuItem execScript(): No Internet connection, skipping API call.");
|
||||
|
19
translate.py
19
translate.py
@ -18,7 +18,7 @@
|
||||
# language using Google Translate.
|
||||
#
|
||||
# Python installation:
|
||||
# pip install BeautifulSoup
|
||||
# pip install beautifulsoup4
|
||||
# pip install deep-translator
|
||||
# NB. For XML formatting:
|
||||
# pip install lxml
|
||||
@ -84,9 +84,15 @@ exceptionIds: list[str] = ["AppName", "AppVersionTitle"]
|
||||
titleIds: list[str] = ["setMode", "tapIcon"]
|
||||
|
||||
i = 1
|
||||
with open("./resources/strings/strings.xml") as f:
|
||||
with open("./resources/strings/strings.xml", "r") as f:
|
||||
c = f.read().replace('\r', '')
|
||||
for l in languages:
|
||||
os.makedirs(f"./resources-{l[0]}/strings/", exist_ok=True)
|
||||
try:
|
||||
with open(f"./resources-{l[0]}/strings/corrections.xml", "r") as r:
|
||||
curr = BeautifulSoup(r.read().replace('\r', ''), features="xml")
|
||||
except FileNotFoundError:
|
||||
curr = BeautifulSoup("", features=["xml"])
|
||||
print(f"{i} of {langLength}: Translating English to {l[2]}")
|
||||
soup = BeautifulSoup(c, features="xml")
|
||||
soup.find(name="strings").insert_before("\n\n")
|
||||
@ -99,7 +105,12 @@ with open("./resources/strings/strings.xml") as f:
|
||||
|
||||
for s in soup.find(name="strings").findAll(name="string"):
|
||||
s.insert_before(" ")
|
||||
if s["id"] not in exceptionIds:
|
||||
if s["id"] in exceptionIds: continue
|
||||
|
||||
s_curr = curr.find(name="string", attrs={ "id": s["id"] })
|
||||
if s_curr:
|
||||
s.string = s_curr.string
|
||||
else:
|
||||
a = GoogleTranslator(source='en', target=l[1]).translate(s.string)
|
||||
if s["id"] in titleIds:
|
||||
s.string = a.title()
|
||||
@ -108,8 +119,8 @@ with open("./resources/strings/strings.xml") as f:
|
||||
for s in soup.find(name="strings").findAll(text=lambda text:isinstance(text, Comment)):
|
||||
s.insert_before(" ")
|
||||
s.replace_with(Comment(" " + GoogleTranslator(source='en', target=l[1]).translate(s) + " "))
|
||||
|
||||
#print(str(soup))
|
||||
os.makedirs(f"./resources-{l[0]}/strings/", exist_ok=True)
|
||||
with open(f"./resources-{l[0]}/strings/strings.xml", "wb") as w:
|
||||
w.write(soup.encode("utf-8"))
|
||||
i += 1
|
||||
|
Reference in New Issue
Block a user