diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..b810e31 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=lf \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3bc6878 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea/ +/LoxBerry-Plugin-P1-Decrypter.iml diff --git a/README-german.md b/README-german.md new file mode 100644 index 0000000..ed89590 --- /dev/null +++ b/README-german.md @@ -0,0 +1,80 @@ +# Loxberry Plugin: P1 Decrypter + +Dieses Plugin ermöglicht es verschlüsselte Daten von einem Smart Meter über die Kundenschnittstelle P1 an den Miniserver über UDP und/oder an einen seriellen Port am Loxberry zu senden. + +> English readme: [https://github.com/metrophos/LoxBerry-Plugin-P1-Decrypter/blob/main/README.md](https://github.com/metrophos/LoxBerry-Plugin-P1-Decrypter/blob/main/README.md) + +P1 Decrypter Plugin + +## Voraussetzung + +- Smart Meter mit P1 Schnittstelle (Aktuell wurde das Plugin getestet mit dem Smart Meter: Sagemcom T210-D-r in Österreich) +- FTDI USB Kabel zum verbinden des Smart Meter mit Loxberry. + - Zum Beispiel: [https://www.aliexpress.com/item/32945225256.html](https://www.aliexpress.com/item/32945225256.html) +- Der Netzbetreiber muss die Kundenschnittstelle aktivieren und einen Key _"Global Unicast Encryption Key (GUEK)"_ zur Verfügung stellen + - Normalerweise kann über die Weboberfläche des Netzbetreibers die Kundenschnittstelle aktiviert und der Key angezeigt werden + - Das Aktivieren über die Weboberfläche des Netzbetreibers kann etwas Zeit in Anspruch nehmen + +## Smart Meter + +### T210-D-r (Österreich) + +Sagemcom T210-D-r + +| OBIS-Code | Einheit | Beschreibung | +|-----------|--------------|---------------------------------------------------------| +| 1-3:0.2.8 | int | P1 port DSMR version | +| 0-0:1.0.0 | YYMMDDhhmmss | Impuls Datum und Zeit | +| 1-0:1.8.0 | Wh | Zählerstand +P (Wirkenergie Bezug) | +| 1-0:1.8.1 | Wh | Active energy import (+A) rate 1 | +| 1-0:1.8.2 | Wh | Active energy import (+A) rate 2 | +| 1-0:1.7.0 | W | aktuelle Leistung +P (momentane Wirkleistung Bezug) | +| 1-0:2.8.0 | Wh | Zählerstand -P (Wirkenergie Lieferung) | +| 1-0:2.8.1 | Wh | Active energy export (-A) rate 1 | +| 1-0:2.8.2 | Wh | Active energy export (-A) rate 2 | +| 1-0:2.7.0 | W | Aktuelle Leistung -P (momentane Wirkleistung Lieferung) | +| 1-0:3.8.0 | varh | Blindenergie +R (Blindenergie Bezug) | +| 1-0:3.8.1 | varh | Reactive energy import (+R) rate 1 | +| 1-0:3.8.2 | varh | Reactive energy import (+R) rate 2 | +| 1-0:3.7.0 | var | Momentanleistung +Q (var) | +| 1-0:4.8.0 | varh | Blindenergie Lieferung -R (Wh) | +| 1-0:4.8.1 | varh | Reactive energy export (-R) rate 1 | +| 1-0:4.8.2 | varh | Reactive energy export (-R) rate 2 | +| 1-0:4.7.0 | var | Momentanleistung -Q (var) | + +## Value mapping + +Das value mapping reduziert die information welche vom Smart Meter kommen. + +Format ist: +``` +'label','regex' +'label','regex' +'label','regex' +... +``` +> Das value mapping kann mittels `raw` switch deaktiviert werden. + +### Beispiel + +Um folgenden Wert `1-0:1.8.0:001234567` vom Original output `1-0:1.8.0(001234567*Wh)` zu erhalten +kann das value mapping wie folgt aussehen: `'1-0:1.8.0','(?<=1-0:1.8.0\().*?(?=\*Wh)'` + +## Miniserver Konfiguration + +- Virtueller UPD Eingang: + - Senderadresse: Deine Loxberry IP + - UPD Empfangsport: 54321 (Bzw. welcher im Plugin konfiguiert wurde) +- Virtueller UPD Einfang Befehl: + - Befehlserkennung (Wenn die Daten wie im Value Mapping Beispiel geschickt werden): `\i1-0:1.8.0:\i\v` + +### Beispiel für den Energiemonitor + +- Die Werte kommen im Beispiel in Watt und müssen noch in Kilowatt umgerechnet werden `AI1/1000` +- Im Beispiel muss der Wert `1-0:1.7.0` und `1-0:2.7.0` mit folgender Formel verbunden werden `(I1-I2)/1000` + +Loxone + +## Danke an: + +- tknaller - Für seinen modifizierten fork: https://github.com/tknaller/smarty_dsmr_proxy \ No newline at end of file diff --git a/README.md b/README.md index 0895891..f7d92fe 100644 --- a/README.md +++ b/README.md @@ -1 +1,77 @@ -# LoxBerry-Plugin-P1-Decrypter \ No newline at end of file +# Loxberry Plugin: P1 Decrypter + +Plugin to decrypt Smart Meter output over P1 customer interface and send it over udp and/or to a serial port. + +> German readme: [https://github.com/metrophos/LoxBerry-Plugin-P1-Decrypter/blob/main/README-german.md](https://github.com/metrophos/LoxBerry-Plugin-P1-Decrypter/blob/main/README-german.md) + +P1 Decrypter Plugin + +## Precondition + +- Smart Meter that has a P1 interface (Tested with Smart Meter: Sagemcom T210-D-r in Austria) +- FTDI USB cable to connect to the Smart Meter + - One possibly option: [https://www.aliexpress.com/item/32945225256.html](https://www.aliexpress.com/item/32945225256.html) +- Your energy provider has to activate your customer interface and provide the encryption key _"Global Unicast Encryption Key (GUEK)"_ + +## Smart Meter + +### T210-D-r (Austria) + +Sagemcom T210-D-r + +| OBIS-Code | Einheit | Beschreibung | +|-----------|--------------|---------------------------------------------------------| +| 1-3:0.2.8 | int | P1 port DSMR version | +| 0-0:1.0.0 | YYMMDDhhmmss | Impuls Datum und Zeit | +| 1-0:1.8.0 | Wh | Zählerstand +P (Wirkenergie Bezug) | +| 1-0:1.8.1 | Wh | Active energy import (+A) rate 1 | +| 1-0:1.8.2 | Wh | Active energy import (+A) rate 2 | +| 1-0:1.7.0 | W | aktuelle Leistung +P (momentane Wirkleistung Bezug) | +| 1-0:2.8.0 | Wh | Zählerstand -P (Wirkenergie Lieferung) | +| 1-0:2.8.1 | Wh | Active energy export (-A) rate 1 | +| 1-0:2.8.2 | Wh | Active energy export (-A) rate 2 | +| 1-0:2.7.0 | W | Aktuelle Leistung -P (momentane Wirkleistung Lieferung) | +| 1-0:3.8.0 | varh | Blindenergie +R (Blindenergie Bezug) | +| 1-0:3.8.1 | varh | Reactive energy import (+R) rate 1 | +| 1-0:3.8.2 | varh | Reactive energy import (+R) rate 2 | +| 1-0:3.7.0 | var | Momentanleistung +Q (var) | +| 1-0:4.8.0 | varh | Blindenergie Lieferung -R (Wh) | +| 1-0:4.8.1 | varh | Reactive energy export (-R) rate 1 | +| 1-0:4.8.2 | varh | Reactive energy export (-R) rate 2 | +| 1-0:4.7.0 | var | Momentanleistung -Q (var) | + +## Value mapping + +If you don't need all informations of your smart meter you can use the value mapping. +Format is: +``` +'label','regex' +'label','regex' +'label','regex' +... +``` + +> Disable value mapping by enable `raw` switch. + +### Example + +To get `1-0:1.8.0:001234567\n` from raw output `1-0:1.8.0(001234567*Wh)`use value mapping like this: `'1-0:1.8.0','(?<=1-0:1.8.0\().*?(?=\*Wh)'` + +## Miniserver configuration + +- Virtual UPD input: + - IP address: Your Loxberry IP + - UPD Port: 54321 (or what you use configured over the plugin configuration) +- Virtual UPD input command: + - command recognition (If you use the value mapping example above): `\i1-0:1.8.0:\i\v` + +### Example to use by energy monitor + +- In this example all values are in Watt. They have to be divide `AI1/1000` +- In this example `1-0:1.7.0` and `1-0:2.7.0` must be connected by this formula `(I1-I2)/1000` + +Loxone + +## Thanks to: + +- tknaller - For his modified fork: https://github.com/tknaller/smarty_dsmr_proxy \ No newline at end of file diff --git a/bin/p1decrypter.py b/bin/p1decrypter.py new file mode 100644 index 0000000..b363552 --- /dev/null +++ b/bin/p1decrypter.py @@ -0,0 +1,350 @@ +import socket +import serial +import binascii +import argparse +import re +import logging +import sys +import os +import configparser +import json +import base64 +from Cryptodome.Cipher import AES + + +class P1decrypter: + def __init__(self): + + self.STATE_IGNORING = 0 + self.STATE_STARTED = 1 + self.STATE_HAS_SYSTEM_TITLE_LENGTH = 2 + self.STATE_HAS_SYSTEM_TITLE = 3 + self.STATE_HAS_SYSTEM_TITLE_SUFFIX = 4 + self.STATE_HAS_DATA_LENGTH = 5 + self.STATE_HAS_SEPARATOR = 6 + self.STATE_HAS_FRAME_COUNTER = 7 + self.STATE_HAS_PAYLOAD = 8 + self.STATE_HAS_GCM_TAG = 9 + self.STATE_DONE = 10 + + # Command line arguments + self._args = {} + + # Serial connection to p1 smart meter interface + self._connection = None + + # Initial empty values. These will be filled as content is read + # and they will be reset each time we go back to the initial state. + self._state = self.STATE_IGNORING + self._buffer = "" + self._buffer_length = 0 + self._next_state = 0 + self._system_title_length = 0 + self._system_title = b"" + self._data_length_bytes = b"" # length of "remaining data" in bytes + self._data_length = 0 # length of "remaining data" as an integer + self._frame_counter = b"" + self._payload = b"" + self._gcm_tag = b"" + + self.LBSCONFIG = "" + self.miniserver_id = "" + self.general_json = {} + + def main(self): + self.args() + + def args(self): + parser = argparse.ArgumentParser() + parser.add_argument('key', help="Global Unicast Encryption Key (GUEK)") + + parser.add_argument('-iport', '--serial-input-port', required=False, default="/dev/ttyUSB0", + help="Serial input port. Default: /dev/ttyUSB0") + parser.add_argument('-ibaudrate', '--serial-input-baudrate', required=False, type=int, default=115200, + help="Serial input baudrate. Default: 115200") + parser.add_argument('-iparity', '--serial-input-parity', required=False, default=serial.PARITY_NONE, + help="Serial input parity. Default: None") + parser.add_argument('-istopbits', '--serial-input-stopbits', required=False, type=int, + default=serial.STOPBITS_ONE, + help="Serial input stopbits. Default: 1") + + parser.add_argument('-m', '--mapping', required=False, + default="'1-0:1.8.0','(?<=1-0:1.8.0\().*?(?=\*Wh)'\n'1-0:1.7.0','(?<=1-0:1.7.0\().*?(?=\*W)'\n'1-0:2.8.0','(?<=1-0:2.8.0\().*?(?=\*Wh)'\n'1-0:2.7.0','(?<=1-0:2.7.0\().*?(?=\*W)'", + help="Value mapping. Default: '1-0:1.8.0','(?<=1-0:1.8.0\().*?(?=\*Wh)',\\n'1-0:1.7.0','(?<=1-0:1.7.0\().*?(?=\*W)'\\n'1-0:2.8.0','(?<=1-0:2.8.0\().*?(?=\*Wh)'\\n'1-0:2.7.0','(?<=1-0:2.7.0\().*?(?=\*W)'") + + parser.add_argument('-a', '--aad', required=False, default="3000112233445566778899AABBCCDDEEFF", + help="Additional authenticated data. Default: 3000112233445566778899AABBCCDDEEFF") + + parser.add_argument('-u', '--send-to-udp', required=False, default=True, action='store_true', + help="Send data to UDP. Default: true") + parser.add_argument('-ui', '--udp-host', help="UDP IP / Host") + parser.add_argument('-up', '--udp-port', type=int, help="UDP port") + + parser.add_argument('-s', '--send-to-serial-port', required=False, default=False, action='store_true', + help="Send data to output serial port. Use socat to generate virtual port e.g.: socat -d -d pty,raw,echo=0,link=/dev/p1decrypterI pty,raw,echo=0,link=/dev/p1decrypterO") + parser.add_argument('-oport', '--serial-output-port', required=False, default="/dev/t210dr", + help="Serial output port. Default: /dev/p1decrypter") + parser.add_argument('-obaudrate', '--serial-output-baudrate', required=False, type=int, default=115200, + help="Serial output baudrate. Default: 115200") + parser.add_argument('-oparity', '--serial-output-parity', required=False, default=serial.PARITY_NONE, + help="Serial output parity. Default: None") + parser.add_argument('-ostopbits', '--serial-output-stopbits', required=False, type=int, + default=serial.STOPBITS_ONE, + help="Serial output stopbits. Default: 1") + + parser.add_argument('-r', '--raw', required=False, default=False, action='store_true', + help="Output raw, without mapping") + parser.add_argument('-v', "--verbose", required=False, default=False, action='store_true', help="Verbose mode") + parser.add_argument('-l', '--logfile', required=False, help="Logfile path") + parser.add_argument('-c', "--configfile", required=False, help="Configfile path") + + self._args = parser.parse_args() + + self.config() + + def config(self): + + if self._args.logfile: + logging.basicConfig(filename=self._args.logfile, + filemode='w', + level=logging.INFO, + format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s', + datefmt='%Y-%m-%d %H:%M:%S') + else: + logging.basicConfig(level=logging.INFO, + format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s', + datefmt='%Y-%m-%d %H:%M:%S', + handlers=[logging.StreamHandler()]) + + if self._args.configfile: + logging.info("Read config file and overwrite arguments") + if not os.path.exists(self._args.configfile): + logging.critical("Configuration file not exsits {0}".format(self._args.configfile)) + sys.exit(-1) + + pluginconfig = configparser.ConfigParser() + pluginconfig.read(self._args.configfile) + + self.LBSCONFIG = os.getenv("LBSCONFIG", os.getcwd()) + self.miniserver_id = pluginconfig.get('P1DECRYPTER', 'MINISERVER_ID') + + self._args.enabled = bool(int(pluginconfig.get('P1DECRYPTER', 'ENABLED'))) + self._args.key = pluginconfig.get('P1DECRYPTER', 'KEY') + self._args.serial_input_port = pluginconfig.get('P1DECRYPTER', 'SERIAL_INPUT_PORT') + self._args.serial_input_baudrate = int(pluginconfig.get('P1DECRYPTER', 'SERIAL_INPUT_BAUDRATE')) + self._args.serial_input_parity = pluginconfig.get('P1DECRYPTER', 'SERIAL_INPUT_PARITY') + self._args.serial_input_stopbits = int(pluginconfig.get('P1DECRYPTER', 'SERIAL_INPUT_STOPBITS')) + self._args.mapping = base64.b64decode( + pluginconfig.get('P1DECRYPTER', 'MAPPING').replace('\\n', '').encode('ascii') + ).decode('ascii') + self._args.aad = pluginconfig.get('P1DECRYPTER', 'AAD') + self._args.send_to_udp = bool(int(pluginconfig.get('P1DECRYPTER', 'SEND_TO_UDP'))) + self._args.udp_host = pluginconfig.get('P1DECRYPTER', 'UDP_HOST') + self._args.udp_port = int(pluginconfig.get('P1DECRYPTER', 'UDP_PORT')) + self._args.send_to_serial_port = bool(int(pluginconfig.get('P1DECRYPTER', 'SEND_TO_SERIAL_PORT'))) + self._args.serial_output_port = pluginconfig.get('P1DECRYPTER', 'SERIAL_OUTPUT_PORT') + self._args.serial_output_baudrate = int(pluginconfig.get('P1DECRYPTER', 'SERIAL_OUTPUT_BAUDRATE')) + self._args.serial_output_parity = pluginconfig.get('P1DECRYPTER', 'SERIAL_OUTPUT_PARITY') + self._args.serial_output_stopbits = int(pluginconfig.get('P1DECRYPTER', 'SERIAL_OUTPUT_STOPBITS')) + self._args.raw = bool(int(pluginconfig.get('P1DECRYPTER', 'RAW'))) + self._args.verbose = bool(int(pluginconfig.get('P1DECRYPTER', 'VERBOSE'))) + else: + self._args.enabled = 1 + + if self._args.verbose: + logging.getLogger().setLevel(logging.DEBUG) + + logging.debug("Arguments: {0}".format(self._args)) + logging.info("Arguments and config processed") + + if self._args.enabled == "0": + logging.warning("P1 Decrypter is not enabled in configuration file. exit") + sys.exit(-1) + + self.miniserver() + + def miniserver(self): + + if not self._args.udp_host and self._args.send_to_udp: + if not self.miniserver_id: + logging.error("No UDP Host or Miniserver ID is set.") + sys.exit(-1) + + config_path = os.path.join(self.LBSCONFIG, "general.json") + logging.info("Try load Miniserver system configuration file {0}".format(config_path)) + with open(config_path, "r") as config_path_handle: + self.general_json = json.load(config_path_handle) + + logging.info("Check if miniserver exists in {0}".format(config_path)) + if not self.miniserver_id in self.general_json["Miniserver"].keys(): + logging.critical( + "Miniserver with id {0} is not configured in {1}".format(self.miniserver_id, config_path)) + sys.exit(-1) + + self._args.udp_host = self.general_json["Miniserver"][self.miniserver_id]["Ipaddress"] + + logging.info("Miniserver ip address: {0}".format(self._args.udp_host)) + + self.connect() + logging.info("Start processing incoming data.") + while True: + self.process() + + def connect(self): + logging.info("Connect to serial input port") + + try: + self._connection = serial.Serial( + port=self._args.serial_input_port, + baudrate=self._args.serial_input_baudrate, + parity=self._args.serial_input_parity, + stopbits=self._args.serial_input_stopbits + ) + except Exception as e: + logging.error("Connection failed: {0}".format(e)) + sys.exit(-1) + + def process(self): + hex_input = binascii.hexlify(self._connection.read()) + + if self._state == self.STATE_IGNORING: + if hex_input == b'db': + logging.debug("STATE_IGNORING: Start byte has been detected. ({0})".format(hex_input)) + self._state = self.STATE_STARTED + self._buffer = b"" + self._buffer_length = 1 + self._system_title_length = 0 + self._system_title = b"" + self._data_length = 0 + self._data_length_bytes = b"" + self._frame_counter = b"" + self._payload = b"" + self._gcm_tag = b"" + else: + return + elif self._state == self.STATE_STARTED: + logging.debug("STATE_HAS_SYSTEM_TITLE_LENGTH: Read length of system title. ({0})".format(hex_input)) + self._state = self.STATE_HAS_SYSTEM_TITLE_LENGTH + self._system_title_length = int(hex_input, 16) + self._buffer_length = self._buffer_length + 1 + self._next_state = 2 + self._system_title_length # start bytes + system title length + elif self._state == self.STATE_HAS_SYSTEM_TITLE_LENGTH: + logging.debug("STATE_HAS_SYSTEM_TITLE_LENGTH: Read system title ({0})".format(hex_input)) + if self._buffer_length > self._next_state: + self._system_title += hex_input + self._state = self.STATE_HAS_SYSTEM_TITLE + self._next_state = self._next_state + 2 # read two more bytes + else: + self._system_title += hex_input + elif self._state == self.STATE_HAS_SYSTEM_TITLE: + logging.debug("STATE_HAS_SYSTEM_TITLE: Read additional byte after system title. ({0})".format(hex_input)) + if hex_input == b'82': + self._next_state = self._next_state + 1 + self._state = self.STATE_HAS_SYSTEM_TITLE_SUFFIX # Ignore separator byte + else: + logging.warning("Expected 0x82 separator byte not found, dropping frame ({0})".format(hex_input)) + self._state = self.STATE_IGNORING + elif self._state == self.STATE_HAS_SYSTEM_TITLE_SUFFIX: + logging.debug("STATE_HAS_SYSTEM_TITLE_SUFFIX: Read length of remaining data. ({0})".format(hex_input)) + if self._buffer_length > self._next_state: + self._data_length_bytes += hex_input + self._data_length = int(self._data_length_bytes, 16) + self._state = self.STATE_HAS_DATA_LENGTH + else: + self._data_length_bytes += hex_input + elif self._state == self.STATE_HAS_DATA_LENGTH: + logging.debug("STATE_HAS_DATA_LENGTH: Read additional byte after data. ({0})".format(hex_input)) + self._state = self.STATE_HAS_SEPARATOR # Ignore separator byte + self._next_state = self._next_state + 1 + 4 # separator byte + 4 bytes for framecounter + elif self._state == self.STATE_HAS_SEPARATOR: + logging.debug("STATE_HAS_DATA_LENGTH: Read frame counter. ({0})".format(hex_input)) + if self._buffer_length > self._next_state: + self._frame_counter += hex_input + self._state = self.STATE_HAS_FRAME_COUNTER + self._next_state = self._next_state + self._data_length - 17 + else: + self._frame_counter += hex_input + elif self._state == self.STATE_HAS_FRAME_COUNTER: + logging.debug("STATE_HAS_FRAME_COUNTER: Read payload. ({0})".format(hex_input)) + if self._buffer_length > self._next_state: + self._payload += hex_input + self._state = self.STATE_HAS_PAYLOAD + self._next_state = self._next_state + 12 + else: + self._payload += hex_input + elif self._state == self.STATE_HAS_PAYLOAD: + logging.debug("STATE_HAS_PAYLOAD: Switch back to STATE_IGNORING and wait for a new start byte. ({0})".format(hex_input)) + if self._buffer_length > self._next_state: + self._gcm_tag += hex_input + self._state = self.STATE_DONE + else: + self._gcm_tag += hex_input + + self._buffer += hex_input + self._buffer_length = self._buffer_length + 1 + + if self._state == self.STATE_DONE: + self.decrypt() + self._state = self.STATE_IGNORING + + # + def decrypt(self): + logging.debug("Full telegram received, start decryption.") + + cipher = AES.new( + binascii.unhexlify(self._args.key), + AES.MODE_GCM, + binascii.unhexlify(self._system_title + self._frame_counter), + mac_len=12 + ) + cipher.update(binascii.unhexlify(self._args.aad)) + self.mapping(cipher.decrypt(binascii.unhexlify(self._payload))) + + def mapping(self, decryption): + logging.debug("Decryption done. Extract data by mapping configuration: {0}".format(decryption)) + + output = "" + + if self._args.raw: + logging.debug("Raw output is enabled. Mapping extraction stopped. Send complete telegram") + output = decryption; + else: + input_multi_array = [] + for i in self._args.mapping.splitlines(): + input_multi_array.append([i.split(',')[0].strip().strip("'"), i.split(',')[1].strip().strip("'")]) + + for i in input_multi_array: + output += i[0] + ":" + re.search(i[1], decryption.decode()).group(0) + "\n" + + output = output.encode() + + if self._args.send_to_udp: + self.send_to_udb(output) + + if self._args.send_to_serial_port: + self.send_to_serial_port(output) + + def send_to_serial_port(self, output): + logging.debug("Send the decrypted data to output serial port: {0}".format(output.decode())) + serial_port = serial.Serial( + port=self._args.serial_output_port, + baudrate=self._args.serial_output_baudrate, + parity=self._args.serial_output_parity, + stopbits=self._args.serial_output_stopbits + ) + serial_port.write(output) + serial_port.close() + + def send_to_udb(self, output): + logging.debug("Send the decrypted data over udp: {0}".format(output.decode())) + connection = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + res = connection.sendto(output, (self._args.udp_host, self._args.udp_port)) + connection.close() + + if res != output.__len__(): + logging.error("Sent bytes not matching. Expected {0} to be {1}".format(output.decode().__len__(), res)) + + +if __name__ == '__main__': + smarty_proxy = P1decrypter() + smarty_proxy.main() diff --git a/config/p1decrypter-default.cfg b/config/p1decrypter-default.cfg new file mode 100644 index 0000000..240178c --- /dev/null +++ b/config/p1decrypter-default.cfg @@ -0,0 +1,20 @@ +[P1DECRYPTER] +ENABLED=0 +KEY=0000112233445566778899AABBCCDDEEFF +SERIAL_INPUT_PORT=/dev/ttyUSB0 +SERIAL_INPUT_BAUDRATE=115200 +SERIAL_INPUT_PARITY=N +SERIAL_INPUT_STOPBITS=1 +MAPPING=JzEtMDoxLjguMCcsJyg/PD0xLTA6MS44LjBcKCkuKj8oPz1cKldoKScNCicxLTA6MS43LjAnLCco\\nPzw9MS0wOjEuNy4wXCgpLio/KD89XCpXKScNCicxLTA6Mi44LjAnLCcoPzw9MS0wOjIuOC4wXCgp\\nLio/KD89XCpXaCknDQonMS0wOjIuNy4wJywnKD88PTEtMDoyLjcuMFwoKS4qPyg/PVwqVykn\\n +AAD=3000112233445566778899AABBCCDDEEFF +SEND_TO_UDP=1 +MINISERVER_ID=1 +UDP_HOST= +UDP_PORT=54321 +SEND_TO_SERIAL_PORT=0 +SERIAL_OUTPUT_PORT=/dev/p1decrypter +SERIAL_OUTPUT_BAUDRATE=115200 +SERIAL_OUTPUT_PARITY=N +SERIAL_OUTPUT_STOPBITS=1 +RAW=0 +VERBOSE=0 \ No newline at end of file diff --git a/config/p1decrypter.cfg b/config/p1decrypter.cfg new file mode 100644 index 0000000..240178c --- /dev/null +++ b/config/p1decrypter.cfg @@ -0,0 +1,20 @@ +[P1DECRYPTER] +ENABLED=0 +KEY=0000112233445566778899AABBCCDDEEFF +SERIAL_INPUT_PORT=/dev/ttyUSB0 +SERIAL_INPUT_BAUDRATE=115200 +SERIAL_INPUT_PARITY=N +SERIAL_INPUT_STOPBITS=1 +MAPPING=JzEtMDoxLjguMCcsJyg/PD0xLTA6MS44LjBcKCkuKj8oPz1cKldoKScNCicxLTA6MS43LjAnLCco\\nPzw9MS0wOjEuNy4wXCgpLio/KD89XCpXKScNCicxLTA6Mi44LjAnLCcoPzw9MS0wOjIuOC4wXCgp\\nLio/KD89XCpXaCknDQonMS0wOjIuNy4wJywnKD88PTEtMDoyLjcuMFwoKS4qPyg/PVwqVykn\\n +AAD=3000112233445566778899AABBCCDDEEFF +SEND_TO_UDP=1 +MINISERVER_ID=1 +UDP_HOST= +UDP_PORT=54321 +SEND_TO_SERIAL_PORT=0 +SERIAL_OUTPUT_PORT=/dev/p1decrypter +SERIAL_OUTPUT_BAUDRATE=115200 +SERIAL_OUTPUT_PARITY=N +SERIAL_OUTPUT_STOPBITS=1 +RAW=0 +VERBOSE=0 \ No newline at end of file diff --git a/cron/cron.05min b/cron/cron.05min new file mode 100644 index 0000000..f061740 --- /dev/null +++ b/cron/cron.05min @@ -0,0 +1,15 @@ +#!/bin/bash + +# This is a sample cron file. According to it's name it will go to +# ~/system/cron/cron.10min. You may also let your Pluginscript create a +# symbolic link dynamically in ~/system/cron/cron.10min which links to your +# cron-script instead (which is prefered). Use NAME from +# /data/system/plugindatabase.dat in that case as scriptname! Otherwise the +# cron script will not be uninstalled cleanly. + +# Will be executed as user "loxberry". + +if ! pgrep -f p1decrypter.py >/dev/null && [ -e REPLACELBPBINDIR/p1decrypter.py ] +then + /usr/bin/python3 REPLACELBPBINDIR/p1decrypter.py KEY --logfile=REPLACELBPLOGDIR/p1decrypter.log --configfile=REPLACELBPCONFIGDIR/p1decrypter.cfg >> REPLACELBPLOGDIR/p1decrypter.log 2>&1 +fi \ No newline at end of file diff --git a/dpkg/apt b/dpkg/apt new file mode 100644 index 0000000..81257d6 --- /dev/null +++ b/dpkg/apt @@ -0,0 +1,6 @@ +# These packages will be installed by apt-get -y install PACKAGENAME +# One line per package, use exact packagename as you would do for apt-get. +# +python3 +python3-serial +python3-pycryptodome \ No newline at end of file diff --git a/icons/icon_128.png b/icons/icon_128.png new file mode 100644 index 0000000..26a3a69 Binary files /dev/null and b/icons/icon_128.png differ diff --git a/icons/icon_256.png b/icons/icon_256.png new file mode 100644 index 0000000..0e7ef7b Binary files /dev/null and b/icons/icon_256.png differ diff --git a/icons/icon_512.png b/icons/icon_512.png new file mode 100644 index 0000000..3653499 Binary files /dev/null and b/icons/icon_512.png differ diff --git a/icons/icon_64.png b/icons/icon_64.png new file mode 100644 index 0000000..e1ff60a Binary files /dev/null and b/icons/icon_64.png differ diff --git a/plugin.cfg b/plugin.cfg new file mode 100644 index 0000000..9a5bc92 --- /dev/null +++ b/plugin.cfg @@ -0,0 +1,80 @@ +[AUTHOR] +# You can also use a Team/Projekt Name here and a generic email address +# like info@..., BUT NEVER CHANGE this information in future updates! It +# will be used to identify your Plugin, handle updates etc. If you change +# this information, LoxBerry could not identify your plugin and handle it as +# a different one - therefore updates will fail. +NAME=Daniel Schwab +EMAIL=p1decrypter@gmail.com + +[PLUGIN] +# The version of your plugin - important if you would like to write an +# upgrade script. Use a correct syntax, which is supported by LoxBerry: +# More info: http://www.loxwiki.eu/x/LYG3AQ +VERSION=1.0.0 + +# Short name and prefered subfolder of your Plugin (do not use blanks - they +# will be filtered - and use lowercase only)! Used for script names in +# daemon or cron (NAME) and as unique installation folder. If these names +# already exist on the installation system, we will add 01, 02, 03 and so +# on. Therefore your script should check THESE TWO VARIABLES for figuring +# out subfolder and scriptnames! You will find this information in +# /data/system/plugindatabase.dat after installation! BUT NEVER CHANGE this +# information in future updates! It will be used to identify your Plugin, +# handle updates etc. If you change this information, Loxberry could not +# identify your plugin and will handle it as a different one - therefore +# updates will definetely fail. +NAME=p1decrypter +FOLDER=p1decrypter + +# Friendly Long Name of your Plugin - 25 Characters maximum! All others maybe +# replaced by "...". You can use blanks, uppercase/lowercase etc. +TITLE=P1 Decrypter + +[AUTOUPDATE] +# If your plugin offers automatic updates, please enable the following option. +# Details here: http://www.loxwiki.eu/x/WoG3AQ +AUTOMATIC_UPDATES=true + +# This is the URL to your release.cfg file. This file will be checked for +# new releases if the user eneables autoupdates. If the version number +# given in the release.cfg file is newer than the installed one, the +# plugin archive will be downloaded from the URL given in the release.cfg file +# and will be installed automatically +RELEASECFG=https://raw.githubusercontent.com/metrophos/LoxBerry-Plugin-P1-Decrypter/master/release.cfg + +# This is the URL to your prerelease.cfg file. This file will be checked for +# new prereleases if the user enables autoupdates for prereleases. If the +# version number given in the release.cfg file is newer than the installed +# one, the plugin archive will be downloaded from the URL given in the +# release.cfg file and will be installed automatically +PRERELEASECFG=https://raw.githubusercontent.com/metrophos/LoxBerry-Plugin-P1-Decrypter/master/prerelease.cfg + +[SYSTEM] +# If a reboot is needed after the plugin was installed, enable the following +# option: +REBOOT=false + +# If your Plugin runs only on special LoxBerry Versions, please set the +# following options. If you plugin is compatible with all versions or at least +# with all future versions, please set this to false or leave it empty.. +# Note! If you use the new Plugin Interface V2 - I think you will, because you +# currently read THIS file ;-) - please set the Minimum version to 0.3.0! +LB_MINIMUM=0.3.1 +LB_MAXIMUM=false + +# If your plugin runs only on a special architecture (e.g. if you use the GPIOs +# on a Raspberry platform), please set the architecture here. You can seperate +# different architectures with a comma. Set to false or leave it empty if your +# plugin do not use any special features of an architecture. +# PUT in QUOTES ""!!! +# Please check http://www.loxwiki.eu:80/x/VoU_AQ section 5.1 for supported +# architectures and strings you should use here. +ARCHITECTURE="raspberry,x86" + +# If you are using the LoxBerry::Log Modul in PHP or Perl and you would like to use User-defined loglevels, +# enable this option. If enabled, the user will be able to choose a loglevel in the Plugin Management Widget. +CUSTOM_LOGLEVELS=false + +# Plugin Interface. Do not change this if you are not knowing what you are doing! +INTERFACE=2.0 diff --git a/postupgrade.sh b/postupgrade.sh new file mode 100644 index 0000000..6181a86 --- /dev/null +++ b/postupgrade.sh @@ -0,0 +1,75 @@ +#!/bin/sh + +# Bash script which is executed in case of an update (if this plugin is already +# installed on the system). This script is executed as very last step (*AFTER* +# postinstall) and can be for example used to save back or convert saved +# userfiles from /tmp back to the system. Use with caution and remember, that +# all systems may be different! +# +# Exit code must be 0 if executed successfull. +# Exit code 1 gives a warning but continues installation. +# Exit code 2 cancels installation. +# +# Will be executed as user "loxberry". +# +# You can use all vars from /etc/environment in this script. +# +# We add 5 additional arguments when executing this script: +# command +# +# For logging, print to STDOUT. You can use the following tags for showing +# different colorized information during plugin installation: +# +# This was ok!" +# This is just for your information." +# This is a warning!" +# This is an error!" +# This is a fail!" + +# To use important variables from command line use the following code: +COMMAND=$0 # Zero argument is shell command +PTEMPDIR=$1 # First argument is temp folder during install +PSHNAME=$2 # Second argument is Plugin-Name for scipts etc. +PDIR=$3 # Third argument is Plugin installation folder +PVERSION=$4 # Forth argument is Plugin version +#LBHOMEDIR=$5 # Comes from /etc/environment now. Fifth argument is + # Base folder of LoxBerry +PTEMPPATH=$6 # Sixth argument is full temp path during install (see also $1) + +# Combine them with /etc/environment +PCGI=$LBPCGI/$PDIR +PHTML=$LBPHTML/$PDIR +PTEMPL=$LBPTEMPL/$PDIR +PDATA=$LBPDATA/$PDIR +PLOG=$LBPLOG/$PDIR # Note! This is stored on a Ramdisk now! +PCONFIG=$LBPCONFIG/$PDIR +PSBIN=$LBPSBIN/$PDIR +PBIN=$LBPBIN/$PDIR + +#echo -n " Current working folder is: " +#pwd +#echo " Command is: $COMMAND" +#echo " Temporary folder is: $PTEMPDIR" +#echo " (Short) Name is: $PSHNAME" +#echo " Installation folder is: $PDIR" +#echo " Plugin version is: $PVERSION" +#echo " Plugin CGI folder is: $PCGI" +#echo " Plugin HTML folder is: $PHTML" +#echo " Plugin Template folder is: $PTEMPL" +#echo " Plugin Data folder is: $PDATA" +#echo " Plugin Log folder (on RAMDISK!) is: $PLOG" +#echo " Plugin CONFIG folder is: $PCONFIG" + +echo " Copy back existing config files" +cp -v -r /tmp/$ARGV1/_upgrade/config/$ARGV3/* $ARGV5/config/plugins/$ARGV3/ + +# echo " Adding new config parameters" +# grep -q -F "VERBOSE=" $ARGV5/config/plugins/$ARGV3/p1decrypter.cfg || echo "VERBOSE=0" >> $ARGV5/config/plugins/$ARGV3/p1decrypter.cfg + +echo " Copy back existing log files" +cp -v -r /tmp/$ARGV1/_upgrade/log/$ARGV3/* $ARGV5/log/plugins/$ARGV3/ + +echo " Remove temporary folders" +rm -rf /tmp/$ARGV1/_upgrade + +exit 0 diff --git a/prerelease.cfg b/prerelease.cfg new file mode 100644 index 0000000..f94208f --- /dev/null +++ b/prerelease.cfg @@ -0,0 +1,15 @@ +[AUTOUPDATE] +# This file is used if you would like to provide automatic updates. Put +# this file into your repo and give a link to this file in your plugin.cfg +# If you would like to release an update, edit VERSION here and give +# the donwload link here + + +# Version of the new release +VERSION=1.0.0 + +# Download URL of the ZIP Archive +ARCHIVEURL=https://github.com/metrophos/LoxBerry-Plugin-P1-Decrypter/archive/1.0.0.zip + +# URL for further information about this release +INFOURL=https://github.com/metrophos/LoxBerry-Plugin-P1-Decrypter/releases diff --git a/preupgrade.sh b/preupgrade.sh new file mode 100644 index 0000000..61032ed --- /dev/null +++ b/preupgrade.sh @@ -0,0 +1,73 @@ +#!/bin/bash + +# Shell script which is executed in case of an update (if this plugin is already +# installed on the system). This script is executed as very first step (*BEFORE* +# preinstall.sh) and can be used e.g. to save existing configfiles to /tmp +# during installation. Use with caution and remember, that all systems may be +# different! +# +# Exit code must be 0 if executed successfull. +# Exit code 1 gives a warning but continues installation. +# Exit code 2 cancels installation. +# +# Will be executed as user "loxberry". +# +# You can use all vars from /etc/environment in this script. +# +# We add 5 additional arguments when executing this script: +# command +# +# For logging, print to STDOUT. You can use the following tags for showing +# different colorized information during plugin installation: +# +# This was ok!" +# This is just for your information." +# This is a warning!" +# This is an error!" +# This is a fail!" + +# To use important variables from command line use the following code: +COMMAND=$0 # Zero argument is shell command +PTEMPDIR=$1 # First argument is temp folder during install +PSHNAME=$2 # Second argument is Plugin-Name for scipts etc. +PDIR=$3 # Third argument is Plugin installation folder +PVERSION=$4 # Forth argument is Plugin version +#LBHOMEDIR=$5 # Comes from /etc/environment now. Fifth argument is + # Base folder of LoxBerry +PTEMPPATH=$6 # Sixth argument is full temp path during install (see also $1) + +# Combine them with /etc/environment +PCGI=$LBPCGI/$PDIR +PHTML=$LBPHTML/$PDIR +PTEMPL=$LBPTEMPL/$PDIR +PDATA=$LBPDATA/$PDIR +PLOG=$LBPLOG/$PDIR # Note! This is stored on a Ramdisk now! +PCONFIG=$LBPCONFIG/$PDIR +PSBIN=$LBPSBIN/$PDIR +PBIN=$LBPBIN/$PDIR + +#echo -n " Current working folder is: " +#pwd +#echo " Command is: $COMMAND" +#echo " Temporary folder is: $PTEMPDIR" +#echo " (Short) Name is: $PSHNAME" +#echo " Installation folder is: $PDIR" +#echo " Plugin version is: $PVERSION" +#echo " Plugin CGI folder is: $PCGI" +#echo " Plugin HTML folder is: $PHTML" +#echo " Plugin Template folder is: $PTEMPL" +#echo " Plugin Data folder is: $PDATA" +#echo " Plugin Log folder (on RAMDISK!) is: $PLOG" +#echo " Plugin CONFIG folder is: $PCONFIG" + +echo " Creating temporary folders for upgrading" +mkdir -p /tmp/$ARGV1/_upgrade/config +mkdir -p /tmp/$ARGV1/_upgrade/log + +echo " Backup existing config files" +cp -v -r $ARGV5/config/plugins/$ARGV3/ /tmp/$ARGV1/_upgrade/config + +echo " Backup existing log files" +cp -v -r $ARGV5/log/plugins/$ARGV3/ /tmp/$ARGV1/_upgrade/log + +exit 0 diff --git a/release.cfg b/release.cfg new file mode 100644 index 0000000..8c50b1b --- /dev/null +++ b/release.cfg @@ -0,0 +1,14 @@ +[AUTOUPDATE] +# This file is used if you would like to provide automatic updates. Put +# this file into your repo and give a link to this file in your plugin.cfg +# If you would like to release an update, edit VERSION here and give +# the donwload link here + +# Version of the new release +VERSION=1.0.3 + +# Download URL of the ZIP Archive +ARCHIVEURL=https://github.com/metrophos/LoxBerry-Plugin-P1-Decrypter/archive/1.0.0.zip + +# URL for further information about this release +INFOURL=https://github.com/metrophos/LoxBerry-Plugin-P1-Decrypter/releases diff --git a/templates/content.html b/templates/content.html new file mode 100644 index 0000000..603e924 --- /dev/null +++ b/templates/content.html @@ -0,0 +1,167 @@ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Status + +
Enable Plugin + +
Key + + Global Unicast Encryption Key (GUEK) +
Value Mapping + + Value mapping of Smart Meter output. Only one per line 'label','regex' +
Miniserver + +
Miniserver UDP-Port + +
SEND_TO_UDP + +

Advanced Options

Serial Input Port + +
Serial Input Baudrate + +
Serial Input Parity + +
Serial Input Stopbits + +
Additional Authenticated Data + +
UDP Host + + If set, this option overrules the Miniserver as host +
Send To Serial Port + +

+ P1 Decrypter will send data to specified output serial port. + Generating virtual serial port example: socat -d -d pty,raw,echo=0,link=/dev/p1decrypter pty,raw,echo=0,link=/dev/smartmeter/p1decrypter +

+
Serial Output Port + +
Serial Output Baudrate + +
Serial Output Parity + +
Serial Output Stopbits + +
Raw + +

+ Disable mapping and send encrypted Smart Meter output directly +

+
Verbose + +
  +
+ + +
+
+
+ + \ No newline at end of file diff --git a/uninstall/uninstall b/uninstall/uninstall new file mode 100644 index 0000000..00f49f5 --- /dev/null +++ b/uninstall/uninstall @@ -0,0 +1,63 @@ +#!/bin/bash + +# Bashscript which is executed by bash when uninstalling the plugin +# Use with caution and remember, that all systems may be different! +# +# Exit code must be 0 if executed successfull. +# Exit code 1 gives a warning but continues deinstallation. +# +# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +# Will be executed as user "root". +# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +# +# You can use all vars from /etc/environment in this script. +# +# We add 5 additional arguments when executing this script: +# command +# +# For logging, print to STDOUT. You can use the following tags for showing +# different colorized information during plugin installation: +# +# This was ok!" +# This is just for your information." +# This is a warning!" +# This is an error!" +# This is a fail!" + +# To use important variables from command line use the following code: +#COMMAND=$0 # Zero argument is shell command +#PTEMPDIR=$1 # First argument is temp folder during install +#PSHNAME=$2 # Second argument is Plugin-Name for scipts etc. +#PDIR=$3 # Third argument is Plugin installation folder +#PVERSION=$4 # Forth argument is Plugin version +#LBHOMEDIR=$5 # Comes from /etc/environment now. Fifth argument is + # Base folder of LoxBerry + +# Combine them with /etc/environment +#PCGI=$LBPCGI/$PDIR +#PHTML=$LBPHTML/$PDIR +#PTEMPL=$LBPTEMPL/$PDIR +#PDATA=$LBPDATA/$PDIR +#PLOG=$LBPLOG/$PDIR # Note! This is stored on a Ramdisk now! +#PCONFIG=$LBPCONFIG/$PDIR +#PSBIN=$LBPSBIN/$PDIR +#PBIN=$LBPBIN/$PDIR +# +#echo " Command is: $COMMAND" +#echo " Temporary folder is: $TEMPDIR" +#echo " (Short) Name is: $PSHNAME" +#echo " Installation folder is: $ARGV3" +#echo " Plugin version is: $ARGV4" +#echo " Plugin CGI folder is: $PCGI" +#echo " Plugin HTML folder is: $PHTML" +#echo " Plugin Template folder is: $PTEMPL" +#echo " Plugin Data folder is: $PDATA" +#echo " Plugin Log folder (on RAMDISK!) is: $PLOG" +#echo " Plugin CONFIG folder is: $PCONFIG" +#echo " Plugin SBIN folder is: $PSBIN" +#echo " Plugin BIN folder is: $PBIN" + +pkill -9 -f p1decrypter.py + +# Exit with Status 0 +exit 0 \ No newline at end of file diff --git a/webfrontend/htmlauth/index.cgi b/webfrontend/htmlauth/index.cgi new file mode 100644 index 0000000..f7296a7 --- /dev/null +++ b/webfrontend/htmlauth/index.cgi @@ -0,0 +1,228 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use CGI; +use LoxBerry::System; +use LoxBerry::Web; +use MIME::Base64; +use Config::Simple; + +## on/off switch values +my @switch = ('0', '1'); +my %switchLabels = ( + '0' => 'No', + '1' => 'Yes' +); + +# get configs +my $p1decrypterCfg = new Config::Simple("$lbpconfigdir/p1decrypter.cfg"); +my $p1decrypterDefaultCfg = new Config::Simple("$lbpconfigdir/p1decrypter-default.cfg"); +my $generalCfg = new Config::Simple("$lbsconfigdir/general.cfg"); +my $plugindata = LoxBerry::System::plugindata(); + +# get request values +my $cgi = CGI->new; +$cgi->import_names('R'); + +# save settings to p1decrypter.cfg +if ($R::action eq "Save & Restart") { + $p1decrypterCfg->param('P1DECRYPTER.ENABLED', $R::enabled); + $p1decrypterCfg->param('P1DECRYPTER.KEY', $R::key); + $p1decrypterCfg->param('P1DECRYPTER.MAPPING', encode_base64($R::mapping)); + $p1decrypterCfg->param('P1DECRYPTER.MINISERVER_ID', $R::miniserver_id); + $p1decrypterCfg->param('P1DECRYPTER.UDP_PORT', $R::upd_port); + + $p1decrypterCfg->param('P1DECRYPTER.SERIAL_INPUT_PORT', $R::serial_input_port); + $p1decrypterCfg->param('P1DECRYPTER.SERIAL_INPUT_BAUDRATE', $R::serial_input_baudrate); + $p1decrypterCfg->param('P1DECRYPTER.SERIAL_INPUT_PARITY', $R::serial_input_parity); + $p1decrypterCfg->param('P1DECRYPTER.SERIAL_INPUT_STOPBITS', $R::serial_input_stopbits); + $p1decrypterCfg->param('P1DECRYPTER.AAD', $R::aad); + $p1decrypterCfg->param('P1DECRYPTER.SEND_TO_UDP', $R::send_to_udp); + $p1decrypterCfg->param('P1DECRYPTER.UDP_HOST', $R::udp_host); + $p1decrypterCfg->param('P1DECRYPTER.SEND_TO_SERIAL_PORT', $R::send_to_serial_port); + $p1decrypterCfg->param('P1DECRYPTER.SERIAL_OUTPUT_PORT', $R::serial_output_port); + $p1decrypterCfg->param('P1DECRYPTER.SERIAL_OUTPUT_BAUDRATE', $R::serial_output_baudrate); + $p1decrypterCfg->param('P1DECRYPTER.SERIAL_OUTPUT_PARITY', $R::serial_output_parity); + $p1decrypterCfg->param('P1DECRYPTER.SERIAL_OUTPUT_STOPBITS', $R::serial_output_stopbits); + $p1decrypterCfg->param('P1DECRYPTER.RAW', $R::raw); + $p1decrypterCfg->param('P1DECRYPTER.VERBOSE', $R::verbose); + + $p1decrypterCfg->save(); +} + +# reset values to p1decrypter-default.cfg +if ($R::action eq "Reset default configruation") { + $p1decrypterCfg->param('P1DECRYPTER.ENABLED', $p1decrypterDefaultCfg->param('P1DECRYPTER.ENABLED')); + $p1decrypterCfg->param('P1DECRYPTER.KEY', $p1decrypterDefaultCfg->param('P1DECRYPTER.KEY')); + $p1decrypterCfg->param('P1DECRYPTER.MAPPING', $p1decrypterDefaultCfg->param('P1DECRYPTER.MAPPING')); + $p1decrypterCfg->param('P1DECRYPTER.MINISERVER_ID', $p1decrypterDefaultCfg->param('P1DECRYPTER.MINISERVER_ID')); + $p1decrypterCfg->param('P1DECRYPTER.UDP_PORT', $p1decrypterDefaultCfg->param('P1DECRYPTER.UDP_PORT')); + + $p1decrypterCfg->param('P1DECRYPTER.SERIAL_INPUT_PORT', $p1decrypterDefaultCfg->param('P1DECRYPTER.SERIAL_INPUT_PORT')); + $p1decrypterCfg->param('P1DECRYPTER.SERIAL_INPUT_BAUDRATE', $p1decrypterDefaultCfg->param('P1DECRYPTER.SERIAL_INPUT_BAUDRATE')); + $p1decrypterCfg->param('P1DECRYPTER.SERIAL_INPUT_PARITY', $p1decrypterDefaultCfg->param('P1DECRYPTER.SERIAL_INPUT_PARITY')); + $p1decrypterCfg->param('P1DECRYPTER.SERIAL_INPUT_STOPBITS', $p1decrypterDefaultCfg->param('P1DECRYPTER.SERIAL_INPUT_STOPBITS')); + $p1decrypterCfg->param('P1DECRYPTER.AAD', $p1decrypterDefaultCfg->param('P1DECRYPTER.AAD')); + $p1decrypterCfg->param('P1DECRYPTER.SEND_TO_UDP', $p1decrypterDefaultCfg->param('P1DECRYPTER.SEND_TO_UDP')); + $p1decrypterCfg->param('P1DECRYPTER.UDP_HOST', $p1decrypterDefaultCfg->param('P1DECRYPTER.UDP_HOST')); + $p1decrypterCfg->param('P1DECRYPTER.SEND_TO_SERIAL_PORT', $p1decrypterDefaultCfg->param('P1DECRYPTER.SEND_TO_SERIAL_PORT')); + $p1decrypterCfg->param('P1DECRYPTER.SERIAL_OUTPUT_PORT', $p1decrypterDefaultCfg->param('P1DECRYPTER.SERIAL_OUTPUT_PORT')); + $p1decrypterCfg->param('P1DECRYPTER.SERIAL_OUTPUT_BAUDRATE', $p1decrypterDefaultCfg->param('P1DECRYPTER.SERIAL_OUTPUT_BAUDRATE')); + $p1decrypterCfg->param('P1DECRYPTER.SERIAL_OUTPUT_PARITY', $p1decrypterDefaultCfg->param('P1DECRYPTER.SERIAL_OUTPUT_PARITY')); + $p1decrypterCfg->param('P1DECRYPTER.SERIAL_OUTPUT_STOPBITS', $p1decrypterDefaultCfg->param('P1DECRYPTER.SERIAL_OUTPUT_STOPBITS')); + $p1decrypterCfg->param('P1DECRYPTER.RAW', $p1decrypterDefaultCfg->param('P1DECRYPTER.RAW')); + $p1decrypterCfg->param('P1DECRYPTER.VERBOSE', $p1decrypterDefaultCfg->param('P1DECRYPTER.VERBOSE')); + + $p1decrypterCfg->save(); +} + +# read settings from p1decrypter.cfg +$cgi->delete_all(); +$R::enabled = $p1decrypterCfg->param('P1DECRYPTER.ENABLED'); +$R::key = $p1decrypterCfg->param('P1DECRYPTER.KEY'); +$R::mapping = decode_base64($p1decrypterCfg->param('P1DECRYPTER.MAPPING')); +$R::miniserver_id = $p1decrypterCfg->param('P1DECRYPTER.MINISERVER_ID'); +$R::upd_port = $p1decrypterCfg->param('P1DECRYPTER.UDP_PORT'); + +$R::serial_input_port = $p1decrypterCfg->param('P1DECRYPTER.SERIAL_INPUT_PORT'); +$R::serial_input_baudrate = $p1decrypterCfg->param('P1DECRYPTER.SERIAL_INPUT_BAUDRATE'); +$R::serial_input_parity = $p1decrypterCfg->param('P1DECRYPTER.SERIAL_INPUT_PARITY'); +$R::serial_input_stopbits = $p1decrypterCfg->param('P1DECRYPTER.SERIAL_INPUT_STOPBITS'); +$R::aad = $p1decrypterCfg->param('P1DECRYPTER.AAD'); +$R::send_to_udp = $p1decrypterCfg->param('P1DECRYPTER.SEND_TO_UDP'); +$R::udp_host = $p1decrypterCfg->param('P1DECRYPTER.UDP_HOST'); +$R::send_to_serial_port = $p1decrypterCfg->param('P1DECRYPTER.SEND_TO_SERIAL_PORT'); +$R::serial_output_port = $p1decrypterCfg->param('P1DECRYPTER.SERIAL_OUTPUT_PORT'); +$R::serial_output_baudrate = $p1decrypterCfg->param('P1DECRYPTER.SERIAL_OUTPUT_BAUDRATE'); +$R::serial_output_parity = $p1decrypterCfg->param('P1DECRYPTER.SERIAL_OUTPUT_PARITY'); +$R::serial_output_stopbits = $p1decrypterCfg->param('P1DECRYPTER.SERIAL_OUTPUT_STOPBITS'); +$R::raw = $p1decrypterCfg->param('P1DECRYPTER.RAW'); +$R::verbose = $p1decrypterCfg->param('P1DECRYPTER.VERBOSE'); + +# restart p1decrypter +if ($R::action eq "Save & Restart" || $R::action eq "Reset default configruation") { + if ($R::enabled eq "1") { + qx(pkill -9 -f p1decrypter.py); + qx(/bin/sh $lbhomedir/system/cron/cron.05min/$plugindata->{PLUGINDB_NAME} > /dev/null 2>&1 &); + sleep(2); + } + else { + qx(pkill -9 -f p1decrypter.py); + } +} + +#-------------------------------------------------- +#-------------------------------------------------- + +# create html elements +my $template = HTML::Template->new( + filename => "$lbptemplatedir/content.html", + associate => $cgi, +); + +## ENABLED switch +$template->param(ENABLED => $cgi->popup_menu( + -name => 'enabled', + -values => \@switch, + -labels => \%switchLabels, + -default => $R::enabled +)); + +## KEY textfield +$template->param(KEY => $cgi->textfield(-name => 'key', -default => $R::key)); + +## MAPPING textarea +$template->param(MAPPING => $cgi->textarea(-name => 'mapping', -default => $R::mapping)); + +## MINISERVER_ID selection +my @miniserverIds = (); +my %miniserverLabels = (); +for (my $i = 1; $i <= $generalCfg->param('BASE.MINISERVERS'); $i++) { + push @miniserverIds, $i; + $miniserverLabels{ $i } = $generalCfg->param("MINISERVER$i.NAME") . " (" . ($generalCfg->param("MINISERVER$i.IPADDRESS")) . ")"; +}; +$template->param(MINISERVER_ID => $cgi->popup_menu( + -name => 'miniserver_id', + -values => \@miniserverIds, + -labels => \%miniserverLabels, + -default => $R::miniserver_id +)); + +## UDP_PORT textfield +$template->param(UDP_PORT => $cgi->textfield(-name => 'upd_port', -default => $R::upd_port)); + +## SERIAL_INPUT_PORT textfield +$template->param(SERIAL_INPUT_PORT => $cgi->textfield(-name => 'serial_input_port', -default => $R::serial_input_port)); + +## SERIAL_INPUT_BAUDRATE textfield +$template->param(SERIAL_INPUT_BAUDRATE => $cgi->textfield(-name => 'serial_input_baudrate', -default => $R::serial_input_baudrate)); + +## SERIAL_INPUT_PARITY textfield +$template->param(SERIAL_INPUT_PARITY => $cgi->textfield(-name => 'serial_input_parity', -default => $R::serial_input_parity)); + +## SERIAL_INPUT_STOPBITS textfield +$template->param(SERIAL_INPUT_STOPBITS => $cgi->textfield(-name => 'serial_input_stopbits', -default => $R::serial_input_stopbits)); + +## AAD textfield +$template->param(AAD => $cgi->textfield(-name => 'aad', -default => $R::aad)); + +## SEND_TO_UDP switch +$template->param(SEND_TO_UDP => $cgi->popup_menu( + -name => 'send_to_udp', + -values => \@switch, + -labels => \%switchLabels, + -default => $R::send_to_udp +)); + +## UDP_HOST textfield +$template->param(UDP_HOST => $cgi->textfield(-name => 'udp_host', -default => $R::udp_host)); + +## SEND_TO_SERIAL_PORT switch +$template->param(SEND_TO_SERIAL_PORT => $cgi->popup_menu( + -name => 'send_to_serial_port', + -values => \@switch, + -labels => \%switchLabels, + -default => $R::send_to_serial_port +)); + +## SERIAL_OUTPUT_PORT textfield +$template->param(SERIAL_OUTPUT_PORT => $cgi->textfield(-name => 'serial_output_port', -default => $R::serial_output_port)); + +## SERIAL_OUTPUT_BAUDRATE textfield +$template->param(SERIAL_OUTPUT_BAUDRATE => $cgi->textfield(-name => 'serial_output_baudrate', -default => $R::serial_output_baudrate)); + +## SERIAL_OUTPUT_PARITY textfield +$template->param(SERIAL_OUTPUT_PARITY => $cgi->textfield(-name => 'serial_output_parity', -default => $R::serial_output_parity)); + +## SERIAL_OUTPUT_STOPBITS textfield +$template->param(SERIAL_OUTPUT_STOPBITS => $cgi->textfield(-name => 'serial_output_stopbits', -default => $R::serial_output_stopbits)); + +## RAW switch +$template->param(RAW => $cgi->popup_menu( + -name => 'raw', + -values => \@switch, + -labels => \%switchLabels, + -default => $R::raw +)); + +## VERBOSE switch +$template->param(VERBOSE => $cgi->popup_menu( + -name => 'verbose', + -values => \@switch, + -labels => \%switchLabels, + -default => $R::verbose +)); + +## pid +my $pid = trim(qx(pgrep -f p1decrypter.py)); +$pid = $pid eq "" ? "$plugindata->{PLUGINDB_TITLE} down" : "$plugindata->{PLUGINDB_TITLE} up (PID: $pid)"; +$template->param(PID => $pid); + + +# write template +LoxBerry::Web::lbheader("$plugindata->{PLUGINDB_TITLE} $plugindata->{PLUGINDB_VERSION}", "https://github.com/metrophos/LoxBerry-Plugin-P1-Decrypter"); +print $template->output(); +LoxBerry::Web::lbfooter(); + +exit;