-
-
+
+
\ No newline at end of file
diff --git a/docs/de_DE/changelog.md b/docs/de_DE/changelog.md
index 6161cba..d412b8b 100644
--- a/docs/de_DE/changelog.md
+++ b/docs/de_DE/changelog.md
@@ -6,6 +6,12 @@
>
>Zur Erinnerung: Wenn keine Informationen zum Update vorhanden sind, bedeutet dies, dass es sich nur um die Aktualisierung von Dokumentation, Übersetzung oder Text handelt
+- Fix une fuite de mémoire sur le démon
+- Corrections mineures
+- Adapatations interface "Liste des commandes"
+- Jeedom 4.4 ou plus requis
+- Debian 11 ou plus requis
+
# 03/11/2021
- Aktualisierung des Abhängigkeitsskripts nach der Kryptografieänderung
@@ -20,7 +26,6 @@
- Neue Darstellung der Objektliste
- Hinzufügung des Tags "V4-Kompatibilität"
-
# 02/06/2020
Erste Version
diff --git a/docs/en_US/changelog.md b/docs/en_US/changelog.md
index 06562eb..ff137cd 100644
--- a/docs/en_US/changelog.md
+++ b/docs/en_US/changelog.md
@@ -6,6 +6,12 @@
>
>As a reminder if there is no information on the update, it means that it only concerns the updating of documentation, translation or text
+- Fix une fuite de mémoire sur le démon
+- Corrections mineures
+- Adapatations interface "Liste des commandes"
+- Jeedom 4.4 ou plus requis
+- Debian 11 ou plus requis
+
# 03/11/2021
- Update of the dependency script following the cryptography change
@@ -20,7 +26,6 @@
- New presentation of the list of objects
- Addition of the tag "V4 compatibility"
-
# 06/02/2020
First Version
diff --git a/docs/es_ES/changelog.md b/docs/es_ES/changelog.md
index 6ad99ad..8c8186a 100644
--- a/docs/es_ES/changelog.md
+++ b/docs/es_ES/changelog.md
@@ -6,13 +6,19 @@
>
>Como recordatorio si no hay información sobre la actualización, significa que solo se refiere a la actualización de documentación, traducción o texto
+- Fix une fuite de mémoire sur le démon
+- Corrections mineures
+- Adapatations interface "Liste des commandes"
+- Jeedom 4.4 ou plus requis
+- Debian 11 ou plus requis
+
# 11/03/2021
- Actualización del script de dependencia tras el cambio de criptografía
# 04/01/2021
-- Bugfix envoie de commande si hci différent de 0
+- La corrección de errores envía un comando si hci es diferente de 0
# 27/11/2020
@@ -20,7 +26,6 @@
- Nueva presentación de la lista de objetos
- Adición de la etiqueta "compatibilidad V4"
-
# 02/02/2020
Primera versión
diff --git a/docs/fr_FR/changelog.md b/docs/fr_FR/changelog.md
index 682fec2..3829896 100644
--- a/docs/fr_FR/changelog.md
+++ b/docs/fr_FR/changelog.md
@@ -6,6 +6,15 @@
>
>Pour rappel s'il n'y a pas d'information sur la mise à jour, c'est que celle-ci concerne uniquement de la mise à jour de documentation, de traduction ou de texte
+# 07/05/2026
+
+- Fix une fuite de mémoire sur le démon
+- Corrections mineures
+- Adapatations interface "Liste des commandes"
+- Corrections des traductions
+- Jeedom 4.4 ou plus requis
+- Debian 11 ou plus requis
+
# 11/03/2021
- Mise à jour du script de dépendances suite au changement cryptography
@@ -20,7 +29,6 @@
- Nouvelle présentation de la liste des objets
- Ajout du tag "Compatibilité V4"
-
# 06/02/2020
Première version
diff --git a/docs/i18n/de_DE.json b/docs/i18n/de_DE.json
index 0ed3554..8a630a3 100644
--- a/docs/i18n/de_DE.json
+++ b/docs/i18n/de_DE.json
@@ -3,6 +3,12 @@
"Changelog Odace SFSP": "Changelog Odace SFSP",
"Plugin Beagle": "Beagle Plugin",
"Pour rappel s'il n'y a pas d'information sur la mise à jour, c'est que celle-ci concerne uniquement de la mise à jour de documentation, de traduction ou de texte": "Zur Erinnerung: Wenn keine Informationen zum Update vorhanden sind, bedeutet dies, dass es sich nur um die Aktualisierung von Dokumentation, Übersetzung oder Text handelt",
+ "Fix une fuite de mémoire sur le démon": "Fix une fuite de mémoire sur le démon",
+ "Corrections mineures": "Corrections mineures",
+ "Adapatations interface \"Liste des commandes": "Adapatations interface \"Liste des commandes",
+ "Jeedom 4": "Jeedom 4",
+ "4 ou plus requis": "4 ou plus requis",
+ "Debian 11 ou plus requis": "Debian 11 ou plus requis",
"11\/03\/2021": "03\/11\/2021",
"Mise à jour du script de dépendances suite au changement cryptography": "Aktualisierung des Abhängigkeitsskripts nach der Kryptografieänderung",
"04\/01\/2021": "04\/01\/2021",
diff --git a/docs/i18n/en_US.json b/docs/i18n/en_US.json
index 7e1e358..fb5f79d 100644
--- a/docs/i18n/en_US.json
+++ b/docs/i18n/en_US.json
@@ -3,6 +3,12 @@
"Changelog Odace SFSP": "Changelog Odace SFSP",
"Plugin Beagle": "Beagle plugin",
"Pour rappel s'il n'y a pas d'information sur la mise à jour, c'est que celle-ci concerne uniquement de la mise à jour de documentation, de traduction ou de texte": "As a reminder if there is no information on the update, it means that it only concerns the updating of documentation, translation or text",
+ "Fix une fuite de mémoire sur le démon": "Fix une fuite de mémoire sur le démon",
+ "Corrections mineures": "Corrections mineures",
+ "Adapatations interface \"Liste des commandes": "Adapatations interface \"Liste des commandes",
+ "Jeedom 4": "Jeedom 4",
+ "4 ou plus requis": "4 ou plus requis",
+ "Debian 11 ou plus requis": "Debian 11 ou plus requis",
"11\/03\/2021": "03\/11\/2021",
"Mise à jour du script de dépendances suite au changement cryptography": "Update of the dependency script following the cryptography change",
"04\/01\/2021": "04\/01\/2021",
diff --git a/docs/i18n/es_ES.json b/docs/i18n/es_ES.json
index 7a359eb..f13f329 100644
--- a/docs/i18n/es_ES.json
+++ b/docs/i18n/es_ES.json
@@ -3,10 +3,16 @@
"Changelog Odace SFSP": "Registro de cambios Odace SFSP",
"Plugin Beagle": "Complemento Beagle",
"Pour rappel s'il n'y a pas d'information sur la mise à jour, c'est que celle-ci concerne uniquement de la mise à jour de documentation, de traduction ou de texte": "Como recordatorio si no hay información sobre la actualización, significa que solo se refiere a la actualización de documentación, traducción o texto",
+ "Fix une fuite de mémoire sur le démon": "Fix une fuite de mémoire sur le démon",
+ "Corrections mineures": "Corrections mineures",
+ "Adapatations interface \"Liste des commandes": "Adapatations interface \"Liste des commandes",
+ "Jeedom 4": "Jeedom 4",
+ "4 ou plus requis": "4 ou plus requis",
+ "Debian 11 ou plus requis": "Debian 11 ou plus requis",
"11\/03\/2021": "11\/03\/2021",
"Mise à jour du script de dépendances suite au changement cryptography": "Actualización del script de dependencia tras el cambio de criptografía",
"04\/01\/2021": "04\/01\/2021",
- "Bugfix envoie de commande si hci différent de 0": "Bugfix envoie de commande si hci différent de 0",
+ "Bugfix envoie de commande si hci différent de 0": "La corrección de errores envía un comando si hci es diferente de 0",
"27\/11\/2020": "27\/11\/2020",
"Optimisations générales": "Optimizaciones generales",
"Nouvelle présentation de la liste des objets": "Nueva presentación de la lista de objetos",
diff --git a/docs/i18n/fr_FR.json b/docs/i18n/fr_FR.json
index fa3d1a8..362747c 100644
--- a/docs/i18n/fr_FR.json
+++ b/docs/i18n/fr_FR.json
@@ -4,6 +4,12 @@
"Plugin Beagle": "Plugin Beagle",
"IMPORTANT": "IMPORTANT",
"Pour rappel s'il n'y a pas d'information sur la mise à jour, c'est que celle-ci concerne uniquement de la mise à jour de documentation, de traduction ou de texte": "Pour rappel s'il n'y a pas d'information sur la mise à jour, c'est que celle-ci concerne uniquement de la mise à jour de documentation, de traduction ou de texte",
+ "Fix une fuite de mémoire sur le démon": "Fix une fuite de mémoire sur le démon",
+ "Corrections mineures": "Corrections mineures",
+ "Adapatations interface \"Liste des commandes": "Adapatations interface \"Liste des commandes",
+ "Jeedom 4": "Jeedom 4",
+ "4 ou plus requis": "4 ou plus requis",
+ "Debian 11 ou plus requis": "Debian 11 ou plus requis",
"11\/03\/2021": "11\/03\/2021",
"Mise à jour du script de dépendances suite au changement cryptography": "Mise à jour du script de dépendances suite au changement cryptography",
"04\/01\/2021": "04\/01\/2021",
diff --git a/docs/i18n/pt_PT.json b/docs/i18n/pt_PT.json
index e26d251..9feb906 100644
--- a/docs/i18n/pt_PT.json
+++ b/docs/i18n/pt_PT.json
@@ -3,6 +3,12 @@
"Changelog Odace SFSP": "Changelog Odace SFSP",
"Plugin Beagle": "Plug-in do Beagle",
"Pour rappel s'il n'y a pas d'information sur la mise à jour, c'est que celle-ci concerne uniquement de la mise à jour de documentation, de traduction ou de texte": "Como lembrete, se não houver informações sobre a atualização, isso significa que se trata apenas da atualização da documentação, tradução ou texto",
+ "Fix une fuite de mémoire sur le démon": "Fix une fuite de mémoire sur le démon",
+ "Corrections mineures": "Corrections mineures",
+ "Adapatations interface \"Liste des commandes": "Adapatations interface \"Liste des commandes",
+ "Jeedom 4": "Jeedom 4",
+ "4 ou plus requis": "4 ou plus requis",
+ "Debian 11 ou plus requis": "Debian 11 ou plus requis",
"11\/03\/2021": "11\/03\/2021",
"Mise à jour du script de dépendances suite au changement cryptography": "Atualização do script de dependência seguindo a mudança de criptografia",
"04\/01\/2021": "04\/01\/2021",
diff --git a/docs/pt_PT/changelog.md b/docs/pt_PT/changelog.md
index 2d11fc7..fe46f50 100644
--- a/docs/pt_PT/changelog.md
+++ b/docs/pt_PT/changelog.md
@@ -6,6 +6,12 @@
>
>Como lembrete, se não houver informações sobre a atualização, isso significa que se trata apenas da atualização da documentação, tradução ou texto
+- Fix une fuite de mémoire sur le démon
+- Corrections mineures
+- Adapatations interface "Liste des commandes"
+- Jeedom 4.4 ou plus requis
+- Debian 11 ou plus requis
+
# 11/03/2021
- Atualização do script de dependência seguindo a mudança de criptografia
@@ -20,7 +26,6 @@
- Nova apresentação da lista de objetos
- Adição da tag "compatibilidade V4"
-
# 02/06/2020
Primeira versão
diff --git a/plugin_info/._info.json b/plugin_info/._info.json
deleted file mode 100644
index 4a3e1c4..0000000
Binary files a/plugin_info/._info.json and /dev/null differ
diff --git a/plugin_info/configuration.php b/plugin_info/configuration.php
index 0d0b8ba..ceae585 100644
--- a/plugin_info/configuration.php
+++ b/plugin_info/configuration.php
@@ -15,46 +15,46 @@
* along with Jeedom. If not, see .
*/
-require_once dirname(__FILE__) . '/../../../core/php/core.inc.php';
+require_once __DIR__ . '/../../../core/php/core.inc.php';
include_file('core', 'authentification', 'php');
if (!isConnect('admin')) {
- throw new Exception('{{401 - Accès non autorisé}}');
+ throw new Exception('{{401 - Accès non autorisé}}');
}
?>
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/plugin_info/info.json b/plugin_info/info.json
index 484fa3c..8421e31 100644
--- a/plugin_info/info.json
+++ b/plugin_info/info.json
@@ -1,45 +1,43 @@
{
- "id": "beagle",
- "name": "Odace SFSP",
- "description": {
- "fr_FR": "Plugin ajoutant le support de la gamme Odace SFSP (sans fil sans pile) Schneider à Jeedom. Il permet d'obtenir les informations des interrupteurs (volets\/ scènes \/ simple \/ double). Il permet de contrôler les actionneurs (volet\/dcl). Il permet aussi de pouvoir utiliser les scènes et les groupes.",
- "en_US": "Plugin adding support for the Odace SFSP range (wireless without battery) Schneider to Jeedom. It is used to obtain information from the switches (shutters \/ scenes \/ single \/ double). It is used to control the actuators (shutter \/ dcl). It also allows you to use scenes and groups",
- "pt_PT": "Plugin adicionando suporte para a gama Odace SFSP (sem fio sem bateria) Schneider para Jeedom. É usado para obter informações dos interruptores (persianas \/ cenas \/ simples \/ duplas). É usado para controlar os atuadores (obturador \/ dcl). Também permite usar cenas e grupos",
- "es_ES": "Plugin ajoutant le support de la gamme Odace SFSP (sans fil sans pile) Schneider à Jeedom. Il permet d'obtenir les informations des interrupteurs (volets\/ scènes \/ simple \/ double). Il permet de contrôler les actionneurs (volet\/dcl). Il permet aussi de pouvoir utiliser les scènes et les groupes",
- "de_DE": "Plugin ajoutant le support de la gamme Odace SFSP (sans fil sans pile) Schneider à Jeedom. Il permet d'obtenir les informations des interrupteurs (volets\/ scènes \/ simple \/ double). Il permet de contrôler les actionneurs (volet\/dcl). Il permet aussi de pouvoir utiliser les scènes et les groupes",
- "ru_RU": "Plugin ajoutant le support de la gamme Odace SFSP (sans fil sans pile) Schneider à Jeedom. Il permet d'obtenir les informations des interrupteurs (volets\/ scènes \/ simple \/ double). Il permet de contrôler les actionneurs (volet\/dcl). Il permet aussi de pouvoir utiliser les scènes et les groupes",
- "id_ID": "Plugin ajoutant le support de la gamme Odace SFSP (sans fil sans pile) Schneider à Jeedom. Il permet d'obtenir les informations des interrupteurs (volets\/ scènes \/ simple \/ double). Il permet de contrôler les actionneurs (volet\/dcl). Il permet aussi de pouvoir utiliser les scènes et les groupes",
- "it_IT": "Plugin ajoutant le support de la gamme Odace SFSP (sans fil sans pile) Schneider à Jeedom. Il permet d'obtenir les informations des interrupteurs (volets\/ scènes \/ simple \/ double). Il permet de contrôler les actionneurs (volet\/dcl). Il permet aussi de pouvoir utiliser les scènes et les groupes",
- "ja_JP": "Plugin ajoutant le support de la gamme Odace SFSP (sans fil sans pile) Schneider à Jeedom. Il permet d'obtenir les informations des interrupteurs (volets\/ scènes \/ simple \/ double). Il permet de contrôler les actionneurs (volet\/dcl). Il permet aussi de pouvoir utiliser les scènes et les groupes",
- "tr": "Plugin ajoutant le support de la gamme Odace SFSP (sans fil sans pile) Schneider à Jeedom. Il permet d'obtenir les informations des interrupteurs (volets\/ scènes \/ simple \/ double). Il permet de contrôler les actionneurs (volet\/dcl). Il permet aussi de pouvoir utiliser les scènes et les groupes"
- },
- "licence": "AGPL",
- "author": "Jeedom SAS",
- "require": "3.2.12",
- "category": "automation protocol",
- "hasDependency": true,
- "hasOwnDeamon": true,
- "changelog": "https:\/\/doc.jeedom.com\/#language#\/plugins\/automation protocol\/beagle\/changelog",
- "documentation": "https:\/\/doc.jeedom.com\/#language#\/plugins\/automation protocol\/beagle\/",
- "language": [
- "fr_FR",
- "en_US",
- "es_ES",
- "de_DE",
- "ru_RU",
- "id_ID",
- "it_IT",
- "ja_JP",
- "pt_PT",
- "tr"
- ],
- "compatibility": [
- "miniplus",
- "smart",
- "rpi",
- "docker",
- "diy",
- "mobile",
- "v4"
- ]
+ "id": "beagle",
+ "name": "Odace SFSP",
+ "description": {
+ "fr_FR": "Plugin ajoutant le support de la gamme Odace SFSP (sans fil sans pile) Schneider à Jeedom. Il permet d'obtenir les informations des interrupteurs (volets/ scènes / simple / double). Il permet de contrôler les actionneurs (volet/dcl). Il permet aussi de pouvoir utiliser les scènes et les groupes.",
+ "en_US": "Plugin adding support for the Odace SFSP range (wireless without battery) Schneider to Jeedom. It is used to obtain information from the switches (shutters / scenes / single / double). It is used to control the actuators (shutter / dcl). It also allows you to use scenes and groups",
+ "pt_PT": "Plugin adicionando suporte para a gama Odace SFSP (sem fio sem bateria) Schneider para Jeedom. É usado para obter informações dos interruptores (persianas / cenas / simples / duplas). É usado para controlar os atuadores (obturador / dcl). Também permite usar cenas e grupos",
+ "es_ES": "Complemento que agrega soporte para la gama Schneider Odace SFSP (inalámbrica sin batería) a Jeedom. Permite obtener información de los interruptores (persianas/escenas/simples/dobles). Permite controlar los actuadores (persiana/dcl). También te permite utilizar escenas y grupos",
+ "de_DE": "Plugin, das die Unterstützung der Odace SFSP-Reihe (drahtlos und batterielos) von Schneider in Jeedom integriert. Es ermöglicht den Abruf von Informationen zu Schaltern (Rollläden/Szenen/Einfach/Doppel). Es ermöglicht die Steuerung von Aktoren (Rollläden/DCL). Außerdem ermöglicht es die Verwendung von Szenen und Gruppen.",
+ "it_IT": "Plugin che aggiunge il supporto della gamma Odace SFSP (wireless senza batteria) di Schneider a Jeedom. Consente di ottenere le informazioni relative agli interruttori (tapparelle/scenari/singolo/doppio). Permette di controllare gli attuatori (tapparelle/dcl). Consente inoltre di utilizzare gli scenari e i gruppi."
+ },
+ "licence": "AGPL",
+ "author": "Jeedom SAS",
+ "require": "4.4",
+ "os": {
+ "min": 11
+ },
+ "category": "automation protocol",
+ "hasDependency": true,
+ "hasOwnDeamon": true,
+ "changelog": "https://doc.jeedom.com/#language#/plugins/automation protocol/beagle/changelog",
+ "documentation": "https://doc.jeedom.com/#language#/plugins/automation protocol/beagle/",
+ "language": [
+ "de_DE",
+ "en_US",
+ "es_ES",
+ "fr_FR",
+ "it_IT",
+ "pt_PT"
+ ],
+ "compatibility": [
+ "smart",
+ "atlas",
+ "luna",
+ "rpi",
+ "docker",
+ "diy",
+ "mobile",
+ "v4"
+ ],
+ "changelog_beta": "https://doc.jeedom.com/#language#/plugins/automation protocol/beagle/beta/changelog",
+ "documentation_beta": "https://doc.jeedom.com/#language#/plugins/automation protocol/beagle/beta"
}
\ No newline at end of file
diff --git a/plugin_info/install.php b/plugin_info/install.php
index f8fb6f1..5ba7400 100644
--- a/plugin_info/install.php
+++ b/plugin_info/install.php
@@ -16,7 +16,7 @@
* along with Jeedom. If not, see .
*/
-require_once dirname(__FILE__) . '/../../../core/php/core.inc.php';
+require_once __DIR__ . '/../../../core/php/core.inc.php';
function beagle_install() {
if (config::byKey('api::beagle::mode') == '') {
@@ -31,7 +31,4 @@ function beagle_update() {
}
function beagle_remove() {
-
}
-
-?>
diff --git a/plugin_info/packages.json b/plugin_info/packages.json
new file mode 100644
index 0000000..c456738
--- /dev/null
+++ b/plugin_info/packages.json
@@ -0,0 +1,26 @@
+{
+ "apt": {
+ "python3-setuptools": {},
+ "rfkill": {},
+ "python3-dev": {},
+ "libffi-dev": {},
+ "libssl-dev": {},
+ "libbluetooth-dev": {},
+ "bluetooth": {},
+ "build-essential": {}
+ },
+ "pip3": {
+ "wheel": {},
+ "requests": {},
+ "setuptools": {
+ "version": "58.0.0"
+ },
+ "cryptography": {
+ "version": "38.0.0"
+ },
+ "pybluez": {}
+ },
+ "post-install": {
+ "script": "plugins/beagle/resources/post_install.sh"
+ }
+}
\ No newline at end of file
diff --git a/resources/beagled/beagle.py b/resources/beagled/beagle.py
index 1edcff6..93f30dc 100644
--- a/resources/beagled/beagle.py
+++ b/resources/beagled/beagle.py
@@ -1,15 +1,9 @@
-import os
-import sys
-import struct
-import bluetooth._bluetooth as bluez
import globals
import logging
-import binascii
-import _thread as thread
-import time
+
class Beagle():
- def __init__(self,trame,mac):
+ def __init__(self, trame, mac):
self.trame = trame
self.mac = mac
self.string = ''
@@ -18,7 +12,7 @@ def __init__(self,trame,mac):
self.type = ''
self.datatrame = ''
self.data = {}
- self.result={}
+ self.result = {}
self.ignore = 0
self.repeat = 0
self.repeateruuid = ''
@@ -26,27 +20,27 @@ def __init__(self,trame,mac):
def parse(self):
try:
- if self.trame[0:14] in ['0201041bffb602','0201061bffb602']:
+ if self.trame[0:14] in ['0201041bffb602', '0201061bffb602']:
self.datatrame = self.trame[22:]
- self.uuid=self.datatrame[2:8]
+ self.uuid = self.datatrame[2:8]
cleanedtrame = self.trame[0:30]+'00'+self.trame[32:]
if self.uuid in globals.lastevent and globals.lastevent[self.uuid] == cleanedtrame:
- self.ignore=1
+ self.ignore = 1
globals.lastevent[self.uuid] = cleanedtrame
self.result['mac'] = self.mac
- self.string =''
+ self.string = ''
self.string += 'Beagle found '
self.type = self.trame[14:18]
- self.cf= self.datatrame[:2]
- self.uuid=self.datatrame[2:8]
+ self.cf = self.datatrame[:2]
+ self.uuid = self.datatrame[2:8]
self.result['uuid'] = self.uuid
self.data['type'] = ''
- repetitionData = str(bin(int(self.trame[30:32],16)))[2:].ljust(8,'0')
- if (repetitionData[1:2] == '0' and repetitionData[2:4]!= '00') :
+ repetitionData = str(bin(int(self.trame[30:32], 16)))[2:].ljust(8, '0')
+ if (repetitionData[1:2] == '0' and repetitionData[2:4] != '00'):
self.repeat = 1
- elif (repetitionData[1:2] == '1' and repetitionData[2:4]!= '11') :
+ elif (repetitionData[1:2] == '1' and repetitionData[2:4] != '11'):
self.repeat = 1
- if (self.repeat == 1) :
+ if (self.repeat == 1):
if (self.uuid in globals.KNOWN_DEVICES):
self.type = globals.types[globals.KNOWN_DEVICES[self.uuid]['model']]
self.string += ' (repeated data) '
@@ -76,16 +70,16 @@ def parse(self):
if self.data['type'] == 'binding':
if self.uuid not in globals.KNOWN_DEVICES:
if not globals.LEARN_MODE:
- logging.debug('It\'s a beagle device but this device is not Included and I\'am not in learn mode ' +str(self.uuid))
+ logging.debug('It\'s a beagle device but this device is not Included and I\'am not in learn mode %s', self.uuid)
return
else:
- logging.debug('It\'s a beagle device but this device is not Included. I\'am learning it ' +str(self.uuid))
+ logging.debug('It\'s a beagle device but this device is not Included. I\'am learning it %s', self.uuid)
globals.LEARN_MODE = False
else:
if self.uuid not in globals.KNOWN_DEVICES:
- logging.debug('It\'s a beagle device but this device is not Included and its not a binding data ' +str(self.uuid))
+ logging.debug('It\'s a beagle device but this device is not Included and its not a binding data %s', self.uuid)
return
- globals.JEEDOM_COM.add_changes('devices::'+self.uuid,self.result)
+ globals.JEEDOM_COM.add_changes('devices::' + self.uuid, self.result)
except Exception as e:
logging.debug(str(e))
@@ -147,7 +141,7 @@ def switch(self):
def dcl(self):
self.result['model'] = 'dcl'
- self.string += 'This is a DCL with UUID '+ self.uuid
+ self.string += 'This is a DCL with UUID ' + self.uuid
if self.cf == '10':
self.data['type'] = 'advertisement'
self.string += ' advertisement'
@@ -172,10 +166,10 @@ def dcl(self):
elif self.trame[32:34] == '13':
self.data['paired'] = 'unpaired'
self.string += ' unpaired'
- self.data['groups'] ={}
+ self.data['groups'] = {}
group1uuid = self.trame[36:44]
- self.data['groups'][group1uuid]={'data':{}}
- self.string += ' group1 : ' +group1uuid
+ self.data['groups'][group1uuid] = {'data': {}}
+ self.string += ' group1 : ' + group1uuid
if self.trame[44:46] == '01':
self.data['groups'][group1uuid]['data']['value'] = '1'
self.data['groups'][group1uuid]['data']['label'] = 'Allumé'
@@ -185,8 +179,8 @@ def dcl(self):
self.data['groups'][group1uuid]['data']['label'] = 'Eteint'
self.string += ' state is OFF'
group2uuid = self.trame[46:54]
- self.data['groups'][group2uuid]={'data':{}}
- self.string += ' group2 : ' +group2uuid
+ self.data['groups'][group2uuid] = {'data': {}}
+ self.string += ' group2 : ' + group2uuid
if self.trame[54:56] == '01':
self.data['groups'][group2uuid]['data']['value'] = '1'
self.data['groups'][group2uuid]['data']['label'] = 'Allumé'
@@ -201,25 +195,25 @@ def dcl(self):
elif self.cf == '1b':
self.data['type'] = 'group'
self.string += ' group'
- self.data['groups'] = [self.trame[38:46],self.trame[46:54]]
+ self.data['groups'] = [self.trame[38:46], self.trame[46:54]]
self.string += ' ' + str(self.data['groups'])
elif self.cf == '1c':
self.data['type'] = 'scene'
self.data['subtype'] = 'custom'
self.string += ' customerscene'
- self.data['scenes'] = [self.trame[38:46],self.trame[46:54],self.trame[54:62]]
+ self.data['scenes'] = [self.trame[38:46], self.trame[46:54], self.trame[54:62]]
self.string += ' ' + str(self.data['scenes'])
elif self.cf == '1d':
self.data['type'] = 'scene'
self.data['subtype'] = 'schneider'
self.string += ' schneiderscene'
- self.data['scenes'] = [self.trame[38:46],self.trame[46:54],self.trame[54:62]]
+ self.data['scenes'] = [self.trame[38:46], self.trame[46:54], self.trame[54:62]]
self.string += ' ' + str(self.data['scenes'])
return
-
+
def generic(self):
self.result['model'] = 'generic'
- self.string += 'This is a Generic with UUID '+ self.uuid
+ self.string += 'This is a Generic with UUID ' + self.uuid
if self.cf == '20':
self.data['type'] = 'advertisement'
self.string += ' advertisement'
@@ -244,10 +238,10 @@ def generic(self):
elif self.trame[32:34] == '13':
self.data['paired'] = 'unpaired'
self.string += ' unpaired'
- self.data['groups'] ={}
+ self.data['groups'] = {}
group1uuid = self.trame[36:44]
- self.data['groups'][group1uuid]={'data':{}}
- self.string += ' group1 : ' +group1uuid
+ self.data['groups'][group1uuid] = {'data': {}}
+ self.string += ' group1 : ' + group1uuid
if self.trame[44:46] == '01':
self.data['groups'][group1uuid]['data']['value'] = '1'
self.data['groups'][group1uuid]['data']['label'] = 'Allumé'
@@ -257,8 +251,8 @@ def generic(self):
self.data['groups'][group1uuid]['data']['label'] = 'Eteint'
self.string += ' state is OFF'
group2uuid = self.trame[46:54]
- self.data['groups'][group2uuid]={'data':{}}
- self.string += ' group2 : ' +group2uuid
+ self.data['groups'][group2uuid] = {'data': {}}
+ self.string += ' group2 : ' + group2uuid
if self.trame[54:56] == '01':
self.data['groups'][group2uuid]['data']['value'] = '1'
self.data['groups'][group2uuid]['data']['label'] = 'Allumé'
@@ -273,25 +267,25 @@ def generic(self):
elif self.cf == '2b':
self.data['type'] = 'group'
self.string += ' group'
- self.data['groups'] = [self.trame[38:46],self.trame[46:54]]
+ self.data['groups'] = [self.trame[38:46], self.trame[46:54]]
self.string += ' ' + str(self.data['groups'])
elif self.cf == '2c':
self.data['type'] = 'scene'
self.data['subtype'] = 'custom'
self.string += ' customerscene'
- self.data['scenes'] = [self.trame[38:46],self.trame[46:54],self.trame[54:62]]
+ self.data['scenes'] = [self.trame[38:46], self.trame[46:54], self.trame[54:62]]
self.string += ' ' + str(self.data['scenes'])
elif self.cf == '2d':
self.data['type'] = 'scene'
self.data['subtype'] = 'schneider'
self.string += ' schneiderscene'
- self.data['scenes'] = [self.trame[38:46],self.trame[46:54],self.trame[54:62]]
+ self.data['scenes'] = [self.trame[38:46], self.trame[46:54], self.trame[54:62]]
self.string += ' ' + str(self.data['scenes'])
return
def shutter(self):
self.result['model'] = 'shutter'
- self.string += 'This is a Shutter with UUID '+ self.uuid
+ self.string += 'This is a Shutter with UUID ' + self.uuid
if self.cf == '30':
self.data['type'] = 'advertisement'
self.data['firmware'] = self.trame[58:62]
@@ -306,17 +300,17 @@ def shutter(self):
self.string += ' state is closed'
elif self.trame[32:34] == '05':
self.data['label'] = 'Ouverture'
- position = 100-int(self.trame[44:46],16)
+ position = 100-int(self.trame[44:46], 16)
self.data['value'] = position
self.string += ' state is moving up'
elif self.trame[32:34] == '06':
self.data['label'] = 'Fermeture'
- position = 100-int(self.trame[44:46],16)
+ position = 100-int(self.trame[44:46], 16)
self.data['value'] = position
self.string += ' state is moving down'
elif self.trame[32:34] == '07':
self.data['label'] = 'Arrêté'
- position = 100-int(self.trame[44:46],16)
+ position = 100-int(self.trame[44:46], 16)
self.data['value'] = position
self.string += ' state is stopped with position ' + str(position)
elif self.trame[32:34] == '10':
@@ -331,10 +325,10 @@ def shutter(self):
elif self.trame[32:34] == '13':
self.data['paired'] = 'unpaired'
self.string += ' unpaired'
- self.data['groups'] ={}
+ self.data['groups'] = {}
group1uuid = self.trame[36:44]
- self.data['groups'][group1uuid]={'data':{}}
- self.string += ' group1 : ' +group1uuid
+ self.data['groups'][group1uuid] = {'data': {}}
+ self.string += ' group1 : ' + group1uuid
if self.trame[44:46] == '00':
self.data['groups'][group1uuid]['data']['value'] = '100'
self.data['groups'][group1uuid]['label'] = 'Ouvert'
@@ -353,8 +347,8 @@ def shutter(self):
self.data['groups'][group1uuid]['data']['label'] = 'Arrêté'
self.string += ' state is stopped'
group2uuid = self.trame[46:54]
- self.data['groups'][group2uuid]={'data':{}}
- self.string += ' group2 : ' +group2uuid
+ self.data['groups'][group2uuid] = {'data': {}}
+ self.string += ' group2 : ' + group2uuid
if self.trame[54:56] == '00':
self.data['groups'][group2uuid]['data']['value'] = '100'
self.data['groups'][group2uuid]['data']['label'] = 'Ouvert'
@@ -378,18 +372,18 @@ def shutter(self):
elif self.cf == '3b':
self.data['type'] = 'group'
self.string += ' group'
- self.data['groups'] = [self.trame[38:46],self.trame[46:54]]
+ self.data['groups'] = [self.trame[38:46], self.trame[46:54]]
self.string += ' ' + str(self.data['groups'])
elif self.cf == '3c':
self.data['type'] = 'scene'
self.data['subtype'] = 'custom'
self.string += ' customerscene'
- self.data['scenes'] = [self.trame[38:46],self.trame[46:54],self.trame[54:62]]
+ self.data['scenes'] = [self.trame[38:46], self.trame[46:54], self.trame[54:62]]
self.string += ' ' + str(self.data['scenes'])
elif self.cf == '3d':
self.data['type'] = 'scene'
self.data['subtype'] = 'schneider'
self.string += ' schneiderscene'
- self.data['scenes'] = [self.trame[38:46],self.trame[46:54],self.trame[54:62]]
+ self.data['scenes'] = [self.trame[38:46], self.trame[46:54], self.trame[54:62]]
self.string += ' ' + str(self.data['scenes'])
return
diff --git a/resources/beagled/beagled.py b/resources/beagled/beagled.py
index 382dae4..0e3fa7d 100644
--- a/resources/beagled/beagled.py
+++ b/resources/beagled/beagled.py
@@ -14,121 +14,116 @@
# along with Jeedom. If not, see .
import logging
-import string
import sys
import os
import time
-import datetime
-import re
import signal
-from optparse import OptionParser
-from os.path import join
import json
import argparse
+import traceback
import blescan
-import sys
-import time
import subprocess
import sendadv
-import _thread as thread
+from threading import Thread
import bluetooth._bluetooth as bluez
-try:
- from jeedom.jeedom import *
-except ImportError as e:
- print("Error: importing module jeedom.jeedom " + str(e))
- sys.exit(1)
+from jeedom.jeedom import jeedom_socket, jeedom_utils, jeedom_com, JEEDOM_SOCKET_MESSAGE
+import globals
+
-def read_socket(name):
+def read_socket():
while 1:
try:
- global JEEDOM_SOCKET_MESSAGE
if not JEEDOM_SOCKET_MESSAGE.empty():
logging.debug("Message received in socket JEEDOM_SOCKET_MESSAGE")
message = JEEDOM_SOCKET_MESSAGE.get().decode('utf-8')
- message =json.loads(message)
+ message = json.loads(message)
if message['apikey'] != _apikey:
- logging.error("Invalid apikey from socket : " + str(message))
+ logging.error("Invalid apikey from socket : %s", message)
return
- logging.debug('Received command from jeedom : '+str(message['cmd']))
+ logging.debug('Received command from jeedom : %s', message['cmd'])
if message['cmd'] == 'learnin':
logging.debug('Enter in learn mode')
globals.LEARN_MODE = True
globals.LEARN_BEGIN = int(time.time())
- globals.JEEDOM_COM.send_change_immediate({'learn_mode' : 1});
+ globals.JEEDOM_COM.send_change_immediate({'learn_mode': 1})
elif message['cmd'] == 'ready':
logging.debug('Daemon is ready')
globals.READY = True
elif message['cmd'] == 'learnout':
logging.debug('Leave learn mode')
globals.LEARN_MODE = False
- globals.JEEDOM_COM.send_change_immediate({'learn_mode' : 0});
+ globals.JEEDOM_COM.send_change_immediate({'learn_mode': 0})
elif message['cmd'] == 'add':
- logging.debug('Add device : '+str(message['device']))
+ logging.debug('Add device : %s', message['device'])
if 'uuid' in message['device']:
globals.KNOWN_DEVICES[message['device']['uuid']] = message['device']
logging.debug('Known devices ' + str(globals.KNOWN_DEVICES))
elif message['cmd'] == 'remove':
- logging.debug('Remove device : '+str(message['device']))
+ logging.debug('Remove device : %s', message['device'])
if 'uuid' in message['device']:
del globals.KNOWN_DEVICES[message['device']['uuid']]
logging.debug('Known devices ' + str(globals.KNOWN_DEVICES))
elif message['cmd'] == 'bind':
- logging.debug('Binding device : '+str(message['uuid']))
- sendadv.sendCmd(globals.KNOWN_DEVICES[message['uuid']],'pair')
+ logging.debug('Binding device : %s', message['uuid'])
+ sendadv.sendCmd(globals.KNOWN_DEVICES[message['uuid']], 'pair')
elif message['cmd'] == 'send':
- logging.debug('Sending to device : '+str(message['target']))
- sendadv.sendCmd(globals.KNOWN_DEVICES[message['target']],'advertisement',message['command'])
+ logging.debug('Sending to device : %s', message['target'])
+ sendadv.sendCmd(globals.KNOWN_DEVICES[message['target']], 'advertisement', message['command'])
except Exception as e:
- logging.error('Exception on socket : '+str(e))
+ logging.error('Exception on socket : %s', e)
time.sleep(0.1)
-def heartbeat_handler(delay):
+
+def heartbeat_handler():
while 1:
- if globals.LEARN_MODE and (globals.LEARN_BEGIN + 60) < int(time.time()):
+ if globals.LEARN_MODE and (globals.LEARN_BEGIN + 60) < int(time.time()):
globals.LEARN_MODE = False
logging.debug('Quitting learn mode (60s elapsed)')
- globals.JEEDOM_COM.send_change_immediate({'learn_mode' : 0});
+ globals.JEEDOM_COM.send_change_immediate({'learn_mode': 0})
time.sleep(1)
+
def listen():
jeedom_socket.open()
try:
- thread.start_new_thread( read_socket, ('socket',))
+ Thread(target=read_socket, daemon=True).start()
logging.debug('Read Socket Thread Launched')
- thread.start_new_thread( ble_scan, ('scanner',))
+ Thread(target=ble_scan, daemon=True).start()
logging.debug('Ble Scanner Thread Launched')
- thread.start_new_thread( heartbeat_handler, (19,))
+ Thread(target=heartbeat_handler, daemon=True).start()
logging.debug('Heartbeat Thread Launched')
except KeyboardInterrupt:
shutdown()
-def ble_scan(name):
+
+def ble_scan():
while not globals.READY:
time.sleep(1)
dev_id = globals.IFACE_DEVICE
try:
sock = bluez.hci_open_dev(dev_id)
- logging.debug("Ble thread started on device " + str(dev_id))
+ logging.debug("Ble thread started on device %s", dev_id)
blescan.hci_le_set_scan_parameters(sock)
blescan.hci_enable_le_scan(sock)
while True:
blescan.parse_events(sock, 10)
- except:
- print("Error accessing bluetooth device...")
+ except Exception as e:
+ logging.error("Error accessing bluetooth device...: %s", e)
sys.exit(1)
# ----------------------------------------------------------------------------
def handler(signum=None, frame=None):
- logging.debug("Signal %i caught, exiting..." % int(signum))
+ logging.debug("Signal %i caught, exiting...", signum)
shutdown()
+
def shutdown():
logging.debug("Shutdown")
- logging.debug("Removing PID file " + str(_pidfile))
+ logging.debug("Removing PID file %s", _pidfile)
try:
os.remove(_pidfile)
except:
@@ -137,16 +132,13 @@ def shutdown():
jeedom_socket.close()
except:
pass
- try:
- jeedom_serial.close()
- except:
- pass
logging.debug("Exit 0")
sys.stdout.flush()
- os._exit(0)
+ sys.exit(0)
# ----------------------------------------------------------------------------
+
_log_level = "error"
_socket_port = 55556
_socket_host = 'localhost'
@@ -156,18 +148,17 @@ def shutdown():
_callback = ''
parser = argparse.ArgumentParser(description='Beagle Daemon for Jeedom plugin')
-parser.add_argument("--socketport", help="Socketport for server", type=str)
+parser.add_argument("--socketport", help="Socketport for server", type=int)
parser.add_argument("--loglevel", help="Log Level for the daemon", type=str)
parser.add_argument("--callback", help="Callback", type=str)
parser.add_argument("--apikey", help="Apikey", type=str)
-parser.add_argument("--cycle", help="Cycle to send event", type=str)
+parser.add_argument("--cycle", help="Cycle to send event", type=float)
parser.add_argument("--pid", help="Pid file", type=str)
parser.add_argument("--device", help="Device", type=str)
parser.add_argument("--sockethost", help="Socket Host", type=str)
parser.add_argument("--jeedomkey", help="Jeedom Key", type=str)
args = parser.parse_args()
-
if args.device:
_device = args.device
if args.socketport:
@@ -183,21 +174,20 @@ def shutdown():
if args.cycle:
_cycle = float(args.cycle)
if args.sockethost:
- _sockethost = args.sockethost
+ _sockethost = args.sockethost
if args.jeedomkey:
- globals.jeedomkey = args.jeedomkey
+ globals.jeedomkey = args.jeedomkey
jeedom_utils.set_log_level(_log_level)
logging.info('Start beagled')
-logging.info('Log level : '+str(_log_level))
-logging.info('Socket port : '+str(_socket_port))
-logging.info('Socket host : '+str(_sockethost))
-logging.info('Device : '+str(_device))
-logging.info('PID file : '+str(_pidfile))
-logging.info('Apikey : '+str(_apikey))
-logging.info('Callback : '+str(_callback))
-logging.info('Cycle : '+str(_cycle))
-logging.info('JeedomKey : '+str(globals.jeedomkey))
+logging.info('Log level : %s', _log_level)
+logging.debug('Socket port : %s', _socket_port)
+logging.debug('Socket host : %s', _sockethost)
+logging.info('Device : %s', _device)
+logging.debug('PID file : %s', _pidfile)
+logging.debug('Callback : %s', _callback)
+logging.debug('Cycle : %s', _cycle)
+logging.debug('JeedomKey : %s', globals.jeedomkey)
signal.signal(signal.SIGINT, handler)
signal.signal(signal.SIGTERM, handler)
globals.IFACE_DEVICE = int(_device[-1:])
@@ -207,16 +197,16 @@ def shutdown():
device_id = _device
status, output = subprocess.getstatusoutput(cmd)
bt_mac = output.split("{}:".format(device_id))[1].split("BD Address: ")[1].split(" ")[0].strip()
- logging.debug('Bluetooth Mac adress is ' + bt_mac)
+ logging.debug('Bluetooth Mac adress is %s', bt_mac)
globals.donglemac = bt_mac
- jeedom_utils.write_pid(str(_pidfile))
- globals.JEEDOM_COM = jeedom_com(apikey = _apikey,url = _callback,cycle=_cycle)
+ jeedom_utils.write_pid(_pidfile)
+ globals.JEEDOM_COM = jeedom_com(apikey=_apikey, url=_callback, cycle=_cycle)
if not globals.JEEDOM_COM.test():
logging.error('Network communication issues. Please fixe your Jeedom network configuration.')
shutdown()
- jeedom_socket = jeedom_socket(port=_socket_port,address=_socket_host)
+ jeedom_socket = jeedom_socket(port=_socket_port, address=_socket_host)
listen()
except Exception as e:
- logging.error('Fatal error : '+str(e))
+ logging.error('Fatal error : %s', e)
logging.debug(traceback.format_exc())
shutdown()
diff --git a/resources/beagled/blescan.py b/resources/beagled/blescan.py
index 0a62280..40577d6 100644
--- a/resources/beagled/blescan.py
+++ b/resources/beagled/blescan.py
@@ -15,57 +15,59 @@
# NOTE: Python's struct.pack() will add padding bytes unless you make the endianness explicit. Little endian
# should be used for BLE. Always start a struct.pack() format string with "<"
-import os
import sys
import struct
+import time
import bluetooth._bluetooth as bluez
-import globals
import logging
from beagle import Beagle
LE_META_EVENT = 0x3e
-LE_PUBLIC_ADDRESS=0x00
-LE_RANDOM_ADDRESS=0x01
-LE_SET_SCAN_PARAMETERS_CP_SIZE=7
-OGF_LE_CTL=0x08
-OCF_LE_SET_SCAN_PARAMETERS=0x000B
-OCF_LE_SET_SCAN_ENABLE=0x000C
-OCF_LE_CREATE_CONN=0x000D
+LE_PUBLIC_ADDRESS = 0x00
+LE_RANDOM_ADDRESS = 0x01
+LE_SET_SCAN_PARAMETERS_CP_SIZE = 7
+OGF_LE_CTL = 0x08
+OCF_LE_SET_SCAN_PARAMETERS = 0x000B
+OCF_LE_SET_SCAN_ENABLE = 0x000C
+OCF_LE_CREATE_CONN = 0x000D
LE_ROLE_MASTER = 0x00
LE_ROLE_SLAVE = 0x01
# these are actually subevents of LE_META_EVENT
-EVT_LE_CONN_COMPLETE=0x01
-EVT_LE_ADVERTISING_REPORT=0x02
-EVT_LE_CONN_UPDATE_COMPLETE=0x03
-EVT_LE_READ_REMOTE_USED_FEATURES_COMPLETE=0x04
+EVT_LE_CONN_COMPLETE = 0x01
+EVT_LE_ADVERTISING_REPORT = 0x02
+EVT_LE_CONN_UPDATE_COMPLETE = 0x03
+EVT_LE_READ_REMOTE_USED_FEATURES_COMPLETE = 0x04
# Advertisment event types
-ADV_IND=0x00
-ADV_DIRECT_IND=0x01
-ADV_SCAN_IND=0x02
-ADV_NONCONN_IND=0x03
-ADV_SCAN_RSP=0x04
+ADV_IND = 0x00
+ADV_DIRECT_IND = 0x01
+ADV_SCAN_IND = 0x02
+ADV_NONCONN_IND = 0x03
+ADV_SCAN_RSP = 0x04
def returnnumberpacket(pkt):
myInteger = 0
multiple = 256
for c in pkt:
- myInteger += struct.unpack("B",bytes([c]))[0] * multiple
+ myInteger += struct.unpack("B", bytes([c]))[0] * multiple
multiple = 1
return myInteger
+
def returnstringpacket(pkt):
- myString = "";
+ myString = ""
for c in pkt:
- myString += "%02x" %struct.unpack("B",bytes([c]))[0]
+ myString += "%02x" % struct.unpack("B", bytes([c]))[0]
return myString
+
def printpacket(pkt):
for c in pkt:
- sys.stdout.write("%02x " % struct.unpack("B",bytes([c]))[0])
+ sys.stdout.write("%02x " % struct.unpack("B", bytes([c]))[0])
+
def get_packed_bdaddr(bdaddr_string):
packable_addr = []
@@ -75,49 +77,48 @@ def get_packed_bdaddr(bdaddr_string):
packable_addr.append(int(b, 16))
return struct.pack(" 0 :
- self.send_changes_async()
- logging.debug('Init request module v%s' % (str(requests.__version__),))
+ def __init__(self, apikey='', url='', cycle=0.5, retry=3):
+ self._apikey = apikey
+ self._url = url
+ self._cycle = cycle
+ self._retry = retry
+ self._changes = {}
+ if self._cycle > 0:
+ Thread(target=self.__thread_changes_async, daemon=True).start()
+ logging.info('Init request module v%s', requests.__version__)
+
+ def __thread_changes_async(self):
+ if self._cycle <= 0:
+ return
+ logging.info('Start changes async thread')
+ while True:
+ try:
+ time.sleep(self._cycle)
+ if len(self._changes) == 0:
+ continue
+ changes = self._changes
+ self._changes = {}
+ self.__post_change(changes)
+ except Exception as error:
+ logging.error('Critical error on send_changes_async %s', error)
+
+ def add_changes(self, key: str, value):
+ if key.find('::') != -1:
+ tmp_changes = {}
+ changes = value
+ for k in reversed(key.split('::')):
+ if k not in tmp_changes:
+ tmp_changes[k] = {}
+ tmp_changes[k] = changes
+ changes = tmp_changes
+ tmp_changes = {}
+ if self._cycle <= 0:
+ self.send_change_immediate(changes)
+ else:
+ self.merge_dict(self._changes, changes)
+ else:
+ if self._cycle <= 0:
+ self.send_change_immediate({key: value})
+ else:
+ self._changes[key] = value
+
+ def send_change_immediate(self, change):
+ Thread(target=self.__post_change, args=(change,)).start()
+
+ def __post_change(self, change):
+ logging.debug('Send to jeedom: %s', change)
+ for i in range(self._retry):
+ try:
+ r = requests.post(self._url + '?apikey=' + self._apikey, json=change, timeout=(0.5, 120), verify=False)
+ if r.status_code == requests.codes.ok:
+ return True
+ else:
+ logging.warning('Error on send request to jeedom, return code %s', r.status_code)
+ except Exception as error:
+ logging.error('Error on send request to jeedom "%s" retry: %i/%i', error, i, self._retry)
+ time.sleep(0.5)
+ return False
+
+ def set_change(self, changes):
+ self._changes = changes
+
+ def get_change(self):
+ return self._changes
+
+ def merge_dict(self, d1, d2):
+ for k, v2 in d2.items():
+ v1 = d1.get(k) # returns None if v1 has no value for this key
+ if isinstance(v1, Mapping) and isinstance(v2, Mapping):
+ self.merge_dict(v1, v2)
+ else:
+ d1[k] = v2
+
+ def test(self):
+ try:
+ response = requests.get(self._url + '?apikey=' + self._apikey, verify=False)
+ if response.status_code != requests.codes.ok:
+ logging.error('Callback error: %s %s. Please check your network configuration page', response.status_code, response.reason)
+ return False
+ except Exception as e:
+ logging.error('Callback result as a unknown error: %s. Please check your network configuration page', e)
+ return False
+ return True
- def send_changes_async(self):
- try:
- if len(self.changes) == 0:
- resend_changes = threading.Timer(self.cycle, self.send_changes_async)
- resend_changes.start()
- return
- start_time = datetime.datetime.now()
- changes = self.changes
- self.changes = {}
- logging.debug('Send to jeedom : '+str(changes))
- i=0
- while i < self.retry:
- try:
- r = requests.post(self.url + '?apikey=' + self.apikey, json=changes, timeout=(0.5, 120), verify=False)
- if r.status_code == requests.codes.ok:
- break
- except Exception as error:
- logging.error('Error on send request to jeedom ' + str(error)+' retry : '+str(i)+'/'+str(self.retry))
- i = i + 1
- if r.status_code != requests.codes.ok:
- logging.error('Error on send request to jeedom, return code %s' % (str(r.status_code),))
- dt = datetime.datetime.now() - start_time
- ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0
- timer_duration = self.cycle - ms
- if timer_duration < 0.1:
- timer_duration = 0.1
- if timer_duration > self.cycle:
- timer_duration = self.cycle
- resend_changes = threading.Timer(timer_duration, self.send_changes_async)
- resend_changes.start()
- except Exception as error:
- logging.error('Critical error on send_changes_async %s' % (str(error),))
- resend_changes = threading.Timer(self.cycle, self.send_changes_async)
- resend_changes.start()
-
- def add_changes(self,key,value):
- if key.find('::') != -1:
- tmp_changes = {}
- changes = value
- for k in reversed(key.split('::')):
- if k not in tmp_changes:
- tmp_changes[k] = {}
- tmp_changes[k] = changes
- changes = tmp_changes
- tmp_changes = {}
- if self.cycle <= 0:
- self.send_change_immediate(changes)
- else:
- self.merge_dict(self.changes,changes)
- else:
- if self.cycle <= 0:
- self.send_change_immediate({key:value})
- else:
- self.changes[key] = value
-
- def send_change_immediate(self,change):
- thread.start_new_thread( self.thread_change, (change,))
-
- def thread_change(self,change):
- logging.debug('Send to jeedom : %s' % (str(change),))
- i=0
- while i < self.retry:
- try:
- r = requests.post(self.url + '?apikey=' + self.apikey, json=change, timeout=(0.5, 120), verify=False)
- if r.status_code == requests.codes.ok:
- break
- except Exception as error:
- logging.error('Error on send request to jeedom ' + str(error)+' retry : '+str(i)+'/'+str(self.retry))
- i = i + 1
-
- def set_change(self,changes):
- self.changes = changes
-
- def get_change(self):
- return self.changes
-
- def merge_dict(self,d1, d2):
- for k,v2 in d2.items():
- v1 = d1.get(k) # returns None if v1 has no value for this key
- if ( isinstance(v1, collections.Mapping) and
- isinstance(v2, collections.Mapping) ):
- self.merge_dict(v1, v2)
- else:
- d1[k] = v2
-
- def test(self):
- try:
- response = requests.get(self.url + '?apikey=' + self.apikey, verify=False)
- if response.status_code != requests.codes.ok:
- logging.error('Callback error: %s %s. Please check your network configuration page'% ( response.status_code, response.text,))
- return False
- except Exception as e:
- logging.error('Callback result as a unknown error: %s. Please check your network configuration page ' % (str(e),))
- return False
- return True
-
-# ------------------------------------------------------------------------------
class jeedom_utils():
- @staticmethod
- def convert_log_level(level = 'error'):
- LEVELS = {'debug': logging.DEBUG,
- 'info': logging.INFO,
- 'notice': logging.WARNING,
- 'warning': logging.WARNING,
- 'error': logging.ERROR,
- 'critical': logging.CRITICAL,
- 'none': logging.NOTSET}
- return LEVELS.get(level, logging.NOTSET)
-
- @staticmethod
- def set_log_level(level = 'error'):
- FORMAT = '[%(asctime)s.%(msecs)03d][%(levelname)s] : %(message)s'
- logging.basicConfig(level=jeedom_utils.convert_log_level(level),format=FORMAT,datefmt='%Y-%m-%d %H:%M:%S')
-
- @staticmethod
- def find_tty_usb(idVendor, idProduct, product = None):
- context = pyudev.Context()
- for device in context.list_devices(subsystem='tty'):
- if 'ID_VENDOR' not in device:
- continue
- if device['ID_VENDOR_ID'] != idVendor:
- continue
- if device['ID_MODEL_ID'] != idProduct:
- continue
- if product is not None:
- if 'ID_VENDOR' not in device or device['ID_VENDOR'].lower().find(product.lower()) == -1 :
- continue
- return str(device.device_node)
- return None
-
- @staticmethod
- def stripped(str):
- return "".join([i for i in str if i in range(32, 127)])
-
- @staticmethod
- def ByteToHex( byteStr ):
- return ''.join( [ "%02X " % ord( x ) for x in str(byteStr) ] ).strip()
-
- @staticmethod
- def dec2bin(x, width=8):
- return ''.join(str((x>>i)&1) for i in xrange(width-1,-1,-1))
-
- @staticmethod
- def dec2hex(dec):
- if dec is None:
- return 0
- return hex(dec)[2:]
-
- @staticmethod
- def testBit(int_type, offset):
- mask = 1 << offset
- return(int_type & mask)
-
- @staticmethod
- def clearBit(int_type, offset):
- mask = ~(1 << offset)
- return(int_type & mask)
-
- @staticmethod
- def split_len(seq, length):
- return [seq[i:i+length] for i in range(0, len(seq), length)]
-
- @staticmethod
- def write_pid(path):
- pid = str(os.getpid())
- logging.debug("Writing PID " + pid + " to " + str(path))
- open(path, 'w').write("%s\n" % pid)
+ @staticmethod
+ def convert_log_level(level='error'):
+ LEVELS = {
+ 'debug': logging.DEBUG,
+ 'info': logging.INFO,
+ 'notice': logging.WARNING,
+ 'warning': logging.WARNING,
+ 'error': logging.ERROR,
+ 'critical': logging.CRITICAL,
+ 'none': logging.CRITICAL
+ }
+ return LEVELS.get(level, logging.CRITICAL)
+
+ @staticmethod
+ def set_log_level(level='error'):
+ FORMAT = '[%(asctime)-15s][%(levelname)s] : %(message)s'
+ logging.basicConfig(level=jeedom_utils.convert_log_level(level), format=FORMAT, datefmt="%Y-%m-%d %H:%M:%S")
+
+ @staticmethod
+ def write_pid(path):
+ pid = str(os.getpid())
+ logging.info("Writing PID %s to %s", pid, path)
+ open(path, 'w').write("%s\n" % pid)
- @staticmethod
- def remove_accents(input_str):
- nkfd_form = unicodedata.normalize('NFKD', unicode(input_str))
- return u"".join([c for c in nkfd_form if not unicodedata.combining(c)])
-
-# ------------------------------------------------------------------------------
-
-class jeedom_serial():
-
- def __init__(self,device = '',rate = '',timeout = 1):
- self.device = device
- self.rate = rate
- self.timeout = timeout
- self.port = None
- logging.debug('Init serial module v%s' % (str(serial.VERSION),))
-
- def open(self):
- if self.device:
- logging.debug("Open serial port on device: " + str(self.device)+', rate '+str(self.rate)+', timeout : '+str(self.timeout))
- else:
- logging.error("Device name missing.")
- return False
- logging.debug("Open Serialport")
- try:
- self.port = serial.Serial(self.device,self.rate,timeout=self.timeout)
- except serial.SerialException as e:
- logging.error("Error: Failed to connect on device " + self.device + " Details : " + str(e))
- return False
- if not self.port.isOpen():
- self.port.open()
- time.sleep(0.2)
- self.flushOutput()
- self.flushInput()
- return True
-
- def close(self):
- logging.debug("Close serial port")
- try:
- self.port.close()
- logging.debug("Serial port closed")
- return True
- except:
- logging.error("Failed to close the serial port (" + self.device + ")")
- return False
-
- def write(self,data,fromqueue = False):
- self.port.write(data)
- if fromqueue:
- globals.SENDQUEUE.task_done()
-
- def flushOutput(self,):
- logging.debug("flushOutput serial port ")
- self.port.flushOutput()
-
- def flushInput(self):
- logging.debug("flushInput serial port ")
- self.port.flushInput()
-
- def read(self):
- if self.port.inWaiting() > 0:
- return self.port.read()
- return None
-
- def readbytes(self,number):
- if self.port.inWaiting() > 0:
- return self.port.read(number)
- return None
-
-# ------------------------------------------------------------------------------
JEEDOM_SOCKET_MESSAGE = Queue()
-class jeedom_socket_handler(StreamRequestHandler):
- def handle(self):
- global JEEDOM_SOCKET_MESSAGE
- logging.debug("Client connected to [%s:%d]" % self.client_address)
- lg = self.rfile.readline()
- JEEDOM_SOCKET_MESSAGE.put(lg)
- logging.debug("Message read from socket: " + str(lg.strip()))
- self.netAdapterClientConnected = False
- logging.debug("Client disconnected from [%s:%d]" % self.client_address)
-
-class jeedom_socket():
- def __init__(self,address='localhost', port=55000):
- self.address = address
- self.port = port
- socketserver.TCPServer.allow_reuse_address = True
-
- def open(self):
- self.netAdapter = TCPServer((self.address, self.port), jeedom_socket_handler)
- if self.netAdapter:
- logging.debug("Socket interface started")
- threading.Thread(target=self.loopNetServer, args=()).start()
- else:
- logging.debug("Cannot start socket interface")
-
- def loopNetServer(self):
- logging.debug("LoopNetServer Thread started")
- logging.debug("Listening on: [%s:%d]" % (self.address, self.port))
- self.netAdapter.serve_forever()
- logging.debug("LoopNetServer Thread stopped")
+class jeedom_socket_handler(StreamRequestHandler):
+ def handle(self):
+ global JEEDOM_SOCKET_MESSAGE
+ logging.info("Client connected to [%s:%d]", self.client_address[0], self.client_address[1])
+ lg = self.rfile.readline()
+ JEEDOM_SOCKET_MESSAGE.put(lg)
+ logging.info("Message read from socket: %s", str(lg.strip()))
+ self.netAdapterClientConnected = False
+ logging.info("Client disconnected from [%s:%d]", self.client_address[0], self.client_address[1])
- def close(self):
- self.netAdapter.shutdown()
- def getMessage(self):
- return self.message
+class jeedom_socket():
-# ------------------------------------------------------------------------------
-# END
-# ------------------------------------------------------------------------------
+ def __init__(self, address='localhost', port=55000):
+ self.address = address
+ self.port = port
+ socketserver.TCPServer.allow_reuse_address = True
+
+ def open(self):
+ self.netAdapter = TCPServer((self.address, self.port), jeedom_socket_handler)
+ if self.netAdapter:
+ logging.info("Socket interface started")
+ Thread(target=self.loopNetServer).start()
+ else:
+ logging.error("Cannot start socket interface")
+
+ def loopNetServer(self):
+ logging.info("LoopNetServer Thread started")
+ logging.info("Listening on: [%s:%d]", self.address, self.port)
+ self.netAdapter.serve_forever()
+ logging.info("LoopNetServer Thread stopped")
+
+ def close(self):
+ self.netAdapter.shutdown()
diff --git a/resources/beagled/sendadv.py b/resources/beagled/sendadv.py
index 579ac0e..58bebaf 100644
--- a/resources/beagled/sendadv.py
+++ b/resources/beagled/sendadv.py
@@ -1,16 +1,15 @@
# coding: utf-8
import time
-import struct
import os
import binascii
-import base64
import globals
import logging
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import cmac
from cryptography.hazmat.primitives.ciphers import algorithms
-def build_trame(device,type,data =''):
+
+def build_frame(device, type, data=''):
Param = 'FF'
targetUUID = device['uuid']
cfModel = globals.cftarget[device['model']]
@@ -28,72 +27,82 @@ def build_trame(device,type,data =''):
targetUUID = 'FF' + targetUUID
if 'options' in data:
Param = hex(100-int(data['options']))[2:]
- data = globals.gateway['advertisement']+globals.uuidController+'01'+dataAc + cfModel +targetUUID+Param+'FFFF'
- counter = str(random())
- data = data+counter
- return header+data
+ data = globals.gateway['advertisement'] + globals.uuidController + '01' + dataAc + cfModel + targetUUID + Param + 'FFFF'
+ counter = random()
+ data = data + counter
+ return header + data
+
def random():
- counter = binascii.b2a_hex(os.urandom(1)) +binascii.b2a_hex(os.urandom(1))
+ counter = binascii.b2a_hex(os.urandom(1)) + binascii.b2a_hex(os.urandom(1))
return counter.decode().upper()
-def compute(macaddr,trame):
- mac = "".join(reversed([macaddr.replace(':','')[i:i+2] for i in range(0, len(macaddr.replace(':','')), 2)])).lower()
+
+def compute(macaddr, trame):
+ mac = "".join(reversed([macaddr.replace(':', '')[i:i+2] for i in range(0, len(macaddr.replace(':', '')), 2)])).lower()
replaced = trame[22:30]+'FF'+trame[32:]
- payload = replaced.replace(' ','').lower()
+ payload = replaced.replace(' ', '').lower()
s = mac + payload
logging.debug('Mac payload is ' + str(s))
buffer = binascii.unhexlify(str(s))
return buffer
-def hash(secret,buffer):
+
+def hash(secret: str, buffer: bytes):
print(secret)
c = cmac.CMAC(algorithms.AES(binascii.unhexlify(secret)), backend=default_backend())
c.update(buffer)
- hash =binascii.hexlify(bytearray(c.finalize()))
+ hash = binascii.hexlify(bytearray(c.finalize()))
return hash
-def sendCmd(device,type,data=''):
- logging.debug('Sending command for device ' + str(device))
- trame = str(build_trame(device,type,data))
- logging.debug('Command data is ' + str(trame))
- buffer = compute(globals.donglemac,trame)
- key = (globals.gateway['binding']+globals.uuidController+str(globals.jeedomkey)).replace(' ','').lower()
+
+def sendCmd(device, type, data=''):
+ logging.debug('Sending command for device %s', device)
+ frame = str(build_frame(device, type, data))
+ logging.debug('Command data is %s', frame)
+ buffer = compute(globals.donglemac, frame)
+ key = (globals.gateway['binding'] + globals.uuidController + str(globals.jeedomkey)).replace(' ', '').lower()
if type == 'pair':
- key = globals.uniquekey.replace(' ','').lower()
- hashed = hash(key,buffer)
- logging.debug('Hashed data is ' + str(hashed))
+ key = globals.uniquekey.replace(' ', '').lower()
+ hashed = hash(key, buffer)
+ logging.debug('Hashed data is %s', hashed)
cmac = hashed.decode()[0:8]
- payload = (trame+cmac).upper()
- logging.debug('Final payload is ' + str(payload))
+ payload = (frame+cmac).upper()
+ logging.debug('Final payload is %s', payload)
finalpayload = ' '.join(payload[i:i+2] for i in range(0, len(payload), 2)).upper()
send(finalpayload)
-def send(payload):
+
+def send(payload: str):
valid = checkpayload(payload)
if valid:
- logging.debug('Payload is valid sending : ' + str(payload))
- #os.system('sudo hciconfig hci'+str(globals.IFACE_DEVICE) + ' up')
- os.system('sudo hcitool -i hci'+str(globals.IFACE_DEVICE) + ' cmd 0x08 0x0008 1F '+ str(payload))
- os.system('sudo hcitool -i hci'+str(globals.IFACE_DEVICE) + ' cmd 0x08 0x0006 A0 00 A0 00 03 00 00 00 00 00 00 00 00 07 00')
- os.system('sudo hcitool -i hci'+str(globals.IFACE_DEVICE) + ' cmd 0x08 0x000a 01')
+ logging.debug('Payload is valid sending : %s', payload)
+ # os.system('sudo hciconfig hci'+str(globals.IFACE_DEVICE) + ' up')
+ os.system('sudo hcitool -i hci' + str(globals.IFACE_DEVICE) + ' cmd 0x08 0x0008 1F ' + payload)
+ os.system('sudo hcitool -i hci' + str(globals.IFACE_DEVICE) + ' cmd 0x08 0x0006 A0 00 A0 00 03 00 00 00 00 00 00 00 00 07 00')
+ os.system('sudo hcitool -i hci' + str(globals.IFACE_DEVICE) + ' cmd 0x08 0x000a 01')
time.sleep(0.5)
- os.system('sudo hcitool -i hci'+str(globals.IFACE_DEVICE) + ' cmd 0x08 0x000a 00')
+ os.system('sudo hcitool -i hci' + str(globals.IFACE_DEVICE) + ' cmd 0x08 0x000a 00')
else:
- logging.debug('Invalid Payload : ' + str(payload))
+ logging.debug('Invalid Payload : %s', payload)
-def checkpayload(payload):
+
+def checkpayload(payload: str):
result = True
- logging.debug('Validating payload : ' + str(payload))
- length = len(payload.replace(' ',''))
- if int(length) == 62:
+ logging.debug('Validating payload : %s', payload)
+ payload = payload.replace(' ', '')
+ length = len(payload)
+ if length == 62:
logging.debug('Correct Length for payload')
else:
- logging.debug('Invalid Length for payload : ' + str(length) + ' we want 62')
- result=False
- if payload.replace(' ','')[0:22] == globals.uniqueHeader+globals.types['gateway']+globals.headerVV+globals.headerFS:
+ logging.debug('Invalid Length for payload : %i ; we want 62', length)
+ result = False
+
+ header_received = payload[0:22]
+ header_expected = globals.uniqueHeader + globals.types['gateway'] + globals.headerVV + globals.headerFS
+ if header_received == header_expected:
logging.debug('Correct header for payload')
else:
- logging.debug('Incorrect header for payload ' + payload.replace(' ','')[0:22] + ' we want ' + globals.uniqueHeader+globals.types['gateway']+globals.headerVV+globals.headerFS)
- result=False
- return result
\ No newline at end of file
+ logging.debug('Incorrect header for payload %s ; we want %s', header_received, header_expected)
+ result = False
+ return result
diff --git a/resources/install_apt.sh b/resources/install_apt.sh
deleted file mode 100644
index 8e2a2d7..0000000
--- a/resources/install_apt.sh
+++ /dev/null
@@ -1,36 +0,0 @@
-PROGRESS_FILE=/tmp/dependancy_beagle_in_progress
-if [ ! -z $1 ]; then
- PROGRESS_FILE=$1
-fi
-touch ${PROGRESS_FILE}
-echo 0 > ${PROGRESS_FILE}
-echo "********************************************************"
-echo "* Installation des dépendances *"
-echo "********************************************************"
-sudo apt-get update
-echo 10 > ${PROGRESS_FILE}
-sudo apt-get -y install python3-pip python3-setuptools rfkill
-echo 20 > ${PROGRESS_FILE}
-sudo apt-get -y install python3-dev libffi-dev libssl-dev libbluetooth-dev bluetooth build-essential python3-pip
-echo 25 > ${PROGRESS_FILE}
-sudo pip3 install requests
-echo 35 > ${PROGRESS_FILE}
-sudo pip3 install pyserial
-echo 45 > ${PROGRESS_FILE}
-sudo pip3 install pyudev
-echo 75 > ${PROGRESS_FILE}
-sudo pip3 install wheel
-echo 80 > ${PROGRESS_FILE}
-sudo pip3 install cryptography==2.6.1
-echo 90 > ${PROGRESS_FILE}
-sudo pip3 install pybluez
-echo 99 > ${PROGRESS_FILE}
-sudo rfkill unblock all >/dev/null 2>&1
-sudo hciconfig hci0 up >/dev/null 2>&1
-sudo hciconfig hci1 up >/dev/null 2>&1
-sudo hciconfig hci2 up >/dev/null 2>&1
-echo 100 > ${PROGRESS_FILE}
-echo "********************************************************"
-echo "* Installation terminée *"
-echo "********************************************************"
-rm ${PROGRESS_FILE}
diff --git a/resources/post_install.sh b/resources/post_install.sh
new file mode 100644
index 0000000..5ebd769
--- /dev/null
+++ b/resources/post_install.sh
@@ -0,0 +1,16 @@
+cd "$(dirname "$0")"
+SCRIPT_DIR="$(pwd)"
+
+# this version of pybluez (0.30) doesn't work on debian 11 but version 0.23 doesn't work on debian 12 => block on 0.23 for now
+# if [ -d "$SCRIPT_DIR/python_venv/bin" ]; then
+# echo "patch pybluez on debian >= 12"
+# sudo $SCRIPT_DIR/python_venv/bin/python3 -m pip install --force-reinstall --upgrade git+https://github.com/pybluez/pybluez.git#egg=pybluez
+# else
+# echo "patch pybluez on debian 11"
+# sudo pip3 install git+https://github.com/pybluez/pybluez.git#egg=pybluez
+# fi
+
+sudo rfkill unblock all >/dev/null 2>&1
+sudo hciconfig hci0 up >/dev/null 2>&1
+sudo hciconfig hci1 up >/dev/null 2>&1
+sudo hciconfig hci2 up >/dev/null 2>&1