diff --git a/.vscode/settings.json b/.vscode/settings.json index 41b6fdb..01b6ec9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -54,5 +54,12 @@ "editor.quickSuggestions": { "other": "off" }, - "editor.snippetSuggestions": "none" + "editor.snippetSuggestions": "none", + "python.analysis.extraPaths": [ + "${workspaceFolder}/modules", + "${workspaceFolder}/modules/lib", + "${workspaceFolder}/modules/simul", + "${workspaceFolder}/modules/lib/tools", + "${workspaceFolder}/modules/lib/shell" + ] } \ No newline at end of file diff --git a/build.py b/build.py index 6a550c2..26ddf75 100755 --- a/build.py +++ b/build.py @@ -18,8 +18,11 @@ # Mov to gif : # ffmpeg -i video.mov -vf "fps=3,scale=640:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" -loop 0 output.gif # 640x360 +# +# Vmware share folder add next lines in /etc/fstab : +# vmhgfs-fuse /mnt/hgfs fuse defaults,allow_other 0 0 -MICROPYTHON_VERSION ="f4811b0b42f10aa12fc2f94c0459344d84c89eb8"#"12f99481518b0ebcb14f00b2323865a845c2a4f1" +MICROPYTHON_VERSION ="f4811b0b42f10aa12fc2f94c0459344d84c89eb8" ESP_IDF_VERSION ="-b v4.4.2" ESP32_CAMERA_VERSION="722497cb19383cd4ee6b5d57bb73148b5af41b24" # Very stable version but cannot be rebuild with chip esp32s3 ESP32_CAMERA_VERSION_S3="5c8349f4cf169c8a61283e0da9b8cff10994d3f3" # Reliability problem but Esp32 S3 firmware can build with it diff --git a/doc/CAMFLASHER.md b/doc/CAMFLASHER.md index c92ef1f..e0d3757 100644 --- a/doc/CAMFLASHER.md +++ b/doc/CAMFLASHER.md @@ -16,40 +16,33 @@ Camflasher with shell commands **upload** and **download**, allows to upload or - [CP210 drivers](https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers) - [FTDI drivers](https://ftdichip.com/drivers/vcp-drivers/) - - Download the firmware associated with your device : - - [ESP32-CAM, ESP32-CAM-MB, ESP32ONE, M5Stack Camera](https://github.com/remibert/pycameresp/releases/download/V13/ESP32CAM-firmware.bin) - - - [ESP32-NODEMCU, LOLIN32, ESP32 without SPIRAM](https://github.com/remibert/pycameresp/releases/download/V13/GENERIC-firmware.bin) + - [ESP32-CAM, ESP32-CAM-MB, ESP32ONE, M5Stack Camera](https://github.com/remibert/pycameresp/releases/download/V14/ESP32CAM-firmware.bin) - - [ESP32-TTGO-T8 or ESP32 with SPIRAM ](https://github.com/remibert/pycameresp/releases/download/V13/GENERIC_SPIRAM-firmware.bin) + - [ESP32-TTGO-T8 or ESP32 with SPIRAM ](https://github.com/remibert/pycameresp/releases/download/V14/GENERIC_SPIRAM-firmware.bin) - - [Pico PI W](https://github.com/remibert/pycameresp/releases/download/V13/PICO_W-firmware.uf2) - Or download the zip for standard micropython firmware : - - [Shell with editor for RP2 pico Pi](https://github.com/remibert/pycameresp/releases/download/V13/shell.zip) + - [Shell with editor](https://github.com/remibert/pycameresp/releases/download/V14/shell.zip) - - [Wifi manager, Http server](https://github.com/remibert/pycameresp/releases/download/V13/server.zip) + - [Wifi manager, Http server](https://github.com/remibert/pycameresp/releases/download/V14/server.zip) - Text editor source files running on python3 and micropython : - - [Text editor](https://github.com/remibert/pycameresp/releases/download/V13/editor.zip) + - [Text editor](https://github.com/remibert/pycameresp/releases/download/V14/editor.zip) It is possible to run the shell with editor, or the servers on a standard micropython platform. The servers, the wifi manager, requires having enough ram and wifi support (ESP32 with SPIRAM for example). Unzip archive and install it with rshell. - Download the camflasher application and unzip it : - - [CamFlasher for Windows 10 64 bits](https://github.com/remibert/pycameresp/releases/download/V13/CamFlasher_windows_10_64.zip) - - - [CamFlasher for Windows seven 64 bits](https://github.com/remibert/pycameresp/releases/download/V13/CamFlasher_windows_7_64.zip) - - - [CamFlasher for OSX Big Sur Intel](https://github.com/remibert/pycameresp/releases/download/V13/CamFlasher_osx.zip) + - [CamFlasher for Windows 10 64 bits](https://github.com/remibert/pycameresp/releases/download/V14/CamFlasher_windows_10_64.zip) - - [CamFlasher for Debian 11 x86_64](https://github.com/remibert/pycameresp/releases/download/V13/CamFlasher_linux.zip) + - [CamFlasher for OSX Big Sur Intel](https://github.com/remibert/pycameresp/releases/download/V14/CamFlasher_osx.zip) + - [CamFlasher for Debian 11 x86_64](https://github.com/remibert/pycameresp/releases/download/V14/CamFlasher_linux.zip) - On linux to be able to operate without being a super user you must enter the commands : diff --git a/doc/DIRECTORIES.md b/doc/DIRECTORIES.md index 08f9557..7aa9032 100644 --- a/doc/DIRECTORIES.md +++ b/doc/DIRECTORIES.md @@ -18,6 +18,7 @@ Below are the directory details : - **modules/lib/motion** : motion detection python sources - **modules/lib/tools** : tools used for all python sources - **modules/lib/server** : Ftp, Http, Pushover, Telnet, Ntp synchronization, user and session python sources +- **modules/lib/electricmeter** : used to monitor the consumption of an electric meter (not present in the firmware) - **modules/lib/wifi** : Wifi and accesspoint python sources - **modules/config** : Configuration saved in this directory diff --git a/doc/FIRMWARE.md b/doc/FIRMWARE.md index 21cdf50..58e78e7 100644 --- a/doc/FIRMWARE.md +++ b/doc/FIRMWARE.md @@ -37,13 +37,12 @@ The first time use command (get source, install required software and build firm And after juste for rebuild use command : - **python3 build.py --patch --build "ESP32CAM"** -Replace **ESP32CAM** by your prefered firmware, add double quote if you want to use wildcards for build many firmwares, for example ESP32 GENERIC : -- **python3 build.py --patch --build "GENERIC"** +Replace **ESP32CAM** by your prefered firmware, add double quote if you want to use wildcards for build many firmwares, for example ESP32 GENERIC SPIRAM : - **python3 build.py --patch --build "GENERIC_SPIRAM"** Or for all ESP32 S2 -- **python3 build.py --patch --build "ESP32CAM" "GENERIC" "GENERIC_SPIRAM"** +- **python3 build.py --patch --build "ESP32CAM" "GENERIC_SPIRAM"** To build ESP32 S3 you must clean all and add option --s3 : -- **python3 build.py --clean --s3 --patch --build "GENERIC_S3_*"** +- **python3 build.py --clean --s3 --patch --build "GENERIC_S3_SPIRAM"** diff --git a/doc/REQUIREMENTS.md b/doc/REQUIREMENTS.md index 61e8891..b58ca5f 100644 --- a/doc/REQUIREMENTS.md +++ b/doc/REQUIREMENTS.md @@ -15,7 +15,9 @@ Below are the devices compatible with pycameresp : ![ESP32CAM](/images/Device_ESP32CAM.jpg "ESP32CAM") ![ESP32CAM-MB](/images/Device_ESP32CAM-MB.jpg "ESP32CAM-MB") -![NODEMCU](/images/Device_NODEMCU.jpg "NODE MCU") ![LOLIN32](/images/Device_LOLIN32.jpg "LOLIN32") ![TTGO](/images/Device_TTGO.jpg "TTGO") ![ESP32ONE](/images/Device_ESP32ONE.jpg "ESP32ONE") -![M5StackCamera](/images/Device_M5StackCamera.jpg "M5StackCamera") \ No newline at end of file +![M5StackCamera](/images/Device_M5StackCamera.jpg "M5StackCamera") +![BPI-Leaf-S3](/images/Device_BPI-Leaf-S3.png "BPI-Leaf-S3") + +**Devices without spiram have been removed, the platform can work, but we often fall into a lack of memory.** \ No newline at end of file diff --git a/doc/SCREENSHOTS.md b/doc/SCREENSHOTS.md index 84dc4ab..f7b5312 100644 --- a/doc/SCREENSHOTS.md +++ b/doc/SCREENSHOTS.md @@ -4,7 +4,11 @@ The web interface allows configuration of the network, servers, etc... gives device information, and allows video streaming. -![WebInterface.gif](/images/WebInterface.gif "Board information web page") +![WebInterface.gif](/images/WebInterface.gif "Web pages") + +Responsive interface for smartphones and tablets + +![ResponsiceWebInterface.gif](/images/ResponsiveWebInterface.gif "Responsive Web pages") Smartphone motion detection notification (with pushover application) diff --git a/doc/lib/electricmeter/config.html b/doc/lib/electricmeter/config.html new file mode 100644 index 0000000..3f36ddc --- /dev/null +++ b/doc/lib/electricmeter/config.html @@ -0,0 +1,837 @@ + + + + + + +lib.electricmeter.config API documentation + + + + + + + + + + + +
+
+
+

Module lib.electricmeter.config

+
+
+

Function define the configuration of the electric meter

+
+ +Expand source code + +
# Distributed under MIT License
+# Copyright (c) 2021 Remi BERTHOLET
+""" Function define the configuration of the electric meter """
+# pylint:disable=anomalous-unicode-escape-in-string
+from tools                 import jsonconfig, strings
+
+class RateConfig(jsonconfig.JsonConfig):
+        """ Kwh rate configuration """
+        def __init__(self):
+                """ Constructor """
+                jsonconfig.JsonConfig.__init__(self)
+
+                self.name = b""
+                self.price = 0.0
+                self.currency = b""
+                self.validity_date = 0
+
+
+class RatesConfig(jsonconfig.JsonConfig):
+        """ Rates list per kwh """
+        config = None
+        def __init__(self):
+                """ Constructor """
+                jsonconfig.JsonConfig.__init__(self)
+                self.rates = []
+
+        def append(self, rate):
+                """ Add new rate in the list """
+                found = False
+                rate = strings.tobytes(rate)
+                for current in self.rates:
+                        if current[b"name"] == strings.tobytes(rate.name) and current[b"validity_date"] == strings.tobytes(rate.validity_date):
+                                found = True
+                                current[b"currency"] = strings.tobytes(rate.currency)
+                                current[b"price"] = rate.price
+                                break
+                if found is False:
+                        self.rates.append(strings.tobytes(rate.__dict__))
+
+        def get(self, index):
+                """ Return the rate at the index """
+                try:
+                        return self.rates[int(index)]
+                except:
+                        return None
+
+        def remove(self, index):
+                """ Remove the rate at the index """
+                try:
+                        del self.rates[int(index)]
+                except:
+                        pass
+
+        def search_rates(self, day):
+                """ Find the current rate """
+                result = {}
+                day = int(day)
+                for rate in self.rates:
+                        # If the rate is valid for the current date
+                        if day >= rate[b"validity_date"]:
+                                # If the same rate already found
+                                if rate[b"name"] in result:
+                                        # If the rate already found is older than the current rate
+                                        if result[rate[b"name"]][b"validity_date"] < rate[b"validity_date"]:
+                                                # Replace by the current rate
+                                                result[rate[b"name"]] = rate
+                                else:
+                                        # Keep the current rate
+                                        result[rate[b"name"]] = rate
+                return result
+
+        @staticmethod
+        def get_config():
+                """ Return the singleton configuration """
+                if RatesConfig.config is None:
+                        RatesConfig.config = RatesConfig()
+                        RatesConfig.config.load()
+                return RatesConfig.config
+
+
+class TimeSlotConfig(jsonconfig.JsonConfig):
+        """ Time slot configuration """
+        def __init__(self):
+                """ Constructor """
+                jsonconfig.JsonConfig.__init__(self)
+                self.rate       = b""
+                self.start_time = 0
+                self.end_time   = 0
+                self.color      = b""
+
+class TimeSlotsConfig(jsonconfig.JsonConfig):
+        """ Time slots list """
+        config = None
+        def __init__(self):
+                """ Constructor """
+                jsonconfig.JsonConfig.__init__(self)
+                self.time_slots = []
+
+        def append(self, time_slot):
+                """ Add new time slot in the list """
+                found = False
+                time_slot = strings.tobytes(time_slot)
+                for current in self.time_slots:
+                        if current[b"start_time"] == time_slot.start_time and current[b"end_time"]   == time_slot.end_time:
+                                current[b"color"] = time_slot.color
+                                current[b"rate"]  = time_slot.rate
+                                found = True
+                                break
+                if found is False:
+                        self.time_slots.append(strings.tobytes(time_slot.__dict__))
+
+        def get(self, index):
+                """ Return the time slot at the index """
+                try:
+                        return self.time_slots[int(index)]
+                except:
+                        return None
+
+        def remove(self, index):
+                """ Remove the time slot at the index """
+                try:
+                        del self.time_slots[int(index)]
+                except:
+                        pass
+
+        def get_prices(self, rates):
+                """ Return the list of prices according to the day """
+                if rates == {}:
+                        result = [{b'rate': b'', b'start_time': 0, b'end_time': 86340, b'color': b'#5498e0', b'price': 0, b'currency': b'not initialized'}]
+                else:
+                        result = self.time_slots[:]
+                        for time_slot in result:
+                                time_slot[b"price"]    = rates[time_slot[b"rate"]][b"price"]
+                                time_slot[b"currency"] = rates[time_slot[b"rate"]][b"currency"]
+                return result
+
+        @staticmethod
+        def get_config():
+                """ Return the singleton configuration """
+                if TimeSlotsConfig.config is None:
+                        TimeSlotsConfig.config = TimeSlotsConfig()
+                        TimeSlotsConfig.config.load()
+                return TimeSlotsConfig.config
+
+        @staticmethod
+        def get_cost(day):
+                """ Get the cost according to the day selected """
+                time_slots = TimeSlotsConfig.get_config()
+                rates      = RatesConfig.get_config()
+                return time_slots.get_prices(rates.search_rates(day))
+
+        @staticmethod
+        def create_empty_slot(size):
+                """ Create empty time slot """
+                time_slots = TimeSlotsConfig()
+                time_slots.load()
+                slot_pulses = {}
+                index = 0
+                while True:
+                        time_slot = time_slots.get(index)
+                        if time_slot is None:
+                                break
+                        slot_pulses[(time_slot[b"start_time"], time_slot[b"end_time"])] = [0]*size
+                        index += 1
+                if len(slot_pulses) == 0:
+                        slot_pulses[(0,1439*60)] = [0]*size
+                return slot_pulses
+
+
+class GeolocationConfig(jsonconfig.JsonConfig):
+        """ Geolocation configuration """
+        config = None
+        def __init__(self):
+                """ Constructor """
+                jsonconfig.JsonConfig.__init__(self)
+                self.latitude  = 44.93
+                self.longitude = 4.87
+
+        @staticmethod
+        def get_config():
+                """ Return the singleton configuration """
+                if GeolocationConfig.config is None:
+                        GeolocationConfig.config = GeolocationConfig()
+                        GeolocationConfig.config.load()
+                return GeolocationConfig.config
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class GeolocationConfig +
+
+

Geolocation configuration

+

Constructor

+
+ +Expand source code + +
class GeolocationConfig(jsonconfig.JsonConfig):
+        """ Geolocation configuration """
+        config = None
+        def __init__(self):
+                """ Constructor """
+                jsonconfig.JsonConfig.__init__(self)
+                self.latitude  = 44.93
+                self.longitude = 4.87
+
+        @staticmethod
+        def get_config():
+                """ Return the singleton configuration """
+                if GeolocationConfig.config is None:
+                        GeolocationConfig.config = GeolocationConfig()
+                        GeolocationConfig.config.load()
+                return GeolocationConfig.config
+
+

Ancestors

+
    +
  • tools.jsonconfig.JsonConfig
  • +
+

Class variables

+
+
var config
+
+
+
+
+

Static methods

+
+
+def get_config() +
+
+

Return the singleton configuration

+
+ +Expand source code + +
@staticmethod
+def get_config():
+        """ Return the singleton configuration """
+        if GeolocationConfig.config is None:
+                GeolocationConfig.config = GeolocationConfig()
+                GeolocationConfig.config.load()
+        return GeolocationConfig.config
+
+
+
+
+
+class RateConfig +
+
+

Kwh rate configuration

+

Constructor

+
+ +Expand source code + +
class RateConfig(jsonconfig.JsonConfig):
+        """ Kwh rate configuration """
+        def __init__(self):
+                """ Constructor """
+                jsonconfig.JsonConfig.__init__(self)
+
+                self.name = b""
+                self.price = 0.0
+                self.currency = b""
+                self.validity_date = 0
+
+

Ancestors

+
    +
  • tools.jsonconfig.JsonConfig
  • +
+
+
+class RatesConfig +
+
+

Rates list per kwh

+

Constructor

+
+ +Expand source code + +
class RatesConfig(jsonconfig.JsonConfig):
+        """ Rates list per kwh """
+        config = None
+        def __init__(self):
+                """ Constructor """
+                jsonconfig.JsonConfig.__init__(self)
+                self.rates = []
+
+        def append(self, rate):
+                """ Add new rate in the list """
+                found = False
+                rate = strings.tobytes(rate)
+                for current in self.rates:
+                        if current[b"name"] == strings.tobytes(rate.name) and current[b"validity_date"] == strings.tobytes(rate.validity_date):
+                                found = True
+                                current[b"currency"] = strings.tobytes(rate.currency)
+                                current[b"price"] = rate.price
+                                break
+                if found is False:
+                        self.rates.append(strings.tobytes(rate.__dict__))
+
+        def get(self, index):
+                """ Return the rate at the index """
+                try:
+                        return self.rates[int(index)]
+                except:
+                        return None
+
+        def remove(self, index):
+                """ Remove the rate at the index """
+                try:
+                        del self.rates[int(index)]
+                except:
+                        pass
+
+        def search_rates(self, day):
+                """ Find the current rate """
+                result = {}
+                day = int(day)
+                for rate in self.rates:
+                        # If the rate is valid for the current date
+                        if day >= rate[b"validity_date"]:
+                                # If the same rate already found
+                                if rate[b"name"] in result:
+                                        # If the rate already found is older than the current rate
+                                        if result[rate[b"name"]][b"validity_date"] < rate[b"validity_date"]:
+                                                # Replace by the current rate
+                                                result[rate[b"name"]] = rate
+                                else:
+                                        # Keep the current rate
+                                        result[rate[b"name"]] = rate
+                return result
+
+        @staticmethod
+        def get_config():
+                """ Return the singleton configuration """
+                if RatesConfig.config is None:
+                        RatesConfig.config = RatesConfig()
+                        RatesConfig.config.load()
+                return RatesConfig.config
+
+

Ancestors

+
    +
  • tools.jsonconfig.JsonConfig
  • +
+

Class variables

+
+
var config
+
+
+
+
+

Static methods

+
+
+def get_config() +
+
+

Return the singleton configuration

+
+ +Expand source code + +
@staticmethod
+def get_config():
+        """ Return the singleton configuration """
+        if RatesConfig.config is None:
+                RatesConfig.config = RatesConfig()
+                RatesConfig.config.load()
+        return RatesConfig.config
+
+
+
+

Methods

+
+
+def append(self, rate) +
+
+

Add new rate in the list

+
+ +Expand source code + +
def append(self, rate):
+        """ Add new rate in the list """
+        found = False
+        rate = strings.tobytes(rate)
+        for current in self.rates:
+                if current[b"name"] == strings.tobytes(rate.name) and current[b"validity_date"] == strings.tobytes(rate.validity_date):
+                        found = True
+                        current[b"currency"] = strings.tobytes(rate.currency)
+                        current[b"price"] = rate.price
+                        break
+        if found is False:
+                self.rates.append(strings.tobytes(rate.__dict__))
+
+
+
+def get(self, index) +
+
+

Return the rate at the index

+
+ +Expand source code + +
def get(self, index):
+        """ Return the rate at the index """
+        try:
+                return self.rates[int(index)]
+        except:
+                return None
+
+
+
+def remove(self, index) +
+
+

Remove the rate at the index

+
+ +Expand source code + +
def remove(self, index):
+        """ Remove the rate at the index """
+        try:
+                del self.rates[int(index)]
+        except:
+                pass
+
+
+
+def search_rates(self, day) +
+
+

Find the current rate

+
+ +Expand source code + +
def search_rates(self, day):
+        """ Find the current rate """
+        result = {}
+        day = int(day)
+        for rate in self.rates:
+                # If the rate is valid for the current date
+                if day >= rate[b"validity_date"]:
+                        # If the same rate already found
+                        if rate[b"name"] in result:
+                                # If the rate already found is older than the current rate
+                                if result[rate[b"name"]][b"validity_date"] < rate[b"validity_date"]:
+                                        # Replace by the current rate
+                                        result[rate[b"name"]] = rate
+                        else:
+                                # Keep the current rate
+                                result[rate[b"name"]] = rate
+        return result
+
+
+
+
+
+class TimeSlotConfig +
+
+

Time slot configuration

+

Constructor

+
+ +Expand source code + +
class TimeSlotConfig(jsonconfig.JsonConfig):
+        """ Time slot configuration """
+        def __init__(self):
+                """ Constructor """
+                jsonconfig.JsonConfig.__init__(self)
+                self.rate       = b""
+                self.start_time = 0
+                self.end_time   = 0
+                self.color      = b""
+
+

Ancestors

+
    +
  • tools.jsonconfig.JsonConfig
  • +
+
+
+class TimeSlotsConfig +
+
+

Time slots list

+

Constructor

+
+ +Expand source code + +
class TimeSlotsConfig(jsonconfig.JsonConfig):
+        """ Time slots list """
+        config = None
+        def __init__(self):
+                """ Constructor """
+                jsonconfig.JsonConfig.__init__(self)
+                self.time_slots = []
+
+        def append(self, time_slot):
+                """ Add new time slot in the list """
+                found = False
+                time_slot = strings.tobytes(time_slot)
+                for current in self.time_slots:
+                        if current[b"start_time"] == time_slot.start_time and current[b"end_time"]   == time_slot.end_time:
+                                current[b"color"] = time_slot.color
+                                current[b"rate"]  = time_slot.rate
+                                found = True
+                                break
+                if found is False:
+                        self.time_slots.append(strings.tobytes(time_slot.__dict__))
+
+        def get(self, index):
+                """ Return the time slot at the index """
+                try:
+                        return self.time_slots[int(index)]
+                except:
+                        return None
+
+        def remove(self, index):
+                """ Remove the time slot at the index """
+                try:
+                        del self.time_slots[int(index)]
+                except:
+                        pass
+
+        def get_prices(self, rates):
+                """ Return the list of prices according to the day """
+                if rates == {}:
+                        result = [{b'rate': b'', b'start_time': 0, b'end_time': 86340, b'color': b'#5498e0', b'price': 0, b'currency': b'not initialized'}]
+                else:
+                        result = self.time_slots[:]
+                        for time_slot in result:
+                                time_slot[b"price"]    = rates[time_slot[b"rate"]][b"price"]
+                                time_slot[b"currency"] = rates[time_slot[b"rate"]][b"currency"]
+                return result
+
+        @staticmethod
+        def get_config():
+                """ Return the singleton configuration """
+                if TimeSlotsConfig.config is None:
+                        TimeSlotsConfig.config = TimeSlotsConfig()
+                        TimeSlotsConfig.config.load()
+                return TimeSlotsConfig.config
+
+        @staticmethod
+        def get_cost(day):
+                """ Get the cost according to the day selected """
+                time_slots = TimeSlotsConfig.get_config()
+                rates      = RatesConfig.get_config()
+                return time_slots.get_prices(rates.search_rates(day))
+
+        @staticmethod
+        def create_empty_slot(size):
+                """ Create empty time slot """
+                time_slots = TimeSlotsConfig()
+                time_slots.load()
+                slot_pulses = {}
+                index = 0
+                while True:
+                        time_slot = time_slots.get(index)
+                        if time_slot is None:
+                                break
+                        slot_pulses[(time_slot[b"start_time"], time_slot[b"end_time"])] = [0]*size
+                        index += 1
+                if len(slot_pulses) == 0:
+                        slot_pulses[(0,1439*60)] = [0]*size
+                return slot_pulses
+
+

Ancestors

+
    +
  • tools.jsonconfig.JsonConfig
  • +
+

Class variables

+
+
var config
+
+
+
+
+

Static methods

+
+
+def create_empty_slot(size) +
+
+

Create empty time slot

+
+ +Expand source code + +
@staticmethod
+def create_empty_slot(size):
+        """ Create empty time slot """
+        time_slots = TimeSlotsConfig()
+        time_slots.load()
+        slot_pulses = {}
+        index = 0
+        while True:
+                time_slot = time_slots.get(index)
+                if time_slot is None:
+                        break
+                slot_pulses[(time_slot[b"start_time"], time_slot[b"end_time"])] = [0]*size
+                index += 1
+        if len(slot_pulses) == 0:
+                slot_pulses[(0,1439*60)] = [0]*size
+        return slot_pulses
+
+
+
+def get_config() +
+
+

Return the singleton configuration

+
+ +Expand source code + +
@staticmethod
+def get_config():
+        """ Return the singleton configuration """
+        if TimeSlotsConfig.config is None:
+                TimeSlotsConfig.config = TimeSlotsConfig()
+                TimeSlotsConfig.config.load()
+        return TimeSlotsConfig.config
+
+
+
+def get_cost(day) +
+
+

Get the cost according to the day selected

+
+ +Expand source code + +
@staticmethod
+def get_cost(day):
+        """ Get the cost according to the day selected """
+        time_slots = TimeSlotsConfig.get_config()
+        rates      = RatesConfig.get_config()
+        return time_slots.get_prices(rates.search_rates(day))
+
+
+
+

Methods

+
+
+def append(self, time_slot) +
+
+

Add new time slot in the list

+
+ +Expand source code + +
def append(self, time_slot):
+        """ Add new time slot in the list """
+        found = False
+        time_slot = strings.tobytes(time_slot)
+        for current in self.time_slots:
+                if current[b"start_time"] == time_slot.start_time and current[b"end_time"]   == time_slot.end_time:
+                        current[b"color"] = time_slot.color
+                        current[b"rate"]  = time_slot.rate
+                        found = True
+                        break
+        if found is False:
+                self.time_slots.append(strings.tobytes(time_slot.__dict__))
+
+
+
+def get(self, index) +
+
+

Return the time slot at the index

+
+ +Expand source code + +
def get(self, index):
+        """ Return the time slot at the index """
+        try:
+                return self.time_slots[int(index)]
+        except:
+                return None
+
+
+
+def get_prices(self, rates) +
+
+

Return the list of prices according to the day

+
+ +Expand source code + +
def get_prices(self, rates):
+        """ Return the list of prices according to the day """
+        if rates == {}:
+                result = [{b'rate': b'', b'start_time': 0, b'end_time': 86340, b'color': b'#5498e0', b'price': 0, b'currency': b'not initialized'}]
+        else:
+                result = self.time_slots[:]
+                for time_slot in result:
+                        time_slot[b"price"]    = rates[time_slot[b"rate"]][b"price"]
+                        time_slot[b"currency"] = rates[time_slot[b"rate"]][b"currency"]
+        return result
+
+
+
+def remove(self, index) +
+
+

Remove the time slot at the index

+
+ +Expand source code + +
def remove(self, index):
+        """ Remove the time slot at the index """
+        try:
+                del self.time_slots[int(index)]
+        except:
+                pass
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/doc/lib/electricmeter/electricmeter.html b/doc/lib/electricmeter/electricmeter.html new file mode 100644 index 0000000..191f17a --- /dev/null +++ b/doc/lib/electricmeter/electricmeter.html @@ -0,0 +1,2127 @@ + + + + + + +lib.electricmeter.electricmeter API documentation + + + + + + + + + + + +
+
+
+

Module lib.electricmeter.electricmeter

+
+
+

Task to count wh from the electric meter

+
+ +Expand source code + +
""" Task to count wh from the electric meter """
+import struct
+import time
+import collections
+import uasyncio
+import machine
+import wifi
+from server.server import Server
+from electricmeter.config   import TimeSlotsConfig
+from electricmeter          import em_lang
+from tools import filesystem, date, strings, fnmatch, logger, tasking, info
+# pylint:disable=consider-using-f-string
+# pylint:disable=consider-iterating-dictionary
+# pylint:disable=missing-function-docstring
+# pylint:disable=global-variable-not-assigned
+# pylint:disable=trailing-whitespace
+# pylint:disable=too-many-lines
+
+# Hourly file format :
+#  - pulses_per_minute:uint8_t * 24*60
+
+# Daily file format :
+#  - (start_time:uint16_t, end_time:uint16_t, pulses_per_day:uint16_t * 31 days) * max_time_slots
+
+# Monthly file format :
+#  - (start_time:uint16_t, end_time:uint16_t, pulses_per_month:uint32_t * 12 months) * max_time_slots
+
+PULSE_DIRECTORY = "pulses"
+PULSE_HOURLY   = ".hourly"
+PULSE_DAILY    = ".daily"
+PULSE_MONTHLY  = ".monthly"
+
+class PulseSensor:
+        """ Detect wh pulse from electric meter """
+        def __init__(self, gpio, min_duration_ns=20_000_000, queue_length=1_500):
+                self.pulses = collections.deque((), queue_length)
+                self.notifier = uasyncio.Event()
+                self.previous_counter = 0
+                self.previous_time = time.time_ns()
+                self.min_duration_ns = min_duration_ns
+
+                # The use of pcnt counter allows to obtain a better reliability of counting
+                self.counter = machine.Counter(0, src=machine.Pin(gpio, mode=machine.Pin.IN), direction=machine.Counter.UP)
+                self.counter.filter_ns(self.min_duration_ns)
+                self.counter.pause()
+                self.counter.value(0)
+
+                self.sensor = machine.Pin(gpio, machine.Pin.IN, machine.Pin.PULL_DOWN)
+                self.sensor.irq(handler=self.detected, trigger=machine.Pin.IRQ_RISING)
+                self.counter.resume()
+
+        def __del__(self):
+                """ Destructor """
+                self.sensor.irq(handler=None)
+                self.counter.deinit()
+
+        def detected(self, pin):
+                """ Callback called when pulse detected """
+                pulse_time     = time.time_ns()
+                pulse_counter  = self.counter.value()
+                if pin.value() == 1:
+                        # If new pulses detected
+                        if pulse_counter != self.previous_counter:
+                                # if the pulse is not too close to the previous one
+                                if self.previous_time + self.min_duration_ns < pulse_time:
+                                        # If the new value is not a counter overflow
+                                        if pulse_counter > self.previous_counter:
+                                                pulse_quantity = pulse_counter - self.previous_counter
+                                        else:
+                                                pulse_quantity = 1
+
+                                        # Save the new counter value
+                                        self.previous_counter = pulse_counter
+
+                                        # Add the current pulse to list
+                                        self.pulses.append((pulse_quantity, pulse_time))
+
+                                        # Wake up pulse counter
+                                        self.notifier.set()
+                                else:
+                                        # Ignore previous pulse
+                                        self.previous_counter = pulse_counter
+
+        async def wait(self):
+                """ Wait the wh pulses and the returns the list of pulses """
+                # Wait pulses
+                await self.notifier.wait()
+
+                # Clear notification flag
+                self.notifier.clear()
+                result = []
+
+                # Empty list of pulses
+                while True:
+                        try:
+                                result.append(self.pulses.popleft())
+                        except IndexError:
+                                break
+                return result
+
+        def simulate(self, pulses):
+                """ Simulates the flashing led on the electric meter """
+                self.sensor.value(1)
+                self.counter.value(pulses)
+                self.detected(self.sensor)
+
+class HourlyCounter:
+        """ Hourly counter of wh consumed """
+        counter = None
+
+        def __init__(self, gpio):
+                """ Constructor """
+                global PULSE_DIRECTORY
+                filesystem.makedir(PULSE_DIRECTORY,True)
+                self.day    = time.time()
+                self.pulses = HourlyCounter.load(HourlyCounter.get_filename(self.day))
+                self.last_save = time.time()
+                self.watt_hour = 0
+                self.previous_pulse_time = None
+                self.day_pulses_counter = 0
+                self.pulse_sensor = PulseSensor(gpio)
+        
+        async def manage(self):
+                """ Manage the counter """
+                # Wait the list of pulses
+                pulses = await self.pulse_sensor.wait()
+                for pulse_quantity, pulse_time in pulses:
+                        # If previous pulse registered
+                        if self.previous_pulse_time is not None:
+                                # Compute the instant power
+                                self.watt_hour = 3600 * 1_000_000_000/((pulse_time - self.previous_pulse_time)/pulse_quantity)
+
+                        # Increase
+                        self.day_pulses_counter += pulse_quantity
+                        self.previous_pulse_time = pulse_time
+                        hour,minute=date.local_time(pulse_time//1_000_000_000)[3:5]
+                        day   = pulse_time//1_000_000_000
+                        minut = hour*60+minute
+                        self.pulses[minut] += pulse_quantity
+
+                        # If day change
+                        if HourlyCounter.is_same_day(day, self.day) is False:
+                                # Save day counter
+                                HourlyCounter.save(HourlyCounter.get_filename(self.day))
+
+                                # Clear day pulses counter
+                                self.day_pulses_counter = 0
+
+                                # Load new day if existing or clear counter (manage day light saving)
+                                self.pulses = self.load(day)
+                                self.day = day
+
+                # Each ten minutes
+                if time.time() > (self.last_save + 67):
+                        # Save pulses file
+                        HourlyCounter.save(HourlyCounter.get_filename(self.day))
+
+                # print("%s %.1f wh pulses=%d"%(date.date_to_string(), self.watt_hour, self.day_pulses_counter))
+                return True
+
+        @staticmethod
+        def is_same_day(day1, day2):
+                """ Indicates if the day 1 is equal to day 2"""
+                if day1//86400 == day2//86400 or day1 is None or day2 is None:
+                        return True
+                else:
+                        return False
+
+        @staticmethod
+        def get_filename(day=None):
+                """ Return the filename according to day """
+                global PULSE_DIRECTORY, PULSE_HOURLY
+                if day is None:
+                        day = time.time()
+                year,month,day = date.local_time(day)[:3]
+                return "%s/%04d-%02d/%04d-%02d-%02d%s"%(PULSE_DIRECTORY, year, month, year, month, day, PULSE_HOURLY)
+
+        @staticmethod
+        def save(filename):
+                """ Save pulses file """
+                try:
+                        filesystem.makedir(filesystem.split(filename)[0], True)
+                        with open(filename, "wb") as file:
+                                file.write(struct.pack("B"*1440, *HourlyCounter.counter.pulses))
+                        HourlyCounter.counter.last_save = time.time()
+                except Exception as err:
+                        logger.exception(err)
+
+        @staticmethod
+        def load(filename, pulses=None):
+                """ Load pulses file """
+                result = [0]*1440
+                try:
+                        with open(filename, "rb") as file:
+                                load_pulses = struct.unpack("B"*1440, file.read())
+                                index = 0
+                                if pulses is None:
+                                        result = list(load_pulses)
+                                else:
+                                        result = pulses
+                                        for pulse in load_pulses:
+                                                result[index] += pulse
+                                                index += 1
+                except Exception as err:
+                        logger.exception(err)
+                return result
+
+        @staticmethod
+        def get_power():
+                """ Return power instantaneous """
+                return HourlyCounter.counter.watt_hour
+
+        def simulate(self, pulses):
+                """ Simulates the flashing led on the electric meter """
+                self.pulse_sensor.simulate(pulses)
+
+        @staticmethod
+        async def task(gpio):
+                """ Task to count wh from the electric meter """
+                HourlyCounter.counter = HourlyCounter(gpio)
+                await tasking.task_monitoring(HourlyCounter.counter.manage)
+
+        @staticmethod
+        def get_datas(selected_date):
+                """ Return the pulse filename according to the selected date """
+                if type(selected_date) == type(b""):
+                        selected_date = date.html_to_date(selected_date)
+
+                if selected_date is None or HourlyCounter.is_same_day(selected_date, time.time()):
+                        result = HourlyCounter.counter.pulses
+                else:
+                        result = HourlyCounter.load(HourlyCounter.get_filename(selected_date))
+                return result
+
+        @staticmethod
+        async def pulse_simulator():
+                """ Simulates the flashing led on the electric meter """
+                import random
+                while True:
+                        HourlyCounter.counter.simulate(random.randint(1, 4))
+                        await uasyncio.sleep(random.randint(1, 5))
+
+class DailyCounter:
+        """ Daily counter of wh consumed """
+        @staticmethod
+        def get_filename(selected_date=None):
+                """ Return the filename according to selected date """
+                global PULSE_DIRECTORY, PULSE_DAILY
+                if selected_date is None:
+                        selected_date = time.time()
+                year,month = date.local_time(selected_date)[:2]
+                return "%s/%04d-%02d/%04d-%02d%s"%(PULSE_DIRECTORY, year, month, year, month, PULSE_DAILY)
+
+        @staticmethod
+        def get_datas(selected_date):
+                """ Return the pulse filename according to the month selected """
+                result = []
+                filename = DailyCounter.get_filename(selected_date)
+                slot_pulses = DailyCounter.load(filename)
+                for time_slot, days in slot_pulses.items():
+                        result.append({"time_slot":time_slot,"days":days})
+                return result
+
+        @staticmethod
+        def load(filename):
+                """ Load daily file """
+                result = {}
+                try:
+                        with open(filename, "rb") as file:
+                                while True:
+                                        data = file.read(2)
+                                        if len(data) == 0:
+                                                break
+                                        start = struct.unpack("H", data)[0]*60
+                                        end   = struct.unpack("H", file.read(2))[0]*60
+                                        days = struct.unpack("H"*31, file.read(2*31))
+                                        result[(start,end)] = days
+                except OSError:
+                        pass
+                except Exception as err:
+                        logger.exception(err)
+                return result
+
+        @staticmethod
+        def save(filename, slot_pulses):
+                """ Save daily file """
+                try:
+                        with open(filename, "wb") as file:
+                                for time_slot, days in slot_pulses.items():
+                                        start, end = time_slot
+                                        file.write(struct.pack("HH", start//60, end//60))
+                                        file.write(struct.pack("H"*len(days), *days))
+                except OSError:
+                        pass
+                except Exception as err:
+                        logger.exception(err)
+
+        @staticmethod
+        async def update(filenames, daily_to_update):
+                """ Update daily files """
+                for key, daily_filename in daily_to_update.items():
+                        year, month = key
+                        print("Update %s\n  "%daily_filename, end="")
+                        hourly_searched = "%s/%s-%s/%s-%s*%s"%(PULSE_DIRECTORY, year, month, year, month, PULSE_HOURLY)
+                        slot_pulses = TimeSlotsConfig.create_empty_slot(31)
+                        for hourly_filename in filenames:
+                                if fnmatch.fnmatch(hourly_filename, hourly_searched):
+                                        name = filesystem.splitext(filesystem.split(hourly_filename)[1])[0]
+                                        day = int(name.split("-")[-1])
+                                        print("%d "%day, end="")
+                                        hourly_pulses = HourlyCounter.load(hourly_filename)
+                                        second = 0
+                                        for start, end in slot_pulses.keys():
+                                                second = 0
+                                                for pulses in hourly_pulses:
+                                                        if start <= second <= end:
+                                                                slot_pulses[(start,end)][day-1] += pulses
+                                                        second += 60
+                                else:
+                                        pass
+                                await uasyncio.sleep_ms(2)
+                        print("")
+                        await uasyncio.sleep_ms(2)
+                        DailyCounter.save(daily_filename, slot_pulses)
+
+class MonthlyCounter:
+        """ Monthly counter of wh consumed """
+        last_update = [0]
+        next_update = [0]
+        force = [True]
+
+        @staticmethod
+        async def update(filenames, monthly_to_update):
+                """ Update monthly files """
+                for year, monthly_filename in monthly_to_update.items():
+                        print("Update %s\n  "%monthly_filename, end="")
+                        daily_searched = "%s/%s-*/%s-*%s"%(PULSE_DIRECTORY, year, year, PULSE_DAILY)
+                        slot_pulses = TimeSlotsConfig.create_empty_slot(12)
+                        for daily_filename in filenames:
+                                if fnmatch.fnmatch(daily_filename, daily_searched):
+                                        name = filesystem.splitext(filesystem.split(daily_filename)[1])[0]
+                                        month = int(name.split("-")[-1])
+                                        print("%d "%month, end="")
+                                        daily_slot_pulses = DailyCounter.load(daily_filename)
+                                        for time_slot, days in daily_slot_pulses.items():
+                                                for day in days:
+                                                        slot_pulses[time_slot][month-1] = slot_pulses[time_slot][month-1]+day
+                                await uasyncio.sleep_ms(2)
+                        print("")
+                        MonthlyCounter.save(monthly_filename, slot_pulses)
+                        await uasyncio.sleep_ms(2)
+
+        @staticmethod
+        def save(filename, slot_pulses):
+                try:
+                        with open(filename, "wb") as file:
+                                for time_slot, months in slot_pulses.items():
+                                        start, end = time_slot
+                                        file.write(struct.pack("HH", start//60, end//60))
+                                        file.write(struct.pack("I"*len(months), *months))
+                except Exception as err:
+                        logger.exception(err)
+
+        @staticmethod
+        def load(filename):
+                """ Load daily file content """
+                result = {}
+                try:
+                        with open(filename, "rb") as file:
+                                while True:
+                                        data = file.read(2)
+                                        if len(data) == 0:
+                                                break
+                                        start = struct.unpack("H", data)[0]*60
+                                        end   = struct.unpack("H", file.read(2))[0]*60
+                                        months = struct.unpack("I"*12, file.read(4*12))
+                                        result[(start,end)] = months
+                except Exception as err:
+                        logger.exception(err)
+                return result
+
+        @staticmethod
+        async def get_updates():
+                """ Build the list of daily and monthly file to update """
+                global PULSE_DIRECTORY, PULSE_MONTHLY, PULSE_DAILY
+                force = MonthlyCounter.force[0]
+                _, filenames = await filesystem.ascandir(PULSE_DIRECTORY, "*", True)
+                filenames.sort()
+                daily_to_update = {}
+                monthly_to_update = {}
+                for filename in filenames:
+                        if fnmatch.fnmatch(filename, "*"+PULSE_HOURLY):
+                                name = filesystem.splitext(filesystem.split(filename)[1])[0]
+                                year, month, day = name.split("-")
+                                daily   = "%s/%s-%s/%s-%s%s"%(PULSE_DIRECTORY, year, month, year, month, PULSE_DAILY)
+                                monthly = "%s/%s%s"%(PULSE_DIRECTORY, year, PULSE_MONTHLY)
+                                update = False
+                                if daily not in daily_to_update:
+                                        if filesystem.exists(daily):
+                                                daily_date  = filesystem.fileinfo(daily)[8]
+                                                hourly_date = filesystem.fileinfo(filename)[8]
+                                                if hourly_date > daily_date or force:
+                                                        update = True
+                                        else:
+                                                update = True
+                                if update:
+                                        daily_to_update[(year, month)] = daily
+                                        monthly_to_update[year] = monthly
+                        await uasyncio.sleep_ms(2)
+                MonthlyCounter.force[0] = False
+                return daily_to_update, monthly_to_update, filenames
+
+        @staticmethod
+        def get_filename(selected_date=None):
+                """ Return the filename according to day """
+                global PULSE_DIRECTORY, PULSE_MONTHLY
+                if selected_date is None:
+                        selected_date = time.time()
+                year = date.local_time(selected_date)[0]
+                return "%s/%04d%s"%(PULSE_DIRECTORY, year, PULSE_MONTHLY)
+
+        @staticmethod
+        def get_datas(selected_date):
+                """ Return the pulse filename according to the month selected """
+                result = []
+                slot_pulses = MonthlyCounter.load(MonthlyCounter.get_filename(selected_date))
+                for time_slot, months in slot_pulses.items():
+                        result.append({"time_slot":time_slot,"months":months})
+                return result
+
+        @staticmethod
+        async def refresh():
+                """ Refresh the counters """
+                if MonthlyCounter.last_update[0] + 599 < time.time():
+                        MonthlyCounter.next_update[0] = 0
+
+        @staticmethod
+        async def manage():
+                """ Rebuild all month files if necessary """
+                while True:
+                        if MonthlyCounter.next_update[0] <= 0:
+                                break
+                        MonthlyCounter.next_update[0] -= 1
+                        await uasyncio.sleep(1)
+
+                daily_to_update, monthly_to_update, filenames = await MonthlyCounter().get_updates()
+                await DailyCounter.update  (filenames, daily_to_update)
+                await MonthlyCounter.update(filenames, monthly_to_update)
+                MonthlyCounter.next_update[0] = 28793
+                MonthlyCounter.last_update[0] = time.time()
+                return True
+
+        @staticmethod
+        async def task():
+                """ Task to count wh from the electric meter """
+                await tasking.task_monitoring(MonthlyCounter.manage)
+
+class Consumption:
+        """ Stores the consumption of a time slot """
+        def __init__(self, name, currency):
+                """ Constructor """
+                self.name = strings.tostrings(name)
+                self.cost = 0.
+                self.pulses = 0
+                self.currency = strings.tostrings(currency)
+
+        def add(self, pulses, price):
+                """ Add wh pulses with its price """
+                cumul_pulses = 0
+                for pulse in pulses:
+                        cumul_pulses += pulse
+
+                self.cost += cumul_pulses*price/1000
+                self.pulses += cumul_pulses
+
+        def to_string(self):
+                """ Convert to string """
+                return "%s : %.2f %s (%.3f kWh)"%(self.name, self.cost, strings.tostrings(self.currency), self.pulses/1000)
+
+class Cost:
+        """ Abstract class to compute the cost """
+        def get_rates(self, selected_date):
+                """ Get the rate according to the selected date """
+                prices = TimeSlotsConfig.get_cost(selected_date)
+                consumptions = {}
+                for price in prices:
+                        consumptions[price[b"rate"]] = Consumption(price[b"rate"], price[b"currency"])
+                return prices, consumptions
+
+        def compute(self, selected_date):
+                """ Compute cost """
+                return {}
+
+        def get_message(self, title, selected_date):
+                """ Get result message """
+                consumptions = self.compute(selected_date)
+                result = " - " + strings.tostrings(title) + " :\n"
+                for consumption in consumptions.values():
+                        result += "   %s\n"%consumption.to_string()
+                return result
+
+class HourlyCost(Cost):
+        """ Hourly cost calculation """
+        def compute(self, selected_date):
+                """ Compute cost """
+                prices, consumptions = self.get_rates(selected_date)
+                pulses = HourlyCounter.load(HourlyCounter.get_filename(selected_date))
+                second = 0
+                for pulse in pulses:
+                        for price in prices:
+                                if price[b"start_time"] <= second <= price[b"end_time"]:
+                                        consumptions[price[b"rate"]].add([pulse], price[b"price"])
+                                        break
+                        second += 60
+                return consumptions
+
+class DailyCost(Cost):
+        """ Daily cost calculation """
+        def compute(self, selected_date):
+                """ Compute cost """
+                slot_pulses = DailyCounter.load(DailyCounter.get_filename(selected_date))
+                prices,consumptions = self.get_rates(selected_date)
+                for slot_time, pulses in slot_pulses.items():
+                        for price in prices:
+                                start, end = slot_time
+                                if price[b"start_time"] == start and price[b"end_time"] == end:
+                                        consumptions[price[b"rate"]].add(pulses, price[b"price"])
+                                        break
+                return consumptions
+
+def daily_notifier():
+        """ Get electricmeter daily notification """
+        selected_date = time.time() - 86400
+        message = "\n"
+        cost = HourlyCost()
+        message += cost.get_message(em_lang.item_day, selected_date)
+        cost = DailyCost()
+        message += cost.get_message(em_lang.item_month, selected_date)
+        message += " - Lan Ip : %s\n"%wifi.Station.get_info()[0]
+        message += " - Wan Ip : %s\n"%Server.context.wan_ip
+        message += " - Uptime : %s\n"%strings.tostrings(info.uptime())
+        return message
+
+def create_electric_meter(loop, gpio=21):
+        """ Create user task """
+        Server.set_daily_notifier(daily_notifier)
+        loop.create_task(HourlyCounter.task(gpio))
+        loop.create_task(MonthlyCounter.task())
+        if filesystem.ismicropython() is False:
+                loop.create_task(HourlyCounter.pulse_simulator())
+
+
+
+
+
+
+
+

Functions

+
+
+def create_electric_meter(loop, gpio=21) +
+
+

Create user task

+
+ +Expand source code + +
def create_electric_meter(loop, gpio=21):
+        """ Create user task """
+        Server.set_daily_notifier(daily_notifier)
+        loop.create_task(HourlyCounter.task(gpio))
+        loop.create_task(MonthlyCounter.task())
+        if filesystem.ismicropython() is False:
+                loop.create_task(HourlyCounter.pulse_simulator())
+
+
+
+def daily_notifier() +
+
+

Get electricmeter daily notification

+
+ +Expand source code + +
def daily_notifier():
+        """ Get electricmeter daily notification """
+        selected_date = time.time() - 86400
+        message = "\n"
+        cost = HourlyCost()
+        message += cost.get_message(em_lang.item_day, selected_date)
+        cost = DailyCost()
+        message += cost.get_message(em_lang.item_month, selected_date)
+        message += " - Lan Ip : %s\n"%wifi.Station.get_info()[0]
+        message += " - Wan Ip : %s\n"%Server.context.wan_ip
+        message += " - Uptime : %s\n"%strings.tostrings(info.uptime())
+        return message
+
+
+
+
+
+

Classes

+
+
+class Consumption +(name, currency) +
+
+

Stores the consumption of a time slot

+

Constructor

+
+ +Expand source code + +
class Consumption:
+        """ Stores the consumption of a time slot """
+        def __init__(self, name, currency):
+                """ Constructor """
+                self.name = strings.tostrings(name)
+                self.cost = 0.
+                self.pulses = 0
+                self.currency = strings.tostrings(currency)
+
+        def add(self, pulses, price):
+                """ Add wh pulses with its price """
+                cumul_pulses = 0
+                for pulse in pulses:
+                        cumul_pulses += pulse
+
+                self.cost += cumul_pulses*price/1000
+                self.pulses += cumul_pulses
+
+        def to_string(self):
+                """ Convert to string """
+                return "%s : %.2f %s (%.3f kWh)"%(self.name, self.cost, strings.tostrings(self.currency), self.pulses/1000)
+
+

Methods

+
+
+def add(self, pulses, price) +
+
+

Add wh pulses with its price

+
+ +Expand source code + +
def add(self, pulses, price):
+        """ Add wh pulses with its price """
+        cumul_pulses = 0
+        for pulse in pulses:
+                cumul_pulses += pulse
+
+        self.cost += cumul_pulses*price/1000
+        self.pulses += cumul_pulses
+
+
+
+def to_string(self) +
+
+

Convert to string

+
+ +Expand source code + +
def to_string(self):
+        """ Convert to string """
+        return "%s : %.2f %s (%.3f kWh)"%(self.name, self.cost, strings.tostrings(self.currency), self.pulses/1000)
+
+
+
+
+
+class Cost +
+
+

Abstract class to compute the cost

+
+ +Expand source code + +
class Cost:
+        """ Abstract class to compute the cost """
+        def get_rates(self, selected_date):
+                """ Get the rate according to the selected date """
+                prices = TimeSlotsConfig.get_cost(selected_date)
+                consumptions = {}
+                for price in prices:
+                        consumptions[price[b"rate"]] = Consumption(price[b"rate"], price[b"currency"])
+                return prices, consumptions
+
+        def compute(self, selected_date):
+                """ Compute cost """
+                return {}
+
+        def get_message(self, title, selected_date):
+                """ Get result message """
+                consumptions = self.compute(selected_date)
+                result = " - " + strings.tostrings(title) + " :\n"
+                for consumption in consumptions.values():
+                        result += "   %s\n"%consumption.to_string()
+                return result
+
+

Subclasses

+ +

Methods

+
+
+def compute(self, selected_date) +
+
+

Compute cost

+
+ +Expand source code + +
def compute(self, selected_date):
+        """ Compute cost """
+        return {}
+
+
+
+def get_message(self, title, selected_date) +
+
+

Get result message

+
+ +Expand source code + +
def get_message(self, title, selected_date):
+        """ Get result message """
+        consumptions = self.compute(selected_date)
+        result = " - " + strings.tostrings(title) + " :\n"
+        for consumption in consumptions.values():
+                result += "   %s\n"%consumption.to_string()
+        return result
+
+
+
+def get_rates(self, selected_date) +
+
+

Get the rate according to the selected date

+
+ +Expand source code + +
def get_rates(self, selected_date):
+        """ Get the rate according to the selected date """
+        prices = TimeSlotsConfig.get_cost(selected_date)
+        consumptions = {}
+        for price in prices:
+                consumptions[price[b"rate"]] = Consumption(price[b"rate"], price[b"currency"])
+        return prices, consumptions
+
+
+
+
+
+class DailyCost +
+
+

Daily cost calculation

+
+ +Expand source code + +
class DailyCost(Cost):
+        """ Daily cost calculation """
+        def compute(self, selected_date):
+                """ Compute cost """
+                slot_pulses = DailyCounter.load(DailyCounter.get_filename(selected_date))
+                prices,consumptions = self.get_rates(selected_date)
+                for slot_time, pulses in slot_pulses.items():
+                        for price in prices:
+                                start, end = slot_time
+                                if price[b"start_time"] == start and price[b"end_time"] == end:
+                                        consumptions[price[b"rate"]].add(pulses, price[b"price"])
+                                        break
+                return consumptions
+
+

Ancestors

+ +

Inherited members

+ +
+
+class DailyCounter +
+
+

Daily counter of wh consumed

+
+ +Expand source code + +
class DailyCounter:
+        """ Daily counter of wh consumed """
+        @staticmethod
+        def get_filename(selected_date=None):
+                """ Return the filename according to selected date """
+                global PULSE_DIRECTORY, PULSE_DAILY
+                if selected_date is None:
+                        selected_date = time.time()
+                year,month = date.local_time(selected_date)[:2]
+                return "%s/%04d-%02d/%04d-%02d%s"%(PULSE_DIRECTORY, year, month, year, month, PULSE_DAILY)
+
+        @staticmethod
+        def get_datas(selected_date):
+                """ Return the pulse filename according to the month selected """
+                result = []
+                filename = DailyCounter.get_filename(selected_date)
+                slot_pulses = DailyCounter.load(filename)
+                for time_slot, days in slot_pulses.items():
+                        result.append({"time_slot":time_slot,"days":days})
+                return result
+
+        @staticmethod
+        def load(filename):
+                """ Load daily file """
+                result = {}
+                try:
+                        with open(filename, "rb") as file:
+                                while True:
+                                        data = file.read(2)
+                                        if len(data) == 0:
+                                                break
+                                        start = struct.unpack("H", data)[0]*60
+                                        end   = struct.unpack("H", file.read(2))[0]*60
+                                        days = struct.unpack("H"*31, file.read(2*31))
+                                        result[(start,end)] = days
+                except OSError:
+                        pass
+                except Exception as err:
+                        logger.exception(err)
+                return result
+
+        @staticmethod
+        def save(filename, slot_pulses):
+                """ Save daily file """
+                try:
+                        with open(filename, "wb") as file:
+                                for time_slot, days in slot_pulses.items():
+                                        start, end = time_slot
+                                        file.write(struct.pack("HH", start//60, end//60))
+                                        file.write(struct.pack("H"*len(days), *days))
+                except OSError:
+                        pass
+                except Exception as err:
+                        logger.exception(err)
+
+        @staticmethod
+        async def update(filenames, daily_to_update):
+                """ Update daily files """
+                for key, daily_filename in daily_to_update.items():
+                        year, month = key
+                        print("Update %s\n  "%daily_filename, end="")
+                        hourly_searched = "%s/%s-%s/%s-%s*%s"%(PULSE_DIRECTORY, year, month, year, month, PULSE_HOURLY)
+                        slot_pulses = TimeSlotsConfig.create_empty_slot(31)
+                        for hourly_filename in filenames:
+                                if fnmatch.fnmatch(hourly_filename, hourly_searched):
+                                        name = filesystem.splitext(filesystem.split(hourly_filename)[1])[0]
+                                        day = int(name.split("-")[-1])
+                                        print("%d "%day, end="")
+                                        hourly_pulses = HourlyCounter.load(hourly_filename)
+                                        second = 0
+                                        for start, end in slot_pulses.keys():
+                                                second = 0
+                                                for pulses in hourly_pulses:
+                                                        if start <= second <= end:
+                                                                slot_pulses[(start,end)][day-1] += pulses
+                                                        second += 60
+                                else:
+                                        pass
+                                await uasyncio.sleep_ms(2)
+                        print("")
+                        await uasyncio.sleep_ms(2)
+                        DailyCounter.save(daily_filename, slot_pulses)
+
+

Static methods

+
+
+def get_datas(selected_date) +
+
+

Return the pulse filename according to the month selected

+
+ +Expand source code + +
@staticmethod
+def get_datas(selected_date):
+        """ Return the pulse filename according to the month selected """
+        result = []
+        filename = DailyCounter.get_filename(selected_date)
+        slot_pulses = DailyCounter.load(filename)
+        for time_slot, days in slot_pulses.items():
+                result.append({"time_slot":time_slot,"days":days})
+        return result
+
+
+
+def get_filename(selected_date=None) +
+
+

Return the filename according to selected date

+
+ +Expand source code + +
@staticmethod
+def get_filename(selected_date=None):
+        """ Return the filename according to selected date """
+        global PULSE_DIRECTORY, PULSE_DAILY
+        if selected_date is None:
+                selected_date = time.time()
+        year,month = date.local_time(selected_date)[:2]
+        return "%s/%04d-%02d/%04d-%02d%s"%(PULSE_DIRECTORY, year, month, year, month, PULSE_DAILY)
+
+
+
+def load(filename) +
+
+

Load daily file

+
+ +Expand source code + +
@staticmethod
+def load(filename):
+        """ Load daily file """
+        result = {}
+        try:
+                with open(filename, "rb") as file:
+                        while True:
+                                data = file.read(2)
+                                if len(data) == 0:
+                                        break
+                                start = struct.unpack("H", data)[0]*60
+                                end   = struct.unpack("H", file.read(2))[0]*60
+                                days = struct.unpack("H"*31, file.read(2*31))
+                                result[(start,end)] = days
+        except OSError:
+                pass
+        except Exception as err:
+                logger.exception(err)
+        return result
+
+
+
+def save(filename, slot_pulses) +
+
+

Save daily file

+
+ +Expand source code + +
@staticmethod
+def save(filename, slot_pulses):
+        """ Save daily file """
+        try:
+                with open(filename, "wb") as file:
+                        for time_slot, days in slot_pulses.items():
+                                start, end = time_slot
+                                file.write(struct.pack("HH", start//60, end//60))
+                                file.write(struct.pack("H"*len(days), *days))
+        except OSError:
+                pass
+        except Exception as err:
+                logger.exception(err)
+
+
+
+async def update(filenames, daily_to_update) +
+
+

Update daily files

+
+ +Expand source code + +
@staticmethod
+async def update(filenames, daily_to_update):
+        """ Update daily files """
+        for key, daily_filename in daily_to_update.items():
+                year, month = key
+                print("Update %s\n  "%daily_filename, end="")
+                hourly_searched = "%s/%s-%s/%s-%s*%s"%(PULSE_DIRECTORY, year, month, year, month, PULSE_HOURLY)
+                slot_pulses = TimeSlotsConfig.create_empty_slot(31)
+                for hourly_filename in filenames:
+                        if fnmatch.fnmatch(hourly_filename, hourly_searched):
+                                name = filesystem.splitext(filesystem.split(hourly_filename)[1])[0]
+                                day = int(name.split("-")[-1])
+                                print("%d "%day, end="")
+                                hourly_pulses = HourlyCounter.load(hourly_filename)
+                                second = 0
+                                for start, end in slot_pulses.keys():
+                                        second = 0
+                                        for pulses in hourly_pulses:
+                                                if start <= second <= end:
+                                                        slot_pulses[(start,end)][day-1] += pulses
+                                                second += 60
+                        else:
+                                pass
+                        await uasyncio.sleep_ms(2)
+                print("")
+                await uasyncio.sleep_ms(2)
+                DailyCounter.save(daily_filename, slot_pulses)
+
+
+
+
+
+class HourlyCost +
+
+

Hourly cost calculation

+
+ +Expand source code + +
class HourlyCost(Cost):
+        """ Hourly cost calculation """
+        def compute(self, selected_date):
+                """ Compute cost """
+                prices, consumptions = self.get_rates(selected_date)
+                pulses = HourlyCounter.load(HourlyCounter.get_filename(selected_date))
+                second = 0
+                for pulse in pulses:
+                        for price in prices:
+                                if price[b"start_time"] <= second <= price[b"end_time"]:
+                                        consumptions[price[b"rate"]].add([pulse], price[b"price"])
+                                        break
+                        second += 60
+                return consumptions
+
+

Ancestors

+ +

Inherited members

+ +
+
+class HourlyCounter +(gpio) +
+
+

Hourly counter of wh consumed

+

Constructor

+
+ +Expand source code + +
class HourlyCounter:
+        """ Hourly counter of wh consumed """
+        counter = None
+
+        def __init__(self, gpio):
+                """ Constructor """
+                global PULSE_DIRECTORY
+                filesystem.makedir(PULSE_DIRECTORY,True)
+                self.day    = time.time()
+                self.pulses = HourlyCounter.load(HourlyCounter.get_filename(self.day))
+                self.last_save = time.time()
+                self.watt_hour = 0
+                self.previous_pulse_time = None
+                self.day_pulses_counter = 0
+                self.pulse_sensor = PulseSensor(gpio)
+        
+        async def manage(self):
+                """ Manage the counter """
+                # Wait the list of pulses
+                pulses = await self.pulse_sensor.wait()
+                for pulse_quantity, pulse_time in pulses:
+                        # If previous pulse registered
+                        if self.previous_pulse_time is not None:
+                                # Compute the instant power
+                                self.watt_hour = 3600 * 1_000_000_000/((pulse_time - self.previous_pulse_time)/pulse_quantity)
+
+                        # Increase
+                        self.day_pulses_counter += pulse_quantity
+                        self.previous_pulse_time = pulse_time
+                        hour,minute=date.local_time(pulse_time//1_000_000_000)[3:5]
+                        day   = pulse_time//1_000_000_000
+                        minut = hour*60+minute
+                        self.pulses[minut] += pulse_quantity
+
+                        # If day change
+                        if HourlyCounter.is_same_day(day, self.day) is False:
+                                # Save day counter
+                                HourlyCounter.save(HourlyCounter.get_filename(self.day))
+
+                                # Clear day pulses counter
+                                self.day_pulses_counter = 0
+
+                                # Load new day if existing or clear counter (manage day light saving)
+                                self.pulses = self.load(day)
+                                self.day = day
+
+                # Each ten minutes
+                if time.time() > (self.last_save + 67):
+                        # Save pulses file
+                        HourlyCounter.save(HourlyCounter.get_filename(self.day))
+
+                # print("%s %.1f wh pulses=%d"%(date.date_to_string(), self.watt_hour, self.day_pulses_counter))
+                return True
+
+        @staticmethod
+        def is_same_day(day1, day2):
+                """ Indicates if the day 1 is equal to day 2"""
+                if day1//86400 == day2//86400 or day1 is None or day2 is None:
+                        return True
+                else:
+                        return False
+
+        @staticmethod
+        def get_filename(day=None):
+                """ Return the filename according to day """
+                global PULSE_DIRECTORY, PULSE_HOURLY
+                if day is None:
+                        day = time.time()
+                year,month,day = date.local_time(day)[:3]
+                return "%s/%04d-%02d/%04d-%02d-%02d%s"%(PULSE_DIRECTORY, year, month, year, month, day, PULSE_HOURLY)
+
+        @staticmethod
+        def save(filename):
+                """ Save pulses file """
+                try:
+                        filesystem.makedir(filesystem.split(filename)[0], True)
+                        with open(filename, "wb") as file:
+                                file.write(struct.pack("B"*1440, *HourlyCounter.counter.pulses))
+                        HourlyCounter.counter.last_save = time.time()
+                except Exception as err:
+                        logger.exception(err)
+
+        @staticmethod
+        def load(filename, pulses=None):
+                """ Load pulses file """
+                result = [0]*1440
+                try:
+                        with open(filename, "rb") as file:
+                                load_pulses = struct.unpack("B"*1440, file.read())
+                                index = 0
+                                if pulses is None:
+                                        result = list(load_pulses)
+                                else:
+                                        result = pulses
+                                        for pulse in load_pulses:
+                                                result[index] += pulse
+                                                index += 1
+                except Exception as err:
+                        logger.exception(err)
+                return result
+
+        @staticmethod
+        def get_power():
+                """ Return power instantaneous """
+                return HourlyCounter.counter.watt_hour
+
+        def simulate(self, pulses):
+                """ Simulates the flashing led on the electric meter """
+                self.pulse_sensor.simulate(pulses)
+
+        @staticmethod
+        async def task(gpio):
+                """ Task to count wh from the electric meter """
+                HourlyCounter.counter = HourlyCounter(gpio)
+                await tasking.task_monitoring(HourlyCounter.counter.manage)
+
+        @staticmethod
+        def get_datas(selected_date):
+                """ Return the pulse filename according to the selected date """
+                if type(selected_date) == type(b""):
+                        selected_date = date.html_to_date(selected_date)
+
+                if selected_date is None or HourlyCounter.is_same_day(selected_date, time.time()):
+                        result = HourlyCounter.counter.pulses
+                else:
+                        result = HourlyCounter.load(HourlyCounter.get_filename(selected_date))
+                return result
+
+        @staticmethod
+        async def pulse_simulator():
+                """ Simulates the flashing led on the electric meter """
+                import random
+                while True:
+                        HourlyCounter.counter.simulate(random.randint(1, 4))
+                        await uasyncio.sleep(random.randint(1, 5))
+
+

Class variables

+
+
var counter
+
+
+
+
+

Static methods

+
+
+def get_datas(selected_date) +
+
+

Return the pulse filename according to the selected date

+
+ +Expand source code + +
@staticmethod
+def get_datas(selected_date):
+        """ Return the pulse filename according to the selected date """
+        if type(selected_date) == type(b""):
+                selected_date = date.html_to_date(selected_date)
+
+        if selected_date is None or HourlyCounter.is_same_day(selected_date, time.time()):
+                result = HourlyCounter.counter.pulses
+        else:
+                result = HourlyCounter.load(HourlyCounter.get_filename(selected_date))
+        return result
+
+
+
+def get_filename(day=None) +
+
+

Return the filename according to day

+
+ +Expand source code + +
@staticmethod
+def get_filename(day=None):
+        """ Return the filename according to day """
+        global PULSE_DIRECTORY, PULSE_HOURLY
+        if day is None:
+                day = time.time()
+        year,month,day = date.local_time(day)[:3]
+        return "%s/%04d-%02d/%04d-%02d-%02d%s"%(PULSE_DIRECTORY, year, month, year, month, day, PULSE_HOURLY)
+
+
+
+def get_power() +
+
+

Return power instantaneous

+
+ +Expand source code + +
@staticmethod
+def get_power():
+        """ Return power instantaneous """
+        return HourlyCounter.counter.watt_hour
+
+
+
+def is_same_day(day1, day2) +
+
+

Indicates if the day 1 is equal to day 2

+
+ +Expand source code + +
@staticmethod
+def is_same_day(day1, day2):
+        """ Indicates if the day 1 is equal to day 2"""
+        if day1//86400 == day2//86400 or day1 is None or day2 is None:
+                return True
+        else:
+                return False
+
+
+
+def load(filename, pulses=None) +
+
+

Load pulses file

+
+ +Expand source code + +
@staticmethod
+def load(filename, pulses=None):
+        """ Load pulses file """
+        result = [0]*1440
+        try:
+                with open(filename, "rb") as file:
+                        load_pulses = struct.unpack("B"*1440, file.read())
+                        index = 0
+                        if pulses is None:
+                                result = list(load_pulses)
+                        else:
+                                result = pulses
+                                for pulse in load_pulses:
+                                        result[index] += pulse
+                                        index += 1
+        except Exception as err:
+                logger.exception(err)
+        return result
+
+
+
+async def pulse_simulator() +
+
+

Simulates the flashing led on the electric meter

+
+ +Expand source code + +
@staticmethod
+async def pulse_simulator():
+        """ Simulates the flashing led on the electric meter """
+        import random
+        while True:
+                HourlyCounter.counter.simulate(random.randint(1, 4))
+                await uasyncio.sleep(random.randint(1, 5))
+
+
+
+def save(filename) +
+
+

Save pulses file

+
+ +Expand source code + +
@staticmethod
+def save(filename):
+        """ Save pulses file """
+        try:
+                filesystem.makedir(filesystem.split(filename)[0], True)
+                with open(filename, "wb") as file:
+                        file.write(struct.pack("B"*1440, *HourlyCounter.counter.pulses))
+                HourlyCounter.counter.last_save = time.time()
+        except Exception as err:
+                logger.exception(err)
+
+
+
+async def task(gpio) +
+
+

Task to count wh from the electric meter

+
+ +Expand source code + +
@staticmethod
+async def task(gpio):
+        """ Task to count wh from the electric meter """
+        HourlyCounter.counter = HourlyCounter(gpio)
+        await tasking.task_monitoring(HourlyCounter.counter.manage)
+
+
+
+

Methods

+
+
+async def manage(self) +
+
+

Manage the counter

+
+ +Expand source code + +
async def manage(self):
+        """ Manage the counter """
+        # Wait the list of pulses
+        pulses = await self.pulse_sensor.wait()
+        for pulse_quantity, pulse_time in pulses:
+                # If previous pulse registered
+                if self.previous_pulse_time is not None:
+                        # Compute the instant power
+                        self.watt_hour = 3600 * 1_000_000_000/((pulse_time - self.previous_pulse_time)/pulse_quantity)
+
+                # Increase
+                self.day_pulses_counter += pulse_quantity
+                self.previous_pulse_time = pulse_time
+                hour,minute=date.local_time(pulse_time//1_000_000_000)[3:5]
+                day   = pulse_time//1_000_000_000
+                minut = hour*60+minute
+                self.pulses[minut] += pulse_quantity
+
+                # If day change
+                if HourlyCounter.is_same_day(day, self.day) is False:
+                        # Save day counter
+                        HourlyCounter.save(HourlyCounter.get_filename(self.day))
+
+                        # Clear day pulses counter
+                        self.day_pulses_counter = 0
+
+                        # Load new day if existing or clear counter (manage day light saving)
+                        self.pulses = self.load(day)
+                        self.day = day
+
+        # Each ten minutes
+        if time.time() > (self.last_save + 67):
+                # Save pulses file
+                HourlyCounter.save(HourlyCounter.get_filename(self.day))
+
+        # print("%s %.1f wh pulses=%d"%(date.date_to_string(), self.watt_hour, self.day_pulses_counter))
+        return True
+
+
+
+def simulate(self, pulses) +
+
+

Simulates the flashing led on the electric meter

+
+ +Expand source code + +
def simulate(self, pulses):
+        """ Simulates the flashing led on the electric meter """
+        self.pulse_sensor.simulate(pulses)
+
+
+
+
+
+class MonthlyCounter +
+
+

Monthly counter of wh consumed

+
+ +Expand source code + +
class MonthlyCounter:
+        """ Monthly counter of wh consumed """
+        last_update = [0]
+        next_update = [0]
+        force = [True]
+
+        @staticmethod
+        async def update(filenames, monthly_to_update):
+                """ Update monthly files """
+                for year, monthly_filename in monthly_to_update.items():
+                        print("Update %s\n  "%monthly_filename, end="")
+                        daily_searched = "%s/%s-*/%s-*%s"%(PULSE_DIRECTORY, year, year, PULSE_DAILY)
+                        slot_pulses = TimeSlotsConfig.create_empty_slot(12)
+                        for daily_filename in filenames:
+                                if fnmatch.fnmatch(daily_filename, daily_searched):
+                                        name = filesystem.splitext(filesystem.split(daily_filename)[1])[0]
+                                        month = int(name.split("-")[-1])
+                                        print("%d "%month, end="")
+                                        daily_slot_pulses = DailyCounter.load(daily_filename)
+                                        for time_slot, days in daily_slot_pulses.items():
+                                                for day in days:
+                                                        slot_pulses[time_slot][month-1] = slot_pulses[time_slot][month-1]+day
+                                await uasyncio.sleep_ms(2)
+                        print("")
+                        MonthlyCounter.save(monthly_filename, slot_pulses)
+                        await uasyncio.sleep_ms(2)
+
+        @staticmethod
+        def save(filename, slot_pulses):
+                try:
+                        with open(filename, "wb") as file:
+                                for time_slot, months in slot_pulses.items():
+                                        start, end = time_slot
+                                        file.write(struct.pack("HH", start//60, end//60))
+                                        file.write(struct.pack("I"*len(months), *months))
+                except Exception as err:
+                        logger.exception(err)
+
+        @staticmethod
+        def load(filename):
+                """ Load daily file content """
+                result = {}
+                try:
+                        with open(filename, "rb") as file:
+                                while True:
+                                        data = file.read(2)
+                                        if len(data) == 0:
+                                                break
+                                        start = struct.unpack("H", data)[0]*60
+                                        end   = struct.unpack("H", file.read(2))[0]*60
+                                        months = struct.unpack("I"*12, file.read(4*12))
+                                        result[(start,end)] = months
+                except Exception as err:
+                        logger.exception(err)
+                return result
+
+        @staticmethod
+        async def get_updates():
+                """ Build the list of daily and monthly file to update """
+                global PULSE_DIRECTORY, PULSE_MONTHLY, PULSE_DAILY
+                force = MonthlyCounter.force[0]
+                _, filenames = await filesystem.ascandir(PULSE_DIRECTORY, "*", True)
+                filenames.sort()
+                daily_to_update = {}
+                monthly_to_update = {}
+                for filename in filenames:
+                        if fnmatch.fnmatch(filename, "*"+PULSE_HOURLY):
+                                name = filesystem.splitext(filesystem.split(filename)[1])[0]
+                                year, month, day = name.split("-")
+                                daily   = "%s/%s-%s/%s-%s%s"%(PULSE_DIRECTORY, year, month, year, month, PULSE_DAILY)
+                                monthly = "%s/%s%s"%(PULSE_DIRECTORY, year, PULSE_MONTHLY)
+                                update = False
+                                if daily not in daily_to_update:
+                                        if filesystem.exists(daily):
+                                                daily_date  = filesystem.fileinfo(daily)[8]
+                                                hourly_date = filesystem.fileinfo(filename)[8]
+                                                if hourly_date > daily_date or force:
+                                                        update = True
+                                        else:
+                                                update = True
+                                if update:
+                                        daily_to_update[(year, month)] = daily
+                                        monthly_to_update[year] = monthly
+                        await uasyncio.sleep_ms(2)
+                MonthlyCounter.force[0] = False
+                return daily_to_update, monthly_to_update, filenames
+
+        @staticmethod
+        def get_filename(selected_date=None):
+                """ Return the filename according to day """
+                global PULSE_DIRECTORY, PULSE_MONTHLY
+                if selected_date is None:
+                        selected_date = time.time()
+                year = date.local_time(selected_date)[0]
+                return "%s/%04d%s"%(PULSE_DIRECTORY, year, PULSE_MONTHLY)
+
+        @staticmethod
+        def get_datas(selected_date):
+                """ Return the pulse filename according to the month selected """
+                result = []
+                slot_pulses = MonthlyCounter.load(MonthlyCounter.get_filename(selected_date))
+                for time_slot, months in slot_pulses.items():
+                        result.append({"time_slot":time_slot,"months":months})
+                return result
+
+        @staticmethod
+        async def refresh():
+                """ Refresh the counters """
+                if MonthlyCounter.last_update[0] + 599 < time.time():
+                        MonthlyCounter.next_update[0] = 0
+
+        @staticmethod
+        async def manage():
+                """ Rebuild all month files if necessary """
+                while True:
+                        if MonthlyCounter.next_update[0] <= 0:
+                                break
+                        MonthlyCounter.next_update[0] -= 1
+                        await uasyncio.sleep(1)
+
+                daily_to_update, monthly_to_update, filenames = await MonthlyCounter().get_updates()
+                await DailyCounter.update  (filenames, daily_to_update)
+                await MonthlyCounter.update(filenames, monthly_to_update)
+                MonthlyCounter.next_update[0] = 28793
+                MonthlyCounter.last_update[0] = time.time()
+                return True
+
+        @staticmethod
+        async def task():
+                """ Task to count wh from the electric meter """
+                await tasking.task_monitoring(MonthlyCounter.manage)
+
+

Class variables

+
+
var force
+
+
+
+
var last_update
+
+
+
+
var next_update
+
+
+
+
+

Static methods

+
+
+def get_datas(selected_date) +
+
+

Return the pulse filename according to the month selected

+
+ +Expand source code + +
@staticmethod
+def get_datas(selected_date):
+        """ Return the pulse filename according to the month selected """
+        result = []
+        slot_pulses = MonthlyCounter.load(MonthlyCounter.get_filename(selected_date))
+        for time_slot, months in slot_pulses.items():
+                result.append({"time_slot":time_slot,"months":months})
+        return result
+
+
+
+def get_filename(selected_date=None) +
+
+

Return the filename according to day

+
+ +Expand source code + +
@staticmethod
+def get_filename(selected_date=None):
+        """ Return the filename according to day """
+        global PULSE_DIRECTORY, PULSE_MONTHLY
+        if selected_date is None:
+                selected_date = time.time()
+        year = date.local_time(selected_date)[0]
+        return "%s/%04d%s"%(PULSE_DIRECTORY, year, PULSE_MONTHLY)
+
+
+
+async def get_updates() +
+
+

Build the list of daily and monthly file to update

+
+ +Expand source code + +
@staticmethod
+async def get_updates():
+        """ Build the list of daily and monthly file to update """
+        global PULSE_DIRECTORY, PULSE_MONTHLY, PULSE_DAILY
+        force = MonthlyCounter.force[0]
+        _, filenames = await filesystem.ascandir(PULSE_DIRECTORY, "*", True)
+        filenames.sort()
+        daily_to_update = {}
+        monthly_to_update = {}
+        for filename in filenames:
+                if fnmatch.fnmatch(filename, "*"+PULSE_HOURLY):
+                        name = filesystem.splitext(filesystem.split(filename)[1])[0]
+                        year, month, day = name.split("-")
+                        daily   = "%s/%s-%s/%s-%s%s"%(PULSE_DIRECTORY, year, month, year, month, PULSE_DAILY)
+                        monthly = "%s/%s%s"%(PULSE_DIRECTORY, year, PULSE_MONTHLY)
+                        update = False
+                        if daily not in daily_to_update:
+                                if filesystem.exists(daily):
+                                        daily_date  = filesystem.fileinfo(daily)[8]
+                                        hourly_date = filesystem.fileinfo(filename)[8]
+                                        if hourly_date > daily_date or force:
+                                                update = True
+                                else:
+                                        update = True
+                        if update:
+                                daily_to_update[(year, month)] = daily
+                                monthly_to_update[year] = monthly
+                await uasyncio.sleep_ms(2)
+        MonthlyCounter.force[0] = False
+        return daily_to_update, monthly_to_update, filenames
+
+
+
+def load(filename) +
+
+

Load daily file content

+
+ +Expand source code + +
@staticmethod
+def load(filename):
+        """ Load daily file content """
+        result = {}
+        try:
+                with open(filename, "rb") as file:
+                        while True:
+                                data = file.read(2)
+                                if len(data) == 0:
+                                        break
+                                start = struct.unpack("H", data)[0]*60
+                                end   = struct.unpack("H", file.read(2))[0]*60
+                                months = struct.unpack("I"*12, file.read(4*12))
+                                result[(start,end)] = months
+        except Exception as err:
+                logger.exception(err)
+        return result
+
+
+
+async def manage() +
+
+

Rebuild all month files if necessary

+
+ +Expand source code + +
@staticmethod
+async def manage():
+        """ Rebuild all month files if necessary """
+        while True:
+                if MonthlyCounter.next_update[0] <= 0:
+                        break
+                MonthlyCounter.next_update[0] -= 1
+                await uasyncio.sleep(1)
+
+        daily_to_update, monthly_to_update, filenames = await MonthlyCounter().get_updates()
+        await DailyCounter.update  (filenames, daily_to_update)
+        await MonthlyCounter.update(filenames, monthly_to_update)
+        MonthlyCounter.next_update[0] = 28793
+        MonthlyCounter.last_update[0] = time.time()
+        return True
+
+
+
+async def refresh() +
+
+

Refresh the counters

+
+ +Expand source code + +
@staticmethod
+async def refresh():
+        """ Refresh the counters """
+        if MonthlyCounter.last_update[0] + 599 < time.time():
+                MonthlyCounter.next_update[0] = 0
+
+
+
+def save(filename, slot_pulses) +
+
+
+
+ +Expand source code + +
@staticmethod
+def save(filename, slot_pulses):
+        try:
+                with open(filename, "wb") as file:
+                        for time_slot, months in slot_pulses.items():
+                                start, end = time_slot
+                                file.write(struct.pack("HH", start//60, end//60))
+                                file.write(struct.pack("I"*len(months), *months))
+        except Exception as err:
+                logger.exception(err)
+
+
+
+async def task() +
+
+

Task to count wh from the electric meter

+
+ +Expand source code + +
@staticmethod
+async def task():
+        """ Task to count wh from the electric meter """
+        await tasking.task_monitoring(MonthlyCounter.manage)
+
+
+
+async def update(filenames, monthly_to_update) +
+
+

Update monthly files

+
+ +Expand source code + +
@staticmethod
+async def update(filenames, monthly_to_update):
+        """ Update monthly files """
+        for year, monthly_filename in monthly_to_update.items():
+                print("Update %s\n  "%monthly_filename, end="")
+                daily_searched = "%s/%s-*/%s-*%s"%(PULSE_DIRECTORY, year, year, PULSE_DAILY)
+                slot_pulses = TimeSlotsConfig.create_empty_slot(12)
+                for daily_filename in filenames:
+                        if fnmatch.fnmatch(daily_filename, daily_searched):
+                                name = filesystem.splitext(filesystem.split(daily_filename)[1])[0]
+                                month = int(name.split("-")[-1])
+                                print("%d "%month, end="")
+                                daily_slot_pulses = DailyCounter.load(daily_filename)
+                                for time_slot, days in daily_slot_pulses.items():
+                                        for day in days:
+                                                slot_pulses[time_slot][month-1] = slot_pulses[time_slot][month-1]+day
+                        await uasyncio.sleep_ms(2)
+                print("")
+                MonthlyCounter.save(monthly_filename, slot_pulses)
+                await uasyncio.sleep_ms(2)
+
+
+
+
+
+class PulseSensor +(gpio, min_duration_ns=20000000, queue_length=1500) +
+
+

Detect wh pulse from electric meter

+
+ +Expand source code + +
class PulseSensor:
+        """ Detect wh pulse from electric meter """
+        def __init__(self, gpio, min_duration_ns=20_000_000, queue_length=1_500):
+                self.pulses = collections.deque((), queue_length)
+                self.notifier = uasyncio.Event()
+                self.previous_counter = 0
+                self.previous_time = time.time_ns()
+                self.min_duration_ns = min_duration_ns
+
+                # The use of pcnt counter allows to obtain a better reliability of counting
+                self.counter = machine.Counter(0, src=machine.Pin(gpio, mode=machine.Pin.IN), direction=machine.Counter.UP)
+                self.counter.filter_ns(self.min_duration_ns)
+                self.counter.pause()
+                self.counter.value(0)
+
+                self.sensor = machine.Pin(gpio, machine.Pin.IN, machine.Pin.PULL_DOWN)
+                self.sensor.irq(handler=self.detected, trigger=machine.Pin.IRQ_RISING)
+                self.counter.resume()
+
+        def __del__(self):
+                """ Destructor """
+                self.sensor.irq(handler=None)
+                self.counter.deinit()
+
+        def detected(self, pin):
+                """ Callback called when pulse detected """
+                pulse_time     = time.time_ns()
+                pulse_counter  = self.counter.value()
+                if pin.value() == 1:
+                        # If new pulses detected
+                        if pulse_counter != self.previous_counter:
+                                # if the pulse is not too close to the previous one
+                                if self.previous_time + self.min_duration_ns < pulse_time:
+                                        # If the new value is not a counter overflow
+                                        if pulse_counter > self.previous_counter:
+                                                pulse_quantity = pulse_counter - self.previous_counter
+                                        else:
+                                                pulse_quantity = 1
+
+                                        # Save the new counter value
+                                        self.previous_counter = pulse_counter
+
+                                        # Add the current pulse to list
+                                        self.pulses.append((pulse_quantity, pulse_time))
+
+                                        # Wake up pulse counter
+                                        self.notifier.set()
+                                else:
+                                        # Ignore previous pulse
+                                        self.previous_counter = pulse_counter
+
+        async def wait(self):
+                """ Wait the wh pulses and the returns the list of pulses """
+                # Wait pulses
+                await self.notifier.wait()
+
+                # Clear notification flag
+                self.notifier.clear()
+                result = []
+
+                # Empty list of pulses
+                while True:
+                        try:
+                                result.append(self.pulses.popleft())
+                        except IndexError:
+                                break
+                return result
+
+        def simulate(self, pulses):
+                """ Simulates the flashing led on the electric meter """
+                self.sensor.value(1)
+                self.counter.value(pulses)
+                self.detected(self.sensor)
+
+

Methods

+
+
+def detected(self, pin) +
+
+

Callback called when pulse detected

+
+ +Expand source code + +
def detected(self, pin):
+        """ Callback called when pulse detected """
+        pulse_time     = time.time_ns()
+        pulse_counter  = self.counter.value()
+        if pin.value() == 1:
+                # If new pulses detected
+                if pulse_counter != self.previous_counter:
+                        # if the pulse is not too close to the previous one
+                        if self.previous_time + self.min_duration_ns < pulse_time:
+                                # If the new value is not a counter overflow
+                                if pulse_counter > self.previous_counter:
+                                        pulse_quantity = pulse_counter - self.previous_counter
+                                else:
+                                        pulse_quantity = 1
+
+                                # Save the new counter value
+                                self.previous_counter = pulse_counter
+
+                                # Add the current pulse to list
+                                self.pulses.append((pulse_quantity, pulse_time))
+
+                                # Wake up pulse counter
+                                self.notifier.set()
+                        else:
+                                # Ignore previous pulse
+                                self.previous_counter = pulse_counter
+
+
+
+def simulate(self, pulses) +
+
+

Simulates the flashing led on the electric meter

+
+ +Expand source code + +
def simulate(self, pulses):
+        """ Simulates the flashing led on the electric meter """
+        self.sensor.value(1)
+        self.counter.value(pulses)
+        self.detected(self.sensor)
+
+
+
+async def wait(self) +
+
+

Wait the wh pulses and the returns the list of pulses

+
+ +Expand source code + +
async def wait(self):
+        """ Wait the wh pulses and the returns the list of pulses """
+        # Wait pulses
+        await self.notifier.wait()
+
+        # Clear notification flag
+        self.notifier.clear()
+        result = []
+
+        # Empty list of pulses
+        while True:
+                try:
+                        result.append(self.pulses.popleft())
+                except IndexError:
+                        break
+        return result
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/doc/lib/electricmeter/em_lang.html b/doc/lib/electricmeter/em_lang.html new file mode 100644 index 0000000..134c40a --- /dev/null +++ b/doc/lib/electricmeter/em_lang.html @@ -0,0 +1,71 @@ + + + + + + +lib.electricmeter.em_lang API documentation + + + + + + + + + + + +
+
+
+

Module lib.electricmeter.em_lang

+
+
+

Language selected and regional time

+
+ +Expand source code + +
# Distributed under MIT License
+# Copyright (c) 2021 Remi BERTHOLET
+# pylint:disable=consider-using-f-string
+""" Language selected and regional time """
+from tools import region, strings, logger
+
+try:
+        exec(b"from electricmeter.em_lang_%s import *"%region.RegionConfig.get().lang)
+        logger.syslog("Select electricmeter lang : %s"%strings.tostrings(region.RegionConfig.get().lang))
+except Exception as err:
+        logger.syslog(err)
+        from electricmeter.em_lang_english import *
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/doc/lib/electricmeter/em_lang_english.html b/doc/lib/electricmeter/em_lang_english.html new file mode 100644 index 0000000..9389f6d --- /dev/null +++ b/doc/lib/electricmeter/em_lang_english.html @@ -0,0 +1,100 @@ + + + + + + +lib.electricmeter.em_lang_english API documentation + + + + + + + + + + + +
+
+
+

Module lib.electricmeter.em_lang_english

+
+
+

English text for electricmeter

+
+ +Expand source code + +
# Distributed under MIT License
+# Copyright (c) 2021 Remi BERTHOLET
+""" English text for electricmeter """
+title_electricmeter = b"Power consumption per "
+menu_electricmeter  = b"Electric meter"
+
+rate                = b"Rate"
+field_rate          = b"Name defining the rate"
+price               = b"Price per kWh"
+currency            = b"Currency"
+field_currency      = b"Currency to be used for the fare"
+validy_date         = b"Validity date"
+step_minutes        = b"minutes"
+name                = b"Name"
+type_price          = b"Prix"
+type_power          = b"kWh"
+
+title_rate          = b"Rate per kWh"
+add_button          = b"Add"
+remove_button       = b"\xF0\x9F\x97\x91"
+from_the            = b"per kWh, "
+remove_dialog       = b"do you want to delete ?"
+
+item_time_slots     = b"Time slots"
+title_time_slots    = b"Time slots"
+field_start         = b"Start time"
+field_end           = b"End time"
+field_time_rate     = b"Rate name"
+field_color         = b"Rate color"
+
+item_hour           = b"Hour"
+item_day            = b"Day"
+item_month          = b"Month"
+item_year           = b"Year"
+item_rate           = b"Rate"
+power_consumed      = b"Instantaneous power consumption"
+
+item_geolocation    = b"Geolocation"
+latitude            = b"Latitude"
+longitude           = b"Longitude"
+temperature         = b"Temperature"
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/doc/lib/electricmeter/em_lang_french.html b/doc/lib/electricmeter/em_lang_french.html new file mode 100644 index 0000000..ff68fc1 --- /dev/null +++ b/doc/lib/electricmeter/em_lang_french.html @@ -0,0 +1,101 @@ + + + + + + +lib.electricmeter.em_lang_french API documentation + + + + + + + + + + + +
+
+
+

Module lib.electricmeter.em_lang_french

+
+
+

Textes en francais pour watt metre

+
+ +Expand source code + +
# Distributed under MIT License
+# Copyright (c) 2021 Remi BERTHOLET
+""" Textes en francais pour watt metre """
+title_electricmeter = b"Puissance consomm\xC3\xA9e par "
+menu_electricmeter  = b"Compteur"
+
+rate                = b"Tarif"
+field_rate          = b"Nom d\xC3\xA9finissant le tarif"
+price               = b"Prix au kwh"
+currency            = b"Devise"
+field_currency      = b"Devise \xc3\xa0 utiliser pour le tarif"
+validy_date         = b"Date de validit\xC3\xA9"
+step_minutes        = b"minutes"
+name                = b"Nom"
+type_price          = b"Prix"
+type_power          = b"kWh"
+
+title_rate          = b"Tarif du kWh"
+add_button          = b"Ajouter"
+remove_button       = b"\xF0\x9F\x97\x91"
+from_the            = b"par kWh, "
+remove_dialog       = b"Voulez vous supprimer ?"
+
+item_time_slots     = b"Plages horaire"
+title_time_slots    = b"Plages horaire"
+field_start         = b"Heure de d\xC3\xA9but"
+field_end           = b"Heure de fin"
+field_time_rate     = b"Nom du tarif"
+field_color         = b"Couleur du tarif"
+
+item_hour           = b"Heure"
+item_day            = b"Jour"
+item_month          = b"Mois"
+item_year           = b"Ann\xC3\xA9e"
+item_rate           = b"Tarif"
+
+power_consumed      = b"Puissance instantan\xC3\xA9e"
+
+item_geolocation    = b"G\xC3\xA9olocalisation"
+latitude            = b"Latitude"
+longitude           = b"Longitude"
+temperature         = b"Temp\xC3\xA9rature"
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/doc/lib/electricmeter/htmlpage.html b/doc/lib/electricmeter/htmlpage.html new file mode 100644 index 0000000..2493ee2 --- /dev/null +++ b/doc/lib/electricmeter/htmlpage.html @@ -0,0 +1,317 @@ + + + + + + +lib.electricmeter.htmlpage API documentation + + + + + + + + + + + +
+
+
+

Module lib.electricmeter.htmlpage

+
+
+
+ +Expand source code + +
page = b"""
+<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js"></script>
+<canvas id="line-chart"></canvas>
+<span id="legend"></span>
+<script>
+var STEP         = %d;
+var DAY_SELECTED = '%s';
+var DATE         = '%s';
+var CHART_TYPE   = '%s';
+var chart   = null;
+
+function pad(num, size) 
+{
+        num = parseInt(num);
+        num = num.toString();
+        while (num.length < size) 
+        {
+                num = "0" + num;
+        }
+        return num;
+}
+
+function capitalize(string) 
+{
+        return string.charAt(0).toUpperCase() + string.slice(1);
+}
+
+class Cost
+{
+        constructor(name, currency, color)
+        {
+                this.total_cost   = 0;
+                this.total_pulses = 0;
+                this.currency = currency;
+                this.name  = name;
+                this.color = color;
+                this.pulses = [];
+                this.costs  = [];
+        }
+}
+
+class Costs
+{
+        constructor(datas)
+        {
+                this.costs = new Map();
+                this.datas = datas;
+                this.labels = [];
+                this.step_pulses = 0;
+                for (let i = 0; i < this.datas.rates.length; i++)
+                {
+                        var rate = this.datas.rates[i];
+                        if (!this.costs.has(rate.rate))
+                        {
+                                this.costs.set(rate.rate, new Cost(rate.rate, rate.currency, rate.color));
+                        }
+                }
+        }
+                
+        add_step(minute, pulses)
+        {
+                var rate = null;
+                minute *= 60;
+                for (let i = 0; i < this.datas.rates.length; i++)
+                {
+                        rate = this.datas.rates[i];
+                        if (minute >= rate.start_time && minute <= rate.end_time)
+                        {
+                                rate = this.datas.rates[i];
+                                break;
+                        }
+                }
+                
+                for (var [name, cost] of this.costs)
+                {
+                        if (cost.name === rate.rate)
+                        {
+                                var current_cost = (rate.price / 1000.) * pulses
+                                if (STEP <= 15)
+                                {
+                                        cost.pulses.push(pulses);
+                                }
+                                else
+                                {
+                                        cost.pulses.push((pulses/1000.).toFixed(2));
+                                }
+                                cost.costs.push(current_cost.toFixed(2));
+                                cost.total_cost   += current_cost;
+                                cost.total_pulses += pulses;
+                        }
+                        else
+                        {
+                                cost.pulses.push(0);
+                                cost.costs.push(0);
+                        }
+                }
+        }
+
+        parse_step(minute, pulses)
+        {
+                this.step_pulses += pulses;
+                if ((minute + 1)%%STEP == 0)
+                {
+                        var time = pad((minute+1)/60, 2) + ":" + pad((minute+1)%%60, 2);
+                        this.labels.push(time);
+                        this.add_step(minute, this.step_pulses);
+                        this.step_pulses = 0;
+                }
+        }
+        
+        parse()
+        {
+                for (let minute = 0; minute < this.datas.pulses.length; minute++)
+                {
+                        this.parse_step(minute, this.datas.pulses[minute]);
+                }
+        }
+        
+        get_data_set()
+        {
+                var result = [];
+                var data = null;
+                var unit = "";
+                for (var [name, cost] of this.costs)
+                {
+                        if (CHART_TYPE === "price")
+                        {
+                                unit = cost.currency;
+                                data = cost.costs;
+                        }
+                        else
+                        {
+                                if (STEP <= 15)
+                                {
+                                        unit = "Wh";
+                                }
+                                else
+                                {
+                                        unit = "kWh";
+                                }
+                                data = cost.pulses;
+                        }
+                        result.push(
+                                { 
+                                        data: data,
+                                        label: cost.name + " (" + unit + ")",
+                                        backgroundColor: cost.color,
+                                        fill: false,
+                                        pointRadius:1,
+                                        borderWidth:0
+                                });
+                        
+                }
+                
+                return result;
+        }
+        
+        get_labels()
+        {
+                return this.labels;
+        }
+        
+        get_legend()
+        {
+                var legend = "<br>";
+                var result = capitalize(DATE) + ' : ';
+                var total = 0;
+                var currency = "";
+                if (CHART_TYPE === "price")
+                {
+                        for (var [name, cost] of this.costs)
+                        {
+                                total += cost.total_cost;
+                                currency = cost.currency;
+                                legend += "<li>" + cost.name  + " : " + cost.total_cost.toFixed(2) + " " + cost.currency + "</li>";
+                        }
+                        result += total.toFixed(2) + currency;
+                }
+                else
+                {
+                        for (var [name, cost] of this.costs)
+                        {
+                                total += cost.total_pulses;
+                                legend += "<li>" + cost.name  + " : " + (cost.total_pulses / 1000).toFixed(2) + " kWh </li>";
+                        }
+                        result += (total/1000).toFixed(2) + " kWh";
+                }
+                document.getElementById("legend").innerHTML="<ul>" + capitalize(legend) + "</ul>";
+                return result;
+        }
+}
+
+
+function show_chart(datas)
+{
+        var costs = new Costs(datas);
+        
+        costs.parse();
+        if (!(chart === null))
+        {
+                chart.destroy();
+                chart = null;
+        }
+        costs.get_legend();
+        chart = new Chart(document.getElementById("line-chart"), 
+        {
+                type: 'bar',
+                data: 
+                {
+                        labels: costs.get_labels(),
+                        datasets: costs.get_data_set()
+                },
+                options: 
+                {
+                        responsive: true,
+                        animation:
+                        {
+                                duration: 0
+                        },
+                        plugins:
+                        {
+                                legend:
+                                {
+                                        position: 'top',
+                                },
+                                title:
+                                {
+                                        display: true,
+                                        text: costs.get_legend()
+                                }
+                        },
+                        scales:
+                        {
+                                x:
+                                {
+                                        stacked: true,
+                                }
+                        }
+                }
+        });
+}
+
+async function download_datas()
+{
+        const response = await fetch('/pulses_rates?day='+DAY_SELECTED);
+        var data = await response.json();
+        show_chart(data);
+}
+
+download_datas();
+
+const d = new Date();
+var currentDate = d.getFullYear() + "-" + pad(d.getMonth()+1,2) + "-" + pad(d.getDate(),2);
+if (DAY_SELECTED === currentDate)
+{
+        setInterval(download_datas, 5000);
+}
+</script>
+"""
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/doc/lib/electricmeter/index.html b/doc/lib/electricmeter/index.html new file mode 100644 index 0000000..1ddc877 --- /dev/null +++ b/doc/lib/electricmeter/index.html @@ -0,0 +1,107 @@ + + + + + + +lib.electricmeter API documentation + + + + + + + + + + + +
+
+
+

Module lib.electricmeter

+
+
+

Class to manage the electricmeter

+
+ +Expand source code + +
# Distributed under MIT License
+# Copyright (c) 2021 Remi BERTHOLET
+""" Class to manage the electricmeter """
+from electricmeter.webpage import *
+from electricmeter.config import *
+from electricmeter.electricmeter import *
+
+
+
+

Sub-modules

+
+
lib.electricmeter.config
+
+

Function define the configuration of the electric meter

+
+
lib.electricmeter.electricmeter
+
+

Task to count wh from the electric meter

+
+
lib.electricmeter.em_lang
+
+

Language selected and regional time

+
+
lib.electricmeter.em_lang_english
+
+

English text for electricmeter

+
+
lib.electricmeter.em_lang_french
+
+

Textes en francais pour watt metre

+
+
lib.electricmeter.htmlpage
+
+
+
+
lib.electricmeter.webpage
+
+

Function define the web page to view recent motion detection

+
+
+
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/doc/lib/electricmeter/webpage.html b/doc/lib/electricmeter/webpage.html new file mode 100644 index 0000000..3bb4d08 --- /dev/null +++ b/doc/lib/electricmeter/webpage.html @@ -0,0 +1,952 @@ + + + + + + +lib.electricmeter.webpage API documentation + + + + + + + + + + + +
+
+
+

Module lib.electricmeter.webpage

+
+
+

Function define the web page to view recent motion detection

+
+ +Expand source code + +
# Distributed under MIT License
+# Copyright (c) 2021 Remi BERTHOLET
+""" Function define the web page to view recent motion detection """
+# pylint:disable=anomalous-unicode-escape-in-string
+# pylint:disable=wrong-import-order
+# import time
+# import uos
+import json
+from server.httpserver      import HttpServer
+from htmltemplate           import *
+from webpage.mainpage       import main_frame, manage_default_button
+from tools                  import date, strings, lang
+from electricmeter.config   import RateConfig, TimeSlotConfig, RatesConfig, TimeSlotsConfig, GeolocationConfig
+from electricmeter          import electricmeter, em_lang
+
+
+@HttpServer.add_route(b'/hourly', menu=em_lang.menu_electricmeter, item=em_lang.item_hour)
+async def hourly_page_page(request, response, args):
+        """ Daily consumption graph hour by hour """
+        geolocation = GeolocationConfig.get_config()
+        day  = date.html_to_date(request.params.get(b"day",b""))
+
+        if   request.params.get(b"direction",b"") == b"next":
+                day += 86400
+        elif request.params.get(b"direction",b"") == b"previous":
+                day -= 86400
+
+        temperature = request.params.get(b"temperature")
+        if temperature is None or temperature == b"0":
+                with_temperature = b"false"
+                temperature = None
+        else:
+                with_temperature = b"true"
+
+        step = int(request.params.get(b"step",b"30"))
+        steps = []
+        for s in [1,2,5,10,15,30]:
+                if s == step:
+                        selected = True
+                else:
+                        selected = False
+                steps.append(Option(text=b"%d %s"%(s, em_lang.step_minutes), value=b"%d"%s, selected=selected))
+
+        unit = request.params.get(b"unit",b"power")
+
+        with  open(WWW_DIR + "electricmeter.html", "rb") as file:
+                content = file.read()
+
+        page_content = \
+        [
+                Div(
+                [
+                        Div(
+                        [
+                                Button(type=b"submit", text=b"&lt;-", name=b"direction",  value=b"previous"),Space(),
+                                Button(type=b"submit", text=b"-&gt;", name=b"direction",  value=b"next")
+                        ], class_=b'col-md-2 mb-1'),
+                        Div(
+                        [
+                                Input (type=b"date",   class_=b"form-label border rounded p-1", name=b"day", value= date.date_to_html(day), event=b'onchange="this.form.submit()"'),Space(),
+                        ], class_=b'col-md-2'),
+                        Div(
+                        [
+                                Select(steps, spacer=b"", text=em_lang.step_minutes, name=b"step",                 event=b'onchange="this.form.submit()"')
+                        ], class_=b'col-md-2'),
+                        Div([
+                                Select(
+                                [
+                                        Option(text=em_lang.type_price, value=b"price", selected= True if unit == b"price" else False),
+                                        Option(text=em_lang.type_power, value=b"power", selected= True if unit == b"power" else False),
+                                ], spacer=b"", text=em_lang.step_minutes, name=b"unit",                 event=b'onchange="this.form.submit()"')
+                        ], class_=b'col-md-1'),
+                        Div(
+                        [
+                                Switch(text=em_lang.temperature, name=b"temperature", checked=temperature, event=b'onchange="this.form.submit()"')
+                        ], class_=b'col-md-2')
+                ], class_=b"row"),
+
+                Tag(content%(b"hourly",
+                        step,
+                        date.date_to_html(day),
+                        lang.translate_date(day),
+                        unit,
+                        em_lang.power_consumed,
+                        geolocation.latitude,
+                        geolocation.longitude,
+                        with_temperature))
+        ]
+        page = main_frame(request, response, args,em_lang.title_electricmeter + em_lang.item_hour.lower(),Form(page_content))
+        await response.send_page(page)
+
+
+@HttpServer.add_route(b'/hourly_datas')
+async def hourly_page_datas(request, response, args):
+        """ Send pulses of hours and rates """
+        day    = date.html_to_date(request.params.get(b"day",b""))
+        result = {"pulses":None}
+        result["rates"] = strings.tostrings(TimeSlotsConfig.get_cost(day))
+        try:
+                result["pulses"] = electricmeter.HourlyCounter.get_datas(day)
+                await response.send_buffer(b"pulses", buffer=strings.tobytes(json.dumps(result)), mime_type=b"application/json")
+        except Exception as err:
+                await response.send_not_found(err)
+
+
+@HttpServer.add_route(b'/power_datas')
+async def power_page_datas(request, response, args):
+        """ Send current power consumed """
+        try:
+                power = {"power":electricmeter.HourlyCounter.get_power()}
+                await response.send_buffer(b"pulses", buffer=strings.tobytes(json.dumps(power)), mime_type=b"application/json")
+        except Exception as err:
+                await response.send_not_found(err)
+
+
+@HttpServer.add_route(b'/daily', menu=em_lang.menu_electricmeter, item=em_lang.item_day)
+async def daily_page(request, response, args):
+        """ Consumption graph day by day """
+        geolocation = GeolocationConfig.get_config()
+        y,m = date.local_time()[:2]
+        year   = int(request.params.get(b"year",b"%d"%y))
+        month  = int(request.params.get(b"month",b"%d"%m))
+
+        if   request.params.get(b"direction",b"") == b"next":
+                if month == 12:
+                        month = 1
+                        year += 1
+                else:
+                        month += 1
+        elif request.params.get(b"direction",b"") == b"previous":
+                if month == 1:
+                        month = 12
+                        year -= 1
+                else:
+                        month -= 1
+
+        day  = date.html_to_date(b"%04d-%02d-01"%(year, month))
+
+        unit = request.params.get(b"unit",b"power")
+        temperature = request.params.get(b"temperature")
+        if temperature is None or temperature == b"0":
+                with_temperature = b"false"
+                temperature = None
+        else:
+                with_temperature = b"true"
+
+        months_combo = []
+        month_id = 1
+        for m in lang.months:
+                months_combo.append(Option(text=m, value=b"%d"%month_id, selected= True if month_id == month else False))
+                month_id += 1
+
+        with  open(WWW_DIR + "electricmeter.html", "rb") as file:
+                content = file.read()
+
+        page_content = \
+        [
+                Div(
+                [
+                        Div(
+                        [
+                                Button(type=b"submit", text=b"&lt;-", name=b"direction",  value=b"previous"),Space(),
+                                Button(type=b"submit", text=b"-&gt;", name=b"direction",  value=b"next")
+                        ], class_=b'col-md-2 mb-2'),
+                        Div(
+                        [
+                                Edit (name=b"year", spacer=b"", type=b"number", step=b"1",  required=True, value=b"%d"%year, event=b'onchange="this.form.submit()"'),
+                        ], class_=b'col-md-2'),
+                        Div(
+                        [
+                                Select(months_combo, spacer=b"", name=b"month", event=b'onchange="this.form.submit()"'),
+                        ], class_=b'col-md-2 mb-2'),
+                        Div(
+                        [
+                                Select(
+                                [
+                                        Option(text=em_lang.type_price, value=b"price", selected= True if unit == b"price" else False),
+                                        Option(text=em_lang.type_power, value=b"power", selected= True if unit == b"power" else False),
+                                ], spacer=b"", text=em_lang.step_minutes, name=b"unit",                 event=b'onchange="this.form.submit()"')
+                        ], class_=b'col-md-2'),
+                        Div(
+                        [
+                                Switch(text=em_lang.temperature, name=b"temperature", checked=temperature, event=b'onchange="this.form.submit()"')
+                        ], class_=b'col-md-2')
+                ], class_=b"row"),
+                Tag(content%(b"daily", 86400, date.date_to_html(day), lang.translate_date(day, False), unit, em_lang.power_consumed, geolocation.latitude, geolocation.longitude, with_temperature))
+        ]
+        page = main_frame(request, response, args, em_lang.title_electricmeter + em_lang.item_day.lower(),Form(page_content))
+        await response.send_page(page)
+
+
+@HttpServer.add_route(b'/daily_datas')
+async def daily_datas(request, response, args):
+        """ Send pulses of month and rates """
+        await electricmeter.MonthlyCounter.refresh()
+        month    = date.html_to_date(request.params.get(b"month",b""))
+        result = {"time_slots":None}
+        result["rates"] = strings.tostrings(TimeSlotsConfig.get_cost(month))
+        try:
+                result["time_slots"] = electricmeter.DailyCounter.get_datas(month)
+                await response.send_buffer(b"pulses", buffer=strings.tobytes(json.dumps(result)), mime_type=b"application/json")
+        except Exception as err:
+                await response.send_not_found(err)
+
+
+@HttpServer.add_route(b'/monthly', menu=em_lang.menu_electricmeter, item=em_lang.item_month)
+async def monthly_page(request, response, args):
+        """ Consumption graph month by month """
+        geolocation = GeolocationConfig.get_config()
+        y = date.local_time()[0]
+        year   = int(request.params.get(b"year",b"%d"%y))
+
+        if   request.params.get(b"direction",b"") == b"next":
+                year += 1
+        elif request.params.get(b"direction",b"") == b"previous":
+                year -= 1
+
+        month  = date.html_to_date(b"%04d-01-01"%(year))
+        unit = request.params.get(b"unit",b"power")
+
+
+        with  open(WWW_DIR + "electricmeter.html", "rb") as file:
+                content = file.read()
+
+        page_content = \
+        [
+                Div(
+                [
+                        Div(
+                        [
+                                Button(type=b"submit", text=b"&lt;-", name=b"direction",  value=b"previous"),Space(),
+                                Button(type=b"submit", text=b"-&gt;", name=b"direction",  value=b"next")
+                        ], class_=b'col-md-2 mb-2'),
+                        Div(\
+                        [
+                                Edit (name=b"year", spacer=b"", type=b"number", step=b"1",  required=True, value=b"%d"%year, event=b'onchange="this.form.submit()"'),
+                        ], class_=b'col-md-2'),
+                        Div(
+                        [
+                                Select(
+                                [
+                                        Option(text=em_lang.type_price, value=b"price", selected= True if unit == b"price" else False),
+                                        Option(text=em_lang.type_power, value=b"power", selected= True if unit == b"power" else False),
+                                ], spacer=b"", text=em_lang.step_minutes, name=b"unit",                 event=b'onchange="this.form.submit()"')
+                        ],class_=b'col-md-2')
+                ], class_=b"row"),
+
+                Tag(content%(b"monthly", 86400, date.date_to_html(month), lang.translate_date(month, False), unit, em_lang.power_consumed, geolocation.latitude, geolocation.longitude, b""))
+        ]
+        page = main_frame(request, response, args, em_lang.title_electricmeter + em_lang.item_month.lower(),Form(page_content))
+        await response.send_page(page)
+
+
+@HttpServer.add_route(b'/monthly_datas')
+async def monthly_datas(request, response, args):
+        """ Send pulses of month and rates """
+        await electricmeter.MonthlyCounter.refresh()
+        year   = date.html_to_date(request.params.get(b"year",b""))
+        result = {"time_slots":None}
+        result["rates"] = strings.tostrings(TimeSlotsConfig.get_cost(year))
+        try:
+                result["time_slots"] = electricmeter.MonthlyCounter.get_datas(year)
+                await response.send_buffer(b"pulses", buffer=strings.tobytes(json.dumps(result)), mime_type=b"application/json")
+        except Exception as err:
+                await response.send_not_found(err)
+
+
+@HttpServer.add_route(b'/rate', menu=em_lang.menu_electricmeter, item=em_lang.item_rate)
+async def rate_page(request, response, args):
+        """ Electric rate configuration page """
+        rates   = RatesConfig.get_config()
+        current = RateConfig()
+        # If new rate added
+        if request.params.get(b"add",None) is not None:
+                current.update(request.params, show_error=False)
+                rates.append(current)
+                rates.save()
+        # If a rate must be deleted
+        elif request.params.get(b"remove",None) is not None:
+                rates.remove(request.params.get(b"remove",b"none"))
+                rates.save()
+        # If a rate must be edited
+        elif request.params.get(b"edit",None) is not None:
+                current.update(rates.get(request.params.get(b"edit",b"none")))
+
+        # List all rates
+        rate_items = []
+        identifier = 0
+        while True:
+                rate = rates.get(identifier)
+                if rate is not None:
+                        rate_items.append(
+                                        ListItem(
+                                        [
+                                                Link(text=b" %s : %f %s %s %s "%(rate[b"name"], rate[b"price"], rate[b"currency"], em_lang.from_the, lang.translate_date(rate[b"validity_date"])), href=b"rate?edit=%d"%identifier ),
+                                                Link(text=em_lang.remove_button , class_=b"btn position-absolute top-50 end-0 translate-middle-y", href=b"rate?remove=%d"%identifier, onclick=b"return window.confirm('%s')"%em_lang.remove_dialog)
+                                        ]))
+                else:
+                        break
+                identifier += 1
+
+        # Build page
+        page = main_frame(request, response, args, em_lang.title_rate,
+        [
+                Form(
+                [
+                        Edit  (text=em_lang.name,          name=b"name",          placeholder=em_lang.field_rate,     required=True, value=current.name),
+                        Edit  (text=em_lang.validy_date,   name=b"validity_date", type=b"date",                    required=True, value=b"%04d-%02d-%02d"%date.local_time(current.validity_date)[:3]),
+                        Edit  (text=em_lang.price,         name=b"price",         type=b"number", step=b"0.0001",  required=True, value=b"%f"%current.price),
+                        Edit  (text=em_lang.currency,      name=b"currency",      placeholder=em_lang.field_currency, required=True, value=current.currency),
+                        Submit(text=em_lang.add_button,    name=b"add")
+                ]),
+                List(rate_items)
+        ])
+        await response.send_page(page)
+
+
+@HttpServer.add_route(b'/time_slots', menu=em_lang.menu_electricmeter, item=em_lang.item_time_slots)
+async def time_slots_page(request, response, args):
+        """ Electric time slots configuration page """
+        time_slots = TimeSlotsConfig.get_config()
+        rates   = RatesConfig.get_config()
+        current = TimeSlotConfig()
+
+        # If new time_slot added
+        if request.params.get(b"add",None) is not None:
+                current.update(request.params, show_error=False)
+                time_slots.append(current)
+                time_slots.save()
+        # If a time_slot must be deleted
+        elif request.params.get(b"remove",None) is not None:
+                time_slots.remove(request.params.get(b"remove",b"none"))
+                time_slots.save()
+        # If a rate must be edited
+        elif request.params.get(b"edit",None) is not None:
+                current.update(time_slots.get(request.params.get(b"edit",b"none")))
+
+        # List all rates names
+        rates_combo = []
+        names       = {}
+        identifier  = 0
+        while True:
+                rate = rates.get(identifier)
+                if rate is not None:
+                        if rate[b"name"] not in names:
+                                rates_combo.append(Option(text=rate[b"name"], value=rate[b"name"], selected=b"selected" if current.rate == rate[b"name"] else b""))
+                else:
+                        break
+                names[rate[b"name"]] = b""
+                identifier += 1
+
+        # List all rates
+        time_slots_items = []
+        identifier = 0
+        while True:
+                time_slot = time_slots.get(identifier)
+                if time_slot is not None:
+                        time_slots_items.append(
+                                        ListItem(
+                                        [
+                                                Label(text=b"&nbsp;&nbsp;&nbsp;&nbsp;", style=b"background-color: %s"%time_slot[b"color"]),
+                                                Space(),Space(),
+                                                Link(text=b"%s - %s : %s "%(date.time_to_html(time_slot[b"start_time"]), date.time_to_html(time_slot[b"end_time"]), time_slot[b"rate"]),href=b"time_slots?edit=%d"%identifier),
+                                                Link(text=em_lang.remove_button , class_=b"btn position-absolute top-50 end-0 translate-middle-y", href=b"time_slots?remove=%d"%identifier, onclick=b"return window.confirm('%s')"%em_lang.remove_dialog)
+                                        ]))
+                else:
+                        break
+                identifier += 1
+
+        # Build page
+        page = main_frame(request, response, args, em_lang.title_time_slots,
+        [
+                Form(
+                [
+                        Edit (text=em_lang.field_start,      name=b"start_time", type=b"time", required=True, value=date.time_to_html(current.start_time)),
+                        Edit (text=em_lang.field_end,        name=b"end_time",   type=b"time", required=True, value=date.time_to_html(current.end_time)),
+                        Label(text=em_lang.field_time_rate),
+                        Select(rates_combo,name=b"rate",                                    required=True),
+                        Edit (text=em_lang.field_color,      name=b"color", type=b"color",     required=True, value=current.color),
+                        Submit(text=em_lang.add_button,      name=b"add")
+                ]),
+                List(time_slots_items)
+        ])
+        await response.send_page(page)
+
+
+@HttpServer.add_route(b'/geolocation', menu=em_lang.menu_electricmeter, item=em_lang.item_geolocation)
+async def geolocation_page(request, response, args):
+        """ Determines the geolocation of the device """
+        config = GeolocationConfig.get_config()
+        disabled, action, submit = manage_default_button(request, config)
+
+        page = main_frame(request, response, args, em_lang.item_geolocation,
+                Form(
+                [
+                        Edit  (text=em_lang.latitude,  name=b"latitude",  type=b"number", step=b"0.0001", required=True, min=b"-90.",  max=b"90." , value=b"%.3f"%config.latitude, disabled=disabled),
+                        Edit  (text=em_lang.longitude, name=b"longitude", type=b"number", step=b"0.0001", required=True, min=b"-180.", max=b"180.", value=b"%.3f"%config.longitude,disabled=disabled),
+                        submit, None
+                ]))
+        await response.send_page(page)
+
+
+
+
+
+
+
+

Functions

+
+
+async def daily_datas(request, response, args) +
+
+

Send pulses of month and rates

+
+ +Expand source code + +
@HttpServer.add_route(b'/daily_datas')
+async def daily_datas(request, response, args):
+        """ Send pulses of month and rates """
+        await electricmeter.MonthlyCounter.refresh()
+        month    = date.html_to_date(request.params.get(b"month",b""))
+        result = {"time_slots":None}
+        result["rates"] = strings.tostrings(TimeSlotsConfig.get_cost(month))
+        try:
+                result["time_slots"] = electricmeter.DailyCounter.get_datas(month)
+                await response.send_buffer(b"pulses", buffer=strings.tobytes(json.dumps(result)), mime_type=b"application/json")
+        except Exception as err:
+                await response.send_not_found(err)
+
+
+
+async def daily_page(request, response, args) +
+
+

Consumption graph day by day

+
+ +Expand source code + +
@HttpServer.add_route(b'/daily', menu=em_lang.menu_electricmeter, item=em_lang.item_day)
+async def daily_page(request, response, args):
+        """ Consumption graph day by day """
+        geolocation = GeolocationConfig.get_config()
+        y,m = date.local_time()[:2]
+        year   = int(request.params.get(b"year",b"%d"%y))
+        month  = int(request.params.get(b"month",b"%d"%m))
+
+        if   request.params.get(b"direction",b"") == b"next":
+                if month == 12:
+                        month = 1
+                        year += 1
+                else:
+                        month += 1
+        elif request.params.get(b"direction",b"") == b"previous":
+                if month == 1:
+                        month = 12
+                        year -= 1
+                else:
+                        month -= 1
+
+        day  = date.html_to_date(b"%04d-%02d-01"%(year, month))
+
+        unit = request.params.get(b"unit",b"power")
+        temperature = request.params.get(b"temperature")
+        if temperature is None or temperature == b"0":
+                with_temperature = b"false"
+                temperature = None
+        else:
+                with_temperature = b"true"
+
+        months_combo = []
+        month_id = 1
+        for m in lang.months:
+                months_combo.append(Option(text=m, value=b"%d"%month_id, selected= True if month_id == month else False))
+                month_id += 1
+
+        with  open(WWW_DIR + "electricmeter.html", "rb") as file:
+                content = file.read()
+
+        page_content = \
+        [
+                Div(
+                [
+                        Div(
+                        [
+                                Button(type=b"submit", text=b"&lt;-", name=b"direction",  value=b"previous"),Space(),
+                                Button(type=b"submit", text=b"-&gt;", name=b"direction",  value=b"next")
+                        ], class_=b'col-md-2 mb-2'),
+                        Div(
+                        [
+                                Edit (name=b"year", spacer=b"", type=b"number", step=b"1",  required=True, value=b"%d"%year, event=b'onchange="this.form.submit()"'),
+                        ], class_=b'col-md-2'),
+                        Div(
+                        [
+                                Select(months_combo, spacer=b"", name=b"month", event=b'onchange="this.form.submit()"'),
+                        ], class_=b'col-md-2 mb-2'),
+                        Div(
+                        [
+                                Select(
+                                [
+                                        Option(text=em_lang.type_price, value=b"price", selected= True if unit == b"price" else False),
+                                        Option(text=em_lang.type_power, value=b"power", selected= True if unit == b"power" else False),
+                                ], spacer=b"", text=em_lang.step_minutes, name=b"unit",                 event=b'onchange="this.form.submit()"')
+                        ], class_=b'col-md-2'),
+                        Div(
+                        [
+                                Switch(text=em_lang.temperature, name=b"temperature", checked=temperature, event=b'onchange="this.form.submit()"')
+                        ], class_=b'col-md-2')
+                ], class_=b"row"),
+                Tag(content%(b"daily", 86400, date.date_to_html(day), lang.translate_date(day, False), unit, em_lang.power_consumed, geolocation.latitude, geolocation.longitude, with_temperature))
+        ]
+        page = main_frame(request, response, args, em_lang.title_electricmeter + em_lang.item_day.lower(),Form(page_content))
+        await response.send_page(page)
+
+
+
+async def geolocation_page(request, response, args) +
+
+

Determines the geolocation of the device

+
+ +Expand source code + +
@HttpServer.add_route(b'/geolocation', menu=em_lang.menu_electricmeter, item=em_lang.item_geolocation)
+async def geolocation_page(request, response, args):
+        """ Determines the geolocation of the device """
+        config = GeolocationConfig.get_config()
+        disabled, action, submit = manage_default_button(request, config)
+
+        page = main_frame(request, response, args, em_lang.item_geolocation,
+                Form(
+                [
+                        Edit  (text=em_lang.latitude,  name=b"latitude",  type=b"number", step=b"0.0001", required=True, min=b"-90.",  max=b"90." , value=b"%.3f"%config.latitude, disabled=disabled),
+                        Edit  (text=em_lang.longitude, name=b"longitude", type=b"number", step=b"0.0001", required=True, min=b"-180.", max=b"180.", value=b"%.3f"%config.longitude,disabled=disabled),
+                        submit, None
+                ]))
+        await response.send_page(page)
+
+
+
+async def hourly_page_datas(request, response, args) +
+
+

Send pulses of hours and rates

+
+ +Expand source code + +
@HttpServer.add_route(b'/hourly_datas')
+async def hourly_page_datas(request, response, args):
+        """ Send pulses of hours and rates """
+        day    = date.html_to_date(request.params.get(b"day",b""))
+        result = {"pulses":None}
+        result["rates"] = strings.tostrings(TimeSlotsConfig.get_cost(day))
+        try:
+                result["pulses"] = electricmeter.HourlyCounter.get_datas(day)
+                await response.send_buffer(b"pulses", buffer=strings.tobytes(json.dumps(result)), mime_type=b"application/json")
+        except Exception as err:
+                await response.send_not_found(err)
+
+
+
+async def hourly_page_page(request, response, args) +
+
+

Daily consumption graph hour by hour

+
+ +Expand source code + +
@HttpServer.add_route(b'/hourly', menu=em_lang.menu_electricmeter, item=em_lang.item_hour)
+async def hourly_page_page(request, response, args):
+        """ Daily consumption graph hour by hour """
+        geolocation = GeolocationConfig.get_config()
+        day  = date.html_to_date(request.params.get(b"day",b""))
+
+        if   request.params.get(b"direction",b"") == b"next":
+                day += 86400
+        elif request.params.get(b"direction",b"") == b"previous":
+                day -= 86400
+
+        temperature = request.params.get(b"temperature")
+        if temperature is None or temperature == b"0":
+                with_temperature = b"false"
+                temperature = None
+        else:
+                with_temperature = b"true"
+
+        step = int(request.params.get(b"step",b"30"))
+        steps = []
+        for s in [1,2,5,10,15,30]:
+                if s == step:
+                        selected = True
+                else:
+                        selected = False
+                steps.append(Option(text=b"%d %s"%(s, em_lang.step_minutes), value=b"%d"%s, selected=selected))
+
+        unit = request.params.get(b"unit",b"power")
+
+        with  open(WWW_DIR + "electricmeter.html", "rb") as file:
+                content = file.read()
+
+        page_content = \
+        [
+                Div(
+                [
+                        Div(
+                        [
+                                Button(type=b"submit", text=b"&lt;-", name=b"direction",  value=b"previous"),Space(),
+                                Button(type=b"submit", text=b"-&gt;", name=b"direction",  value=b"next")
+                        ], class_=b'col-md-2 mb-1'),
+                        Div(
+                        [
+                                Input (type=b"date",   class_=b"form-label border rounded p-1", name=b"day", value= date.date_to_html(day), event=b'onchange="this.form.submit()"'),Space(),
+                        ], class_=b'col-md-2'),
+                        Div(
+                        [
+                                Select(steps, spacer=b"", text=em_lang.step_minutes, name=b"step",                 event=b'onchange="this.form.submit()"')
+                        ], class_=b'col-md-2'),
+                        Div([
+                                Select(
+                                [
+                                        Option(text=em_lang.type_price, value=b"price", selected= True if unit == b"price" else False),
+                                        Option(text=em_lang.type_power, value=b"power", selected= True if unit == b"power" else False),
+                                ], spacer=b"", text=em_lang.step_minutes, name=b"unit",                 event=b'onchange="this.form.submit()"')
+                        ], class_=b'col-md-1'),
+                        Div(
+                        [
+                                Switch(text=em_lang.temperature, name=b"temperature", checked=temperature, event=b'onchange="this.form.submit()"')
+                        ], class_=b'col-md-2')
+                ], class_=b"row"),
+
+                Tag(content%(b"hourly",
+                        step,
+                        date.date_to_html(day),
+                        lang.translate_date(day),
+                        unit,
+                        em_lang.power_consumed,
+                        geolocation.latitude,
+                        geolocation.longitude,
+                        with_temperature))
+        ]
+        page = main_frame(request, response, args,em_lang.title_electricmeter + em_lang.item_hour.lower(),Form(page_content))
+        await response.send_page(page)
+
+
+
+async def monthly_datas(request, response, args) +
+
+

Send pulses of month and rates

+
+ +Expand source code + +
@HttpServer.add_route(b'/monthly_datas')
+async def monthly_datas(request, response, args):
+        """ Send pulses of month and rates """
+        await electricmeter.MonthlyCounter.refresh()
+        year   = date.html_to_date(request.params.get(b"year",b""))
+        result = {"time_slots":None}
+        result["rates"] = strings.tostrings(TimeSlotsConfig.get_cost(year))
+        try:
+                result["time_slots"] = electricmeter.MonthlyCounter.get_datas(year)
+                await response.send_buffer(b"pulses", buffer=strings.tobytes(json.dumps(result)), mime_type=b"application/json")
+        except Exception as err:
+                await response.send_not_found(err)
+
+
+
+async def monthly_page(request, response, args) +
+
+

Consumption graph month by month

+
+ +Expand source code + +
@HttpServer.add_route(b'/monthly', menu=em_lang.menu_electricmeter, item=em_lang.item_month)
+async def monthly_page(request, response, args):
+        """ Consumption graph month by month """
+        geolocation = GeolocationConfig.get_config()
+        y = date.local_time()[0]
+        year   = int(request.params.get(b"year",b"%d"%y))
+
+        if   request.params.get(b"direction",b"") == b"next":
+                year += 1
+        elif request.params.get(b"direction",b"") == b"previous":
+                year -= 1
+
+        month  = date.html_to_date(b"%04d-01-01"%(year))
+        unit = request.params.get(b"unit",b"power")
+
+
+        with  open(WWW_DIR + "electricmeter.html", "rb") as file:
+                content = file.read()
+
+        page_content = \
+        [
+                Div(
+                [
+                        Div(
+                        [
+                                Button(type=b"submit", text=b"&lt;-", name=b"direction",  value=b"previous"),Space(),
+                                Button(type=b"submit", text=b"-&gt;", name=b"direction",  value=b"next")
+                        ], class_=b'col-md-2 mb-2'),
+                        Div(\
+                        [
+                                Edit (name=b"year", spacer=b"", type=b"number", step=b"1",  required=True, value=b"%d"%year, event=b'onchange="this.form.submit()"'),
+                        ], class_=b'col-md-2'),
+                        Div(
+                        [
+                                Select(
+                                [
+                                        Option(text=em_lang.type_price, value=b"price", selected= True if unit == b"price" else False),
+                                        Option(text=em_lang.type_power, value=b"power", selected= True if unit == b"power" else False),
+                                ], spacer=b"", text=em_lang.step_minutes, name=b"unit",                 event=b'onchange="this.form.submit()"')
+                        ],class_=b'col-md-2')
+                ], class_=b"row"),
+
+                Tag(content%(b"monthly", 86400, date.date_to_html(month), lang.translate_date(month, False), unit, em_lang.power_consumed, geolocation.latitude, geolocation.longitude, b""))
+        ]
+        page = main_frame(request, response, args, em_lang.title_electricmeter + em_lang.item_month.lower(),Form(page_content))
+        await response.send_page(page)
+
+
+
+async def power_page_datas(request, response, args) +
+
+

Send current power consumed

+
+ +Expand source code + +
@HttpServer.add_route(b'/power_datas')
+async def power_page_datas(request, response, args):
+        """ Send current power consumed """
+        try:
+                power = {"power":electricmeter.HourlyCounter.get_power()}
+                await response.send_buffer(b"pulses", buffer=strings.tobytes(json.dumps(power)), mime_type=b"application/json")
+        except Exception as err:
+                await response.send_not_found(err)
+
+
+
+async def rate_page(request, response, args) +
+
+

Electric rate configuration page

+
+ +Expand source code + +
@HttpServer.add_route(b'/rate', menu=em_lang.menu_electricmeter, item=em_lang.item_rate)
+async def rate_page(request, response, args):
+        """ Electric rate configuration page """
+        rates   = RatesConfig.get_config()
+        current = RateConfig()
+        # If new rate added
+        if request.params.get(b"add",None) is not None:
+                current.update(request.params, show_error=False)
+                rates.append(current)
+                rates.save()
+        # If a rate must be deleted
+        elif request.params.get(b"remove",None) is not None:
+                rates.remove(request.params.get(b"remove",b"none"))
+                rates.save()
+        # If a rate must be edited
+        elif request.params.get(b"edit",None) is not None:
+                current.update(rates.get(request.params.get(b"edit",b"none")))
+
+        # List all rates
+        rate_items = []
+        identifier = 0
+        while True:
+                rate = rates.get(identifier)
+                if rate is not None:
+                        rate_items.append(
+                                        ListItem(
+                                        [
+                                                Link(text=b" %s : %f %s %s %s "%(rate[b"name"], rate[b"price"], rate[b"currency"], em_lang.from_the, lang.translate_date(rate[b"validity_date"])), href=b"rate?edit=%d"%identifier ),
+                                                Link(text=em_lang.remove_button , class_=b"btn position-absolute top-50 end-0 translate-middle-y", href=b"rate?remove=%d"%identifier, onclick=b"return window.confirm('%s')"%em_lang.remove_dialog)
+                                        ]))
+                else:
+                        break
+                identifier += 1
+
+        # Build page
+        page = main_frame(request, response, args, em_lang.title_rate,
+        [
+                Form(
+                [
+                        Edit  (text=em_lang.name,          name=b"name",          placeholder=em_lang.field_rate,     required=True, value=current.name),
+                        Edit  (text=em_lang.validy_date,   name=b"validity_date", type=b"date",                    required=True, value=b"%04d-%02d-%02d"%date.local_time(current.validity_date)[:3]),
+                        Edit  (text=em_lang.price,         name=b"price",         type=b"number", step=b"0.0001",  required=True, value=b"%f"%current.price),
+                        Edit  (text=em_lang.currency,      name=b"currency",      placeholder=em_lang.field_currency, required=True, value=current.currency),
+                        Submit(text=em_lang.add_button,    name=b"add")
+                ]),
+                List(rate_items)
+        ])
+        await response.send_page(page)
+
+
+
+async def time_slots_page(request, response, args) +
+
+

Electric time slots configuration page

+
+ +Expand source code + +
@HttpServer.add_route(b'/time_slots', menu=em_lang.menu_electricmeter, item=em_lang.item_time_slots)
+async def time_slots_page(request, response, args):
+        """ Electric time slots configuration page """
+        time_slots = TimeSlotsConfig.get_config()
+        rates   = RatesConfig.get_config()
+        current = TimeSlotConfig()
+
+        # If new time_slot added
+        if request.params.get(b"add",None) is not None:
+                current.update(request.params, show_error=False)
+                time_slots.append(current)
+                time_slots.save()
+        # If a time_slot must be deleted
+        elif request.params.get(b"remove",None) is not None:
+                time_slots.remove(request.params.get(b"remove",b"none"))
+                time_slots.save()
+        # If a rate must be edited
+        elif request.params.get(b"edit",None) is not None:
+                current.update(time_slots.get(request.params.get(b"edit",b"none")))
+
+        # List all rates names
+        rates_combo = []
+        names       = {}
+        identifier  = 0
+        while True:
+                rate = rates.get(identifier)
+                if rate is not None:
+                        if rate[b"name"] not in names:
+                                rates_combo.append(Option(text=rate[b"name"], value=rate[b"name"], selected=b"selected" if current.rate == rate[b"name"] else b""))
+                else:
+                        break
+                names[rate[b"name"]] = b""
+                identifier += 1
+
+        # List all rates
+        time_slots_items = []
+        identifier = 0
+        while True:
+                time_slot = time_slots.get(identifier)
+                if time_slot is not None:
+                        time_slots_items.append(
+                                        ListItem(
+                                        [
+                                                Label(text=b"&nbsp;&nbsp;&nbsp;&nbsp;", style=b"background-color: %s"%time_slot[b"color"]),
+                                                Space(),Space(),
+                                                Link(text=b"%s - %s : %s "%(date.time_to_html(time_slot[b"start_time"]), date.time_to_html(time_slot[b"end_time"]), time_slot[b"rate"]),href=b"time_slots?edit=%d"%identifier),
+                                                Link(text=em_lang.remove_button , class_=b"btn position-absolute top-50 end-0 translate-middle-y", href=b"time_slots?remove=%d"%identifier, onclick=b"return window.confirm('%s')"%em_lang.remove_dialog)
+                                        ]))
+                else:
+                        break
+                identifier += 1
+
+        # Build page
+        page = main_frame(request, response, args, em_lang.title_time_slots,
+        [
+                Form(
+                [
+                        Edit (text=em_lang.field_start,      name=b"start_time", type=b"time", required=True, value=date.time_to_html(current.start_time)),
+                        Edit (text=em_lang.field_end,        name=b"end_time",   type=b"time", required=True, value=date.time_to_html(current.end_time)),
+                        Label(text=em_lang.field_time_rate),
+                        Select(rates_combo,name=b"rate",                                    required=True),
+                        Edit (text=em_lang.field_color,      name=b"color", type=b"color",     required=True, value=current.color),
+                        Submit(text=em_lang.add_button,      name=b"add")
+                ]),
+                List(time_slots_items)
+        ])
+        await response.send_page(page)
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/doc/lib/htmltemplate/htmlclasses.html b/doc/lib/htmltemplate/htmlclasses.html index a1eba74..15d4f82 100644 --- a/doc/lib/htmltemplate/htmlclasses.html +++ b/doc/lib/htmltemplate/htmlclasses.html @@ -29,15 +29,14 @@

Module lib.htmltemplate.htmlclasses

''' File automatically generated with template.html content '''
 # pylint:disable=missing-function-docstring
+# pylint:disable=global-variable-not-assigned
 # pylint:disable=trailing-whitespace
 # pylint:disable=too-many-lines
 from htmltemplate.template import Template 
 
-# <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
-# <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
-# <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
-# <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
-beg_tagStylesheet = b'''<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"><script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script><script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>'''
+# <link  href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
+# <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
+beg_tagStylesheet = b'''<link  href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet"><script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>'''
 def Stylesheet(*args, **params):
         self = Template(*(("Stylesheet",) + args), **params)
 
@@ -50,10 +49,12 @@ 

Module lib.htmltemplate.htmlclasses

return b'' self.get_end = get_end + self.end_init(**params) return self -# <link rel="stylesheet" href="stylesheet.css"> -beg_tagStylesheetDefault = b'''<link rel="stylesheet" href="stylesheet.css">''' +# <link href="/bootstrap.min.css" rel="stylesheet"/> +# <script src="/bootstrap.bundle.min.js"></script> +beg_tagStylesheetDefault = b'''<link href="/bootstrap.min.css" rel="stylesheet"/><script src="/bootstrap.bundle.min.js"></script>''' def StylesheetDefault(*args, **params): self = Template(*(("StylesheetDefault",) + args), **params) @@ -66,9 +67,12 @@

Module lib.htmltemplate.htmlclasses

return b'' self.get_end = get_end + self.end_init(**params) return self -# <div class="%(class_)s" style="%(style)s" id="%(id)s">%(content)s</div> +# <div class="%(class_)s" style="%(style)s" id="%(id)s"> +# %(content)s +# </div> beg_tagDiv = b'''<div class="%s" style="%s" id="%s">''' end_tagDiv = b'''</div>''' def Div(*args, **params): @@ -84,60 +88,11 @@

Module lib.htmltemplate.htmlclasses

return end_tagDiv self.get_end = get_end - self.content = params.get("content", b"") - self.class_ = params.get("class_", b"") - self.id = params.get("id", b"%d"%id(self)) self.style = params.get("style", b"") - return self - -# <option %(selected)s name="%(text)s" value="%(value)s" %(disabled)s>%(text)s</option> -beg_tagOption = b'''<option %s name="%s" value="%s" %s>%s</option>''' -def Option(*args, **params): - self = Template(*(("Option",) + args), **params) - - def get_begin(self): - global beg_tagOption - return beg_tagOption%( b'selected' if self.selected else b'',self.text,self.value, b'disabled' if self.disabled else b'',self.text) - self.get_begin = get_begin - - def get_end(self): - return b'' - self.get_end = get_end - - self.text = params.get("text", b"") - self.disabled = params.get("disabled", False) - self.value = params.get("value", b"") - self.selected = params.get("selected", b"") - return self - -# <label >%(text)s</label> -# <select class="custom-select %(class_)s" style="%(style)s" id="%(id)s" name="%(name)s" %(disabled)s> -# %(content)s -# </select> -# <br> -# <br> -beg_tagSelect = b'''<label >%s</label><select class="custom-select %s" style="%s" id="%s" name="%s" %s>''' -end_tagSelect = b'''</select><br><br>''' -def Select(*args, **params): - self = Template(*(("Select",) + args), **params) - - def get_begin(self): - global beg_tagSelect - return beg_tagSelect%(self.text,self.class_,self.style,self.id,self.name, b'disabled' if self.disabled else b'') - self.get_begin = get_begin - - def get_end(self): - global end_tagSelect - return end_tagSelect - self.get_end = get_end - self.content = params.get("content", b"") self.id = params.get("id", b"%d"%id(self)) - self.disabled = params.get("disabled", False) - self.style = params.get("style", b"") - self.text = params.get("text", b"") self.class_ = params.get("class_", b"") - self.name = params.get("name", b"%d"%id(self)) + self.end_init(**params) return self # <h1 class="%(class_)s" style="%(style)s" id="%(id)s">%(text)s</h1> @@ -154,10 +109,11 @@

Module lib.htmltemplate.htmlclasses

return b'' self.get_end = get_end + self.style = params.get("style", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") self.id = params.get("id", b"%d"%id(self)) - self.style = params.get("style", b"") + self.class_ = params.get("class_", b"") + self.end_init(**params) return self # <h2 class="%(class_)s" style="%(style)s" id="%(id)s">%(text)s</h2> @@ -174,10 +130,11 @@

Module lib.htmltemplate.htmlclasses

return b'' self.get_end = get_end + self.style = params.get("style", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") self.id = params.get("id", b"%d"%id(self)) - self.style = params.get("style", b"") + self.class_ = params.get("class_", b"") + self.end_init(**params) return self # <h3 class="%(class_)s" style="%(style)s" id="%(id)s">%(text)s</h3> @@ -194,10 +151,11 @@

Module lib.htmltemplate.htmlclasses

return b'' self.get_end = get_end + self.style = params.get("style", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") self.id = params.get("id", b"%d"%id(self)) - self.style = params.get("style", b"") + self.class_ = params.get("class_", b"") + self.end_init(**params) return self # <h4 class="%(class_)s" style="%(style)s" id="%(id)s">%(text)s</h4> @@ -214,10 +172,11 @@

Module lib.htmltemplate.htmlclasses

return b'' self.get_end = get_end + self.style = params.get("style", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") self.id = params.get("id", b"%d"%id(self)) - self.style = params.get("style", b"") + self.class_ = params.get("class_", b"") + self.end_init(**params) return self # <label class="%(class_)s" style="%(style)s" id="%(id)s">%(text)s</label> @@ -234,42 +193,47 @@

Module lib.htmltemplate.htmlclasses

return b'' self.get_end = get_end + self.style = params.get("style", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") self.id = params.get("id", b"%d"%id(self)) - self.style = params.get("style", b"") + self.class_ = params.get("class_", b"") + self.end_init(**params) return self -# <div class="form-group"> -# <input class="form-control %(class_)s" style="%(style)s" id="%(id)s" pattern="%(pattern)s" placeholder="%(placeholder)s" type="%(type)s" value="%(value)s" name="%(name)s" %(disabled)s /> -# </div> -beg_tagInput = b'''<div class="form-group"><input class="form-control %s" style="%s" id="%s" pattern="%s" placeholder="%s" type="%s" value="%s" name="%s" %s /></div>''' +# <input class="%(class_)s" style="%(style)s" id="%(id)s" pattern="%(pattern)s" placeholder="%(placeholder)s" type="%(type)s" value="%(value)s" name="%(name)s" %(disabled)s %(event)s min="%(min)s" max="%(max)s" %(required)s/> +beg_tagInput = b'''<input class="%s" style="%s" id="%s" pattern="%s" placeholder="%s" type="%s" value="%s" name="%s" %s %s min="%s" max="%s" %s/>''' def Input(*args, **params): self = Template(*(("Input",) + args), **params) def get_begin(self): global beg_tagInput - return beg_tagInput%(self.class_,self.style,self.id,self.pattern,self.placeholder,self.type,self.value,self.name, b'disabled' if self.disabled else b'') + return beg_tagInput%(self.class_,self.style,self.id,self.pattern,self.placeholder,self.type,self.value,self.name, b'disabled' if self.disabled else b'',self.event,self.min,self.max, b'required' if self.required else b'') self.get_begin = get_begin def get_end(self): return b'' self.get_end = get_end + self.pattern = params.get("pattern", b"*") self.id = params.get("id", b"%d"%id(self)) + self.class_ = params.get("class_", b"") + self.max = params.get("max", b"") + self.style = params.get("style", b"") + self.event = params.get("event", b"") + self.name = params.get("name", b"%d"%id(self)) self.placeholder = params.get("placeholder", b"") + self.min = params.get("min", b"") + self.type = params.get("type", b"") self.disabled = params.get("disabled", False) - self.style = params.get("style", b"") + self.required = params.get("required", False) self.value = params.get("value", b"") - self.type = params.get("type", b"") - self.class_ = params.get("class_", b"") - self.name = params.get("name", b"%d"%id(self)) - self.pattern = params.get("pattern", b"*") + self.end_init(**params) return self +# <div class="form-group %(spacer)s"> # <label for="customRange">%(text)s</label> # <div style="display: flex;"> -# <input type="range" class="custom-range %(class_)s" style="%(style)s" id="slider_%(id)s" name="%(name)s" min="%(min)s" max="%(max)s" step="%(step)s" value="%(value)s" %(disabled)s oninput="onchange_%(id)s()" /> +# <input type="range" class="form-range %(class_)s" style="%(style)s" id="slider_%(id)s" name="%(name)s" min="%(min)s" max="%(max)s" step="%(step)s" value="%(value)s" %(disabled)s oninput="onchange_%(id)s()" /> # <span id="value_%(id)s"/> # </div> # <script type="text/javascript"> @@ -279,71 +243,158 @@

Module lib.htmltemplate.htmlclasses

# } # onchange_%(id)s(); # </script> -beg_tagSlider = b'''<label for="customRange">%s</label><div style="display: flex;"><input type="range" class="custom-range %s" style="%s" id="slider_%s" name="%s" min="%s" max="%s" step="%s" value="%s" %s oninput="onchange_%s()" /><span id="value_%s"/></div><script type="text/javascript">function onchange_%s(){document.getElementById("value_%s").innerHTML = "&nbsp;" + document.getElementById("slider_%s").value;}onchange_%s();</script>''' +# </div> +beg_tagSlider = b'''<div class="form-group %s"><label for="customRange">%s</label><div style="display: flex;"><input type="range" class="form-range %s" style="%s" id="slider_%s" name="%s" min="%s" max="%s" step="%s" value="%s" %s oninput="onchange_%s()" /><span id="value_%s"/></div><script type="text/javascript">function onchange_%s(){document.getElementById("value_%s").innerHTML = "&nbsp;" + document.getElementById("slider_%s").value;}onchange_%s();</script></div>''' def Slider(*args, **params): self = Template(*(("Slider",) + args), **params) def get_begin(self): global beg_tagSlider - return beg_tagSlider%(self.text,self.class_,self.style,self.id,self.name,self.min,self.max,self.step,self.value, b'disabled' if self.disabled else b'',self.id,self.id,self.id,self.id,self.id,self.id) + return beg_tagSlider%(self.spacer,self.text,self.class_,self.style,self.id,self.name,self.min,self.max,self.step,self.value, b'disabled' if self.disabled else b'',self.id,self.id,self.id,self.id,self.id,self.id) self.get_begin = get_begin def get_end(self): return b'' self.get_end = get_end - self.step = params.get("step", b"") self.id = params.get("id", b"%d"%id(self)) - self.disabled = params.get("disabled", False) + self.class_ = params.get("class_", b"") self.max = params.get("max", b"") - self.min = params.get("min", b"") self.style = params.get("style", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") self.name = params.get("name", b"%d"%id(self)) + self.spacer = params.get("spacer", b"") + self.min = params.get("min", b"") + self.disabled = params.get("disabled", False) + self.value = params.get("value", b"") + self.step = params.get("step", b"") + self.end_init(**params) + return self + +# <option %(selected)s name="%(name)s" value="%(value)s" %(disabled)s>%(text)s</option> +beg_tagOption = b'''<option %s name="%s" value="%s" %s>%s</option>''' +def Option(*args, **params): + self = Template(*(("Option",) + args), **params) + + def get_begin(self): + global beg_tagOption + return beg_tagOption%( b'selected' if self.selected else b'',self.name,self.value, b'disabled' if self.disabled else b'',self.text) + self.get_begin = get_begin + + def get_end(self): + return b'' + self.get_end = get_end + + self.text = params.get("text", b"") + self.name = params.get("name", b"%d"%id(self)) + self.selected = params.get("selected", b"") + self.disabled = params.get("disabled", False) self.value = params.get("value", b"") + self.end_init(**params) return self -# <div class="form-group"> -# <label >%(text)s</label> -# <input type="%(type)s" class="form-control %(class_)s" style="%(style)s" id="%(id)s" pattern="%(pattern)s" placeholder="%(placeholder)s" type="%(type)s" value="%(value)s" name="%(name)s" %(disabled)s /> +# <div class="form-group %(spacer)s" > +# <select class="form-select %(class_)s" style="%(style)s" id="%(id)s" name="%(name)s" %(disabled)s %(event)s> +# %(content)s +# </select> # </div> -beg_tagEdit = b'''<div class="form-group"><label >%s</label><input type="%s" class="form-control %s" style="%s" id="%s" pattern="%s" placeholder="%s" type="%s" value="%s" name="%s" %s /></div>''' +beg_tagSelect = b'''<div class="form-group %s" ><select class="form-select %s" style="%s" id="%s" name="%s" %s %s>''' +end_tagSelect = b'''</select></div>''' +def Select(*args, **params): + self = Template(*(("Select",) + args), **params) + + def get_begin(self): + global beg_tagSelect + return beg_tagSelect%(self.spacer,self.class_,self.style,self.id,self.name, b'disabled' if self.disabled else b'',self.event) + self.get_begin = get_begin + + def get_end(self): + global end_tagSelect + return end_tagSelect + self.get_end = get_end + + self.content = params.get("content", b"") + self.id = params.get("id", b"%d"%id(self)) + self.class_ = params.get("class_", b"") + self.style = params.get("style", b"") + self.name = params.get("name", b"%d"%id(self)) + self.spacer = params.get("spacer", b"") + self.disabled = params.get("disabled", False) + self.event = params.get("event", b"") + self.end_init(**params) + return self + +# <div class="form-group %(spacer)s"> +# %(content)s +# </div> +beg_tagFormGroup = b'''<div class="form-group %s">''' +end_tagFormGroup = b'''</div>''' +def FormGroup(*args, **params): + self = Template(*(("FormGroup",) + args), **params) + + def get_begin(self): + global beg_tagFormGroup + return beg_tagFormGroup%(self.spacer) + self.get_begin = get_begin + + def get_end(self): + global end_tagFormGroup + return end_tagFormGroup + self.get_end = get_end + + self.content = params.get("content", b"") + self.spacer = params.get("spacer", b"") + self.end_init(**params) + return self + +# <div class="form-group %(spacer)s"> +# <label class="form-check-label">%(text)s</label> +# <input type="%(type)s" class="form-control form-label %(class_)s" style="%(style)s" id="%(id)s" pattern="%(pattern)s" placeholder="%(placeholder)s" type="%(type)s" value="%(value)s" name="%(name)s" min="%(min)s" max="%(max)s" step="%(step)s" %(disabled)s %(required)s %(event)s/> +# </div> +beg_tagEdit = b'''<div class="form-group %s"><label class="form-check-label">%s</label><input type="%s" class="form-control form-label %s" style="%s" id="%s" pattern="%s" placeholder="%s" type="%s" value="%s" name="%s" min="%s" max="%s" step="%s" %s %s %s/></div>''' def Edit(*args, **params): self = Template(*(("Edit",) + args), **params) def get_begin(self): global beg_tagEdit - return beg_tagEdit%(self.text,self.type,self.class_,self.style,self.id,self.pattern,self.placeholder,self.type,self.value,self.name, b'disabled' if self.disabled else b'') + return beg_tagEdit%(self.spacer,self.text,self.type,self.class_,self.style,self.id,self.pattern,self.placeholder,self.type,self.value,self.name,self.min,self.max,self.step, b'disabled' if self.disabled else b'', b'required' if self.required else b'',self.event) self.get_begin = get_begin def get_end(self): return b'' self.get_end = get_end + self.pattern = params.get("pattern", b"*") self.id = params.get("id", b"%d"%id(self)) + self.class_ = params.get("class_", b"") + self.max = params.get("max", b"") + self.style = params.get("style", b"") + self.text = params.get("text", b"") + self.event = params.get("event", b"") + self.name = params.get("name", b"%d"%id(self)) self.placeholder = params.get("placeholder", b"") + self.spacer = params.get("spacer", b"") + self.min = params.get("min", b"") + self.type = params.get("type", b"") self.disabled = params.get("disabled", False) - self.text = params.get("text", b"") - self.style = params.get("style", b"") + self.required = params.get("required", False) self.value = params.get("value", b"") - self.type = params.get("type", b"") - self.class_ = params.get("class_", b"") - self.name = params.get("name", b"%d"%id(self)) - self.pattern = params.get("pattern", b"*") + self.step = params.get("step", b"") + self.end_init(**params) return self -# <div class="custom-control custom-switch"> -# <input type="hidden" value="0" name="%(name)s" /><input type="checkbox" class="custom-control-input %(class_)s" style="%(style)s" id="%(id)s" value="%(value)s" name="%(name)s" %(checked)s %(disabled)s onchange="%(onchange)s" /> -# <label class="custom-control-label" for="%(id)s">%(text)s</label> +# <div class="form-check form-switch %(spacer)s"> +# <input type="hidden" value="0" name="%(name)s" /> +# <input type="checkbox" class="form-check-input %(class_)s" style="%(style)s" id="%(id)s" value="%(value)s" name="%(name)s" %(checked)s %(disabled)s %(event)s/> +# <label class="form-check-label" for="%(id)s">%(text)s</label> # </div> -beg_tagSwitch = b'''<div class="custom-control custom-switch"><input type="hidden" value="0" name="%s" /><input type="checkbox" class="custom-control-input %s" style="%s" id="%s" value="%s" name="%s" %s %s onchange="%s" /><label class="custom-control-label" for="%s">%s</label></div>''' +beg_tagSwitch = b'''<div class="form-check form-switch %s"><input type="hidden" value="0" name="%s" /><input type="checkbox" class="form-check-input %s" style="%s" id="%s" value="%s" name="%s" %s %s %s/><label class="form-check-label" for="%s">%s</label></div>''' def Switch(*args, **params): self = Template(*(("Switch",) + args), **params) def get_begin(self): global beg_tagSwitch - return beg_tagSwitch%(self.name,self.class_,self.style,self.id,self.value,self.name, b'checked' if self.checked else b'', b'disabled' if self.disabled else b'',self.onchange,self.id,self.text) + return beg_tagSwitch%(self.spacer,self.name,self.class_,self.style,self.id,self.value,self.name, b'checked' if self.checked else b'', b'disabled' if self.disabled else b'',self.event,self.id,self.text) self.get_begin = get_begin def get_end(self): @@ -351,28 +402,30 @@

Module lib.htmltemplate.htmlclasses

self.get_end = get_end self.id = params.get("id", b"%d"%id(self)) - self.checked = params.get("checked", True) - self.onchange = params.get("onchange", b"") - self.disabled = params.get("disabled", False) + self.class_ = params.get("class_", b"") self.style = params.get("style", b"") + self.event = params.get("event", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") self.name = params.get("name", b"%d"%id(self)) + self.spacer = params.get("spacer", b"") + self.disabled = params.get("disabled", False) self.value = params.get("value", b"") + self.checked = params.get("checked", True) + self.end_init(**params) return self -# <div class="form-check"> +# <div class="form-check %(spacer)s"> # <label class="form-check-label"> -# <input type="checkbox" class="form-check-input %(class_)s" style="%(style)s" id="%(id)s" value="%(value)s" name="%(name)s" %(checked)s %(disabled)s>%(text)s</input> +# <input type="radio" class="form-check-input %(class_)s" style="%(style)s" id="%(id)s" name="%(name)s" %(checked)s %(disabled)s %(event)s>%(text)s</input> # </label> # </div> -beg_tagCheckbox = b'''<div class="form-check"><label class="form-check-label"><input type="checkbox" class="form-check-input %s" style="%s" id="%s" value="%s" name="%s" %s %s>%s</input></label></div>''' -def Checkbox(*args, **params): - self = Template(*(("Checkbox",) + args), **params) +beg_tagRadio = b'''<div class="form-check %s"><label class="form-check-label"><input type="radio" class="form-check-input %s" style="%s" id="%s" name="%s" %s %s %s>%s</input></label></div>''' +def Radio(*args, **params): + self = Template(*(("Radio",) + args), **params) def get_begin(self): - global beg_tagCheckbox - return beg_tagCheckbox%(self.class_,self.style,self.id,self.value,self.name, b'checked' if self.checked else b'', b'disabled' if self.disabled else b'',self.text) + global beg_tagRadio + return beg_tagRadio%(self.spacer,self.class_,self.style,self.id,self.name, b'checked' if self.checked else b'', b'disabled' if self.disabled else b'',self.event,self.text) self.get_begin = get_begin def get_end(self): @@ -380,65 +433,77 @@

Module lib.htmltemplate.htmlclasses

self.get_end = get_end self.id = params.get("id", b"%d"%id(self)) - self.checked = params.get("checked", True) - self.disabled = params.get("disabled", False) + self.class_ = params.get("class_", b"") self.style = params.get("style", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") self.name = params.get("name", b"%d"%id(self)) - self.value = params.get("value", b"") + self.spacer = params.get("spacer", b"") + self.disabled = params.get("disabled", False) + self.event = params.get("event", b"") + self.checked = params.get("checked", True) + self.end_init(**params) return self -# <button class="btn btn-outline-primary %(class_)s" style="%(style)s" id="%(id)s" type="%(type)s" name="%(name)s" value="%(value)s" %(disabled)s>%(text)s</button> -beg_tagButton = b'''<button class="btn btn-outline-primary %s" style="%s" id="%s" type="%s" name="%s" value="%s" %s>%s</button>''' -def Button(*args, **params): - self = Template(*(("Button",) + args), **params) +# <div class="form-group %(spacer)s"> +# <label >%(text)s</label> +# <input list="%(id)s" class="form-control %(class_)s" style="%(style)s" pattern="%(pattern)s" placeholder="%(placeholder)s" value="%(value)s" name="%(name)s" %(disabled)s> +# <datalist id="%(id)s"> +# %(content)s +# </datalist> +# </input> +# </div> +beg_tagComboBox = b'''<div class="form-group %s"><label >%s</label><input list="%s" class="form-control %s" style="%s" pattern="%s" placeholder="%s" value="%s" name="%s" %s><datalist id="%s">''' +end_tagComboBox = b'''</datalist></input></div>''' +def ComboBox(*args, **params): + self = Template(*(("ComboBox",) + args), **params) def get_begin(self): - global beg_tagButton - return beg_tagButton%(self.class_,self.style,self.id,self.type,self.name,self.value, b'disabled' if self.disabled else b'',self.text) + global beg_tagComboBox + return beg_tagComboBox%(self.spacer,self.text,self.id,self.class_,self.style,self.pattern,self.placeholder,self.value,self.name, b'disabled' if self.disabled else b'',self.id) self.get_begin = get_begin def get_end(self): - return b'' + global end_tagComboBox + return end_tagComboBox self.get_end = get_end + self.pattern = params.get("pattern", b"*") + self.content = params.get("content", b"") self.id = params.get("id", b"%d"%id(self)) - self.disabled = params.get("disabled", False) - self.text = params.get("text", b"") - self.style = params.get("style", b"") - self.type = params.get("type", b"") self.class_ = params.get("class_", b"") + self.style = params.get("style", b"") + self.text = params.get("text", b"") self.name = params.get("name", b"%d"%id(self)) + self.placeholder = params.get("placeholder", b"") + self.spacer = params.get("spacer", b"") + self.disabled = params.get("disabled", False) self.value = params.get("value", b"") + self.end_init(**params) return self -# <div class="form-check"> -# <label class="form-check-label"> -# <input type="radio" class="form-check-input %(class_)s" style="%(style)s" id="%(id)s" name="%(name)s" %(checked)s %(disabled)s onchange="%(onchange)s">%(text)s</input> -# </label> -# </div> -beg_tagRadio = b'''<div class="form-check"><label class="form-check-label"><input type="radio" class="form-check-input %s" style="%s" id="%s" name="%s" %s %s onchange="%s">%s</input></label></div>''' -def Radio(*args, **params): - self = Template(*(("Radio",) + args), **params) +# <button class="btn btn-outline-primary %(class_)s" style="%(style)s" id="%(id)s" type="%(type)s" name="%(name)s" value="%(value)s" %(disabled)s>%(text)s</button> +beg_tagButton = b'''<button class="btn btn-outline-primary %s" style="%s" id="%s" type="%s" name="%s" value="%s" %s>%s</button>''' +def Button(*args, **params): + self = Template(*(("Button",) + args), **params) def get_begin(self): - global beg_tagRadio - return beg_tagRadio%(self.class_,self.style,self.id,self.name, b'checked' if self.checked else b'', b'disabled' if self.disabled else b'',self.onchange,self.text) + global beg_tagButton + return beg_tagButton%(self.class_,self.style,self.id,self.type,self.name,self.value, b'disabled' if self.disabled else b'',self.text) self.get_begin = get_begin def get_end(self): return b'' self.get_end = get_end - self.checked = params.get("checked", True) self.id = params.get("id", b"%d"%id(self)) - self.onchange = params.get("onchange", b"") - self.disabled = params.get("disabled", False) + self.class_ = params.get("class_", b"") self.style = params.get("style", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") self.name = params.get("name", b"%d"%id(self)) + self.type = params.get("type", b"") + self.disabled = params.get("disabled", False) + self.value = params.get("value", b"") + self.end_init(**params) return self # <input type="file" style="display:none" id="%(id)s" onchange="UploadFile_%(id)s()" name="%(name)s" accept="%(accept)s" %(disabled)s /> @@ -470,13 +535,14 @@

Module lib.htmltemplate.htmlclasses

return b'' self.get_end = get_end - self.id = params.get("id", b"%d"%id(self)) self.path = params.get("path", b"") + self.id = params.get("id", b"%d"%id(self)) + self.text = params.get("text", b"") + self.name = params.get("name", b"%d"%id(self)) self.alert = params.get("alert", b"") self.accept = params.get("accept", b"") - self.text = params.get("text", b"") self.disabled = params.get("disabled", False) - self.name = params.get("name", b"%d"%id(self)) + self.end_init(**params) return self # <a href="%(path)s" download="%(filename)s" class="btn btn-outline-primary " name="%(name)s" %(disabled)s>%(text)s</a> @@ -494,23 +560,24 @@

Module lib.htmltemplate.htmlclasses

self.get_end = get_end self.path = params.get("path", b"") - self.filename = params.get("filename", b"") self.text = params.get("text", b"") - self.disabled = params.get("disabled", False) self.name = params.get("name", b"%d"%id(self)) + self.filename = params.get("filename", b"") + self.disabled = params.get("disabled", False) + self.end_init(**params) return self -# <form class="container %(class_)s" style="%(style)s" id="%(id)s" method="%(method)s" action="%(action)s"> +# <form class="container %(spacer)s %(class_)s" style="%(style)s" id="%(id)s" method="%(method)s" action="%(action)s" %(novalidate)s> # %(content)s # </form> -beg_tagForm = b'''<form class="container %s" style="%s" id="%s" method="%s" action="%s">''' +beg_tagForm = b'''<form class="container %s %s" style="%s" id="%s" method="%s" action="%s" %s>''' end_tagForm = b'''</form>''' def Form(*args, **params): self = Template(*(("Form",) + args), **params) def get_begin(self): global beg_tagForm - return beg_tagForm%(self.class_,self.style,self.id,self.method,self.action) + return beg_tagForm%(self.spacer,self.class_,self.style,self.id,self.method,self.action, b'novalidate' if self.novalidate else b'') self.get_begin = get_begin def get_end(self): @@ -519,11 +586,14 @@

Module lib.htmltemplate.htmlclasses

self.get_end = get_end self.content = params.get("content", b"") + self.novalidate = params.get("novalidate", False) self.id = params.get("id", b"%d"%id(self)) - self.action = params.get("action", b"") - self.method = params.get("method", b"") - self.style = params.get("style", b"") self.class_ = params.get("class_", b"") + self.style = params.get("style", b"") + self.method = params.get("method", b"") + self.spacer = params.get("spacer", b"") + self.action = params.get("action", b"") + self.end_init(**params) return self # <br/> @@ -540,6 +610,7 @@

Module lib.htmltemplate.htmlclasses

return b'' self.get_end = get_end + self.end_init(**params) return self # <div class="container %(class_)s" style="%(style)s" id="%(id)s"> @@ -560,10 +631,11 @@

Module lib.htmltemplate.htmlclasses

return end_tagContainer self.get_end = get_end + self.style = params.get("style", b"") self.content = params.get("content", b"") - self.class_ = params.get("class_", b"") self.id = params.get("id", b"%d"%id(self)) - self.style = params.get("style", b"") + self.class_ = params.get("class_", b"") + self.end_init(**params) return self # <div class="card-header %(class_)s" style="%(style)s" id="%(id)s">%(text)s %(content)s</div> @@ -584,9 +656,10 @@

Module lib.htmltemplate.htmlclasses

self.content = params.get("content", b"") self.id = params.get("id", b"%d"%id(self)) + self.class_ = params.get("class_", b"") self.style = params.get("style", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") + self.end_init(**params) return self # <div class="card-body %(class_)s" style="%(style)s" id="%(id)s">%(content)s</div> @@ -605,13 +678,14 @@

Module lib.htmltemplate.htmlclasses

return end_tagCardBody self.get_end = get_end + self.style = params.get("style", b"") self.content = params.get("content", b"") - self.class_ = params.get("class_", b"") self.id = params.get("id", b"%d"%id(self)) - self.style = params.get("style", b"") + self.class_ = params.get("class_", b"") + self.end_init(**params) return self -# <div class="card %(class_)s" style="%(style)s" id="%(id)s"> +# <div class="card %(spacer)s" style="%(style)s" id="%(id)s"> # %(content)s # </div> beg_tagCard = b'''<div class="card %s" style="%s" id="%s">''' @@ -621,7 +695,7 @@

Module lib.htmltemplate.htmlclasses

def get_begin(self): global beg_tagCard - return beg_tagCard%(self.class_,self.style,self.id) + return beg_tagCard%(self.spacer,self.style,self.id) self.get_begin = get_begin def get_end(self): @@ -629,10 +703,11 @@

Module lib.htmltemplate.htmlclasses

return end_tagCard self.get_end = get_end + self.style = params.get("style", b"") self.content = params.get("content", b"") - self.class_ = params.get("class_", b"") self.id = params.get("id", b"%d"%id(self)) - self.style = params.get("style", b"") + self.spacer = params.get("spacer", b"") + self.end_init(**params) return self # <p class="%(class_)s" style="%(style)s" id="%(id)s">%(content)s%(text)s</p> @@ -653,9 +728,10 @@

Module lib.htmltemplate.htmlclasses

self.content = params.get("content", b"") self.id = params.get("id", b"%d"%id(self)) + self.class_ = params.get("class_", b"") self.style = params.get("style", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") + self.end_init(**params) return self # <li class="list-group-item %(class_)s" style="%(style)s" id="%(id)s">%(content)s%(text)s</li> @@ -676,9 +752,10 @@

Module lib.htmltemplate.htmlclasses

self.content = params.get("content", b"") self.id = params.get("id", b"%d"%id(self)) + self.class_ = params.get("class_", b"") self.style = params.get("style", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") + self.end_init(**params) return self # <button class="btn %(class_)s" style="%(style)s" id="%(id)s">%(content)s%(text)s</button><br> @@ -699,9 +776,10 @@

Module lib.htmltemplate.htmlclasses

self.content = params.get("content", b"") self.id = params.get("id", b"%d"%id(self)) + self.class_ = params.get("class_", b"") self.style = params.get("style", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") + self.end_init(**params) return self # <ul class="list-group %(class_)s" style="%(style)s" id="%(id)s"> @@ -722,10 +800,11 @@

Module lib.htmltemplate.htmlclasses

return end_tagList self.get_end = get_end + self.style = params.get("style", b"") self.content = params.get("content", b"") - self.class_ = params.get("class_", b"") self.id = params.get("id", b"%d"%id(self)) - self.style = params.get("style", b"") + self.class_ = params.get("class_", b"") + self.end_init(**params) return self # <button class="btn btn-outline-primary %(class_)s" style="%(style)s" id="%(id)s" type="submit" name="%(name)s" value="%(value)s" onclick="%(onclick)s">%(content)s%(text)s</button> @@ -746,12 +825,13 @@

Module lib.htmltemplate.htmlclasses

self.content = params.get("content", b"") self.id = params.get("id", b"%d"%id(self)) + self.class_ = params.get("class_", b"") self.onclick = params.get("onclick", b"") self.style = params.get("style", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") self.name = params.get("name", b"%d"%id(self)) self.value = params.get("value", b"") + self.end_init(**params) return self # <a class="btn btn-outline-primary %(class_)s" style="%(style)s" id="%(id)s" href="%(href)s">%(content)s%(text)s</a> @@ -771,22 +851,23 @@

Module lib.htmltemplate.htmlclasses

self.get_end = get_end self.content = params.get("content", b"") - self.id = params.get("id", b"%d"%id(self)) self.href = params.get("href", b"") + self.id = params.get("id", b"%d"%id(self)) + self.class_ = params.get("class_", b"") self.style = params.get("style", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") + self.end_init(**params) return self -# <a class="%(class_)s" style="%(style)s" id="%(id)s" href="%(href)s">%(content)s%(text)s</a> -beg_tagLink = b'''<a class="%s" style="%s" id="%s" href="%s">''' +# <a class="%(class_)s" style="%(style)s" id="%(id)s" href="%(href)s" onclick="%(onclick)s">%(content)s%(text)s</a> +beg_tagLink = b'''<a class="%s" style="%s" id="%s" href="%s" onclick="%s">''' end_tagLink = b'''%s</a>''' def Link(*args, **params): self = Template(*(("Link",) + args), **params) def get_begin(self): global beg_tagLink - return beg_tagLink%(self.class_,self.style,self.id,self.href) + return beg_tagLink%(self.class_,self.style,self.id,self.href,self.onclick) self.get_begin = get_begin def get_end(self): @@ -795,118 +876,19 @@

Module lib.htmltemplate.htmlclasses

self.get_end = get_end self.content = params.get("content", b"") - self.id = params.get("id", b"%d"%id(self)) self.href = params.get("href", b"") - self.style = params.get("style", b"") - self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") - return self - -# <li class="nav-item"> -# <a class="nav-link btn-outline-primary %(active)s %(class_)s" style="%(style)s" id="%(id)s" href="%(href)s" %(disabled)s>%(content)s%(text)s</a> -# </li> -beg_tagTabItem = b'''<li class="nav-item"><a class="nav-link btn-outline-primary %s %s" style="%s" id="%s" href="%s" %s>''' -end_tagTabItem = b'''%s</a></li>''' -def TabItem(*args, **params): - self = Template(*(("TabItem",) + args), **params) - - def get_begin(self): - global beg_tagTabItem - return beg_tagTabItem%( b'active' if self.active else b'',self.class_,self.style,self.id,self.href, b'disabled' if self.disabled else b'') - self.get_begin = get_begin - - def get_end(self): - global end_tagTabItem - return end_tagTabItem%(self.text) - self.get_end = get_end - - self.content = params.get("content", b"") self.id = params.get("id", b"%d"%id(self)) - self.href = params.get("href", b"") - self.disabled = params.get("disabled", False) - self.active = params.get("active", False) - self.style = params.get("style", b"") - self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") - return self - -# <ul class="nav nav-pills nav-stacked flex-column %(class_)s" style="%(style)s" id="%(id)s"> -# %(content)s -# </ul> -beg_tagTab = b'''<ul class="nav nav-pills nav-stacked flex-column %s" style="%s" id="%s">''' -end_tagTab = b'''</ul>''' -def Tab(*args, **params): - self = Template(*(("Tab",) + args), **params) - - def get_begin(self): - global beg_tagTab - return beg_tagTab%(self.class_,self.style,self.id) - self.get_begin = get_begin - - def get_end(self): - global end_tagTab - return end_tagTab - self.get_end = get_end - - self.content = params.get("content", b"") self.class_ = params.get("class_", b"") - self.id = params.get("id", b"%d"%id(self)) - self.style = params.get("style", b"") - return self - -# <a class="dropdown-item %(class_)s" style="%(style)s" id="%(id)s" href="%(href)s">%(text)s</a> -beg_tagDropdownItem = b'''<a class="dropdown-item %s" style="%s" id="%s" href="%s">%s</a>''' -def DropdownItem(*args, **params): - self = Template(*(("DropdownItem",) + args), **params) - - def get_begin(self): - global beg_tagDropdownItem - return beg_tagDropdownItem%(self.class_,self.style,self.id,self.href,self.text) - self.get_begin = get_begin - - def get_end(self): - return b'' - self.get_end = get_end - - self.id = params.get("id", b"%d"%id(self)) - self.href = params.get("href", b"") self.style = params.get("style", b"") + self.onclick = params.get("onclick", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") + self.end_init(**params) return self -# <li class="nav-item dropdown"> -# <a class="nav-link dropdown-toggle %(class_)s" style="%(style)s" id="%(id)s" data-toggle="dropdown"> -# %(text)s -# </a> -# <div class="dropdown-menu"> -# %(content)s -# </div> +# <li> +# <a class="dropdown-item %(active)s %(class_)s" style="%(style)s" id="%(id)s" href="%(href)s" %(disabled)s>%(text)s</a> # </li> -beg_tagDropdown = b'''<li class="nav-item dropdown"><a class="nav-link dropdown-toggle %s" style="%s" id="%s" data-toggle="dropdown">%s</a><div class="dropdown-menu">''' -end_tagDropdown = b'''</div></li>''' -def Dropdown(*args, **params): - self = Template(*(("Dropdown",) + args), **params) - - def get_begin(self): - global beg_tagDropdown - return beg_tagDropdown%(self.class_,self.style,self.id,self.text) - self.get_begin = get_begin - - def get_end(self): - global end_tagDropdown - return end_tagDropdown - self.get_end = get_end - - self.content = params.get("content", b"") - self.id = params.get("id", b"%d"%id(self)) - self.style = params.get("style", b"") - self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") - return self - -# <a class="dropdown-item %(active)s %(class_)s" style="%(style)s margin-right: 2px;" id="%(id)s" href="%(href)s" %(disabled)s>%(text)s</a> -beg_tagMenuItem = b'''<a class="dropdown-item %s %s" style="%s margin-right: 2px;" id="%s" href="%s" %s>%s</a>''' +beg_tagMenuItem = b'''<li><a class="dropdown-item %s %s" style="%s" id="%s" href="%s" %s>%s</a></li>''' def MenuItem(*args, **params): self = Template(*(("MenuItem",) + args), **params) @@ -919,23 +901,24 @@

Module lib.htmltemplate.htmlclasses

return b'' self.get_end = get_end - self.id = params.get("id", b"%d"%id(self)) - self.href = params.get("href", b"") - self.disabled = params.get("disabled", False) self.active = params.get("active", False) + self.href = params.get("href", b"") + self.id = params.get("id", b"%d"%id(self)) + self.class_ = params.get("class_", b"") self.style = params.get("style", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") + self.disabled = params.get("disabled", False) + self.end_init(**params) return self # <li class="nav-item dropdown"> -# <a class="nav-link dropdown-toggle" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" %(disabled)s>%(text)s</a> -# <div class="dropdown-menu"> +# <a class="nav-link dropdown-toggle" href="#" id="dropdown10" data-bs-toggle="dropdown" aria-expanded=" false" %(disabled)s>%(text)s</a> +# <ul class="dropdown-menu " aria-labelledby="dropdown10"> # %(content)s -# </div> +# </ul> # </li> -beg_tagMenu = b'''<li class="nav-item dropdown"><a class="nav-link dropdown-toggle" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" %s>%s</a><div class="dropdown-menu">''' -end_tagMenu = b'''</div></li>''' +beg_tagMenu = b'''<li class="nav-item dropdown"><a class="nav-link dropdown-toggle" href="#" id="dropdown10" data-bs-toggle="dropdown" aria-expanded=" false" %s>%s</a><ul class="dropdown-menu " aria-labelledby="dropdown10">''' +end_tagMenu = b'''</ul></li>''' def Menu(*args, **params): self = Template(*(("Menu",) + args), **params) @@ -949,24 +932,32 @@

Module lib.htmltemplate.htmlclasses

return end_tagMenu self.get_end = get_end - self.content = params.get("content", b"") self.text = params.get("text", b"") + self.content = params.get("content", b"") self.disabled = params.get("disabled", False) + self.end_init(**params) return self -# <nav class="navbar navbar-expand-sm bg-light navbar-light" style="background-color: #e3f2fd;"> -# <ul class="navbar-nav" style="%(style)s" id="%(id)s"> +# <nav class="navbar navbar-expand-lg fixed-top navbar-light bg-light " style="%(style)s"> +# <div class="container-fluid"> +# <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation"> +# <span class="navbar-toggler-icon"></span> +# </button> +# <div class="collapse navbar-collapse" id="navbarCollapse"> +# <ul class="navbar-nav me-auto mb-2 mb-md-0"> # %(content)s # </ul> +# </div> +# </div> # </nav> -beg_tagMenuBar = b'''<nav class="navbar navbar-expand-sm bg-light navbar-light" style="background-color: #e3f2fd;"><ul class="navbar-nav" style="%s" id="%s">''' -end_tagMenuBar = b'''</ul></nav>''' +beg_tagMenuBar = b'''<nav class="navbar navbar-expand-lg fixed-top navbar-light bg-light " style="%s"><div class="container-fluid"><button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbarCollapse"><ul class="navbar-nav me-auto mb-2 mb-md-0">''' +end_tagMenuBar = b'''</ul></div></div></nav>''' def MenuBar(*args, **params): self = Template(*(("MenuBar",) + args), **params) def get_begin(self): global beg_tagMenuBar - return beg_tagMenuBar%(self.style,self.id) + return beg_tagMenuBar%(self.style) self.get_begin = get_begin def get_end(self): @@ -974,120 +965,9 @@

Module lib.htmltemplate.htmlclasses

return end_tagMenuBar self.get_end = get_end - self.content = params.get("content", b"") - self.id = params.get("id", b"%d"%id(self)) - self.style = params.get("style", b"") - return self - -# <a class="dropdown-item %(active)s %(class_)s" style="%(style)s margin-right: 2px;" id="%(id)s" href="%(href)s" %(disabled)s>%(text)s</a> -beg_tagMenuItem_ = b'''<a class="dropdown-item %s %s" style="%s margin-right: 2px;" id="%s" href="%s" %s>%s</a>''' -def MenuItem_(*args, **params): - self = Template(*(("MenuItem_",) + args), **params) - - def get_begin(self): - global beg_tagMenuItem_ - return beg_tagMenuItem_%( b'active' if self.active else b'',self.class_,self.style,self.id,self.href, b'disabled' if self.disabled else b'',self.text) - self.get_begin = get_begin - - def get_end(self): - return b'' - self.get_end = get_end - - self.id = params.get("id", b"%d"%id(self)) - self.href = params.get("href", b"") - self.disabled = params.get("disabled", False) - self.active = params.get("active", False) - self.style = params.get("style", b"") - self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") - return self - -# <div class="dropdown %(active)s %(class_)s" style="%(style)s" id="%(id)s" > -# <button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" %(disabled)s>%(text)s</button> -# <div class="dropdown-menu"> -# %(content)s -# </div> -# </div> -beg_tagMenu_ = b'''<div class="dropdown %s %s" style="%s" id="%s" ><button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" %s>%s</button><div class="dropdown-menu">''' -end_tagMenu_ = b'''</div></div>''' -def Menu_(*args, **params): - self = Template(*(("Menu_",) + args), **params) - - def get_begin(self): - global beg_tagMenu_ - return beg_tagMenu_%( b'active' if self.active else b'',self.class_,self.style,self.id, b'disabled' if self.disabled else b'',self.text) - self.get_begin = get_begin - - def get_end(self): - global end_tagMenu_ - return end_tagMenu_ - self.get_end = get_end - - self.class_ = params.get("class_", b"") - self.content = params.get("content", b"") - self.id = params.get("id", b"%d"%id(self)) - self.active = params.get("active", False) - self.style = params.get("style", b"") - self.text = params.get("text", b"") - self.disabled = params.get("disabled", False) - return self - -# <div class="btn-group%(class_)s" style="%(style)s" id="%(id)s"> -# %(content)s -# </div> -beg_tagMenuBar_ = b'''<div class="btn-group%s" style="%s" id="%s">''' -end_tagMenuBar_ = b'''</div>''' -def MenuBar_(*args, **params): - self = Template(*(("MenuBar_",) + args), **params) - - def get_begin(self): - global beg_tagMenuBar_ - return beg_tagMenuBar_%(self.class_,self.style,self.id) - self.get_begin = get_begin - - def get_end(self): - global end_tagMenuBar_ - return end_tagMenuBar_ - self.get_end = get_end - - self.content = params.get("content", b"") - self.class_ = params.get("class_", b"") - self.id = params.get("id", b"%d"%id(self)) self.style = params.get("style", b"") - return self - -# <label >%(text)s</label> -# <input list="%(id)s" class="form-control %(class_)s" style="%(style)s" pattern="%(pattern)s" placeholder="%(placeholder)s" value="%(value)s" name="%(name)s" %(disabled)s> -# <datalist id="%(id)s"> -# %(content)s -# </datalist> -# </input> -# <br> -beg_tagComboBox = b'''<label >%s</label><input list="%s" class="form-control %s" style="%s" pattern="%s" placeholder="%s" value="%s" name="%s" %s><datalist id="%s">''' -end_tagComboBox = b'''</datalist></input><br>''' -def ComboBox(*args, **params): - self = Template(*(("ComboBox",) + args), **params) - - def get_begin(self): - global beg_tagComboBox - return beg_tagComboBox%(self.text,self.id,self.class_,self.style,self.pattern,self.placeholder,self.value,self.name, b'disabled' if self.disabled else b'',self.id) - self.get_begin = get_begin - - def get_end(self): - global end_tagComboBox - return end_tagComboBox - self.get_end = get_end - self.content = params.get("content", b"") - self.id = params.get("id", b"%d"%id(self)) - self.placeholder = params.get("placeholder", b"") - self.disabled = params.get("disabled", False) - self.style = params.get("style", b"") - self.value = params.get("value", b"") - self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") - self.name = params.get("name", b"%d"%id(self)) - self.pattern = params.get("pattern", b"*") + self.end_init(**params) return self # <img src="%(src)s" class="%(class_)s" style="%(style)s" id="%(id)s" alt="%(alt)s"> @@ -1104,18 +984,19 @@

Module lib.htmltemplate.htmlclasses

return b'' self.get_end = get_end - self.id = params.get("id", b"%d"%id(self)) self.src = params.get("src", b"") - self.style = params.get("style", b"") + self.id = params.get("id", b"%d"%id(self)) self.class_ = params.get("class_", b"") + self.style = params.get("style", b"") self.alt = params.get("alt", b"") + self.end_init(**params) return self # <div class="alert alert-success alert-dismissible fade show"> -# <button type="button" class="close" data-dismiss="alert">&times;</button> +# <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> # %(content)s%(text)s # </div> -beg_tagAlertSuccess = b'''<div class="alert alert-success alert-dismissible fade show"><button type="button" class="close" data-dismiss="alert">&times;</button>''' +beg_tagAlertSuccess = b'''<div class="alert alert-success alert-dismissible fade show"><button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>''' end_tagAlertSuccess = b'''%s</div>''' def AlertSuccess(*args, **params): self = Template(*(("AlertSuccess",) + args), **params) @@ -1130,15 +1011,16 @@

Module lib.htmltemplate.htmlclasses

return end_tagAlertSuccess%(self.text) self.get_end = get_end - self.content = params.get("content", b"") self.text = params.get("text", b"") + self.content = params.get("content", b"") + self.end_init(**params) return self # <div class="alert alert-warning alert-dismissible fade show"> -# <button type="button" class="close" data-dismiss="alert">&times;</button> +# <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> # %(content)s%(text)s # </div> -beg_tagAlertWarning = b'''<div class="alert alert-warning alert-dismissible fade show"><button type="button" class="close" data-dismiss="alert">&times;</button>''' +beg_tagAlertWarning = b'''<div class="alert alert-warning alert-dismissible fade show"><button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>''' end_tagAlertWarning = b'''%s</div>''' def AlertWarning(*args, **params): self = Template(*(("AlertWarning",) + args), **params) @@ -1153,15 +1035,16 @@

Module lib.htmltemplate.htmlclasses

return end_tagAlertWarning%(self.text) self.get_end = get_end - self.content = params.get("content", b"") self.text = params.get("text", b"") + self.content = params.get("content", b"") + self.end_init(**params) return self # <div class="alert alert-danger alert-dismissible fade show"> -# <button type="button" class="close" data-dismiss="alert">&times;</button> +# <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> # %(content)s%(text)s # </div> -beg_tagAlertError = b'''<div class="alert alert-danger alert-dismissible fade show"><button type="button" class="close" data-dismiss="alert">&times;</button>''' +beg_tagAlertError = b'''<div class="alert alert-danger alert-dismissible fade show"><button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>''' end_tagAlertError = b'''%s</div>''' def AlertError(*args, **params): self = Template(*(("AlertError",) + args), **params) @@ -1176,8 +1059,9 @@

Module lib.htmltemplate.htmlclasses

return end_tagAlertError%(self.text) self.get_end = get_end - self.content = params.get("content", b"") self.text = params.get("text", b"") + self.content = params.get("content", b"") + self.end_init(**params) return self # %(content)s @@ -1193,6 +1077,7 @@

Module lib.htmltemplate.htmlclasses

self.get_end = get_end self.content = params.get("content", b"") + self.end_init(**params) return self # <button type="button" class="btn btn-outline-primary %(class_)s" style="%(style)s" id="%(id)s" name="%(name)s" %(disabled)s onclick="oncommand_%(id)s()" >%(text)s</button> @@ -1226,19 +1111,21 @@

Module lib.htmltemplate.htmlclasses

return b'' self.get_end = get_end - self.class_ = params.get("class_", b"") - self.confirm = params.get("confirm", b"") - self.id = params.get("id", b"%d"%id(self)) self.path = params.get("path", b"") + self.id = params.get("id", b"%d"%id(self)) + self.class_ = params.get("class_", b"") self.style = params.get("style", b"") self.text = params.get("text", b"") - self.disabled = params.get("disabled", False) self.name = params.get("name", b"%d"%id(self)) + self.disabled = params.get("disabled", False) + self.confirm = params.get("confirm", b"") + self.end_init(**params) return self +# <div class="form-group %(spacer)s"> # <label >%(text)s</label> # <div style="display: flex;"> -# <input type="range" class="custom-range %(class_)s" style="%(style)s" id="slider_%(id)s" name="%(name)s" min="%(min)s" max="%(max)s" step="%(step)s" value="%(value)s" %(disabled)s oninput="onchange_%(id)s()" onmouseup="oncommand_%(id)s()" /> +# <input type="range" class="form-range %(class_)s" style="%(style)s" id="slider_%(id)s" name="%(name)s" min="%(min)s" max="%(max)s" step="%(step)s" value="%(value)s" %(disabled)s oninput="onchange_%(id)s()" onmouseup="oncommand_%(id)s()" /> # <span id="value_%(id)s"/> # </div> # <script type="text/javascript"> @@ -1254,34 +1141,38 @@

Module lib.htmltemplate.htmlclasses

# xhttp.send(); # } # </script> -beg_tagSliderCmd = b'''<label >%s</label><div style="display: flex;"><input type="range" class="custom-range %s" style="%s" id="slider_%s" name="%s" min="%s" max="%s" step="%s" value="%s" %s oninput="onchange_%s()" onmouseup="oncommand_%s()" /><span id="value_%s"/></div><script type="text/javascript">function onchange_%s(){document.getElementById("value_%s").innerHTML = "&nbsp;" + document.getElementById("slider_%s").value;}onchange_%s();function oncommand_%s(){var xhttp = new XMLHttpRequest();xhttp.open("GET","%s?name="+document.getElementById("slider_%s").name+"&value="+document.getElementById("slider_%s").value,true);xhttp.send();}</script>''' +# </div> +beg_tagSliderCmd = b'''<div class="form-group %s"><label >%s</label><div style="display: flex;"><input type="range" class="form-range %s" style="%s" id="slider_%s" name="%s" min="%s" max="%s" step="%s" value="%s" %s oninput="onchange_%s()" onmouseup="oncommand_%s()" /><span id="value_%s"/></div><script type="text/javascript">function onchange_%s(){document.getElementById("value_%s").innerHTML = "&nbsp;" + document.getElementById("slider_%s").value;}onchange_%s();function oncommand_%s(){var xhttp = new XMLHttpRequest();xhttp.open("GET","%s?name="+document.getElementById("slider_%s").name+"&value="+document.getElementById("slider_%s").value,true);xhttp.send();}</script></div>''' def SliderCmd(*args, **params): self = Template(*(("SliderCmd",) + args), **params) def get_begin(self): global beg_tagSliderCmd - return beg_tagSliderCmd%(self.text,self.class_,self.style,self.id,self.name,self.min,self.max,self.step,self.value, b'disabled' if self.disabled else b'',self.id,self.id,self.id,self.id,self.id,self.id,self.id,self.id,self.path,self.id,self.id) + return beg_tagSliderCmd%(self.spacer,self.text,self.class_,self.style,self.id,self.name,self.min,self.max,self.step,self.value, b'disabled' if self.disabled else b'',self.id,self.id,self.id,self.id,self.id,self.id,self.id,self.id,self.path,self.id,self.id) self.get_begin = get_begin def get_end(self): return b'' self.get_end = get_end - - self.step = params.get("step", b"") - self.id = params.get("id", b"%d"%id(self)) + self.path = params.get("path", b"") - self.disabled = params.get("disabled", False) + self.id = params.get("id", b"%d"%id(self)) + self.class_ = params.get("class_", b"") self.max = params.get("max", b"") - self.min = params.get("min", b"") self.style = params.get("style", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") self.name = params.get("name", b"%d"%id(self)) + self.spacer = params.get("spacer", b"") + self.min = params.get("min", b"") + self.disabled = params.get("disabled", False) self.value = params.get("value", b"") + self.step = params.get("step", b"") + self.end_init(**params) return self +# <div class="form-group %(spacer)s"> # <label >%(text)s</label> -# <select class="btn btn-outline-primary custom-select %(class_)s" style="%(style)s" id="%(id)s" name="%(name)s" %(disabled)s oninput="oncommand_%(id)s()"> +# <select class="btn btn-outline-primary form-select %(class_)s" style="%(style)s" id="%(id)s" name="%(name)s" %(disabled)s oninput="oncommand_%(id)s()"> # %(content)s # </select> # <script type="text/javascript"> @@ -1292,14 +1183,15 @@

Module lib.htmltemplate.htmlclasses

# xhttp.send(); # } # </script> -beg_tagComboCmd = b'''<label >%s</label><select class="btn btn-outline-primary custom-select %s" style="%s" id="%s" name="%s" %s oninput="oncommand_%s()">''' -end_tagComboCmd = b'''</select><script type="text/javascript">function oncommand_%s(){var xhttp = new XMLHttpRequest();xhttp.open("GET","%s?name="+document.getElementById("%s").name+"&value="+document.getElementById("%s").value,true);xhttp.send();}</script>''' +# </div> +beg_tagComboCmd = b'''<div class="form-group %s"><label >%s</label><select class="btn btn-outline-primary form-select %s" style="%s" id="%s" name="%s" %s oninput="oncommand_%s()">''' +end_tagComboCmd = b'''</select><script type="text/javascript">function oncommand_%s(){var xhttp = new XMLHttpRequest();xhttp.open("GET","%s?name="+document.getElementById("%s").name+"&value="+document.getElementById("%s").value,true);xhttp.send();}</script></div>''' def ComboCmd(*args, **params): self = Template(*(("ComboCmd",) + args), **params) def get_begin(self): global beg_tagComboCmd - return beg_tagComboCmd%(self.text,self.class_,self.style,self.id,self.name, b'disabled' if self.disabled else b'',self.id) + return beg_tagComboCmd%(self.spacer,self.text,self.class_,self.style,self.id,self.name, b'disabled' if self.disabled else b'',self.id) self.get_begin = get_begin def get_end(self): @@ -1308,18 +1200,21 @@

Module lib.htmltemplate.htmlclasses

self.get_end = get_end self.content = params.get("content", b"") - self.id = params.get("id", b"%d"%id(self)) self.path = params.get("path", b"") - self.disabled = params.get("disabled", False) + self.id = params.get("id", b"%d"%id(self)) + self.class_ = params.get("class_", b"") self.style = params.get("style", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") self.name = params.get("name", b"%d"%id(self)) + self.spacer = params.get("spacer", b"") + self.disabled = params.get("disabled", False) + self.end_init(**params) return self -# <div class="custom-control custom-switch"> -# <input type="checkbox" class="custom-control-input %(class_)s" style="%(style)s" id="%(id)s" value="%(value)s" name="%(name)s" %(checked)s %(disabled)s oninput="oncommand_%(id)s()" /> -# <label class="custom-control-label" for="%(id)s">%(text)s</label> +# <div class="form-group %(spacer)s"> +# <div class="form-check form-switch" style="height: 1.5em;"> +# <input type="checkbox" class="form-check-input %(class_)s" style="%(style)s" id="%(id)s" value="%(value)s" name="%(name)s" %(checked)s %(disabled)s oninput="oncommand_%(id)s()" /> +# <label class="form-check-label" for="%(id)s">%(text)s</label> # </div> # <script type="text/javascript"> # function oncommand_%(id)s() @@ -1329,30 +1224,34 @@

Module lib.htmltemplate.htmlclasses

# xhttp.send(); # } # </script> -beg_tagSwitchCmd = b'''<div class="custom-control custom-switch"><input type="checkbox" class="custom-control-input %s" style="%s" id="%s" value="%s" name="%s" %s %s oninput="oncommand_%s()" /><label class="custom-control-label" for="%s">%s</label></div><script type="text/javascript">function oncommand_%s(){var xhttp = new XMLHttpRequest();xhttp.open("GET","%s?name="+document.getElementById("%s").name+"&value="+document.getElementById("%s").checked,true);xhttp.send();}</script>''' +# </div> +beg_tagSwitchCmd = b'''<div class="form-group %s"><div class="form-check form-switch" style="height: 1.5em;"><input type="checkbox" class="form-check-input %s" style="%s" id="%s" value="%s" name="%s" %s %s oninput="oncommand_%s()" /><label class="form-check-label" for="%s">%s</label></div><script type="text/javascript">function oncommand_%s(){var xhttp = new XMLHttpRequest();xhttp.open("GET","%s?name="+document.getElementById("%s").name+"&value="+document.getElementById("%s").checked,true);xhttp.send();}</script></div>''' def SwitchCmd(*args, **params): self = Template(*(("SwitchCmd",) + args), **params) def get_begin(self): global beg_tagSwitchCmd - return beg_tagSwitchCmd%(self.class_,self.style,self.id,self.value,self.name, b'checked' if self.checked else b'', b'disabled' if self.disabled else b'',self.id,self.id,self.text,self.id,self.path,self.id,self.id) + return beg_tagSwitchCmd%(self.spacer,self.class_,self.style,self.id,self.value,self.name, b'checked' if self.checked else b'', b'disabled' if self.disabled else b'',self.id,self.id,self.text,self.id,self.path,self.id,self.id) self.get_begin = get_begin def get_end(self): return b'' self.get_end = get_end - self.id = params.get("id", b"%d"%id(self)) - self.checked = params.get("checked", True) self.path = params.get("path", b"") - self.disabled = params.get("disabled", False) + self.id = params.get("id", b"%d"%id(self)) + self.class_ = params.get("class_", b"") self.style = params.get("style", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") self.name = params.get("name", b"%d"%id(self)) + self.spacer = params.get("spacer", b"") + self.disabled = params.get("disabled", False) self.value = params.get("value", b"") + self.checked = params.get("checked", True) + self.end_init(**params) return self +# <main class="form-signin"> # <div class="modal-dialog modal-dialog-centered %(class_)s" style="align-items:center;min-height:100vh; %(style)s" id="%(id)s"> # <div class="modal-content shadow-lg p-3 mb-5 bg-white rounded"> # <div class="modal-body" style="padding:10px 10px;"> @@ -1360,8 +1259,9 @@

Module lib.htmltemplate.htmlclasses

# </div> # </div> # </div> -beg_tagModal = b'''<div class="modal-dialog modal-dialog-centered %s" style="align-items:center;min-height:100vh; %s" id="%s"><div class="modal-content shadow-lg p-3 mb-5 bg-white rounded"><div class="modal-body" style="padding:10px 10px;">''' -end_tagModal = b'''</div></div></div>''' +# </main> +beg_tagModal = b'''<main class="form-signin"><div class="modal-dialog modal-dialog-centered %s" style="align-items:center;min-height:100vh; %s" id="%s"><div class="modal-content shadow-lg p-3 mb-5 bg-white rounded"><div class="modal-body" style="padding:10px 10px;">''' +end_tagModal = b'''</div></div></div></main>''' def Modal(*args, **params): self = Template(*(("Modal",) + args), **params) @@ -1375,23 +1275,92 @@

Module lib.htmltemplate.htmlclasses

return end_tagModal self.get_end = get_end + self.style = params.get("style", b"") self.content = params.get("content", b"") + self.id = params.get("id", b"%d"%id(self)) + self.class_ = params.get("class_", b"") + self.end_init(**params) + return self + +# <span class="%(class_)s" style="%(style)s" id="%(id)s">&nbsp;</span> +beg_tagSpace = b'''<span class="%s" style="%s" id="%s">&nbsp;</span>''' +def Space(*args, **params): + self = Template(*(("Space",) + args), **params) + + def get_begin(self): + global beg_tagSpace + return beg_tagSpace%(self.class_,self.style,self.id) + self.get_begin = get_begin + + def get_end(self): + return b'' + self.get_end = get_end + + self.style = params.get("style", b"") + self.id = params.get("id", b"%d"%id(self)) self.class_ = params.get("class_", b"") + self.end_init(**params) + return self + +# <li class="page-item"><a class="page-link %(active)s %(class_)s" style="%(style)s" id="%(id)s" href="%(href)s" %(disabled)s>%(text)s</a></li> +beg_tagPageItem = b'''<li class="page-item"><a class="page-link %s %s" style="%s" id="%s" href="%s" %s>%s</a></li>''' +def PageItem(*args, **params): + self = Template(*(("PageItem",) + args), **params) + + def get_begin(self): + global beg_tagPageItem + return beg_tagPageItem%( b'active' if self.active else b'',self.class_,self.style,self.id,self.href, b'disabled' if self.disabled else b'',self.text) + self.get_begin = get_begin + + def get_end(self): + return b'' + self.get_end = get_end + + self.active = params.get("active", False) + self.href = params.get("href", b"") self.id = params.get("id", b"%d"%id(self)) + self.class_ = params.get("class_", b"") self.style = params.get("style", b"") + self.text = params.get("text", b"") + self.disabled = params.get("disabled", False) + self.end_init(**params) + return self + +# <ul class="pagination" id="%(id)s" class="%(class_)s"> +# %(content)s +# </ul> +beg_tagPagination = b'''<ul class="pagination" id="%s" class="%s">''' +end_tagPagination = b'''</ul>''' +def Pagination(*args, **params): + self = Template(*(("Pagination",) + args), **params) + + def get_begin(self): + global beg_tagPagination + return beg_tagPagination%(self.id,self.class_) + self.get_begin = get_begin + + def get_end(self): + global end_tagPagination + return end_tagPagination + self.get_end = get_end + + self.content = params.get("content", b"") + self.id = params.get("id", b"%d"%id(self)) + self.class_ = params.get("class_", b"") + self.end_init(**params) return self # <html lang="fr" charset="utf-8"> # <head> # <title>%(title)s</title> -# <meta name="viewport" content="" charset="UTF-8"/> +# <meta name="viewport" content="width=device-width, initial-scale=1" charset="UTF-8"/> # <link rel="icon" href="data:,"> # </head> # <body class="%(class_)s" style="%(style)s"> # %(content)s # </body> # </html> -beg_tagPage = b'''<html lang="fr" charset="utf-8"><head><title>%s</title><meta name="viewport" content="" charset="UTF-8"/><link rel="icon" href="data:,"></head><body class="%s" style="%s">''' +beg_tagPage = b'''<html lang="fr" charset="utf-8"><head><title>%s</title><meta name="viewport" content="width=device-width, initial-scale=1" charset="UTF-8"/><link rel="icon" href="data:,"></head><body class="%s" style="%s">''' end_tagPage = b'''</body></html>''' def Page(*args, **params): self = Template(*(("Page",) + args), **params) @@ -1406,10 +1375,11 @@

Module lib.htmltemplate.htmlclasses

return end_tagPage self.get_end = get_end - self.class_ = params.get("class_", b"") + self.style = params.get("style", b"") self.content = params.get("content", b"") self.title = params.get("title", b"") - self.style = params.get("style", b"") + self.class_ = params.get("class_", b"") + self.end_init(**params) return self
@@ -1442,8 +1412,9 @@

Functions

return end_tagAlertError%(self.text) self.get_end = get_end - self.content = params.get("content", b"") self.text = params.get("text", b"") + self.content = params.get("content", b"") + self.end_init(**params) return self @@ -1469,8 +1440,9 @@

Functions

return end_tagAlertSuccess%(self.text) self.get_end = get_end - self.content = params.get("content", b"") self.text = params.get("text", b"") + self.content = params.get("content", b"") + self.end_init(**params) return self @@ -1496,8 +1468,9 @@

Functions

return end_tagAlertWarning%(self.text) self.get_end = get_end - self.content = params.get("content", b"") self.text = params.get("text", b"") + self.content = params.get("content", b"") + self.end_init(**params) return self @@ -1522,6 +1495,7 @@

Functions

return b'' self.get_end = get_end + self.end_init(**params) return self @@ -1547,13 +1521,14 @@

Functions

self.get_end = get_end self.id = params.get("id", b"%d"%id(self)) - self.disabled = params.get("disabled", False) - self.text = params.get("text", b"") - self.style = params.get("style", b"") - self.type = params.get("type", b"") self.class_ = params.get("class_", b"") + self.style = params.get("style", b"") + self.text = params.get("text", b"") self.name = params.get("name", b"%d"%id(self)) + self.type = params.get("type", b"") + self.disabled = params.get("disabled", False) self.value = params.get("value", b"") + self.end_init(**params) return self @@ -1578,14 +1553,15 @@

Functions

return b'' self.get_end = get_end - self.class_ = params.get("class_", b"") - self.confirm = params.get("confirm", b"") - self.id = params.get("id", b"%d"%id(self)) self.path = params.get("path", b"") + self.id = params.get("id", b"%d"%id(self)) + self.class_ = params.get("class_", b"") self.style = params.get("style", b"") self.text = params.get("text", b"") - self.disabled = params.get("disabled", False) self.name = params.get("name", b"%d"%id(self)) + self.disabled = params.get("disabled", False) + self.confirm = params.get("confirm", b"") + self.end_init(**params) return self @@ -1613,9 +1589,10 @@

Functions

self.content = params.get("content", b"") self.id = params.get("id", b"%d"%id(self)) + self.class_ = params.get("class_", b"") self.style = params.get("style", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") + self.end_init(**params) return self @@ -1642,11 +1619,12 @@

Functions

self.get_end = get_end self.content = params.get("content", b"") - self.id = params.get("id", b"%d"%id(self)) self.href = params.get("href", b"") + self.id = params.get("id", b"%d"%id(self)) + self.class_ = params.get("class_", b"") self.style = params.get("style", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") + self.end_init(**params) return self @@ -1664,7 +1642,7 @@

Functions

def get_begin(self): global beg_tagCard - return beg_tagCard%(self.class_,self.style,self.id) + return beg_tagCard%(self.spacer,self.style,self.id) self.get_begin = get_begin def get_end(self): @@ -1672,10 +1650,11 @@

Functions

return end_tagCard self.get_end = get_end + self.style = params.get("style", b"") self.content = params.get("content", b"") - self.class_ = params.get("class_", b"") self.id = params.get("id", b"%d"%id(self)) - self.style = params.get("style", b"") + self.spacer = params.get("spacer", b"") + self.end_init(**params) return self @@ -1701,10 +1680,11 @@

Functions

return end_tagCardBody self.get_end = get_end + self.style = params.get("style", b"") self.content = params.get("content", b"") - self.class_ = params.get("class_", b"") self.id = params.get("id", b"%d"%id(self)) - self.style = params.get("style", b"") + self.class_ = params.get("class_", b"") + self.end_init(**params) return self @@ -1732,41 +1712,10 @@

Functions

self.content = params.get("content", b"") self.id = params.get("id", b"%d"%id(self)) - self.style = params.get("style", b"") - self.text = params.get("text", b"") self.class_ = params.get("class_", b"") - return self - - -
-def Checkbox(*args, **params) -
-
-
-
- -Expand source code - -
def Checkbox(*args, **params):
-        self = Template(*(("Checkbox",) + args), **params)
-
-        def get_begin(self):
-                global beg_tagCheckbox
-                return beg_tagCheckbox%(self.class_,self.style,self.id,self.value,self.name, b'checked' if self.checked else b'', b'disabled' if self.disabled else b'',self.text)
-        self.get_begin     = get_begin
-
-        def get_end(self):
-                return b''
-        self.get_end       = get_end
-
-        self.id           = params.get("id", b"%d"%id(self))
-        self.checked      = params.get("checked", True)
-        self.disabled     = params.get("disabled", False)
         self.style        = params.get("style", b"")
         self.text         = params.get("text", b"")
-        self.class_       = params.get("class_", b"")
-        self.name         = params.get("name", b"%d"%id(self))
-        self.value        = params.get("value", b"")
+        self.end_init(**params)
         return self
@@ -1784,7 +1733,7 @@

Functions

def get_begin(self): global beg_tagComboBox - return beg_tagComboBox%(self.text,self.id,self.class_,self.style,self.pattern,self.placeholder,self.value,self.name, b'disabled' if self.disabled else b'',self.id) + return beg_tagComboBox%(self.spacer,self.text,self.id,self.class_,self.style,self.pattern,self.placeholder,self.value,self.name, b'disabled' if self.disabled else b'',self.id) self.get_begin = get_begin def get_end(self): @@ -1792,16 +1741,18 @@

Functions

return end_tagComboBox self.get_end = get_end + self.pattern = params.get("pattern", b"*") self.content = params.get("content", b"") self.id = params.get("id", b"%d"%id(self)) - self.placeholder = params.get("placeholder", b"") - self.disabled = params.get("disabled", False) + self.class_ = params.get("class_", b"") self.style = params.get("style", b"") - self.value = params.get("value", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") self.name = params.get("name", b"%d"%id(self)) - self.pattern = params.get("pattern", b"*") + self.placeholder = params.get("placeholder", b"") + self.spacer = params.get("spacer", b"") + self.disabled = params.get("disabled", False) + self.value = params.get("value", b"") + self.end_init(**params) return self @@ -1819,7 +1770,7 @@

Functions

def get_begin(self): global beg_tagComboCmd - return beg_tagComboCmd%(self.text,self.class_,self.style,self.id,self.name, b'disabled' if self.disabled else b'',self.id) + return beg_tagComboCmd%(self.spacer,self.text,self.class_,self.style,self.id,self.name, b'disabled' if self.disabled else b'',self.id) self.get_begin = get_begin def get_end(self): @@ -1828,13 +1779,15 @@

Functions

self.get_end = get_end self.content = params.get("content", b"") - self.id = params.get("id", b"%d"%id(self)) self.path = params.get("path", b"") - self.disabled = params.get("disabled", False) + self.id = params.get("id", b"%d"%id(self)) + self.class_ = params.get("class_", b"") self.style = params.get("style", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") self.name = params.get("name", b"%d"%id(self)) + self.spacer = params.get("spacer", b"") + self.disabled = params.get("disabled", False) + self.end_init(**params) return self @@ -1860,10 +1813,11 @@

Functions

return end_tagContainer self.get_end = get_end + self.style = params.get("style", b"") self.content = params.get("content", b"") - self.class_ = params.get("class_", b"") self.id = params.get("id", b"%d"%id(self)) - self.style = params.get("style", b"") + self.class_ = params.get("class_", b"") + self.end_init(**params) return self @@ -1889,10 +1843,11 @@

Functions

return end_tagDiv self.get_end = get_end + self.style = params.get("style", b"") self.content = params.get("content", b"") - self.class_ = params.get("class_", b"") self.id = params.get("id", b"%d"%id(self)) - self.style = params.get("style", b"") + self.class_ = params.get("class_", b"") + self.end_init(**params) return self @@ -1918,45 +1873,16 @@

Functions

self.get_end = get_end self.path = params.get("path", b"") - self.filename = params.get("filename", b"") self.text = params.get("text", b"") - self.disabled = params.get("disabled", False) self.name = params.get("name", b"%d"%id(self)) + self.filename = params.get("filename", b"") + self.disabled = params.get("disabled", False) + self.end_init(**params) return self -
-def Dropdown(*args, **params) -
-
-
-
- -Expand source code - -
def Dropdown(*args, **params):
-        self = Template(*(("Dropdown",) + args), **params)
-
-        def get_begin(self):
-                global beg_tagDropdown
-                return beg_tagDropdown%(self.class_,self.style,self.id,self.text)
-        self.get_begin     = get_begin
-
-        def get_end(self):
-                global end_tagDropdown
-                return end_tagDropdown
-        self.get_end       = get_end
-
-        self.content      = params.get("content", b"")
-        self.id           = params.get("id", b"%d"%id(self))
-        self.style        = params.get("style", b"")
-        self.text         = params.get("text", b"")
-        self.class_       = params.get("class_", b"")
-        return self
-
-
-
-def DropdownItem(*args, **params) +
+def Edit(*args, **params)
@@ -1964,28 +1890,40 @@

Functions

Expand source code -
def DropdownItem(*args, **params):
-        self = Template(*(("DropdownItem",) + args), **params)
+
def Edit(*args, **params):
+        self = Template(*(("Edit",) + args), **params)
 
         def get_begin(self):
-                global beg_tagDropdownItem
-                return beg_tagDropdownItem%(self.class_,self.style,self.id,self.href,self.text)
+                global beg_tagEdit
+                return beg_tagEdit%(self.spacer,self.text,self.type,self.class_,self.style,self.id,self.pattern,self.placeholder,self.type,self.value,self.name,self.min,self.max,self.step, b'disabled' if self.disabled else b'', b'required' if self.required else b'',self.event)
         self.get_begin     = get_begin
 
         def get_end(self):
                 return b''
         self.get_end       = get_end
 
+        self.pattern      = params.get("pattern", b"*")
         self.id           = params.get("id", b"%d"%id(self))
-        self.href         = params.get("href", b"")
+        self.class_       = params.get("class_", b"")
+        self.max          = params.get("max", b"")
         self.style        = params.get("style", b"")
         self.text         = params.get("text", b"")
-        self.class_       = params.get("class_", b"")
+        self.event        = params.get("event", b"")
+        self.name         = params.get("name", b"%d"%id(self))
+        self.placeholder  = params.get("placeholder", b"")
+        self.spacer       = params.get("spacer", b"")
+        self.min          = params.get("min", b"")
+        self.type         = params.get("type", b"")
+        self.disabled     = params.get("disabled", False)
+        self.required     = params.get("required", False)
+        self.value        = params.get("value", b"")
+        self.step         = params.get("step", b"")
+        self.end_init(**params)
         return self
-
-def Edit(*args, **params) +
+def Form(*args, **params)
@@ -1993,33 +1931,33 @@

Functions

Expand source code -
def Edit(*args, **params):
-        self = Template(*(("Edit",) + args), **params)
+
def Form(*args, **params):
+        self = Template(*(("Form",) + args), **params)
 
         def get_begin(self):
-                global beg_tagEdit
-                return beg_tagEdit%(self.text,self.type,self.class_,self.style,self.id,self.pattern,self.placeholder,self.type,self.value,self.name, b'disabled' if self.disabled else b'')
+                global beg_tagForm
+                return beg_tagForm%(self.spacer,self.class_,self.style,self.id,self.method,self.action, b'novalidate' if self.novalidate else b'')
         self.get_begin     = get_begin
 
         def get_end(self):
-                return b''
+                global end_tagForm
+                return end_tagForm
         self.get_end       = get_end
 
+        self.content      = params.get("content", b"")
+        self.novalidate   = params.get("novalidate", False)
         self.id           = params.get("id", b"%d"%id(self))
-        self.placeholder  = params.get("placeholder", b"")
-        self.disabled     = params.get("disabled", False)
-        self.text         = params.get("text", b"")
-        self.style        = params.get("style", b"")
-        self.value        = params.get("value", b"")
-        self.type         = params.get("type", b"")
         self.class_       = params.get("class_", b"")
-        self.name         = params.get("name", b"%d"%id(self))
-        self.pattern      = params.get("pattern", b"*")
+        self.style        = params.get("style", b"")
+        self.method       = params.get("method", b"")
+        self.spacer       = params.get("spacer", b"")
+        self.action       = params.get("action", b"")
+        self.end_init(**params)
         return self
-
-def Form(*args, **params) +
+def FormGroup(*args, **params)
@@ -2027,25 +1965,22 @@

Functions

Expand source code -
def Form(*args, **params):
-        self = Template(*(("Form",) + args), **params)
+
def FormGroup(*args, **params):
+        self = Template(*(("FormGroup",) + args), **params)
 
         def get_begin(self):
-                global beg_tagForm
-                return beg_tagForm%(self.class_,self.style,self.id,self.method,self.action)
+                global beg_tagFormGroup
+                return beg_tagFormGroup%(self.spacer)
         self.get_begin     = get_begin
 
         def get_end(self):
-                global end_tagForm
-                return end_tagForm
+                global end_tagFormGroup
+                return end_tagFormGroup
         self.get_end       = get_end
 
         self.content      = params.get("content", b"")
-        self.id           = params.get("id", b"%d"%id(self))
-        self.action       = params.get("action", b"")
-        self.method       = params.get("method", b"")
-        self.style        = params.get("style", b"")
-        self.class_       = params.get("class_", b"")
+        self.spacer       = params.get("spacer", b"")
+        self.end_init(**params)
         return self
@@ -2070,11 +2005,12 @@

Functions

return b'' self.get_end = get_end - self.id = params.get("id", b"%d"%id(self)) self.src = params.get("src", b"") - self.style = params.get("style", b"") + self.id = params.get("id", b"%d"%id(self)) self.class_ = params.get("class_", b"") + self.style = params.get("style", b"") self.alt = params.get("alt", b"") + self.end_init(**params) return self
@@ -2092,22 +2028,27 @@

Functions

def get_begin(self): global beg_tagInput - return beg_tagInput%(self.class_,self.style,self.id,self.pattern,self.placeholder,self.type,self.value,self.name, b'disabled' if self.disabled else b'') + return beg_tagInput%(self.class_,self.style,self.id,self.pattern,self.placeholder,self.type,self.value,self.name, b'disabled' if self.disabled else b'',self.event,self.min,self.max, b'required' if self.required else b'') self.get_begin = get_begin def get_end(self): return b'' self.get_end = get_end + self.pattern = params.get("pattern", b"*") self.id = params.get("id", b"%d"%id(self)) + self.class_ = params.get("class_", b"") + self.max = params.get("max", b"") + self.style = params.get("style", b"") + self.event = params.get("event", b"") + self.name = params.get("name", b"%d"%id(self)) self.placeholder = params.get("placeholder", b"") + self.min = params.get("min", b"") + self.type = params.get("type", b"") self.disabled = params.get("disabled", False) - self.style = params.get("style", b"") + self.required = params.get("required", False) self.value = params.get("value", b"") - self.type = params.get("type", b"") - self.class_ = params.get("class_", b"") - self.name = params.get("name", b"%d"%id(self)) - self.pattern = params.get("pattern", b"*") + self.end_init(**params) return self
@@ -2132,10 +2073,11 @@

Functions

return b'' self.get_end = get_end + self.style = params.get("style", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") self.id = params.get("id", b"%d"%id(self)) - self.style = params.get("style", b"") + self.class_ = params.get("class_", b"") + self.end_init(**params) return self
@@ -2153,7 +2095,7 @@

Functions

def get_begin(self): global beg_tagLink - return beg_tagLink%(self.class_,self.style,self.id,self.href) + return beg_tagLink%(self.class_,self.style,self.id,self.href,self.onclick) self.get_begin = get_begin def get_end(self): @@ -2162,11 +2104,13 @@

Functions

self.get_end = get_end self.content = params.get("content", b"") - self.id = params.get("id", b"%d"%id(self)) self.href = params.get("href", b"") + self.id = params.get("id", b"%d"%id(self)) + self.class_ = params.get("class_", b"") self.style = params.get("style", b"") + self.onclick = params.get("onclick", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") + self.end_init(**params) return self
@@ -2192,10 +2136,11 @@

Functions

return end_tagList self.get_end = get_end + self.style = params.get("style", b"") self.content = params.get("content", b"") - self.class_ = params.get("class_", b"") self.id = params.get("id", b"%d"%id(self)) - self.style = params.get("style", b"") + self.class_ = params.get("class_", b"") + self.end_init(**params) return self
@@ -2223,9 +2168,10 @@

Functions

self.content = params.get("content", b"") self.id = params.get("id", b"%d"%id(self)) + self.class_ = params.get("class_", b"") self.style = params.get("style", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") + self.end_init(**params) return self
@@ -2251,9 +2197,10 @@

Functions

return end_tagMenu self.get_end = get_end - self.content = params.get("content", b"") self.text = params.get("text", b"") + self.content = params.get("content", b"") self.disabled = params.get("disabled", False) + self.end_init(**params) return self @@ -2271,7 +2218,7 @@

Functions

def get_begin(self): global beg_tagMenuBar - return beg_tagMenuBar%(self.style,self.id) + return beg_tagMenuBar%(self.style) self.get_begin = get_begin def get_end(self): @@ -2279,38 +2226,9 @@

Functions

return end_tagMenuBar self.get_end = get_end - self.content = params.get("content", b"") - self.id = params.get("id", b"%d"%id(self)) self.style = params.get("style", b"") - return self - - -
-def MenuBar_(*args, **params) -
-
-
-
- -Expand source code - -
def MenuBar_(*args, **params):
-        self = Template(*(("MenuBar_",) + args), **params)
-
-        def get_begin(self):
-                global beg_tagMenuBar_
-                return beg_tagMenuBar_%(self.class_,self.style,self.id)
-        self.get_begin     = get_begin
-
-        def get_end(self):
-                global end_tagMenuBar_
-                return end_tagMenuBar_
-        self.get_end       = get_end
-
         self.content      = params.get("content", b"")
-        self.class_       = params.get("class_", b"")
-        self.id           = params.get("id", b"%d"%id(self))
-        self.style        = params.get("style", b"")
+        self.end_init(**params)
         return self
@@ -2335,18 +2253,19 @@

Functions

return b'' self.get_end = get_end - self.id = params.get("id", b"%d"%id(self)) - self.href = params.get("href", b"") - self.disabled = params.get("disabled", False) self.active = params.get("active", False) + self.href = params.get("href", b"") + self.id = params.get("id", b"%d"%id(self)) + self.class_ = params.get("class_", b"") self.style = params.get("style", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") + self.disabled = params.get("disabled", False) + self.end_init(**params) return self -
-def MenuItem_(*args, **params) +
+def Modal(*args, **params)
@@ -2354,30 +2273,29 @@

Functions

Expand source code -
def MenuItem_(*args, **params):
-        self = Template(*(("MenuItem_",) + args), **params)
+
def Modal(*args, **params):
+        self = Template(*(("Modal",) + args), **params)
 
         def get_begin(self):
-                global beg_tagMenuItem_
-                return beg_tagMenuItem_%( b'active' if self.active else b'',self.class_,self.style,self.id,self.href, b'disabled' if self.disabled else b'',self.text)
+                global beg_tagModal
+                return beg_tagModal%(self.class_,self.style,self.id)
         self.get_begin     = get_begin
 
         def get_end(self):
-                return b''
+                global end_tagModal
+                return end_tagModal
         self.get_end       = get_end
 
-        self.id           = params.get("id", b"%d"%id(self))
-        self.href         = params.get("href", b"")
-        self.disabled     = params.get("disabled", False)
-        self.active       = params.get("active", False)
         self.style        = params.get("style", b"")
-        self.text         = params.get("text", b"")
+        self.content      = params.get("content", b"")
+        self.id           = params.get("id", b"%d"%id(self))
         self.class_       = params.get("class_", b"")
+        self.end_init(**params)
         return self
-
-def Menu_(*args, **params) +
+def Option(*args, **params)
@@ -2385,31 +2303,29 @@

Functions

Expand source code -
def Menu_(*args, **params):
-        self = Template(*(("Menu_",) + args), **params)
+
def Option(*args, **params):
+        self = Template(*(("Option",) + args), **params)
 
         def get_begin(self):
-                global beg_tagMenu_
-                return beg_tagMenu_%( b'active' if self.active else b'',self.class_,self.style,self.id, b'disabled' if self.disabled else b'',self.text)
+                global beg_tagOption
+                return beg_tagOption%( b'selected' if self.selected else b'',self.name,self.value, b'disabled' if self.disabled else b'',self.text)
         self.get_begin     = get_begin
 
         def get_end(self):
-                global end_tagMenu_
-                return end_tagMenu_
+                return b''
         self.get_end       = get_end
 
-        self.class_       = params.get("class_", b"")
-        self.content      = params.get("content", b"")
-        self.id           = params.get("id", b"%d"%id(self))
-        self.active       = params.get("active", False)
-        self.style        = params.get("style", b"")
         self.text         = params.get("text", b"")
+        self.name         = params.get("name", b"%d"%id(self))
+        self.selected     = params.get("selected", b"")
         self.disabled     = params.get("disabled", False)
+        self.value        = params.get("value", b"")
+        self.end_init(**params)
         return self
-
-def Modal(*args, **params) +
+def Page(*args, **params)
@@ -2417,28 +2333,29 @@

Functions

Expand source code -
def Modal(*args, **params):
-        self = Template(*(("Modal",) + args), **params)
+
def Page(*args, **params):
+        self = Template(*(("Page",) + args), **params)
 
         def get_begin(self):
-                global beg_tagModal
-                return beg_tagModal%(self.class_,self.style,self.id)
+                global beg_tagPage
+                return beg_tagPage%(self.title,self.class_,self.style)
         self.get_begin     = get_begin
 
         def get_end(self):
-                global end_tagModal
-                return end_tagModal
+                global end_tagPage
+                return end_tagPage
         self.get_end       = get_end
 
+        self.style        = params.get("style", b"")
         self.content      = params.get("content", b"")
+        self.title        = params.get("title", b"")
         self.class_       = params.get("class_", b"")
-        self.id           = params.get("id", b"%d"%id(self))
-        self.style        = params.get("style", b"")
+        self.end_init(**params)
         return self
-
-def Option(*args, **params) +
+def PageItem(*args, **params)
@@ -2446,27 +2363,31 @@

Functions

Expand source code -
def Option(*args, **params):
-        self = Template(*(("Option",) + args), **params)
+
def PageItem(*args, **params):
+        self = Template(*(("PageItem",) + args), **params)
 
         def get_begin(self):
-                global beg_tagOption
-                return beg_tagOption%( b'selected' if self.selected else b'',self.text,self.value, b'disabled' if self.disabled else b'',self.text)
+                global beg_tagPageItem
+                return beg_tagPageItem%( b'active' if self.active else b'',self.class_,self.style,self.id,self.href, b'disabled' if self.disabled else b'',self.text)
         self.get_begin     = get_begin
 
         def get_end(self):
                 return b''
         self.get_end       = get_end
 
+        self.active       = params.get("active", False)
+        self.href         = params.get("href", b"")
+        self.id           = params.get("id", b"%d"%id(self))
+        self.class_       = params.get("class_", b"")
+        self.style        = params.get("style", b"")
         self.text         = params.get("text", b"")
         self.disabled     = params.get("disabled", False)
-        self.value        = params.get("value", b"")
-        self.selected     = params.get("selected", b"")
+        self.end_init(**params)
         return self
-
-def Page(*args, **params) +
+def Pagination(*args, **params)
@@ -2474,23 +2395,23 @@

Functions

Expand source code -
def Page(*args, **params):
-        self = Template(*(("Page",) + args), **params)
+
def Pagination(*args, **params):
+        self = Template(*(("Pagination",) + args), **params)
 
         def get_begin(self):
-                global beg_tagPage
-                return beg_tagPage%(self.title,self.class_,self.style)
+                global beg_tagPagination
+                return beg_tagPagination%(self.id,self.class_)
         self.get_begin     = get_begin
 
         def get_end(self):
-                global end_tagPage
-                return end_tagPage
+                global end_tagPagination
+                return end_tagPagination
         self.get_end       = get_end
 
-        self.class_       = params.get("class_", b"")
         self.content      = params.get("content", b"")
-        self.title        = params.get("title", b"")
-        self.style        = params.get("style", b"")
+        self.id           = params.get("id", b"%d"%id(self))
+        self.class_       = params.get("class_", b"")
+        self.end_init(**params)
         return self
@@ -2518,9 +2439,10 @@

Functions

self.content = params.get("content", b"") self.id = params.get("id", b"%d"%id(self)) + self.class_ = params.get("class_", b"") self.style = params.get("style", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") + self.end_init(**params) return self
@@ -2538,21 +2460,23 @@

Functions

def get_begin(self): global beg_tagRadio - return beg_tagRadio%(self.class_,self.style,self.id,self.name, b'checked' if self.checked else b'', b'disabled' if self.disabled else b'',self.onchange,self.text) + return beg_tagRadio%(self.spacer,self.class_,self.style,self.id,self.name, b'checked' if self.checked else b'', b'disabled' if self.disabled else b'',self.event,self.text) self.get_begin = get_begin def get_end(self): return b'' self.get_end = get_end - self.checked = params.get("checked", True) self.id = params.get("id", b"%d"%id(self)) - self.onchange = params.get("onchange", b"") - self.disabled = params.get("disabled", False) + self.class_ = params.get("class_", b"") self.style = params.get("style", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") self.name = params.get("name", b"%d"%id(self)) + self.spacer = params.get("spacer", b"") + self.disabled = params.get("disabled", False) + self.event = params.get("event", b"") + self.checked = params.get("checked", True) + self.end_init(**params) return self
@@ -2570,7 +2494,7 @@

Functions

def get_begin(self): global beg_tagSelect - return beg_tagSelect%(self.text,self.class_,self.style,self.id,self.name, b'disabled' if self.disabled else b'') + return beg_tagSelect%(self.spacer,self.class_,self.style,self.id,self.name, b'disabled' if self.disabled else b'',self.event) self.get_begin = get_begin def get_end(self): @@ -2580,11 +2504,13 @@

Functions

self.content = params.get("content", b"") self.id = params.get("id", b"%d"%id(self)) - self.disabled = params.get("disabled", False) - self.style = params.get("style", b"") - self.text = params.get("text", b"") self.class_ = params.get("class_", b"") + self.style = params.get("style", b"") self.name = params.get("name", b"%d"%id(self)) + self.spacer = params.get("spacer", b"") + self.disabled = params.get("disabled", False) + self.event = params.get("event", b"") + self.end_init(**params) return self
@@ -2602,23 +2528,25 @@

Functions

def get_begin(self): global beg_tagSlider - return beg_tagSlider%(self.text,self.class_,self.style,self.id,self.name,self.min,self.max,self.step,self.value, b'disabled' if self.disabled else b'',self.id,self.id,self.id,self.id,self.id,self.id) + return beg_tagSlider%(self.spacer,self.text,self.class_,self.style,self.id,self.name,self.min,self.max,self.step,self.value, b'disabled' if self.disabled else b'',self.id,self.id,self.id,self.id,self.id,self.id) self.get_begin = get_begin def get_end(self): return b'' self.get_end = get_end - self.step = params.get("step", b"") self.id = params.get("id", b"%d"%id(self)) - self.disabled = params.get("disabled", False) + self.class_ = params.get("class_", b"") self.max = params.get("max", b"") - self.min = params.get("min", b"") self.style = params.get("style", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") self.name = params.get("name", b"%d"%id(self)) + self.spacer = params.get("spacer", b"") + self.min = params.get("min", b"") + self.disabled = params.get("disabled", False) self.value = params.get("value", b"") + self.step = params.get("step", b"") + self.end_init(**params) return self
@@ -2636,24 +2564,54 @@

Functions

def get_begin(self): global beg_tagSliderCmd - return beg_tagSliderCmd%(self.text,self.class_,self.style,self.id,self.name,self.min,self.max,self.step,self.value, b'disabled' if self.disabled else b'',self.id,self.id,self.id,self.id,self.id,self.id,self.id,self.id,self.path,self.id,self.id) + return beg_tagSliderCmd%(self.spacer,self.text,self.class_,self.style,self.id,self.name,self.min,self.max,self.step,self.value, b'disabled' if self.disabled else b'',self.id,self.id,self.id,self.id,self.id,self.id,self.id,self.id,self.path,self.id,self.id) self.get_begin = get_begin def get_end(self): return b'' self.get_end = get_end - self.step = params.get("step", b"") - self.id = params.get("id", b"%d"%id(self)) self.path = params.get("path", b"") - self.disabled = params.get("disabled", False) + self.id = params.get("id", b"%d"%id(self)) + self.class_ = params.get("class_", b"") self.max = params.get("max", b"") - self.min = params.get("min", b"") self.style = params.get("style", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") self.name = params.get("name", b"%d"%id(self)) + self.spacer = params.get("spacer", b"") + self.min = params.get("min", b"") + self.disabled = params.get("disabled", False) self.value = params.get("value", b"") + self.step = params.get("step", b"") + self.end_init(**params) + return self
+ + +
+def Space(*args, **params) +
+
+
+
+ +Expand source code + +
def Space(*args, **params):
+        self = Template(*(("Space",) + args), **params)
+
+        def get_begin(self):
+                global beg_tagSpace
+                return beg_tagSpace%(self.class_,self.style,self.id)
+        self.get_begin     = get_begin
+
+        def get_end(self):
+                return b''
+        self.get_end       = get_end
+
+        self.style        = params.get("style", b"")
+        self.id           = params.get("id", b"%d"%id(self))
+        self.class_       = params.get("class_", b"")
+        self.end_init(**params)
         return self
@@ -2678,6 +2636,7 @@

Functions

return b'' self.get_end = get_end + self.end_init(**params) return self
@@ -2702,6 +2661,7 @@

Functions

return b'' self.get_end = get_end + self.end_init(**params) return self
@@ -2729,12 +2689,13 @@

Functions

self.content = params.get("content", b"") self.id = params.get("id", b"%d"%id(self)) + self.class_ = params.get("class_", b"") self.onclick = params.get("onclick", b"") self.style = params.get("style", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") self.name = params.get("name", b"%d"%id(self)) self.value = params.get("value", b"") + self.end_init(**params) return self
@@ -2752,7 +2713,7 @@

Functions

def get_begin(self): global beg_tagSwitch - return beg_tagSwitch%(self.name,self.class_,self.style,self.id,self.value,self.name, b'checked' if self.checked else b'', b'disabled' if self.disabled else b'',self.onchange,self.id,self.text) + return beg_tagSwitch%(self.spacer,self.name,self.class_,self.style,self.id,self.value,self.name, b'checked' if self.checked else b'', b'disabled' if self.disabled else b'',self.event,self.id,self.text) self.get_begin = get_begin def get_end(self): @@ -2760,14 +2721,16 @@

Functions

self.get_end = get_end self.id = params.get("id", b"%d"%id(self)) - self.checked = params.get("checked", True) - self.onchange = params.get("onchange", b"") - self.disabled = params.get("disabled", False) + self.class_ = params.get("class_", b"") self.style = params.get("style", b"") + self.event = params.get("event", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") self.name = params.get("name", b"%d"%id(self)) + self.spacer = params.get("spacer", b"") + self.disabled = params.get("disabled", False) self.value = params.get("value", b"") + self.checked = params.get("checked", True) + self.end_init(**params) return self
@@ -2785,84 +2748,24 @@

Functions

def get_begin(self): global beg_tagSwitchCmd - return beg_tagSwitchCmd%(self.class_,self.style,self.id,self.value,self.name, b'checked' if self.checked else b'', b'disabled' if self.disabled else b'',self.id,self.id,self.text,self.id,self.path,self.id,self.id) + return beg_tagSwitchCmd%(self.spacer,self.class_,self.style,self.id,self.value,self.name, b'checked' if self.checked else b'', b'disabled' if self.disabled else b'',self.id,self.id,self.text,self.id,self.path,self.id,self.id) self.get_begin = get_begin def get_end(self): return b'' self.get_end = get_end - self.id = params.get("id", b"%d"%id(self)) - self.checked = params.get("checked", True) self.path = params.get("path", b"") - self.disabled = params.get("disabled", False) + self.id = params.get("id", b"%d"%id(self)) + self.class_ = params.get("class_", b"") self.style = params.get("style", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") self.name = params.get("name", b"%d"%id(self)) - self.value = params.get("value", b"") - return self
- - -
-def Tab(*args, **params) -
-
-
-
- -Expand source code - -
def Tab(*args, **params):
-        self = Template(*(("Tab",) + args), **params)
-
-        def get_begin(self):
-                global beg_tagTab
-                return beg_tagTab%(self.class_,self.style,self.id)
-        self.get_begin     = get_begin
-
-        def get_end(self):
-                global end_tagTab
-                return end_tagTab
-        self.get_end       = get_end
-
-        self.content      = params.get("content", b"")
-        self.class_       = params.get("class_", b"")
-        self.id           = params.get("id", b"%d"%id(self))
-        self.style        = params.get("style", b"")
-        return self
-
-
-
-def TabItem(*args, **params) -
-
-
-
- -Expand source code - -
def TabItem(*args, **params):
-        self = Template(*(("TabItem",) + args), **params)
-
-        def get_begin(self):
-                global beg_tagTabItem
-                return beg_tagTabItem%( b'active' if self.active else b'',self.class_,self.style,self.id,self.href, b'disabled' if self.disabled else b'')
-        self.get_begin     = get_begin
-
-        def get_end(self):
-                global end_tagTabItem
-                return end_tagTabItem%(self.text)
-        self.get_end       = get_end
-
-        self.content      = params.get("content", b"")
-        self.id           = params.get("id", b"%d"%id(self))
-        self.href         = params.get("href", b"")
+        self.spacer       = params.get("spacer", b"")
         self.disabled     = params.get("disabled", False)
-        self.active       = params.get("active", False)
-        self.style        = params.get("style", b"")
-        self.text         = params.get("text", b"")
-        self.class_       = params.get("class_", b"")
+        self.value        = params.get("value", b"")
+        self.checked      = params.get("checked", True)
+        self.end_init(**params)
         return self
@@ -2887,6 +2790,7 @@

Functions

self.get_end = get_end self.content = params.get("content", b"") + self.end_init(**params) return self @@ -2911,10 +2815,11 @@

Functions

return b'' self.get_end = get_end + self.style = params.get("style", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") self.id = params.get("id", b"%d"%id(self)) - self.style = params.get("style", b"") + self.class_ = params.get("class_", b"") + self.end_init(**params) return self @@ -2939,10 +2844,11 @@

Functions

return b'' self.get_end = get_end + self.style = params.get("style", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") self.id = params.get("id", b"%d"%id(self)) - self.style = params.get("style", b"") + self.class_ = params.get("class_", b"") + self.end_init(**params) return self @@ -2967,10 +2873,11 @@

Functions

return b'' self.get_end = get_end + self.style = params.get("style", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") self.id = params.get("id", b"%d"%id(self)) - self.style = params.get("style", b"") + self.class_ = params.get("class_", b"") + self.end_init(**params) return self @@ -2995,10 +2902,11 @@

Functions

return b'' self.get_end = get_end + self.style = params.get("style", b"") self.text = params.get("text", b"") - self.class_ = params.get("class_", b"") self.id = params.get("id", b"%d"%id(self)) - self.style = params.get("style", b"") + self.class_ = params.get("class_", b"") + self.end_init(**params) return self @@ -3023,13 +2931,14 @@

Functions

return b'' self.get_end = get_end - self.id = params.get("id", b"%d"%id(self)) self.path = params.get("path", b"") + self.id = params.get("id", b"%d"%id(self)) + self.text = params.get("text", b"") + self.name = params.get("name", b"%d"%id(self)) self.alert = params.get("alert", b"") self.accept = params.get("accept", b"") - self.text = params.get("text", b"") self.disabled = params.get("disabled", False) - self.name = params.get("name", b"%d"%id(self)) + self.end_init(**params) return self @@ -3062,16 +2971,14 @@

Index

  • Card
  • CardBody
  • CardHeader
  • -
  • Checkbox
  • ComboBox
  • ComboCmd
  • Container
  • Div
  • DownloadFile
  • -
  • Dropdown
  • -
  • DropdownItem
  • Edit
  • Form
  • +
  • FormGroup
  • Image
  • Input
  • Label
  • @@ -3080,25 +2987,23 @@

    Index

  • ListItem
  • Menu
  • MenuBar
  • -
  • MenuBar_
  • MenuItem
  • -
  • MenuItem_
  • -
  • Menu_
  • Modal
  • Option
  • Page
  • +
  • PageItem
  • +
  • Pagination
  • Paragraph
  • Radio
  • Select
  • Slider
  • SliderCmd
  • +
  • Space
  • Stylesheet
  • StylesheetDefault
  • Submit
  • Switch
  • SwitchCmd
  • -
  • Tab
  • -
  • TabItem
  • Tag
  • Title1
  • Title2
  • diff --git a/doc/lib/htmltemplate/htmlparser.html b/doc/lib/htmltemplate/htmlparser.html index 9c60641..febfc08 100644 --- a/doc/lib/htmltemplate/htmlparser.html +++ b/doc/lib/htmltemplate/htmlparser.html @@ -36,6 +36,7 @@

    Module lib.htmltemplate.htmlparser

    """ This script parse the template.html file and creates the template classes that can be used to compose a web page. This automatically creates the content of file lib/htmltemplate/htmlclasses.py """ +# pylint:disable=consider-using-f-string import re def findall(pattern, text): @@ -60,7 +61,7 @@

    Module lib.htmltemplate.htmlparser

    elif end_tag is not None: end_tag += s if var != "": - if var in ["disabled","checked","active","selected"]: + if var in ["disabled","checked","active","selected","required","novalidate"]: end_format += " b'%s' if self.%s else b'',"%(var, var) else: end_format += "self.%s,"%var @@ -68,7 +69,7 @@

    Module lib.htmltemplate.htmlparser

    else: begin_tag += s if var != "": - if var in ["disabled","checked","active","selected"]: + if var in ["disabled","checked","active","selected","required","novalidate"]: begin_format += " b'%s' if self.%s else b'',"%(var, var) else: begin_format += "self.%s,"%var @@ -83,10 +84,11 @@

    Module lib.htmltemplate.htmlparser

    """ Parse the www/template.html and createsthe content of file lib/htmltemplate/htmlclasses.py """ from htmltemplate import WWW_DIR, TEMPLATE_FILE, TEMPLATE_PY # pylint: disable=duplicate-string-formatting-argument + # pylint:disable=unspecified-encoding print("Parse html template") lines = open(WWW_DIR+TEMPLATE_FILE).readlines() py_class_file = open(TEMPLATE_PY,"w") - py_class_file.write("''' File automatically generated with template.html content '''\n# pylint:disable=missing-function-docstring\n# pylint:disable=trailing-whitespace\n# pylint:disable=too-many-lines\nfrom htmltemplate.template import Template \n") + py_class_file.write("''' File automatically generated with template.html content '''\n# pylint:disable=missing-function-docstring\n# pylint:disable=global-variable-not-assigned\n# pylint:disable=trailing-whitespace\n# pylint:disable=too-many-lines\nfrom htmltemplate.template import Template \n") stack = [] for line in lines: @@ -148,12 +150,13 @@

    Module lib.htmltemplate.htmlparser

    py_class_file.write('\tself.{:<12} = params.get("{}", b"*")\n'.format(attribute,attribute)) elif attribute in ["id","name"]: py_class_file.write('\tself.{:<12} = params.get("{}", b"%d"%id(self))\n'.format(attribute,attribute)) - elif attribute in ["disabled","active"]: + elif attribute in ["disabled","active","novalidate","required"]: py_class_file.write('\tself.{:<12} = params.get("{}", False)\n'.format(attribute,attribute)) elif attribute in ["checked"]: py_class_file.write('\tself.{:<12} = params.get("{}", True)\n'.format(attribute,attribute)) else: py_class_file.write('\tself.{:<12} = params.get("{}", b"")\n'.format(attribute,attribute)) + py_class_file.write('\tself.end_init(**params)\n') py_class_file.write('\treturn self\n') else: raise SyntaxError() @@ -204,7 +207,7 @@

    Functions

    elif end_tag is not None: end_tag += s if var != "": - if var in ["disabled","checked","active","selected"]: + if var in ["disabled","checked","active","selected","required","novalidate"]: end_format += " b'%s' if self.%s else b'',"%(var, var) else: end_format += "self.%s,"%var @@ -212,7 +215,7 @@

    Functions

    else: begin_tag += s if var != "": - if var in ["disabled","checked","active","selected"]: + if var in ["disabled","checked","active","selected","required","novalidate"]: begin_format += " b'%s' if self.%s else b'',"%(var, var) else: begin_format += "self.%s,"%var @@ -237,10 +240,11 @@

    Functions

    """ Parse the www/template.html and createsthe content of file lib/htmltemplate/htmlclasses.py """ from htmltemplate import WWW_DIR, TEMPLATE_FILE, TEMPLATE_PY # pylint: disable=duplicate-string-formatting-argument + # pylint:disable=unspecified-encoding print("Parse html template") lines = open(WWW_DIR+TEMPLATE_FILE).readlines() py_class_file = open(TEMPLATE_PY,"w") - py_class_file.write("''' File automatically generated with template.html content '''\n# pylint:disable=missing-function-docstring\n# pylint:disable=trailing-whitespace\n# pylint:disable=too-many-lines\nfrom htmltemplate.template import Template \n") + py_class_file.write("''' File automatically generated with template.html content '''\n# pylint:disable=missing-function-docstring\n# pylint:disable=global-variable-not-assigned\n# pylint:disable=trailing-whitespace\n# pylint:disable=too-many-lines\nfrom htmltemplate.template import Template \n") stack = [] for line in lines: @@ -302,12 +306,13 @@

    Functions

    py_class_file.write('\tself.{:<12} = params.get("{}", b"*")\n'.format(attribute,attribute)) elif attribute in ["id","name"]: py_class_file.write('\tself.{:<12} = params.get("{}", b"%d"%id(self))\n'.format(attribute,attribute)) - elif attribute in ["disabled","active"]: + elif attribute in ["disabled","active","novalidate","required"]: py_class_file.write('\tself.{:<12} = params.get("{}", False)\n'.format(attribute,attribute)) elif attribute in ["checked"]: py_class_file.write('\tself.{:<12} = params.get("{}", True)\n'.format(attribute,attribute)) else: py_class_file.write('\tself.{:<12} = params.get("{}", b"")\n'.format(attribute,attribute)) + py_class_file.write('\tself.end_init(**params)\n') py_class_file.write('\treturn self\n') else: raise SyntaxError() diff --git a/doc/lib/htmltemplate/template.html b/doc/lib/htmltemplate/template.html index a00637d..dc8332a 100644 --- a/doc/lib/htmltemplate/template.html +++ b/doc/lib/htmltemplate/template.html @@ -34,9 +34,12 @@

    Module lib.htmltemplate.template

    class Template: """ Base class of html templates """ + default_spacer = b"mb-3" def __init__(self, classname, *args, **params): + """ """ self.classname = classname self.children = [] + self.spacer = b"" if len(args) > 0: children = args[0] else: @@ -44,6 +47,11 @@

    Module lib.htmltemplate.template

    if children != []: self.add_children(children) + def end_init(self, **params): + """ Terminate initialisation""" + if params.get("spacer", None) is None: + self.spacer = Template.default_spacer + def add_children(self, children): """ Add children of html template in the current instance """ if type(children) == type([]) or type(children) == type((0,)): @@ -93,9 +101,12 @@

    Classes

    class Template:
             """ Base class of html templates """
    +        default_spacer = b"mb-3"
             def __init__(self, classname, *args, **params):
    +                """ """
                     self.classname = classname
                     self.children = []
    +                self.spacer = b""
                     if len(args) > 0:
                             children = args[0]
                     else:
    @@ -103,6 +114,11 @@ 

    Classes

    if children != []: self.add_children(children) + def end_init(self, **params): + """ Terminate initialisation""" + if params.get("spacer", None) is None: + self.spacer = Template.default_spacer + def add_children(self, children): """ Add children of html template in the current instance """ if type(children) == type([]) or type(children) == type((0,)): @@ -130,6 +146,13 @@

    Classes

    except Exception as err: await file.write(logger.html_exception(err))
    +

    Class variables

    +
    +
    var default_spacer
    +
    +
    +
    +

    Methods

    @@ -150,6 +173,21 @@

    Methods

    self.children.append(children)
    +
    +def end_init(self, **params) +
    +
    +

    Terminate initialisation

    +
    + +Expand source code + +
    def end_init(self, **params):
    +        """ Terminate initialisation"""
    +        if params.get("spacer", None) is None:
    +                self.spacer = Template.default_spacer
    +
    +
    async def write(self, file)
    @@ -201,6 +239,8 @@

    Index

    Template

    diff --git a/doc/lib/index.html b/doc/lib/index.html index 4dd3e94..3a1d471 100644 --- a/doc/lib/index.html +++ b/doc/lib/index.html @@ -26,6 +26,10 @@

    Namespace lib

    Sub-modules

    +
    lib.electricmeter
    +
    +

    Class to manage the electricmeter

    +
    lib.htmltemplate

    Class to manage the html templates

    @@ -77,6 +81,7 @@

    Index

    • Sub-modules

        +
      • lib.electricmeter
      • lib.htmltemplate
      • lib.motion
      • lib.server
      • diff --git a/doc/lib/motion/historic.html b/doc/lib/motion/historic.html index adf5a92..b669a98 100644 --- a/doc/lib/motion/historic.html +++ b/doc/lib/motion/historic.html @@ -29,6 +29,7 @@

        Module lib.motion.historic

        # Distributed under MIT License
         # Copyright (c) 2021 Remi BERTHOLET
        +# pylint:disable=consider-using-f-string
         """ Manage the motion detection history file """
         import re
         import json
        @@ -36,9 +37,9 @@ 

        Module lib.motion.historic

        import uos from tools import logger,sdcard,tasking,filesystem,strings,info -MAX_DAYS_DISPLAYED = 21 +MAX_DAYS_DISPLAYED = 28 MAX_DAYS_REMOVED = 14 -MAX_MOTIONS = MAX_DAYS_DISPLAYED*50 +MAX_MOTIONS = 400 class Historic: """ Manage the motion detection history file """ @@ -81,7 +82,7 @@

        Module lib.motion.historic

        name = strings.tostrings(name) item = Historic.create_item(root + "/" + path + "/" + name +".json", motion_info) res1 = sdcard.SdCard.save(path, name + ".jpg" , image) - res2 = sdcard.SdCard.save(path, name + ".json", json.dumps(item)) + res2 = sdcard.SdCard.save(path, name + ".json", json.dumps(item, separators=(',', ':'))) Historic.add_item(item) result = res1 and res2 except Exception as err: @@ -108,6 +109,26 @@

        Module lib.motion.historic

        # Remove the "/" before filename item [0] = item [0].lstrip("/") + # If the differences are in an old format + if type(item[3]) == type(""): + diffs = [] + diff_val = 0 + i = 0 + for diff in item[3]: + if diff == "#": + diff_val |= 1 + + if (i %32 == 31): + diffs.append(diff_val) + diff_val = 0 + else: + diff_val <<= 1 + i += 1 + diff_max = len(item[3]) + diff_val <<= (31 - (diff_max%32)) + diffs.append(diff_val) + item[3] = diffs + # Add json file to the historic Historic.historic.insert(0,item) @@ -119,7 +140,7 @@

        Module lib.motion.historic

        try: await Historic.acquire() Historic.historic.clear() - + last_day = "" # For all motions for motion in motions: try: @@ -132,6 +153,9 @@

        Module lib.motion.historic

        filename = filename.lstrip("/") if filesystem.exists(filename): Historic.add_item(motion_item) + if last_day != motion_item[0][4:14]: + last_day = motion_item[0][4:14] + print("Build historic day %s"%last_day) except OSError as err: logger.syslog(err) # If sd card not responding properly @@ -142,7 +166,8 @@

        Module lib.motion.historic

        finally: if file: file.close() - await uasyncio.sleep_ms(3) + if filesystem.ismicropython(): + await uasyncio.sleep_ms(2) except Exception as err: logger.syslog(err) finally: @@ -152,15 +177,14 @@

        Module lib.motion.historic

        async def get_json(): """ Read the historic from disk """ root = Historic.get_root() - result = b"" + result = b"[]" if root: + await Historic.reduce_history() try: await Historic.acquire() Historic.historic.sort() Historic.historic.reverse() - while len(Historic.historic) > MAX_MOTIONS: - del Historic.historic[-1] - result = strings.tobytes(json.dumps(Historic.historic)) + result = strings.tobytes(json.dumps(Historic.historic, separators=(',', ':'))) except Exception as err: logger.syslog(err) finally: @@ -184,7 +208,7 @@

        Module lib.motion.historic

        for day in lastdays: logger.syslog(" %s"%day) logger.syslog("End historic creation") - logger.syslog(strings.tostrings(info.flashinfo(mountpoint=sdcard.SdCard.get_mountpoint(), display=False))) + logger.syslog(strings.tostrings(info.flashinfo(mountpoint=sdcard.SdCard.get_mountpoint()))) except Exception as err: logger.syslog(err) @@ -203,7 +227,8 @@

        Module lib.motion.historic

        if typ & 0xF000 != 0x4000: if re.match(pattern, name): result.append(name) - await uasyncio.sleep_ms(5) + if filesystem.ismicropython(): + await uasyncio.sleep_ms(3) result.sort() if older is False: result.reverse() @@ -220,29 +245,31 @@

        Module lib.motion.historic

        await Historic.acquire() years = await Historic.scan_dir(root, r"\d\d\d\d", older) for year in years: - pathYear = root + "/" + year - months = await Historic.scan_dir(pathYear, r"\d\d", older) + path_year = root + "/" + year + months = await Historic.scan_dir(path_year, r"\d\d", older) for month in months: - pathMonth = pathYear + "/" + month - days = await Historic.scan_dir(pathMonth, r"\d\d", older) + path_month = path_year + "/" + month + days = await Historic.scan_dir(path_month, r"\d\d", older) for day in days: - pathDay = pathMonth + "/" + day - hours = await Historic.scan_dir(pathDay, r"\d\dh\d\d", older) + print("Scan historic day %s/%s/%s"%(year, month, day)) + path_day = path_month + "/" + day + hours = await Historic.scan_dir(path_day, r"\d\dh\d\d", older) lastdays.append("%s/%s/%s"%(year, month, day)) - if len(lastdays) > max_days or len(motions) > MAX_MOTIONS: - motions.sort() - if older is False: - motions.reverse() - return motions, lastdays + for hour in hours: - pathHour = pathDay + "/" + hour + path_hour = path_day + "/" + hour if older: extension = "jpg" else: extension = "json" - detections = await Historic.scan_dir(pathHour, r"\d\d.*\."+extension, older, directory=False) + detections = await Historic.scan_dir(path_hour, r"\d\d.*\."+extension, older, directory=False) for detection in detections: - motions.append(pathHour + "/" + detection) + if len(lastdays) > max_days or len(motions) > MAX_MOTIONS: + motions.sort() + if older is False: + motions.reverse() + return motions, lastdays + motions.append(path_hour + "/" + detection) motions.sort() if older is False: motions.reverse() @@ -293,17 +320,47 @@

        Module lib.motion.historic

        else: shell.rmdir(directory, recursive=True, simulate=simulate, force=force) + @staticmethod + async def reduce_history(): + """ Reduce the history length """ + try: + await Historic.acquire() + + if len(Historic.historic) > MAX_MOTIONS: + while len(Historic.historic) > MAX_MOTIONS: + del Historic.historic[-1] + + finally: + await Historic.release() + + @staticmethod + async def get_last_days(): + """ Return the list of last days """ + last_days = set() + try: + await Historic.acquire() + for item in Historic.historic: + filename = item[0].lstrip("/").split("/") + date_ = b"%s/%s/%s"%(strings.tobytes(filename[1]),strings.tobytes(filename[2]),strings.tobytes(filename[3])) + last_days.add(date_) + finally: + await Historic.release() + return last_days + @staticmethod async def remove_older(force=False): """ Remove older files to make space """ root = Historic.get_root() if root: + await Historic.reduce_history() + # If not enough space available on sdcard if sdcard.SdCard.is_not_enough_space(low=True) or force: logger.syslog("Start cleanup historic") Historic.first_extract[0] = False olders, lastdays = await Historic.scan_directories(MAX_DAYS_REMOVED, True) previous = "" + for motion in olders: try: await Historic.acquire() @@ -317,16 +374,16 @@

        Module lib.motion.historic

        await Historic.release() if sdcard.SdCard.is_not_enough_space(low=False) is False: break - logger.syslog("End cleanup historic : %s"%(strings.tostrings(info.flashinfo(mountpoint=sdcard.SdCard.get_mountpoint(), display=False)))) + logger.syslog("End cleanup historic : %s"%(strings.tostrings(info.flashinfo(mountpoint=sdcard.SdCard.get_mountpoint())))) @staticmethod async def periodic(): """ Internal periodic task """ from server.server import Server if filesystem.ismicropython(): - await Server.wait_resume(241) + await Server.wait_resume(31) else: - await Server.wait_resume(3) + await Server.wait_resume(1) if Historic.motion_in_progress[0] is False: if sdcard.SdCard.is_mounted() is False: @@ -407,7 +464,7 @@

        Classes

        name = strings.tostrings(name) item = Historic.create_item(root + "/" + path + "/" + name +".json", motion_info) res1 = sdcard.SdCard.save(path, name + ".jpg" , image) - res2 = sdcard.SdCard.save(path, name + ".json", json.dumps(item)) + res2 = sdcard.SdCard.save(path, name + ".json", json.dumps(item, separators=(',', ':'))) Historic.add_item(item) result = res1 and res2 except Exception as err: @@ -434,6 +491,26 @@

        Classes

        # Remove the "/" before filename item [0] = item [0].lstrip("/") + # If the differences are in an old format + if type(item[3]) == type(""): + diffs = [] + diff_val = 0 + i = 0 + for diff in item[3]: + if diff == "#": + diff_val |= 1 + + if (i %32 == 31): + diffs.append(diff_val) + diff_val = 0 + else: + diff_val <<= 1 + i += 1 + diff_max = len(item[3]) + diff_val <<= (31 - (diff_max%32)) + diffs.append(diff_val) + item[3] = diffs + # Add json file to the historic Historic.historic.insert(0,item) @@ -445,7 +522,7 @@

        Classes

        try: await Historic.acquire() Historic.historic.clear() - + last_day = "" # For all motions for motion in motions: try: @@ -458,6 +535,9 @@

        Classes

        filename = filename.lstrip("/") if filesystem.exists(filename): Historic.add_item(motion_item) + if last_day != motion_item[0][4:14]: + last_day = motion_item[0][4:14] + print("Build historic day %s"%last_day) except OSError as err: logger.syslog(err) # If sd card not responding properly @@ -468,7 +548,8 @@

        Classes

        finally: if file: file.close() - await uasyncio.sleep_ms(3) + if filesystem.ismicropython(): + await uasyncio.sleep_ms(2) except Exception as err: logger.syslog(err) finally: @@ -478,15 +559,14 @@

        Classes

        async def get_json(): """ Read the historic from disk """ root = Historic.get_root() - result = b"" + result = b"[]" if root: + await Historic.reduce_history() try: await Historic.acquire() Historic.historic.sort() Historic.historic.reverse() - while len(Historic.historic) > MAX_MOTIONS: - del Historic.historic[-1] - result = strings.tobytes(json.dumps(Historic.historic)) + result = strings.tobytes(json.dumps(Historic.historic, separators=(',', ':'))) except Exception as err: logger.syslog(err) finally: @@ -510,7 +590,7 @@

        Classes

        for day in lastdays: logger.syslog(" %s"%day) logger.syslog("End historic creation") - logger.syslog(strings.tostrings(info.flashinfo(mountpoint=sdcard.SdCard.get_mountpoint(), display=False))) + logger.syslog(strings.tostrings(info.flashinfo(mountpoint=sdcard.SdCard.get_mountpoint()))) except Exception as err: logger.syslog(err) @@ -529,7 +609,8 @@

        Classes

        if typ & 0xF000 != 0x4000: if re.match(pattern, name): result.append(name) - await uasyncio.sleep_ms(5) + if filesystem.ismicropython(): + await uasyncio.sleep_ms(3) result.sort() if older is False: result.reverse() @@ -546,29 +627,31 @@

        Classes

        await Historic.acquire() years = await Historic.scan_dir(root, r"\d\d\d\d", older) for year in years: - pathYear = root + "/" + year - months = await Historic.scan_dir(pathYear, r"\d\d", older) + path_year = root + "/" + year + months = await Historic.scan_dir(path_year, r"\d\d", older) for month in months: - pathMonth = pathYear + "/" + month - days = await Historic.scan_dir(pathMonth, r"\d\d", older) + path_month = path_year + "/" + month + days = await Historic.scan_dir(path_month, r"\d\d", older) for day in days: - pathDay = pathMonth + "/" + day - hours = await Historic.scan_dir(pathDay, r"\d\dh\d\d", older) + print("Scan historic day %s/%s/%s"%(year, month, day)) + path_day = path_month + "/" + day + hours = await Historic.scan_dir(path_day, r"\d\dh\d\d", older) lastdays.append("%s/%s/%s"%(year, month, day)) - if len(lastdays) > max_days or len(motions) > MAX_MOTIONS: - motions.sort() - if older is False: - motions.reverse() - return motions, lastdays + for hour in hours: - pathHour = pathDay + "/" + hour + path_hour = path_day + "/" + hour if older: extension = "jpg" else: extension = "json" - detections = await Historic.scan_dir(pathHour, r"\d\d.*\."+extension, older, directory=False) + detections = await Historic.scan_dir(path_hour, r"\d\d.*\."+extension, older, directory=False) for detection in detections: - motions.append(pathHour + "/" + detection) + if len(lastdays) > max_days or len(motions) > MAX_MOTIONS: + motions.sort() + if older is False: + motions.reverse() + return motions, lastdays + motions.append(path_hour + "/" + detection) motions.sort() if older is False: motions.reverse() @@ -619,17 +702,47 @@

        Classes

        else: shell.rmdir(directory, recursive=True, simulate=simulate, force=force) + @staticmethod + async def reduce_history(): + """ Reduce the history length """ + try: + await Historic.acquire() + + if len(Historic.historic) > MAX_MOTIONS: + while len(Historic.historic) > MAX_MOTIONS: + del Historic.historic[-1] + + finally: + await Historic.release() + + @staticmethod + async def get_last_days(): + """ Return the list of last days """ + last_days = set() + try: + await Historic.acquire() + for item in Historic.historic: + filename = item[0].lstrip("/").split("/") + date_ = b"%s/%s/%s"%(strings.tobytes(filename[1]),strings.tobytes(filename[2]),strings.tobytes(filename[3])) + last_days.add(date_) + finally: + await Historic.release() + return last_days + @staticmethod async def remove_older(force=False): """ Remove older files to make space """ root = Historic.get_root() if root: + await Historic.reduce_history() + # If not enough space available on sdcard if sdcard.SdCard.is_not_enough_space(low=True) or force: logger.syslog("Start cleanup historic") Historic.first_extract[0] = False olders, lastdays = await Historic.scan_directories(MAX_DAYS_REMOVED, True) previous = "" + for motion in olders: try: await Historic.acquire() @@ -643,16 +756,16 @@

        Classes

        await Historic.release() if sdcard.SdCard.is_not_enough_space(low=False) is False: break - logger.syslog("End cleanup historic : %s"%(strings.tostrings(info.flashinfo(mountpoint=sdcard.SdCard.get_mountpoint(), display=False)))) + logger.syslog("End cleanup historic : %s"%(strings.tostrings(info.flashinfo(mountpoint=sdcard.SdCard.get_mountpoint())))) @staticmethod async def periodic(): """ Internal periodic task """ from server.server import Server if filesystem.ismicropython(): - await Server.wait_resume(241) + await Server.wait_resume(31) else: - await Server.wait_resume(3) + await Server.wait_resume(1) if Historic.motion_in_progress[0] is False: if sdcard.SdCard.is_mounted() is False: @@ -726,6 +839,26 @@

        Static methods

        # Remove the "/" before filename item [0] = item [0].lstrip("/") + # If the differences are in an old format + if type(item[3]) == type(""): + diffs = [] + diff_val = 0 + i = 0 + for diff in item[3]: + if diff == "#": + diff_val |= 1 + + if (i %32 == 31): + diffs.append(diff_val) + diff_val = 0 + else: + diff_val <<= 1 + i += 1 + diff_max = len(item[3]) + diff_val <<= (31 - (diff_max%32)) + diffs.append(diff_val) + item[3] = diffs + # Add json file to the historic Historic.historic.insert(0,item)
        @@ -751,7 +884,7 @@

        Static methods

        name = strings.tostrings(name) item = Historic.create_item(root + "/" + path + "/" + name +".json", motion_info) res1 = sdcard.SdCard.save(path, name + ".jpg" , image) - res2 = sdcard.SdCard.save(path, name + ".json", json.dumps(item)) + res2 = sdcard.SdCard.save(path, name + ".json", json.dumps(item, separators=(',', ':'))) Historic.add_item(item) result = res1 and res2 except Exception as err: @@ -778,7 +911,7 @@

        Static methods

        try: await Historic.acquire() Historic.historic.clear() - + last_day = "" # For all motions for motion in motions: try: @@ -791,6 +924,9 @@

        Static methods

        filename = filename.lstrip("/") if filesystem.exists(filename): Historic.add_item(motion_item) + if last_day != motion_item[0][4:14]: + last_day = motion_item[0][4:14] + print("Build historic day %s"%last_day) except OSError as err: logger.syslog(err) # If sd card not responding properly @@ -801,7 +937,8 @@

        Static methods

        finally: if file: file.close() - await uasyncio.sleep_ms(3) + if filesystem.ismicropython(): + await uasyncio.sleep_ms(2) except Exception as err: logger.syslog(err) finally: @@ -854,7 +991,7 @@

        Static methods

        for day in lastdays: logger.syslog(" %s"%day) logger.syslog("End historic creation") - logger.syslog(strings.tostrings(info.flashinfo(mountpoint=sdcard.SdCard.get_mountpoint(), display=False))) + logger.syslog(strings.tostrings(info.flashinfo(mountpoint=sdcard.SdCard.get_mountpoint()))) except Exception as err: logger.syslog(err) @@ -872,15 +1009,14 @@

        Static methods

        async def get_json(): """ Read the historic from disk """ root = Historic.get_root() - result = b"" + result = b"[]" if root: + await Historic.reduce_history() try: await Historic.acquire() Historic.historic.sort() Historic.historic.reverse() - while len(Historic.historic) > MAX_MOTIONS: - del Historic.historic[-1] - result = strings.tobytes(json.dumps(Historic.historic)) + result = strings.tobytes(json.dumps(Historic.historic, separators=(',', ':'))) except Exception as err: logger.syslog(err) finally: @@ -888,6 +1024,30 @@

        Static methods

        return result
    +
    +async def get_last_days() +
    +
    +

    Return the list of last days

    +
    + +Expand source code + +
    @staticmethod
    +async def get_last_days():
    +        """ Return the list of last days """
    +        last_days = set()
    +        try:
    +                await Historic.acquire()
    +                for item in Historic.historic:
    +                        filename = item[0].lstrip("/").split("/")
    +                        date_ = b"%s/%s/%s"%(strings.tobytes(filename[1]),strings.tobytes(filename[2]),strings.tobytes(filename[3]))
    +                        last_days.add(date_)
    +        finally:
    +                await Historic.release()
    +        return last_days
    +
    +
    def get_root()
    @@ -934,9 +1094,9 @@

    Static methods

    """ Internal periodic task """ from server.server import Server if filesystem.ismicropython(): - await Server.wait_resume(241) + await Server.wait_resume(31) else: - await Server.wait_resume(3) + await Server.wait_resume(1) if Historic.motion_in_progress[0] is False: if sdcard.SdCard.is_mounted() is False: @@ -962,6 +1122,29 @@

    Static methods

    await tasking.task_monitoring(Historic.periodic) +
    +async def reduce_history() +
    +
    +

    Reduce the history length

    +
    + +Expand source code + +
    @staticmethod
    +async def reduce_history():
    +        """ Reduce the history length """
    +        try:
    +                await Historic.acquire()
    +
    +                if len(Historic.historic) > MAX_MOTIONS:
    +                        while len(Historic.historic) > MAX_MOTIONS:
    +                                del Historic.historic[-1]
    +
    +        finally:
    +                await Historic.release()
    +
    +
    async def release()
    @@ -1037,12 +1220,15 @@

    Static methods

    """ Remove older files to make space """ root = Historic.get_root() if root: + await Historic.reduce_history() + # If not enough space available on sdcard if sdcard.SdCard.is_not_enough_space(low=True) or force: logger.syslog("Start cleanup historic") Historic.first_extract[0] = False olders, lastdays = await Historic.scan_directories(MAX_DAYS_REMOVED, True) previous = "" + for motion in olders: try: await Historic.acquire() @@ -1056,7 +1242,7 @@

    Static methods

    await Historic.release() if sdcard.SdCard.is_not_enough_space(low=False) is False: break - logger.syslog("End cleanup historic : %s"%(strings.tostrings(info.flashinfo(mountpoint=sdcard.SdCard.get_mountpoint(), display=False)))) + logger.syslog("End cleanup historic : %s"%(strings.tostrings(info.flashinfo(mountpoint=sdcard.SdCard.get_mountpoint()))))
    @@ -1083,7 +1269,8 @@

    Static methods

    if typ & 0xF000 != 0x4000: if re.match(pattern, name): result.append(name) - await uasyncio.sleep_ms(5) + if filesystem.ismicropython(): + await uasyncio.sleep_ms(3) result.sort() if older is False: result.reverse() @@ -1110,29 +1297,31 @@

    Static methods

    await Historic.acquire() years = await Historic.scan_dir(root, r"\d\d\d\d", older) for year in years: - pathYear = root + "/" + year - months = await Historic.scan_dir(pathYear, r"\d\d", older) + path_year = root + "/" + year + months = await Historic.scan_dir(path_year, r"\d\d", older) for month in months: - pathMonth = pathYear + "/" + month - days = await Historic.scan_dir(pathMonth, r"\d\d", older) + path_month = path_year + "/" + month + days = await Historic.scan_dir(path_month, r"\d\d", older) for day in days: - pathDay = pathMonth + "/" + day - hours = await Historic.scan_dir(pathDay, r"\d\dh\d\d", older) + print("Scan historic day %s/%s/%s"%(year, month, day)) + path_day = path_month + "/" + day + hours = await Historic.scan_dir(path_day, r"\d\dh\d\d", older) lastdays.append("%s/%s/%s"%(year, month, day)) - if len(lastdays) > max_days or len(motions) > MAX_MOTIONS: - motions.sort() - if older is False: - motions.reverse() - return motions, lastdays + for hour in hours: - pathHour = pathDay + "/" + hour + path_hour = path_day + "/" + hour if older: extension = "jpg" else: extension = "json" - detections = await Historic.scan_dir(pathHour, r"\d\d.*\."+extension, older, directory=False) + detections = await Historic.scan_dir(path_hour, r"\d\d.*\."+extension, older, directory=False) for detection in detections: - motions.append(pathHour + "/" + detection) + if len(lastdays) > max_days or len(motions) > MAX_MOTIONS: + motions.sort() + if older is False: + motions.reverse() + return motions, lastdays + motions.append(path_hour + "/" + detection) motions.sort() if older is False: motions.reverse() @@ -1203,6 +1392,7 @@

    extract
  • first_extract
  • get_json
  • +
  • get_last_days
  • get_root
  • historic
  • lock
  • @@ -1210,6 +1400,7 @@

    motion_in_progress
  • periodic
  • periodic_task
  • +
  • reduce_history
  • release
  • remove_files
  • remove_older
  • diff --git a/doc/lib/motion/motion.html b/doc/lib/motion/motion.html index fd07bbe..9d5a358 100644 --- a/doc/lib/motion/motion.html +++ b/doc/lib/motion/motion.html @@ -29,6 +29,7 @@

    Module lib.motion.motion

    # Distributed under MIT License
     # Copyright (c) 2021 Remi BERTHOLET
    +# pylint:disable=consider-using-f-string
     """ Motion detection only work with ESP32CAM (Requires specially modified ESP32CAM firmware to handle motion detection.) """
     from gc import collect
     import sys
    @@ -40,7 +41,7 @@ 

    Module lib.motion.motion

    from server.presence import Presence from motion.historic import Historic from video.video import Camera -from tools import logger,jsonconfig,lang,linearfunction,tasking,strings,filesystem +from tools import logger,jsonconfig,lang,linearfunction,tasking,strings,filesystem,date class MotionConfig(jsonconfig.JsonConfig): """ Configuration class of motion detection """ @@ -99,9 +100,9 @@

    Module lib.motion.motion

    self.index = self.baseIndex[0] self.filename = None self.motion_id = None - self.date = strings.date_to_string() - self.filename = strings.date_to_filename() - path = strings.date_to_path() + self.date = date.date_to_string() + self.filename = date.date_to_filename() + path = date.date_to_path() if path[-1] in [0x30,0x31,0x32,0x33,0x34]: path = path[:-1] + b"0" else: @@ -464,7 +465,7 @@

    Module lib.motion.motion

    index = image.index diffs += b"%d:%d%s%s"%(image.get_motion_id(), image.get_diff_count(), (0x41 + ((256-image.get_diff_histo())//10)).to_bytes(1, 'big'), trace) if display: - line = b"\r%s %s L%d (%d) "%(strings.date_to_bytes()[12:], bytes(diffs), mean_light, index) + line = b"\r%s %s L%d (%d) "%(date.time_to_html(), bytes(diffs), mean_light, index) if filesystem.ismicropython(): sys.stdout.write(line) else: @@ -1328,9 +1329,9 @@

    Methods

    self.index = self.baseIndex[0] self.filename = None self.motion_id = None - self.date = strings.date_to_string() - self.filename = strings.date_to_filename() - path = strings.date_to_path() + self.date = date.date_to_string() + self.filename = date.date_to_filename() + path = date.date_to_path() if path[-1] in [0x30,0x31,0x32,0x33,0x34]: path = path[:-1] + b"0" else: @@ -1982,7 +1983,7 @@

    Methods

    index = image.index diffs += b"%d:%d%s%s"%(image.get_motion_id(), image.get_diff_count(), (0x41 + ((256-image.get_diff_histo())//10)).to_bytes(1, 'big'), trace) if display: - line = b"\r%s %s L%d (%d) "%(strings.date_to_bytes()[12:], bytes(diffs), mean_light, index) + line = b"\r%s %s L%d (%d) "%(date.time_to_html(), bytes(diffs), mean_light, index) if filesystem.ismicropython(): sys.stdout.write(line) else: @@ -2192,7 +2193,7 @@

    Methods

    index = image.index diffs += b"%d:%d%s%s"%(image.get_motion_id(), image.get_diff_count(), (0x41 + ((256-image.get_diff_histo())//10)).to_bytes(1, 'big'), trace) if display: - line = b"\r%s %s L%d (%d) "%(strings.date_to_bytes()[12:], bytes(diffs), mean_light, index) + line = b"\r%s %s L%d (%d) "%(date.time_to_html(), bytes(diffs), mean_light, index) if filesystem.ismicropython(): sys.stdout.write(line) else: diff --git a/doc/lib/server/dnsclient.html b/doc/lib/server/dnsclient.html index e4367dc..8fb6fe2 100644 --- a/doc/lib/server/dnsclient.html +++ b/doc/lib/server/dnsclient.html @@ -31,6 +31,7 @@

    Module lib.server.dnsclient

    # Distributed under MIT License # Copyright (c) 2021 Remi BERTHOLET # DNS spec https://www2.cs.duke.edu/courses/fall16/compsci356/DNS/DNS-primer.pdf +# pylint:disable=consider-using-f-string import struct import random import socket diff --git a/doc/lib/server/ftpserver.html b/doc/lib/server/ftpserver.html index 1b91e35..0f6e598 100644 --- a/doc/lib/server/ftpserver.html +++ b/doc/lib/server/ftpserver.html @@ -38,6 +38,7 @@

    Module lib.server.ftpserver

    # historically based on : # https://github.com/robert-hh/FTP-Server-for-ESP8266-ESP32-and-PYBD/blob/master/ftp.py # but I have modified a lot, there must still be some original functions. +# pylint:disable=consider-using-f-string """ Ftp server main class. This class contains few lines of code, this is to save memory. The core of the server is in the other class FtpServerCore, which is loaded into memory only when connecting an FTP client. diff --git a/doc/lib/server/ftpservercore.html b/doc/lib/server/ftpservercore.html index dec2e59..4bc822f 100644 --- a/doc/lib/server/ftpservercore.html +++ b/doc/lib/server/ftpservercore.html @@ -32,6 +32,8 @@

    Module lib.server.ftpservercore

    # historically based on : # https://github.com/robert-hh/FTP-Server-for-ESP8266-ESP32-and-PYBD/blob/master/ftp.py # but I have modified a lot, there must still be some original functions. +# pylint:disable=consider-using-f-string +# pylint:disable=unspecified-encoding """ Ftp server implementation core class """ import socket import os @@ -41,7 +43,7 @@

    Module lib.server.ftpservercore

    from server.user import User from wifi.accesspoint import AccessPoint from wifi.station import Station -from tools import logger,fnmatch,filesystem,strings +from tools import logger,fnmatch,filesystem,strings,date MONTHS = [b"Jan", b"Feb", b"Mar", b"Apr", b"May", b"Jun", b"Jul", b"Aug", b"Sep", b"Oct", b"Nov", b"Dec"] @@ -79,7 +81,12 @@

    Module lib.server.ftpservercore

    self.received = None self.remoteaddr = None self.client = None - logger.syslog(b"[FTP] Open data %d"%self.dataport) + self.log(b"Open data %d"%self.dataport) + + def log(self, err, msg="", write=False): + """ Log message """ + if write: + logger.syslog(err, msg=msg, write=write) def get_ip(self): """ Get the ip address of the board """ @@ -100,12 +107,12 @@

    Module lib.server.ftpservercore

    """ Destroy ftp instance """ self.close() - def get_file_description(self, filename, typ, size, date, now, full): + def get_file_description(self, filename, typ, size, date_, now, full): """ Build list of file description """ if full: file_permissions = b"drwxr-xr-x" if (typ & 0xF000 == 0x4000) else b"-rw-r--r--" - d = strings.local_time(date) + d = date.local_time(date_) year,month,day,hour,minute,_,_,_ = d[:8] if year != now[0] and month != now[1]: @@ -132,10 +139,10 @@

    Module lib.server.ftpservercore

    if pattern is None: accepted = True else: - accepted = fnmatch(strings.tostrings(filename), strings.tostrings(pattern)) + accepted = fnmatch.fnmatch(strings.tostrings(filename), strings.tostrings(pattern)) if accepted: if quantity > 100: - date = 0 + date_ = 0 else: sta = (0,0,0,0,0,0,0,0,0) try: @@ -144,9 +151,9 @@

    Module lib.server.ftpservercore

    sta = filesystem.fileinfo(strings.tostrings(filesystem.abspathbytes(path,strings.tobytes(filename)))) except Exception: pass - date = sta[8] + date_ = sta[8] - description += self.get_file_description(filename, typ, size, date, now, full) + description += self.get_file_description(filename, typ, size, date_, now, full) counter += 1 if counter == 20: counter = 0 @@ -158,11 +165,11 @@

    Module lib.server.ftpservercore

    def send_file_list(self, path, stream_, full): """ Send the list of file """ - now = strings.local_time() + now = date.local_time() try: self.send_file_list_with_pattern(path, stream_, full, now) except Exception as err: - logger.syslog(err) + self.log(err, write=True) pattern = path.split(b"/")[-1] path = path[:-(len(pattern) + 1)] if path == b"": @@ -175,7 +182,7 @@

    Module lib.server.ftpservercore

    async def send_response(self, code, message): """ Send response to ftp client """ - logger.syslog(b"[FTP] %d %s"%(code, message)) + self.log(b"%d %s"%(code, message)) await self.client.write(b'%d %s\r\n'%(code,message)) async def send_error(self, err): @@ -191,7 +198,7 @@

    Module lib.server.ftpservercore

    else: showError = True if showError: - logger.syslog(err, msg=b"[FTP] cmd='%s' cwd='%s' root='%s' path='%s' payload='%s'"%(self.command, self.cwd, self.root, self.path, self.payload)) + self.log(err, msg=b"cmd='%s' cwd='%s' root='%s' path='%s' payload='%s'"%(self.command, self.cwd, self.root, self.path, self.payload)) await self.send_response(550, b"Failed") async def USER(self): @@ -242,7 +249,7 @@

    Module lib.server.ftpservercore

    self.cwd = self.path await self.send_response(250,b"CWD command successful.") except Exception as err: - logger.syslog(err) + self.log(err, write=True) await self.send_error(b"Path not existing") else: await self.send_error(b"Path too long") @@ -266,7 +273,7 @@

    Module lib.server.ftpservercore

    await self.send_response(227, b"Entering Passive Mode (%s,%d,%d)"%(self.addr.replace(b'.',b','), self.dataport>>8, self.dataport%256)) self.close_pasv() self.pasvsocket, self.data_addr = self.datasocket.accept() - logger.syslog(b"[FTP] PASV Accepted") + self.log(b"PASV Accepted") async def PORT(self): """ Ftp command PORT """ @@ -280,7 +287,7 @@

    Module lib.server.ftpservercore

    self.pasvsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.pasvsocket.settimeout(1000) self.pasvsocket.connect((self.data_addr, self.dataport)) - logger.syslog("[FTP] Data connection with: %s"%strings.tostrings(self.data_addr)) + self.log("Data connection with: %s"%strings.tostrings(self.data_addr)) await self.send_response(200, b"OK") else: await self.send_response(504, b"Fail") @@ -297,7 +304,7 @@

    Module lib.server.ftpservercore

    place = self.cwd await self.send_response(150, b"Connection accepted.") # Start list files listsocket = stream.Socket(self.pasvsocket) - logger.syslog("[FTP] List '%s'"%(strings.tostrings(self.root+place))) + self.log("List %s"%(strings.tostrings(self.root+place))) self.send_file_list(self.root + place, listsocket, self.command == b"LIST" or self.payload == b"-l") listsocket.close() await self.send_response(226, b"Transfert complete.") # End list files @@ -311,14 +318,14 @@

    Module lib.server.ftpservercore

    await self.send_response(211, b"TYPE: Binary STRU: File MODE: Stream") else: await self.send_response(213,b"Directory listing:") - logger.syslog("[FTP] List '%s'"%strings.tostrings(self.root+self.path)) + self.log("List %s"%strings.tostrings(self.root+self.path)) self.send_file_list(self.root + self.path, self.client, True) await self.send_response(213, b"Stat end") async def RETR(self): """ Ftp command RETR """ await self.send_response(150, b"Start send file") - logger.syslog("[FTP] Send file '%s'"%strings.tostrings(self.root+self.path)) + self.log("Send %s"%strings.tostrings(self.root+self.path), write=True) filename = self.root + self.path if filesystem.ismicropython(): @@ -339,7 +346,7 @@

    Module lib.server.ftpservercore

    def close_pasv(self): """ Close PASV connection """ if self.pasvsocket is not None: - logger.syslog(b"[FTP] Close PASV") + self.log(b"Close PASV") self.pasvsocket.close() self.pasvsocket = None @@ -355,14 +362,14 @@

    Module lib.server.ftpservercore

    async def STOR(self): """ Ftp command STOR """ await self.send_response(150, b"Start receive file") - logger.syslog("[FTP] Receive file '%s'"%strings.tostrings(self.root + self.path)) + self.log("Receive %s"%strings.tostrings(self.root + self.path), write=True) filename = self.root + self.path if filesystem.ismicropython(): try: self.write_file(filename, self.pasvsocket) except Exception as err: - logger.syslog(err) + self.log(err, write=True) directory, file = filesystem.split(strings.tostrings(filename)) filesystem.makedir(directory, True) self.write_file(filename, self.pasvsocket) @@ -378,7 +385,7 @@

    Module lib.server.ftpservercore

    async def DELE(self): """ Ftp command DELE """ - logger.syslog("[FTP] Delete '%s'"%strings.tostrings(self.root + self.path)) + self.log("Delete %s"%strings.tostrings(self.root + self.path), write=True) os.remove(strings.tostrings(self.root + self.path)) await self.send_ok() @@ -408,7 +415,7 @@

    Module lib.server.ftpservercore

    async def RNTO(self): """ Ftp command RNTO """ if self.fromname is not None: - logger.syslog("[FTP] Rename '%s' to '%s'"%(strings.tostrings(self.root + self.fromname), strings.tostrings(self.root + self.path))) + self.log("Rename %s to %s"%(strings.tostrings(self.root + self.fromname), strings.tostrings(self.root + self.path)), write=True) os.rename(strings.tostrings(self.root + self.fromname), strings.tostrings(self.root + self.path)) await self.send_ok() else: @@ -430,8 +437,8 @@

    Module lib.server.ftpservercore

    try: self.received = await self.client.readline() except Exception as err: - logger.syslog(err) - logger.syslog(b"[FTP] Reset connection") + self.log(err, write=True) + self.log(b"Reset connection") self.quit = True if len(self.received) <= 0: @@ -445,7 +452,7 @@

    Module lib.server.ftpservercore

    self.command = self.received.split(b" ")[0].upper() self.payload = self.received[len(self.command):].lstrip() self.path = filesystem.abspathbytes(self.cwd, self.payload) - logger.syslog(b"[FTP] '%s' id=%08X cwd='%s' payload='%s' path='%s'"%(message, id(self), self.cwd, self.payload, self.path)) + self.log(b"'%s' id=%08X cwd='%s' payload='%s' path='%s'"%(message, id(self), self.cwd, self.payload, self.path)) async def treat_command(self): """ Treat ftp command """ @@ -466,7 +473,7 @@

    Module lib.server.ftpservercore

    else: await self.unsupported_command() except Exception as err: - logger.syslog(err) + self.log(err, write=True) await self.send_error(err) async def on_connection(self, reader, writer): @@ -474,7 +481,7 @@

    Module lib.server.ftpservercore

    Server.slow_down() self.remoteaddr = strings.tobytes(writer.get_extra_info('peername')[0]) self.addr = self.get_ip() - logger.syslog("[FTP] Connected from %s"%strings.tostrings(self.remoteaddr)) + self.log("Connected from %s"%strings.tostrings(self.remoteaddr), write=True) self.client = stream.Stream(reader, writer) try: await self.send_response(220, b"Ftp " + strings.tobytes(os.uname()[4]) + b".") @@ -483,12 +490,12 @@

    Module lib.server.ftpservercore

    await self.receive_command() await self.treat_command() except Exception as err: - logger.syslog(err) + self.log(err, write=True) await self.send_error(err) finally: self.close_pasv() await self.client.close() - logger.syslog("[FTP] Disconnected")
    + self.log("Disconnected", write=True)
    @@ -544,7 +551,12 @@

    Classes

    self.received = None self.remoteaddr = None self.client = None - logger.syslog(b"[FTP] Open data %d"%self.dataport) + self.log(b"Open data %d"%self.dataport) + + def log(self, err, msg="", write=False): + """ Log message """ + if write: + logger.syslog(err, msg=msg, write=write) def get_ip(self): """ Get the ip address of the board """ @@ -565,12 +577,12 @@

    Classes

    """ Destroy ftp instance """ self.close() - def get_file_description(self, filename, typ, size, date, now, full): + def get_file_description(self, filename, typ, size, date_, now, full): """ Build list of file description """ if full: file_permissions = b"drwxr-xr-x" if (typ & 0xF000 == 0x4000) else b"-rw-r--r--" - d = strings.local_time(date) + d = date.local_time(date_) year,month,day,hour,minute,_,_,_ = d[:8] if year != now[0] and month != now[1]: @@ -597,10 +609,10 @@

    Classes

    if pattern is None: accepted = True else: - accepted = fnmatch(strings.tostrings(filename), strings.tostrings(pattern)) + accepted = fnmatch.fnmatch(strings.tostrings(filename), strings.tostrings(pattern)) if accepted: if quantity > 100: - date = 0 + date_ = 0 else: sta = (0,0,0,0,0,0,0,0,0) try: @@ -609,9 +621,9 @@

    Classes

    sta = filesystem.fileinfo(strings.tostrings(filesystem.abspathbytes(path,strings.tobytes(filename)))) except Exception: pass - date = sta[8] + date_ = sta[8] - description += self.get_file_description(filename, typ, size, date, now, full) + description += self.get_file_description(filename, typ, size, date_, now, full) counter += 1 if counter == 20: counter = 0 @@ -623,11 +635,11 @@

    Classes

    def send_file_list(self, path, stream_, full): """ Send the list of file """ - now = strings.local_time() + now = date.local_time() try: self.send_file_list_with_pattern(path, stream_, full, now) except Exception as err: - logger.syslog(err) + self.log(err, write=True) pattern = path.split(b"/")[-1] path = path[:-(len(pattern) + 1)] if path == b"": @@ -640,7 +652,7 @@

    Classes

    async def send_response(self, code, message): """ Send response to ftp client """ - logger.syslog(b"[FTP] %d %s"%(code, message)) + self.log(b"%d %s"%(code, message)) await self.client.write(b'%d %s\r\n'%(code,message)) async def send_error(self, err): @@ -656,7 +668,7 @@

    Classes

    else: showError = True if showError: - logger.syslog(err, msg=b"[FTP] cmd='%s' cwd='%s' root='%s' path='%s' payload='%s'"%(self.command, self.cwd, self.root, self.path, self.payload)) + self.log(err, msg=b"cmd='%s' cwd='%s' root='%s' path='%s' payload='%s'"%(self.command, self.cwd, self.root, self.path, self.payload)) await self.send_response(550, b"Failed") async def USER(self): @@ -707,7 +719,7 @@

    Classes

    self.cwd = self.path await self.send_response(250,b"CWD command successful.") except Exception as err: - logger.syslog(err) + self.log(err, write=True) await self.send_error(b"Path not existing") else: await self.send_error(b"Path too long") @@ -731,7 +743,7 @@

    Classes

    await self.send_response(227, b"Entering Passive Mode (%s,%d,%d)"%(self.addr.replace(b'.',b','), self.dataport>>8, self.dataport%256)) self.close_pasv() self.pasvsocket, self.data_addr = self.datasocket.accept() - logger.syslog(b"[FTP] PASV Accepted") + self.log(b"PASV Accepted") async def PORT(self): """ Ftp command PORT """ @@ -745,7 +757,7 @@

    Classes

    self.pasvsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.pasvsocket.settimeout(1000) self.pasvsocket.connect((self.data_addr, self.dataport)) - logger.syslog("[FTP] Data connection with: %s"%strings.tostrings(self.data_addr)) + self.log("Data connection with: %s"%strings.tostrings(self.data_addr)) await self.send_response(200, b"OK") else: await self.send_response(504, b"Fail") @@ -762,7 +774,7 @@

    Classes

    place = self.cwd await self.send_response(150, b"Connection accepted.") # Start list files listsocket = stream.Socket(self.pasvsocket) - logger.syslog("[FTP] List '%s'"%(strings.tostrings(self.root+place))) + self.log("List %s"%(strings.tostrings(self.root+place))) self.send_file_list(self.root + place, listsocket, self.command == b"LIST" or self.payload == b"-l") listsocket.close() await self.send_response(226, b"Transfert complete.") # End list files @@ -776,14 +788,14 @@

    Classes

    await self.send_response(211, b"TYPE: Binary STRU: File MODE: Stream") else: await self.send_response(213,b"Directory listing:") - logger.syslog("[FTP] List '%s'"%strings.tostrings(self.root+self.path)) + self.log("List %s"%strings.tostrings(self.root+self.path)) self.send_file_list(self.root + self.path, self.client, True) await self.send_response(213, b"Stat end") async def RETR(self): """ Ftp command RETR """ await self.send_response(150, b"Start send file") - logger.syslog("[FTP] Send file '%s'"%strings.tostrings(self.root+self.path)) + self.log("Send %s"%strings.tostrings(self.root+self.path), write=True) filename = self.root + self.path if filesystem.ismicropython(): @@ -804,7 +816,7 @@

    Classes

    def close_pasv(self): """ Close PASV connection """ if self.pasvsocket is not None: - logger.syslog(b"[FTP] Close PASV") + self.log(b"Close PASV") self.pasvsocket.close() self.pasvsocket = None @@ -820,14 +832,14 @@

    Classes

    async def STOR(self): """ Ftp command STOR """ await self.send_response(150, b"Start receive file") - logger.syslog("[FTP] Receive file '%s'"%strings.tostrings(self.root + self.path)) + self.log("Receive %s"%strings.tostrings(self.root + self.path), write=True) filename = self.root + self.path if filesystem.ismicropython(): try: self.write_file(filename, self.pasvsocket) except Exception as err: - logger.syslog(err) + self.log(err, write=True) directory, file = filesystem.split(strings.tostrings(filename)) filesystem.makedir(directory, True) self.write_file(filename, self.pasvsocket) @@ -843,7 +855,7 @@

    Classes

    async def DELE(self): """ Ftp command DELE """ - logger.syslog("[FTP] Delete '%s'"%strings.tostrings(self.root + self.path)) + self.log("Delete %s"%strings.tostrings(self.root + self.path), write=True) os.remove(strings.tostrings(self.root + self.path)) await self.send_ok() @@ -873,7 +885,7 @@

    Classes

    async def RNTO(self): """ Ftp command RNTO """ if self.fromname is not None: - logger.syslog("[FTP] Rename '%s' to '%s'"%(strings.tostrings(self.root + self.fromname), strings.tostrings(self.root + self.path))) + self.log("Rename %s to %s"%(strings.tostrings(self.root + self.fromname), strings.tostrings(self.root + self.path)), write=True) os.rename(strings.tostrings(self.root + self.fromname), strings.tostrings(self.root + self.path)) await self.send_ok() else: @@ -895,8 +907,8 @@

    Classes

    try: self.received = await self.client.readline() except Exception as err: - logger.syslog(err) - logger.syslog(b"[FTP] Reset connection") + self.log(err, write=True) + self.log(b"Reset connection") self.quit = True if len(self.received) <= 0: @@ -910,7 +922,7 @@

    Classes

    self.command = self.received.split(b" ")[0].upper() self.payload = self.received[len(self.command):].lstrip() self.path = filesystem.abspathbytes(self.cwd, self.payload) - logger.syslog(b"[FTP] '%s' id=%08X cwd='%s' payload='%s' path='%s'"%(message, id(self), self.cwd, self.payload, self.path)) + self.log(b"'%s' id=%08X cwd='%s' payload='%s' path='%s'"%(message, id(self), self.cwd, self.payload, self.path)) async def treat_command(self): """ Treat ftp command """ @@ -931,7 +943,7 @@

    Classes

    else: await self.unsupported_command() except Exception as err: - logger.syslog(err) + self.log(err, write=True) await self.send_error(err) async def on_connection(self, reader, writer): @@ -939,7 +951,7 @@

    Classes

    Server.slow_down() self.remoteaddr = strings.tobytes(writer.get_extra_info('peername')[0]) self.addr = self.get_ip() - logger.syslog("[FTP] Connected from %s"%strings.tostrings(self.remoteaddr)) + self.log("Connected from %s"%strings.tostrings(self.remoteaddr), write=True) self.client = stream.Stream(reader, writer) try: await self.send_response(220, b"Ftp " + strings.tobytes(os.uname()[4]) + b".") @@ -948,12 +960,12 @@

    Classes

    await self.receive_command() await self.treat_command() except Exception as err: - logger.syslog(err) + self.log(err, write=True) await self.send_error(err) finally: self.close_pasv() await self.client.close() - logger.syslog("[FTP] Disconnected") + self.log("Disconnected", write=True)

    Class variables

    @@ -996,7 +1008,7 @@

    Methods

    self.cwd = self.path await self.send_response(250,b"CWD command successful.") except Exception as err: - logger.syslog(err) + self.log(err, write=True) await self.send_error(b"Path not existing") else: await self.send_error(b"Path too long") @@ -1013,7 +1025,7 @@

    Methods

    async def DELE(self):
             """ Ftp command DELE """
    -        logger.syslog("[FTP] Delete '%s'"%strings.tostrings(self.root + self.path))
    +        self.log("Delete %s"%strings.tostrings(self.root + self.path), write=True)
             os.remove(strings.tostrings(self.root + self.path))
             await self.send_ok()
    @@ -1049,7 +1061,7 @@

    Methods

    place = self.cwd await self.send_response(150, b"Connection accepted.") # Start list files listsocket = stream.Socket(self.pasvsocket) - logger.syslog("[FTP] List '%s'"%(strings.tostrings(self.root+place))) + self.log("List %s"%(strings.tostrings(self.root+place))) self.send_file_list(self.root + place, listsocket, self.command == b"LIST" or self.payload == b"-l") listsocket.close() await self.send_response(226, b"Transfert complete.") # End list files @@ -1131,7 +1143,7 @@

    Methods

    await self.send_response(227, b"Entering Passive Mode (%s,%d,%d)"%(self.addr.replace(b'.',b','), self.dataport>>8, self.dataport%256)) self.close_pasv() self.pasvsocket, self.data_addr = self.datasocket.accept() - logger.syslog(b"[FTP] PASV Accepted") + self.log(b"PASV Accepted")
    @@ -1155,7 +1167,7 @@

    Methods

    self.pasvsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.pasvsocket.settimeout(1000) self.pasvsocket.connect((self.data_addr, self.dataport)) - logger.syslog("[FTP] Data connection with: %s"%strings.tostrings(self.data_addr)) + self.log("Data connection with: %s"%strings.tostrings(self.data_addr)) await self.send_response(200, b"OK") else: await self.send_response(504, b"Fail")
    @@ -1202,7 +1214,7 @@

    Methods

    async def RETR(self):
             """ Ftp command RETR """
             await self.send_response(150, b"Start send file")
    -        logger.syslog("[FTP] Send file '%s'"%strings.tostrings(self.root+self.path))
    +        self.log("Send %s"%strings.tostrings(self.root+self.path), write=True)
             filename = self.root + self.path
     
             if filesystem.ismicropython():
    @@ -1263,7 +1275,7 @@ 

    Methods

    async def RNTO(self):
             """ Ftp command RNTO """
             if self.fromname is not None:
    -                logger.syslog("[FTP] Rename '%s' to '%s'"%(strings.tostrings(self.root + self.fromname), strings.tostrings(self.root + self.path)))
    +                self.log("Rename %s to %s"%(strings.tostrings(self.root + self.fromname), strings.tostrings(self.root + self.path)), write=True)
                     os.rename(strings.tostrings(self.root + self.fromname), strings.tostrings(self.root + self.path))
                     await self.send_ok()
             else:
    @@ -1303,7 +1315,7 @@ 

    Methods

    await self.send_response(211, b"TYPE: Binary STRU: File MODE: Stream") else: await self.send_response(213,b"Directory listing:") - logger.syslog("[FTP] List '%s'"%strings.tostrings(self.root+self.path)) + self.log("List %s"%strings.tostrings(self.root+self.path)) self.send_file_list(self.root + self.path, self.client, True) await self.send_response(213, b"Stat end")
    @@ -1320,14 +1332,14 @@

    Methods

    async def STOR(self):
             """ Ftp command STOR """
             await self.send_response(150, b"Start receive file")
    -        logger.syslog("[FTP] Receive file '%s'"%strings.tostrings(self.root + self.path))
    +        self.log("Receive %s"%strings.tostrings(self.root + self.path), write=True)
             filename = self.root + self.path
     
             if filesystem.ismicropython():
                     try:
                             self.write_file(filename, self.pasvsocket)
                     except Exception as err:
    -                        logger.syslog(err)
    +                        self.log(err, write=True)
                             directory, file = filesystem.split(strings.tostrings(filename))
                             filesystem.makedir(directory, True)
                             self.write_file(filename, self.pasvsocket)
    @@ -1473,13 +1485,13 @@ 

    Methods

    def close_pasv(self):
             """ Close PASV connection """
             if self.pasvsocket is not None:
    -                logger.syslog(b"[FTP] Close PASV")
    +                self.log(b"Close PASV")
                     self.pasvsocket.close()
                     self.pasvsocket = None
    -def get_file_description(self, filename, typ, size, date, now, full) +def get_file_description(self, filename, typ, size, date_, now, full)

    Build list of file description

    @@ -1487,12 +1499,12 @@

    Methods

    Expand source code -
    def get_file_description(self, filename, typ, size, date, now, full):
    +
    def get_file_description(self, filename, typ, size, date_, now, full):
             """ Build list of file description """
             if full:
                     file_permissions = b"drwxr-xr-x" if (typ & 0xF000 == 0x4000) else b"-rw-r--r--"
     
    -                d = strings.local_time(date)
    +                d = date.local_time(date_)
                     year,month,day,hour,minute,_,_,_ = d[:8]
     
                     if year != now[0] and month != now[1]:
    @@ -1523,6 +1535,21 @@ 

    Methods

    return result
    +
    +def log(self, err, msg='', write=False) +
    +
    +

    Log message

    +
    + +Expand source code + +
    def log(self, err, msg="", write=False):
    +        """ Log message """
    +        if write:
    +                logger.syslog(err, msg=msg, write=write)
    +
    +
    async def on_connection(self, reader, writer)
    @@ -1537,7 +1564,7 @@

    Methods

    Server.slow_down() self.remoteaddr = strings.tobytes(writer.get_extra_info('peername')[0]) self.addr = self.get_ip() - logger.syslog("[FTP] Connected from %s"%strings.tostrings(self.remoteaddr)) + self.log("Connected from %s"%strings.tostrings(self.remoteaddr), write=True) self.client = stream.Stream(reader, writer) try: await self.send_response(220, b"Ftp " + strings.tobytes(os.uname()[4]) + b".") @@ -1546,12 +1573,12 @@

    Methods

    await self.receive_command() await self.treat_command() except Exception as err: - logger.syslog(err) + self.log(err, write=True) await self.send_error(err) finally: self.close_pasv() await self.client.close() - logger.syslog("[FTP] Disconnected")
    + self.log("Disconnected", write=True)
    @@ -1569,8 +1596,8 @@

    Methods

    try: self.received = await self.client.readline() except Exception as err: - logger.syslog(err) - logger.syslog(b"[FTP] Reset connection") + self.log(err, write=True) + self.log(b"Reset connection") self.quit = True if len(self.received) <= 0: @@ -1584,7 +1611,7 @@

    Methods

    self.command = self.received.split(b" ")[0].upper() self.payload = self.received[len(self.command):].lstrip() self.path = filesystem.abspathbytes(self.cwd, self.payload) - logger.syslog(b"[FTP] '%s' id=%08X cwd='%s' payload='%s' path='%s'"%(message, id(self), self.cwd, self.payload, self.path))
    + self.log(b"'%s' id=%08X cwd='%s' payload='%s' path='%s'"%(message, id(self), self.cwd, self.payload, self.path))
    @@ -1609,7 +1636,7 @@

    Methods

    else: showError = True if showError: - logger.syslog(err, msg=b"[FTP] cmd='%s' cwd='%s' root='%s' path='%s' payload='%s'"%(self.command, self.cwd, self.root, self.path, self.payload)) + self.log(err, msg=b"cmd='%s' cwd='%s' root='%s' path='%s' payload='%s'"%(self.command, self.cwd, self.root, self.path, self.payload)) await self.send_response(550, b"Failed")
    @@ -1624,11 +1651,11 @@

    Methods

    def send_file_list(self, path, stream_, full):
             """ Send the list of file """
    -        now = strings.local_time()
    +        now = date.local_time()
             try:
                     self.send_file_list_with_pattern(path, stream_, full, now)
             except Exception as err:
    -                logger.syslog(err)
    +                self.log(err, write=True)
                     pattern = path.split(b"/")[-1]
                     path = path[:-(len(pattern) + 1)]
                     if path == b"":
    @@ -1660,10 +1687,10 @@ 

    Methods

    if pattern is None: accepted = True else: - accepted = fnmatch(strings.tostrings(filename), strings.tostrings(pattern)) + accepted = fnmatch.fnmatch(strings.tostrings(filename), strings.tostrings(pattern)) if accepted: if quantity > 100: - date = 0 + date_ = 0 else: sta = (0,0,0,0,0,0,0,0,0) try: @@ -1672,9 +1699,9 @@

    Methods

    sta = filesystem.fileinfo(strings.tostrings(filesystem.abspathbytes(path,strings.tobytes(filename)))) except Exception: pass - date = sta[8] + date_ = sta[8] - description += self.get_file_description(filename, typ, size, date, now, full) + description += self.get_file_description(filename, typ, size, date_, now, full) counter += 1 if counter == 20: counter = 0 @@ -1710,7 +1737,7 @@

    Methods

    async def send_response(self, code, message):
             """ Send response to ftp client """
    -        logger.syslog(b"[FTP] %d %s"%(code, message))
    +        self.log(b"%d %s"%(code, message))
             await self.client.write(b'%d %s\r\n'%(code,message))
    @@ -1742,7 +1769,7 @@

    Methods

    else: await self.unsupported_command() except Exception as err: - logger.syslog(err) + self.log(err, write=True) await self.send_error(err)
    @@ -1831,6 +1858,7 @@

    close_pasv
  • get_file_description
  • get_ip
  • +
  • log
  • on_connection
  • portbase
  • receive_command
  • diff --git a/doc/lib/server/httprequest.html b/doc/lib/server/httprequest.html index f38a864..b51b203 100644 --- a/doc/lib/server/httprequest.html +++ b/doc/lib/server/httprequest.html @@ -37,12 +37,12 @@

    Module lib.server.httprequest

    # historically based on : # https://github.com/jczic/MicroWebSrv/blob/master/microWebSocket.py # but I have modified a lot, there must still be some original functions. +# pylint:disable=consider-using-f-string """ These classes manage http responses and requests. The set of request and response are in bytes format. I no longer use strings, because they are between 20 and 30 times slower. It may sound a bit more complicated, but it's a lot quick. """ - import hashlib import time from binascii import hexlify, b2a_base64 @@ -73,7 +73,8 @@

    Module lib.server.httprequest

    b".gif" : b"image/gif", b".jpeg" : b"image/jpeg", b".svg" : b"image/svg+xml", - b".ico" : b"image/x-icon" + b".ico" : b"image/x-icon", + b".bin" : b"application/octet-stream" } class Http: @@ -94,6 +95,7 @@

    Module lib.server.httprequest

    self.content_file = None self.identifier = None self.request = request + self.chunk_size = 0 def __del__(self): if self.content_file is not None: @@ -207,16 +209,19 @@

    Module lib.server.httprequest

    data = await streamio.readline() if data != b"": spl = data.split() - self.method = spl[0] - path = spl[1] - proto = spl[2] - if self.request is False: - self.status = path - paths = path.split(b"?", 1) - if len(paths) > 1: - self.unserialize_params(paths[1]) - self.path = self.unquote(paths[0]) - await self.unserialize_headers(streamio) + if len(spl) >= 2: + self.method = spl[0] + path = spl[1] + if self.request is False: + self.status = path + paths = path.split(b"?", 1) + if len(paths) > 1: + self.unserialize_params(paths[1]) + self.path = self.unquote(paths[0]) + + await self.unserialize_headers(streamio) + else: + await self.read_content(streamio) def unserialize_params(self, url): """ Extract parameters from url """ @@ -226,44 +231,56 @@

    Module lib.server.httprequest

    param = [self.unquote(x) for x in pair.split(b"=", 1)] if len(param) == 1: param.append(True) - previousValue = self.params.get(param[0]) - if previousValue is not None: - if previousValue == b'0' and param[1] == b'': + previous_value = self.params.get(param[0]) + if previous_value is not None: + if previous_value == b'0' and param[1] == b'': self.params[param[0]] = b'1' else: - if not isinstance(previousValue, list): - self.params[param[0]] = [previousValue] + if not isinstance(previous_value, list): + self.params[param[0]] = [previous_value] self.params[param[0]].append(param[1]) else: self.params[param[0]] = param[1] async def read_content(self, streamio): """ Read the content of http request """ - length = int(self.headers.get(b"Content-Length","0")) + if self.headers.get(b"Transfer-Encoding",b"") == b"chunked": + length = await streamio.readline() + length = eval(strings.tostrings(b"0x%s"%length.strip())) + self.chunk_size = length + chunk = True + else: + length = int(self.headers.get(b"Content-Length",b"0")) + chunk = False + await self.read_data(length, streamio, chunk) + + async def read_data(self, length, streamio, chunk=False): + """ Read data with length """ # If data small write in memory if length < 4096: - self.content = b"" - while len(self.content) < length: - self.content += await streamio.read(int(self.headers.get(b"Content-Length","0"))) + if chunk is False or self.content is None: + self.content = b"" + data = b"" + while len(data) < length: + data += await streamio.read(length - len(data)) + self.content += data # Data too big write in file else: self.content_file = "%d.tmp"%id(self) - try: - content = open(self.content_file, "wb") + if chunk is False: + attrib = "wb" + else: + attrib = "ab" + with open(self.content_file, attrib) as content: while content.tell() < length: - content.write(await streamio.read(int(self.headers.get(b"Content-Length","0")))) - finally: - content.close() + content.write(await streamio.read(length - len(self.content))) def get_content_filename(self): """ Copy the content into file """ if self.content is not None: self.content_file = "%d.tmp"%id(self) - try: - content = open(self.content_file, "wb") + with open(self.content_file, "wb") as content: content.write(self.content) - finally: - content.close() self.content = None return self.content_file @@ -337,7 +354,7 @@

    Module lib.server.httprequest

    async def serialize_body(self, streamio): """ Serialize body """ result = 0 - noEnd = False + no_end = False # If content existing if self.content is not None: try: @@ -348,8 +365,9 @@

    Module lib.server.httprequest

    else: # Serialize object result += await self.content.serialize(streamio) - noEnd = True + no_end = True except Exception as err: + logger.syslog(err) # Serialize error detected result += await streamio.write(b'Content-Type: text/plain\r\n\r\n') result += await streamio.write(strings.tostrings(logger.exception(err))) @@ -378,7 +396,7 @@

    Module lib.server.httprequest

    if self.headers[b"Content-Type"] != b"multipart/x-mixed-replace": result += await streamio.write(b"--") - if noEnd is False: + if no_end is False: # Terminate serialize request or response result += await streamio.write(b"\r\n") return result @@ -402,6 +420,7 @@

    Module lib.server.httprequest

    """ Class that contains a file """ def __init__(self, filename, content_type=None, base64=False): """ Constructor """ + # pylint:disable=global-variable-not-assigned if type(filename) == type([]): self.filenames = filename else: @@ -414,48 +433,60 @@

    Module lib.server.httprequest

    else: self.content_type = content_type + async def serialize_file(self, filename, streamio): + """ Serialize the content of file named filename """ + result = 0 + found = False + + filename = strings.tostrings(filename) + + # If file existing + if filesystem.exists(filename): + with open(strings.tostrings(filename), "rb") as f: + found = True + result = await streamio.write(b'Content-Type: %s\r\n\r\n'%(self.content_type)) + if server.stream.Bufferedio.is_enough_memory(): + step = 1440*10 + else: + step = 512 + buf = bytearray(step) + f.seek(0,2) + size = f.tell() + f.seek(0) + + if self.base64 and step % 3 != 0: + step = (step//3)*3 + + length_written = 0 + + while size > 0: + if size < step: + buf = bytearray(size) + length = f.readinto(buf) + size -= length + if self.base64: + length_written += await streamio.write(b2a_base64(buf)) + else: + length_written += await streamio.write(buf) + result += length_written + else: + logger.syslog("%s file not found"%filename) + return result, found + async def serialize(self, streamio): """ Serialize file """ found = False + result = 0 try: - f = None # print("Begin send %s"%strings.tostrings(self.filename)) for filename in self.filenames: - if filesystem.exists(filename): - f = open(strings.tostrings(filename), "rb") - if found is False: - result = await streamio.write(b'Content-Type: %s\r\n\r\n'%(self.content_type)) + r, f = await self.serialize_file(filename, streamio) + result += r + if f: found = True - if server.stream.Bufferedio.is_enough_memory(): - step = 1440*10 - else: - step = 512 - buf = bytearray(step) - f.seek(0,2) - size = f.tell() - f.seek(0) - - if self.base64 and step % 3 != 0: - step = (step//3)*3 - - lengthWritten = 0 - - while size > 0: - if size < step: - buf = bytearray(size) - length = f.readinto(buf) - size -= length - if self.base64: - lengthWritten += await streamio.write(b2a_base64(buf)) - else: - lengthWritten += await streamio.write(buf) - # print("End send %s"%strings.tostrings(self.filename)) - result += lengthWritten + # print("End send %s"%strings.tostrings(self.filename)) except Exception as err: pass - finally: - if f: - f.close() if found is False: result = await streamio.write(b'Content-Type: text/plain\r\n\r\n') filenames = b"" @@ -468,6 +499,7 @@

    Module lib.server.httprequest

    """ Class that contains a buffer """ def __init__(self, filename, buffer, content_type=None): """ Constructor """ + # pylint:disable=global-variable-not-assigned self.filename = filename self.buffer = buffer if content_type is None: @@ -525,21 +557,17 @@

    Module lib.server.httprequest

    async def serialize(self, identifier, streamio): """ Serialize multi part file """ result = await self.serialize_header(identifier, streamio) - try: - part = b"" - file = open(strings.tostrings(self.filename),"rb") + with open(strings.tostrings(self.filename),"rb") as file: part = file.read() - finally: - file.close() result += await streamio.write(b"\r\n%s\r\n"%part) result += await streamio.write(b"--%s"%identifier) return result async def get_size(self, identifier): """ Get the size of multi part file """ - headerSize = await self.serialize_header(identifier, server.stream.Bytesio()) - fileSize = filesystem.filesize((strings.tostrings(self.filename))) - return headerSize + fileSize + 4 + len(identifier) + 2 + header_size = await self.serialize_header(identifier, server.stream.Bytesio()) + file_size = filesystem.filesize((strings.tostrings(self.filename))) + return header_size + file_size + 4 + len(identifier) + 2 class PartBin(PartFile): """ Class that contains a binary data, used in multipart request or response """ @@ -565,6 +593,7 @@

    Module lib.server.httprequest

    def __init__(self, streamio, remoteaddr= b"", port = 0, name = ""): """ Constructor """ Http.__init__(self, request=False, remoteaddr=remoteaddr, port=port, name=name) + self.chunk_size = 0 self.streamio = streamio async def send(self, content=None, status=b"200", headers=None): @@ -582,17 +611,27 @@

    Module lib.server.httprequest

    """ Send error to the client web browser """ return await self.send(status=status, content=content) + async def send_not_found(self, err=None): + """ Send page not found """ + if err is None: + content = b"" + elif type(err) == type(b"") or type(err) == type(""): + content = strings.tobytes(err) + else: + content = strings.tobytes(logger.exception(err)) + return await self.send_error(status=b"404", content=content) + async def send_ok(self, content=None): """ Send ok to the client web browser """ return await self.send_error(status=b"200", content=content) - async def send_file(self, filename, mimeType=None, headers=None, base64=False): + async def send_file(self, filename, mime_type=None, headers=None, base64=False): """ Send a file to the client web browser """ - return await self.send(content=ContentFile(filename, mimeType, base64), status=b"200", headers=headers) + return await self.send(content=ContentFile(filename, mime_type, base64), status=b"200", headers=headers) - async def send_buffer(self, filename, buffer, mimeType=None, headers=None): + async def send_buffer(self, filename, buffer, mime_type=None, headers=None): """ Send a file to the client web browser """ - return await self.send(content=ContentBuffer(filename, buffer, mimeType), status=b"200", headers=headers) + return await self.send(content=ContentBuffer(filename, buffer, mime_type), status=b"200", headers=headers) async def send_page(self, page): """ Send a template page to the client web browser """ @@ -619,7 +658,7 @@

    Module lib.server.httprequest

    streamio = self.streamio await self.unserialize(streamio) - async def send(self, streamio): + async def send(self, streamio=None): """ Send request to server """ if streamio is None: streamio = self.streamio @@ -650,6 +689,7 @@

    Classes

    """ Class that contains a buffer """ def __init__(self, filename, buffer, content_type=None): """ Constructor """ + # pylint:disable=global-variable-not-assigned self.filename = filename self.buffer = buffer if content_type is None: @@ -710,6 +750,7 @@

    Methods

    """ Class that contains a file """ def __init__(self, filename, content_type=None, base64=False): """ Constructor """ + # pylint:disable=global-variable-not-assigned if type(filename) == type([]): self.filenames = filename else: @@ -722,48 +763,60 @@

    Methods

    else: self.content_type = content_type + async def serialize_file(self, filename, streamio): + """ Serialize the content of file named filename """ + result = 0 + found = False + + filename = strings.tostrings(filename) + + # If file existing + if filesystem.exists(filename): + with open(strings.tostrings(filename), "rb") as f: + found = True + result = await streamio.write(b'Content-Type: %s\r\n\r\n'%(self.content_type)) + if server.stream.Bufferedio.is_enough_memory(): + step = 1440*10 + else: + step = 512 + buf = bytearray(step) + f.seek(0,2) + size = f.tell() + f.seek(0) + + if self.base64 and step % 3 != 0: + step = (step//3)*3 + + length_written = 0 + + while size > 0: + if size < step: + buf = bytearray(size) + length = f.readinto(buf) + size -= length + if self.base64: + length_written += await streamio.write(b2a_base64(buf)) + else: + length_written += await streamio.write(buf) + result += length_written + else: + logger.syslog("%s file not found"%filename) + return result, found + async def serialize(self, streamio): """ Serialize file """ found = False + result = 0 try: - f = None # print("Begin send %s"%strings.tostrings(self.filename)) for filename in self.filenames: - if filesystem.exists(filename): - f = open(strings.tostrings(filename), "rb") - if found is False: - result = await streamio.write(b'Content-Type: %s\r\n\r\n'%(self.content_type)) + r, f = await self.serialize_file(filename, streamio) + result += r + if f: found = True - if server.stream.Bufferedio.is_enough_memory(): - step = 1440*10 - else: - step = 512 - buf = bytearray(step) - f.seek(0,2) - size = f.tell() - f.seek(0) - - if self.base64 and step % 3 != 0: - step = (step//3)*3 - - lengthWritten = 0 - - while size > 0: - if size < step: - buf = bytearray(size) - length = f.readinto(buf) - size -= length - if self.base64: - lengthWritten += await streamio.write(b2a_base64(buf)) - else: - lengthWritten += await streamio.write(buf) - # print("End send %s"%strings.tostrings(self.filename)) - result += lengthWritten + # print("End send %s"%strings.tostrings(self.filename)) except Exception as err: pass - finally: - if f: - f.close() if found is False: result = await streamio.write(b'Content-Type: text/plain\r\n\r\n') filenames = b"" @@ -786,45 +839,17 @@

    Methods

    async def serialize(self, streamio):
             """ Serialize file """
             found = False
    +        result = 0
             try:
    -                f = None
                     # print("Begin send %s"%strings.tostrings(self.filename))
                     for filename in self.filenames:
    -                        if filesystem.exists(filename):
    -                                f = open(strings.tostrings(filename), "rb")
    -                                if found is False:
    -                                        result = await streamio.write(b'Content-Type: %s\r\n\r\n'%(self.content_type))
    +                        r, f = await self.serialize_file(filename, streamio)
    +                        result += r
    +                        if f:
                                     found = True
    -                                if server.stream.Bufferedio.is_enough_memory():
    -                                        step = 1440*10
    -                                else:
    -                                        step = 512
    -                                buf = bytearray(step)
    -                                f.seek(0,2)
    -                                size = f.tell()
    -                                f.seek(0)
    -
    -                                if self.base64 and step % 3 != 0:
    -                                        step = (step//3)*3
    -
    -                                lengthWritten = 0
    -
    -                                while size > 0:
    -                                        if size < step:
    -                                                buf = bytearray(size)
    -                                        length = f.readinto(buf)
    -                                        size -= length
    -                                        if self.base64:
    -                                                lengthWritten += await streamio.write(b2a_base64(buf))
    -                                        else:
    -                                                lengthWritten += await streamio.write(buf)
    -                                # print("End send %s"%strings.tostrings(self.filename))
    -                                result += lengthWritten
    +                # print("End send %s"%strings.tostrings(self.filename))
             except Exception as err:
                     pass
    -        finally:
    -                if f:
    -                        f.close()
             if found is False:
                     result = await streamio.write(b'Content-Type: text/plain\r\n\r\n')
                     filenames = b""
    @@ -834,6 +859,56 @@ 

    Methods

    return result
    +
    +async def serialize_file(self, filename, streamio) +
    +
    +

    Serialize the content of file named filename

    +
    + +Expand source code + +
    async def serialize_file(self, filename, streamio):
    +        """ Serialize the content of file named filename """
    +        result = 0
    +        found = False
    +
    +        filename = strings.tostrings(filename)
    +
    +        # If file existing
    +        if filesystem.exists(filename):
    +                with open(strings.tostrings(filename), "rb") as f:
    +                        found = True
    +                        result = await streamio.write(b'Content-Type: %s\r\n\r\n'%(self.content_type))
    +                        if server.stream.Bufferedio.is_enough_memory():
    +                                step = 1440*10
    +                        else:
    +                                step = 512
    +                        buf = bytearray(step)
    +                        f.seek(0,2)
    +                        size = f.tell()
    +                        f.seek(0)
    +
    +                        if self.base64 and step % 3 != 0:
    +                                step = (step//3)*3
    +
    +                        length_written = 0
    +
    +                        while size > 0:
    +                                if size < step:
    +                                        buf = bytearray(size)
    +                                length = f.readinto(buf)
    +                                size -= length
    +                                if self.base64:
    +                                        length_written += await streamio.write(b2a_base64(buf))
    +                                else:
    +                                        length_written += await streamio.write(buf)
    +                        result += length_written
    +        else:
    +                logger.syslog("%s file not found"%filename)
    +        return result, found
    +
    +
    @@ -911,6 +986,7 @@

    Methods

    self.content_file = None self.identifier = None self.request = request + self.chunk_size = 0 def __del__(self): if self.content_file is not None: @@ -1024,16 +1100,19 @@

    Methods

    data = await streamio.readline() if data != b"": spl = data.split() - self.method = spl[0] - path = spl[1] - proto = spl[2] - if self.request is False: - self.status = path - paths = path.split(b"?", 1) - if len(paths) > 1: - self.unserialize_params(paths[1]) - self.path = self.unquote(paths[0]) - await self.unserialize_headers(streamio) + if len(spl) >= 2: + self.method = spl[0] + path = spl[1] + if self.request is False: + self.status = path + paths = path.split(b"?", 1) + if len(paths) > 1: + self.unserialize_params(paths[1]) + self.path = self.unquote(paths[0]) + + await self.unserialize_headers(streamio) + else: + await self.read_content(streamio) def unserialize_params(self, url): """ Extract parameters from url """ @@ -1043,44 +1122,56 @@

    Methods

    param = [self.unquote(x) for x in pair.split(b"=", 1)] if len(param) == 1: param.append(True) - previousValue = self.params.get(param[0]) - if previousValue is not None: - if previousValue == b'0' and param[1] == b'': + previous_value = self.params.get(param[0]) + if previous_value is not None: + if previous_value == b'0' and param[1] == b'': self.params[param[0]] = b'1' else: - if not isinstance(previousValue, list): - self.params[param[0]] = [previousValue] + if not isinstance(previous_value, list): + self.params[param[0]] = [previous_value] self.params[param[0]].append(param[1]) else: self.params[param[0]] = param[1] async def read_content(self, streamio): """ Read the content of http request """ - length = int(self.headers.get(b"Content-Length","0")) + if self.headers.get(b"Transfer-Encoding",b"") == b"chunked": + length = await streamio.readline() + length = eval(strings.tostrings(b"0x%s"%length.strip())) + self.chunk_size = length + chunk = True + else: + length = int(self.headers.get(b"Content-Length",b"0")) + chunk = False + await self.read_data(length, streamio, chunk) + + async def read_data(self, length, streamio, chunk=False): + """ Read data with length """ # If data small write in memory if length < 4096: - self.content = b"" - while len(self.content) < length: - self.content += await streamio.read(int(self.headers.get(b"Content-Length","0"))) + if chunk is False or self.content is None: + self.content = b"" + data = b"" + while len(data) < length: + data += await streamio.read(length - len(data)) + self.content += data # Data too big write in file else: self.content_file = "%d.tmp"%id(self) - try: - content = open(self.content_file, "wb") + if chunk is False: + attrib = "wb" + else: + attrib = "ab" + with open(self.content_file, attrib) as content: while content.tell() < length: - content.write(await streamio.read(int(self.headers.get(b"Content-Length","0")))) - finally: - content.close() + content.write(await streamio.read(length - len(self.content))) def get_content_filename(self): """ Copy the content into file """ if self.content is not None: self.content_file = "%d.tmp"%id(self) - try: - content = open(self.content_file, "wb") + with open(self.content_file, "wb") as content: content.write(self.content) - finally: - content.close() self.content = None return self.content_file @@ -1154,7 +1245,7 @@

    Methods

    async def serialize_body(self, streamio): """ Serialize body """ result = 0 - noEnd = False + no_end = False # If content existing if self.content is not None: try: @@ -1165,8 +1256,9 @@

    Methods

    else: # Serialize object result += await self.content.serialize(streamio) - noEnd = True + no_end = True except Exception as err: + logger.syslog(err) # Serialize error detected result += await streamio.write(b'Content-Type: text/plain\r\n\r\n') result += await streamio.write(strings.tostrings(logger.exception(err))) @@ -1195,7 +1287,7 @@

    Methods

    if self.headers[b"Content-Type"] != b"multipart/x-mixed-replace": result += await streamio.write(b"--") - if noEnd is False: + if no_end is False: # Terminate serialize request or response result += await streamio.write(b"\r\n") return result
    @@ -1248,11 +1340,8 @@

    Methods

    """ Copy the content into file """ if self.content is not None: self.content_file = "%d.tmp"%id(self) - try: - content = open(self.content_file, "wb") + with open(self.content_file, "wb") as content: content.write(self.content) - finally: - content.close() self.content = None return self.content_file @@ -1384,21 +1473,46 @@

    Methods

    async def read_content(self, streamio):
             """ Read the content of http request """
    -        length = int(self.headers.get(b"Content-Length","0"))
    +        if self.headers.get(b"Transfer-Encoding",b"") == b"chunked":
    +                length = await streamio.readline()
    +                length = eval(strings.tostrings(b"0x%s"%length.strip()))
    +                self.chunk_size = length
    +                chunk = True
    +        else:
    +                length = int(self.headers.get(b"Content-Length",b"0"))
    +                chunk = False
    +        await self.read_data(length, streamio, chunk)
    + + +
    +async def read_data(self, length, streamio, chunk=False) +
    +
    +

    Read data with length

    +
    + +Expand source code + +
    async def read_data(self, length, streamio, chunk=False):
    +        """ Read data with length """
             # If data small write in memory
             if length < 4096:
    -                self.content = b""
    -                while len(self.content) < length:
    -                        self.content += await streamio.read(int(self.headers.get(b"Content-Length","0")))
    +                if chunk is False or self.content is None:
    +                        self.content = b""
    +                data = b""
    +                while len(data) < length:
    +                        data += await streamio.read(length - len(data))
    +                self.content += data
             # Data too big write in file
             else:
                     self.content_file = "%d.tmp"%id(self)
    -                try:
    -                        content = open(self.content_file, "wb")
    +                if chunk is False:
    +                        attrib = "wb"
    +                else:
    +                        attrib = "ab"
    +                with open(self.content_file, attrib) as content:
                             while content.tell() < length:
    -                                content.write(await streamio.read(int(self.headers.get(b"Content-Length","0"))))
    -                finally:
    -                        content.close()
    + content.write(await streamio.read(length - len(self.content)))
    @@ -1433,7 +1547,7 @@

    Methods

    async def serialize_body(self, streamio):
             """ Serialize body """
             result = 0
    -        noEnd = False
    +        no_end = False
             # If content existing
             if self.content is not None:
                     try:
    @@ -1444,8 +1558,9 @@ 

    Methods

    else: # Serialize object result += await self.content.serialize(streamio) - noEnd = True + no_end = True except Exception as err: + logger.syslog(err) # Serialize error detected result += await streamio.write(b'Content-Type: text/plain\r\n\r\n') result += await streamio.write(strings.tostrings(logger.exception(err))) @@ -1474,7 +1589,7 @@

    Methods

    if self.headers[b"Content-Type"] != b"multipart/x-mixed-replace": result += await streamio.write(b"--") - if noEnd is False: + if no_end is False: # Terminate serialize request or response result += await streamio.write(b"\r\n") return result
    @@ -1660,16 +1775,19 @@

    Methods

    data = await streamio.readline() if data != b"": spl = data.split() - self.method = spl[0] - path = spl[1] - proto = spl[2] - if self.request is False: - self.status = path - paths = path.split(b"?", 1) - if len(paths) > 1: - self.unserialize_params(paths[1]) - self.path = self.unquote(paths[0]) - await self.unserialize_headers(streamio)
    + if len(spl) >= 2: + self.method = spl[0] + path = spl[1] + if self.request is False: + self.status = path + paths = path.split(b"?", 1) + if len(paths) > 1: + self.unserialize_params(paths[1]) + self.path = self.unquote(paths[0]) + + await self.unserialize_headers(streamio) + else: + await self.read_content(streamio)
    @@ -1719,13 +1837,13 @@

    Methods

    param = [self.unquote(x) for x in pair.split(b"=", 1)] if len(param) == 1: param.append(True) - previousValue = self.params.get(param[0]) - if previousValue is not None: - if previousValue == b'0' and param[1] == b'': + previous_value = self.params.get(param[0]) + if previous_value is not None: + if previous_value == b'0' and param[1] == b'': self.params[param[0]] = b'1' else: - if not isinstance(previousValue, list): - self.params[param[0]] = [previousValue] + if not isinstance(previous_value, list): + self.params[param[0]] = [previous_value] self.params[param[0]].append(param[1]) else: self.params[param[0]] = param[1]
    @@ -1757,7 +1875,7 @@

    Methods

    streamio = self.streamio await self.unserialize(streamio) - async def send(self, streamio): + async def send(self, streamio=None): """ Send request to server """ if streamio is None: streamio = self.streamio @@ -1786,7 +1904,7 @@

    Methods

    -async def send(self, streamio) +async def send(self, streamio=None)

    Send request to server

    @@ -1794,7 +1912,7 @@

    Methods

    Expand source code -
    async def send(self, streamio):
    +
    async def send(self, streamio=None):
             """ Send request to server """
             if streamio is None:
                     streamio = self.streamio
    @@ -1817,6 +1935,7 @@ 

    Inherited members

  • get_status
  • quote
  • read_content
  • +
  • read_data
  • serialize
  • serialize_body
  • serialize_header
  • @@ -1850,6 +1969,7 @@

    Inherited members

    def __init__(self, streamio, remoteaddr= b"", port = 0, name = ""): """ Constructor """ Http.__init__(self, request=False, remoteaddr=remoteaddr, port=port, name=name) + self.chunk_size = 0 self.streamio = streamio async def send(self, content=None, status=b"200", headers=None): @@ -1867,17 +1987,27 @@

    Inherited members

    """ Send error to the client web browser """ return await self.send(status=status, content=content) + async def send_not_found(self, err=None): + """ Send page not found """ + if err is None: + content = b"" + elif type(err) == type(b"") or type(err) == type(""): + content = strings.tobytes(err) + else: + content = strings.tobytes(logger.exception(err)) + return await self.send_error(status=b"404", content=content) + async def send_ok(self, content=None): """ Send ok to the client web browser """ return await self.send_error(status=b"200", content=content) - async def send_file(self, filename, mimeType=None, headers=None, base64=False): + async def send_file(self, filename, mime_type=None, headers=None, base64=False): """ Send a file to the client web browser """ - return await self.send(content=ContentFile(filename, mimeType, base64), status=b"200", headers=headers) + return await self.send(content=ContentFile(filename, mime_type, base64), status=b"200", headers=headers) - async def send_buffer(self, filename, buffer, mimeType=None, headers=None): + async def send_buffer(self, filename, buffer, mime_type=None, headers=None): """ Send a file to the client web browser """ - return await self.send(content=ContentBuffer(filename, buffer, mimeType), status=b"200", headers=headers) + return await self.send(content=ContentBuffer(filename, buffer, mime_type), status=b"200", headers=headers) async def send_page(self, page): """ Send a template page to the client web browser """ @@ -1935,7 +2065,7 @@

    Methods

    -async def send_buffer(self, filename, buffer, mimeType=None, headers=None) +async def send_buffer(self, filename, buffer, mime_type=None, headers=None)

    Send a file to the client web browser

    @@ -1943,9 +2073,9 @@

    Methods

    Expand source code -
    async def send_buffer(self, filename, buffer, mimeType=None, headers=None):
    +
    async def send_buffer(self, filename, buffer, mime_type=None, headers=None):
             """ Send a file to the client web browser """
    -        return await self.send(content=ContentBuffer(filename, buffer, mimeType), status=b"200", headers=headers)
    + return await self.send(content=ContentBuffer(filename, buffer, mime_type), status=b"200", headers=headers)
    @@ -1963,7 +2093,7 @@

    Methods

    -async def send_file(self, filename, mimeType=None, headers=None, base64=False) +async def send_file(self, filename, mime_type=None, headers=None, base64=False)

    Send a file to the client web browser

    @@ -1971,9 +2101,29 @@

    Methods

    Expand source code -
    async def send_file(self, filename, mimeType=None, headers=None, base64=False):
    +
    async def send_file(self, filename, mime_type=None, headers=None, base64=False):
             """ Send a file to the client web browser """
    -        return await self.send(content=ContentFile(filename, mimeType, base64), status=b"200", headers=headers)
    + return await self.send(content=ContentFile(filename, mime_type, base64), status=b"200", headers=headers)
    + +
    +
    +async def send_not_found(self, err=None) +
    +
    +

    Send page not found

    +
    + +Expand source code + +
    async def send_not_found(self, err=None):
    +        """ Send page not found """
    +        if err is None:
    +                content = b""
    +        elif type(err) == type(b"") or type(err) == type(""):
    +                content = strings.tobytes(err)
    +        else:
    +                content = strings.tobytes(logger.exception(err))
    +        return await self.send_error(status=b"404", content=content)
    @@ -2022,6 +2172,7 @@

    Inherited members

  • get_status
  • quote
  • read_content
  • +
  • read_data
  • serialize
  • serialize_body
  • serialize_header
  • @@ -2146,21 +2297,17 @@

    Inherited members

    async def serialize(self, identifier, streamio): """ Serialize multi part file """ result = await self.serialize_header(identifier, streamio) - try: - part = b"" - file = open(strings.tostrings(self.filename),"rb") + with open(strings.tostrings(self.filename),"rb") as file: part = file.read() - finally: - file.close() result += await streamio.write(b"\r\n%s\r\n"%part) result += await streamio.write(b"--%s"%identifier) return result async def get_size(self, identifier): """ Get the size of multi part file """ - headerSize = await self.serialize_header(identifier, server.stream.Bytesio()) - fileSize = filesystem.filesize((strings.tostrings(self.filename))) - return headerSize + fileSize + 4 + len(identifier) + 2
    + header_size = await self.serialize_header(identifier, server.stream.Bytesio()) + file_size = filesystem.filesize((strings.tostrings(self.filename))) + return header_size + file_size + 4 + len(identifier) + 2

    Subclasses

      @@ -2179,9 +2326,9 @@

      Methods

      async def get_size(self, identifier):
               """ Get the size of multi part file """
      -        headerSize = await self.serialize_header(identifier, server.stream.Bytesio())
      -        fileSize = filesystem.filesize((strings.tostrings(self.filename)))
      -        return headerSize + fileSize + 4 + len(identifier) + 2
      + header_size = await self.serialize_header(identifier, server.stream.Bytesio()) + file_size = filesystem.filesize((strings.tostrings(self.filename))) + return header_size + file_size + 4 + len(identifier) + 2
      @@ -2196,12 +2343,8 @@

      Methods

      async def serialize(self, identifier, streamio):
               """ Serialize multi part file """
               result = await self.serialize_header(identifier, streamio)
      -        try:
      -                part = b""
      -                file = open(strings.tostrings(self.filename),"rb")
      +        with open(strings.tostrings(self.filename),"rb") as file:
                       part = file.read()
      -        finally:
      -                file.close()
               result += await streamio.write(b"\r\n%s\r\n"%part)
               result +=  await streamio.write(b"--%s"%identifier)
               return result
      @@ -2319,6 +2462,7 @@

      ContentFile

    • @@ -2341,6 +2485,7 @@

      get_status

    • quote
    • read_content
    • +
    • read_data
    • serialize
    • serialize_body
    • serialize_header
    • @@ -2371,6 +2516,7 @@

      send_buffer
    • send_error
    • send_file
    • +
    • send_not_found
    • send_ok
    • send_page
    diff --git a/doc/lib/server/httpserver.html b/doc/lib/server/httpserver.html index 58984df..ecfc590 100644 --- a/doc/lib/server/httpserver.html +++ b/doc/lib/server/httpserver.html @@ -38,6 +38,7 @@

    Module lib.server.httpserver

    # historically based on : # https://github.com/jczic/MicroWebSrv/blob/master/microWebSocket.py # but I have modified a lot, there must still be some original functions. +# pylint:disable=consider-using-f-string """ This class is used to manage an http server. This class contains few lines of code, this is to save memory. The core of the server is in the other class HttpServerCore, which is loaded into memory only when connecting an HTTP client. @@ -74,7 +75,7 @@

    Module lib.server.httpserver

    try: loader() except ModuleNotFound as err: - logger.syslog("Failed to preload html page, %s"%str(err)) + logger.syslog("Preload html page : %s"%str(err)) except Exception as err: logger.syslog(err) @@ -301,7 +302,7 @@

    Classes

    try: loader() except ModuleNotFound as err: - logger.syslog("Failed to preload html page, %s"%str(err)) + logger.syslog("Preload html page : %s"%str(err)) except Exception as err: logger.syslog(err) @@ -598,7 +599,7 @@

    Methods

    try: loader() except ModuleNotFound as err: - logger.syslog("Failed to preload html page, %s"%str(err)) + logger.syslog("Preload html page : %s"%str(err)) except Exception as err: logger.syslog(err)
    diff --git a/doc/lib/server/httpservercore.html b/doc/lib/server/httpservercore.html index 22e3e9d..d2c3a8a 100644 --- a/doc/lib/server/httpservercore.html +++ b/doc/lib/server/httpservercore.html @@ -38,7 +38,7 @@

    Module lib.server.httpservercore

    from server.httpserver import HttpServer from server.httprequest import HttpRequest, HttpResponse from server.stream import Stream -from tools import logger,strings +from tools import logger class HttpServerCore: """ Http server core, it instanciated only if a connection is done to the asynchronous class HttpServer then @@ -59,7 +59,7 @@

    Module lib.server.httpservercore

    # print(request.path) function, args = HttpServer.search_route(request) if function is None: - await response.send_error(status=b"404", content=b"Page not found") + await response.send_not_found() else: await function(request, response, args) except OSError as err: @@ -68,9 +68,10 @@

    Module lib.server.httpservercore

    # Ignore error pass else: - await response.send_error(status=b"404", content=strings.tobytes(logger.exception(err))) + await response.send_not_found(err) except Exception as err: - await response.send_error(status=b"404", content=strings.tobytes(logger.exception(err))) + logger.syslog(err) + await response.send_not_found(err) finally: await stream.close() @@ -115,7 +116,7 @@

    Classes

    # print(request.path) function, args = HttpServer.search_route(request) if function is None: - await response.send_error(status=b"404", content=b"Page not found") + await response.send_not_found() else: await function(request, response, args) except OSError as err: @@ -124,9 +125,10 @@

    Classes

    # Ignore error pass else: - await response.send_error(status=b"404", content=strings.tobytes(logger.exception(err))) + await response.send_not_found(err) except Exception as err: - await response.send_error(status=b"404", content=strings.tobytes(logger.exception(err))) + logger.syslog(err) + await response.send_not_found(err) finally: await stream.close() @@ -152,7 +154,7 @@

    Methods

    # print(request.path) function, args = HttpServer.search_route(request) if function is None: - await response.send_error(status=b"404", content=b"Page not found") + await response.send_not_found() else: await function(request, response, args) except OSError as err: @@ -161,9 +163,10 @@

    Methods

    # Ignore error pass else: - await response.send_error(status=b"404", content=strings.tobytes(logger.exception(err))) + await response.send_not_found(err) except Exception as err: - await response.send_error(status=b"404", content=strings.tobytes(logger.exception(err))) + logger.syslog(err) + await response.send_not_found(err) finally: await stream.close() diff --git a/doc/lib/server/index.html b/doc/lib/server/index.html index 142d62b..f66c301 100644 --- a/doc/lib/server/index.html +++ b/doc/lib/server/index.html @@ -84,6 +84,11 @@

    Sub-modules

    Class used to manage a list of notifier, and postpone notification if wifi station not yet connected

    +
    lib.server.openmeteo
    +
    +

    These classes are used to get meteo informations. +See https://open-meteo.com

    +
    lib.server.periodic

    Periodic task, wifi management, get wan_ip, synchronize time

    @@ -185,6 +190,7 @@

    Index

  • lib.server.httpserver
  • lib.server.httpservercore
  • lib.server.notifier
  • +
  • lib.server.openmeteo
  • lib.server.periodic
  • lib.server.ping
  • lib.server.presence
  • diff --git a/doc/lib/server/notifier.html b/doc/lib/server/notifier.html index 0348485..0d784d3 100644 --- a/doc/lib/server/notifier.html +++ b/doc/lib/server/notifier.html @@ -30,6 +30,7 @@

    Module lib.server.notifier

    # Distributed under MIT License
     # Copyright (c) 2021 Remi BERTHOLET
     """ Class used to manage a list of notifier, and postpone notification if wifi station not yet connected """
    +# pylint:disable=consider-using-f-string
     # pylint:disable=consider-using-enumerate
     import wifi
     from tools import logger,strings
    diff --git a/doc/lib/server/openmeteo.html b/doc/lib/server/openmeteo.html
    new file mode 100644
    index 0000000..5c421bf
    --- /dev/null
    +++ b/doc/lib/server/openmeteo.html
    @@ -0,0 +1,331 @@
    +
    +
    +
    +
    +
    +
    +lib.server.openmeteo API documentation
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    Module lib.server.openmeteo

    +
    +
    +

    These classes are used to get meteo informations. +See https://open-meteo.com

    +
    + +Expand source code + +
    # Distributed under MIT License
    +# Copyright (c) 2021 Remi BERTHOLET
    +""" These classes are used to get meteo informations.
    +See https://open-meteo.com """
    +# pylint:disable=wrong-import-position
    +import json
    +import uasyncio
    +from server.stream import *
    +from server.httprequest import *
    +import wifi
    +from tools import logger,strings
    +
    +class Meteo:
    +        """ Class that manages a push over notification """
    +        def __init__(self, host, port):
    +                """ Constructor
    +                host : hostname b"api.open-meteo.com"
    +                port : port of pushover (80) """
    +                self.host  = host
    +                self.port  = port
    +
    +        async def get(self, parameters, display=True):
    +                """ Get meteo """
    +                result = None
    +                if wifi.Station.is_active():
    +                        try:
    +                                streamio = None
    +                                # Open pushover connection
    +                                reader,writer = await uasyncio.open_connection(strings.tostrings(self.host), self.port)
    +                                streamio = Stream(reader, writer)
    +
    +                                # Create multipart request
    +                                request = HttpRequest(streamio)
    +                                request.set_method(b"GET")
    +                                request.set_path  (b"/v1/forecast"+parameters)
    +                                request.set_header(b"Host",self.host)
    +                                request.set_header(b"Accept",         b"*/*")
    +                                request.set_header(b"Connection",     b"keep-alive")
    +
    +                                response = await read_chunked(streamio, request)
    +                                result = strings.tostrings(response.get_content())
    +                                with open("res.json","wb") as file:
    +                                        file.write(response.get_content())
    +                                result = json.loads(result)
    +
    +                        except Exception as err:
    +                                logger.syslog(err)
    +                        finally:
    +                                if streamio:
    +                                        await streamio.close()
    +                else:
    +                        logger.syslog("Get meteo not sent : wifi not connected", display=display)
    +                return result
    +
    +async def read_chunked(streamio, request):
    +        """ Read http chunked response """
    +        response = HttpResponse(streamio)
    +        ack      = HttpResponse(streamio)
    +        await request.send()
    +
    +        while True:
    +                await response.receive()
    +                # print(response.get_content())
    +                if response.status == b"200":
    +                        if response.headers.get(b"Transfer-Encoding",b"") == b"chunked":
    +                                if response.chunk_size == 0:
    +                                        break
    +                                else:
    +                                        await ack.send_ok()
    +                        else:
    +                                break
    +                else:
    +                        break
    +        return response
    +
    +async def async_get_meteo(parameters, display=True):
    +        """ Asyncio get meteo (only in asyncio) """
    +        meteo = Meteo(host=b"api.open-meteo.com", port=80)
    +        return await meteo.get(parameters, display=display)
    +
    +def get_meteo(parameters):
    +        """ Get meteo function """
    +        loop = uasyncio.get_event_loop()
    +        loop.run_until_complete(async_get_meteo(parameters=parameters))
    +
    +
    +
    +
    +
    +
    +
    +

    Functions

    +
    +
    +async def async_get_meteo(parameters, display=True) +
    +
    +

    Asyncio get meteo (only in asyncio)

    +
    + +Expand source code + +
    async def async_get_meteo(parameters, display=True):
    +        """ Asyncio get meteo (only in asyncio) """
    +        meteo = Meteo(host=b"api.open-meteo.com", port=80)
    +        return await meteo.get(parameters, display=display)
    +
    +
    +
    +def get_meteo(parameters) +
    +
    +

    Get meteo function

    +
    + +Expand source code + +
    def get_meteo(parameters):
    +        """ Get meteo function """
    +        loop = uasyncio.get_event_loop()
    +        loop.run_until_complete(async_get_meteo(parameters=parameters))
    +
    +
    +
    +async def read_chunked(streamio, request) +
    +
    +

    Read http chunked response

    +
    + +Expand source code + +
    async def read_chunked(streamio, request):
    +        """ Read http chunked response """
    +        response = HttpResponse(streamio)
    +        ack      = HttpResponse(streamio)
    +        await request.send()
    +
    +        while True:
    +                await response.receive()
    +                # print(response.get_content())
    +                if response.status == b"200":
    +                        if response.headers.get(b"Transfer-Encoding",b"") == b"chunked":
    +                                if response.chunk_size == 0:
    +                                        break
    +                                else:
    +                                        await ack.send_ok()
    +                        else:
    +                                break
    +                else:
    +                        break
    +        return response
    +
    +
    +
    +
    +
    +

    Classes

    +
    +
    +class Meteo +(host, port) +
    +
    +

    Class that manages a push over notification

    +

    Constructor +host : hostname b"api.open-meteo.com" +port : port of pushover (80)

    +
    + +Expand source code + +
    class Meteo:
    +        """ Class that manages a push over notification """
    +        def __init__(self, host, port):
    +                """ Constructor
    +                host : hostname b"api.open-meteo.com"
    +                port : port of pushover (80) """
    +                self.host  = host
    +                self.port  = port
    +
    +        async def get(self, parameters, display=True):
    +                """ Get meteo """
    +                result = None
    +                if wifi.Station.is_active():
    +                        try:
    +                                streamio = None
    +                                # Open pushover connection
    +                                reader,writer = await uasyncio.open_connection(strings.tostrings(self.host), self.port)
    +                                streamio = Stream(reader, writer)
    +
    +                                # Create multipart request
    +                                request = HttpRequest(streamio)
    +                                request.set_method(b"GET")
    +                                request.set_path  (b"/v1/forecast"+parameters)
    +                                request.set_header(b"Host",self.host)
    +                                request.set_header(b"Accept",         b"*/*")
    +                                request.set_header(b"Connection",     b"keep-alive")
    +
    +                                response = await read_chunked(streamio, request)
    +                                result = strings.tostrings(response.get_content())
    +                                with open("res.json","wb") as file:
    +                                        file.write(response.get_content())
    +                                result = json.loads(result)
    +
    +                        except Exception as err:
    +                                logger.syslog(err)
    +                        finally:
    +                                if streamio:
    +                                        await streamio.close()
    +                else:
    +                        logger.syslog("Get meteo not sent : wifi not connected", display=display)
    +                return result
    +
    +

    Methods

    +
    +
    +async def get(self, parameters, display=True) +
    +
    +

    Get meteo

    +
    + +Expand source code + +
    async def get(self, parameters, display=True):
    +        """ Get meteo """
    +        result = None
    +        if wifi.Station.is_active():
    +                try:
    +                        streamio = None
    +                        # Open pushover connection
    +                        reader,writer = await uasyncio.open_connection(strings.tostrings(self.host), self.port)
    +                        streamio = Stream(reader, writer)
    +
    +                        # Create multipart request
    +                        request = HttpRequest(streamio)
    +                        request.set_method(b"GET")
    +                        request.set_path  (b"/v1/forecast"+parameters)
    +                        request.set_header(b"Host",self.host)
    +                        request.set_header(b"Accept",         b"*/*")
    +                        request.set_header(b"Connection",     b"keep-alive")
    +
    +                        response = await read_chunked(streamio, request)
    +                        result = strings.tostrings(response.get_content())
    +                        with open("res.json","wb") as file:
    +                                file.write(response.get_content())
    +                        result = json.loads(result)
    +
    +                except Exception as err:
    +                        logger.syslog(err)
    +                finally:
    +                        if streamio:
    +                                await streamio.close()
    +        else:
    +                logger.syslog("Get meteo not sent : wifi not connected", display=display)
    +        return result
    +
    +
    +
    +
    +
    +
    +
    + +
    + + + \ No newline at end of file diff --git a/doc/lib/server/periodic.html b/doc/lib/server/periodic.html index e3dae59..e44f098 100644 --- a/doc/lib/server/periodic.html +++ b/doc/lib/server/periodic.html @@ -29,11 +29,12 @@

    Module lib.server.periodic

    # Distributed under MIT License
     # Copyright (c) 2021 Remi BERTHOLET
    +# pylint:disable=consider-using-f-string
     """ Periodic task, wifi management, get wan_ip, synchronize time """
     import uasyncio
     from server.server import ServerConfig, Server
     import wifi
    -from tools import battery, lang, awake, tasking, watchdog, info, system
    +from tools import lang, awake, tasking, watchdog, info, system, support
     
     async def periodic_task():
             """ Periodic task """
    @@ -47,6 +48,8 @@ 

    Module lib.server.periodic

    self.server_config = ServerConfig() self.server_config.load() self.get_login_state = None + self.last_success_notification = None + self.current_time = 0 watchdog.WatchDog.start(watchdog.SHORT_WATCH_DOG) async def check_login(self): @@ -63,12 +66,24 @@

    Module lib.server.periodic

    if login is not None: from server.notifier import Notifier if login: - await Notifier.notify(lang.login_success_detected, display=False, enabled=self.server_config.notify) + if self.last_success_notification is None: + notif = True + self.last_success_notification = self.current_time + elif self.last_success_notification + 5*60 < self.current_time: + self.last_success_notification = self.current_time + notif = True + else: + notif = False + if notif: + await Notifier.notify(lang.login_success_detected, display=False, enabled=self.server_config.notify) else: await Notifier.notify(lang.login_failed_detected, display=False, enabled=self.server_config.notify) async def task(self): """ Periodic task method """ + if support.battery(): + from tools import battery + polling_id = 0 watchdog.WatchDog.start(watchdog.SHORT_WATCH_DOG) @@ -89,12 +104,14 @@

    Module lib.server.periodic

    # Reset brownout counter if wifi connected if wifi.Wifi.is_wan_connected(): - battery.Battery.reset_brownout() + if support.battery(): + battery.Battery.reset_brownout() # Reset watch dog watchdog.WatchDog.feed() await uasyncio.sleep(1) polling_id += 1 + self.current_time += 1 # Check if any problems have occurred and if a reboot is needed if polling_id % 3607: @@ -146,6 +163,8 @@

    Classes

    self.server_config = ServerConfig() self.server_config.load() self.get_login_state = None + self.last_success_notification = None + self.current_time = 0 watchdog.WatchDog.start(watchdog.SHORT_WATCH_DOG) async def check_login(self): @@ -162,12 +181,24 @@

    Classes

    if login is not None: from server.notifier import Notifier if login: - await Notifier.notify(lang.login_success_detected, display=False, enabled=self.server_config.notify) + if self.last_success_notification is None: + notif = True + self.last_success_notification = self.current_time + elif self.last_success_notification + 5*60 < self.current_time: + self.last_success_notification = self.current_time + notif = True + else: + notif = False + if notif: + await Notifier.notify(lang.login_success_detected, display=False, enabled=self.server_config.notify) else: await Notifier.notify(lang.login_failed_detected, display=False, enabled=self.server_config.notify) async def task(self): """ Periodic task method """ + if support.battery(): + from tools import battery + polling_id = 0 watchdog.WatchDog.start(watchdog.SHORT_WATCH_DOG) @@ -188,12 +219,14 @@

    Classes

    # Reset brownout counter if wifi connected if wifi.Wifi.is_wan_connected(): - battery.Battery.reset_brownout() + if support.battery(): + battery.Battery.reset_brownout() # Reset watch dog watchdog.WatchDog.feed() await uasyncio.sleep(1) polling_id += 1 + self.current_time += 1 # Check if any problems have occurred and if a reboot is needed if polling_id % 3607: @@ -225,7 +258,16 @@

    Methods

    if login is not None: from server.notifier import Notifier if login: - await Notifier.notify(lang.login_success_detected, display=False, enabled=self.server_config.notify) + if self.last_success_notification is None: + notif = True + self.last_success_notification = self.current_time + elif self.last_success_notification + 5*60 < self.current_time: + self.last_success_notification = self.current_time + notif = True + else: + notif = False + if notif: + await Notifier.notify(lang.login_success_detected, display=False, enabled=self.server_config.notify) else: await Notifier.notify(lang.login_failed_detected, display=False, enabled=self.server_config.notify)
    @@ -241,6 +283,9 @@

    Methods

    async def task(self):
             """ Periodic task method """
    +        if support.battery():
    +                from tools import battery
    +
             polling_id = 0
     
             watchdog.WatchDog.start(watchdog.SHORT_WATCH_DOG)
    @@ -261,12 +306,14 @@ 

    Methods

    # Reset brownout counter if wifi connected if wifi.Wifi.is_wan_connected(): - battery.Battery.reset_brownout() + if support.battery(): + battery.Battery.reset_brownout() # Reset watch dog watchdog.WatchDog.feed() await uasyncio.sleep(1) polling_id += 1 + self.current_time += 1 # Check if any problems have occurred and if a reboot is needed if polling_id % 3607: diff --git a/doc/lib/server/ping.html b/doc/lib/server/ping.html index 4567d44..091bfac 100644 --- a/doc/lib/server/ping.html +++ b/doc/lib/server/ping.html @@ -38,6 +38,7 @@

    Module lib.server.ping

    # Author: Olav Morken # https://github.com/olavmrk/python-ping/blob/master/ping.py # @data: bytes +# pylint:disable=consider-using-f-string """ Ping network class """ import time import random diff --git a/doc/lib/server/presence.html b/doc/lib/server/presence.html index 193cf7f..9fd1e17 100644 --- a/doc/lib/server/presence.html +++ b/doc/lib/server/presence.html @@ -29,6 +29,7 @@

    Module lib.server.presence

    # Distributed under MIT License
     # Copyright (c) 2021 Remi BERTHOLET
    +# pylint:disable=consider-using-f-string
     """ Presence detection (determine if an occupant is present in the house) """
     import time
     import wifi
    diff --git a/doc/lib/server/pushover.html b/doc/lib/server/pushover.html
    index 2a3f318..96f43f5 100644
    --- a/doc/lib/server/pushover.html
    +++ b/doc/lib/server/pushover.html
    @@ -79,7 +79,6 @@ 

    Module lib.server.pushover

    request.set_method(b"POST") request.set_path (b"/1/messages.json") request.set_header(b"Host",self.host) - request.set_header(b"Accept-Encoding",b"gzip, deflate") request.set_header(b"Accept", b"*/*") request.set_header(b"Connection", b"keep-alive") request.set_header(b"Content-Type", b"multipart/form-data") @@ -256,7 +255,6 @@

    Classes

    request.set_method(b"POST") request.set_path (b"/1/messages.json") request.set_header(b"Host",self.host) - request.set_header(b"Accept-Encoding",b"gzip, deflate") request.set_header(b"Accept", b"*/*") request.set_header(b"Connection", b"keep-alive") request.set_header(b"Content-Type", b"multipart/form-data") @@ -331,7 +329,6 @@

    Methods

    request.set_method(b"POST") request.set_path (b"/1/messages.json") request.set_header(b"Host",self.host) - request.set_header(b"Accept-Encoding",b"gzip, deflate") request.set_header(b"Accept", b"*/*") request.set_header(b"Connection", b"keep-alive") request.set_header(b"Content-Type", b"multipart/form-data") diff --git a/doc/lib/server/server.html b/doc/lib/server/server.html index 58d9a7a..9f9a03b 100644 --- a/doc/lib/server/server.html +++ b/doc/lib/server/server.html @@ -29,11 +29,12 @@

    Module lib.server.server

    # Distributed under MIT License
     # Copyright (c) 2021 Remi BERTHOLET
    +# pylint:disable=consider-using-f-string
     """ Manage server class """
     import time
     import wifi
     import uasyncio
    -from tools import jsonconfig,logger,builddate,lang,watchdog,info,strings,sdcard
    +from tools import jsonconfig,logger,builddate,region,lang,watchdog,info,strings,support,filesystem,date
     if info.iscamera():
             from video.video import Camera
     
    @@ -47,7 +48,10 @@ 

    Module lib.server.server

    self.telnet = True self.wanip = True self.notify = True - self.server_postponed = 7 + if filesystem.ismicropython(): + self.server_postponed = 7 + else: + self.server_postponed = 1 class ServerContext: """ Context to initialize the servers """ @@ -62,7 +66,7 @@

    Module lib.server.server

    self.get_wan_ip_async = None self.wan_ip = None self.server_config = ServerConfig() - self.region_config = lang.RegionConfig() + self.region_config = region.RegionConfig() self.set_date = None self.one_per_day = None self.flushed = False @@ -82,6 +86,22 @@

    Module lib.server.server

    slow_speed = [None] tasks = {} context = None + daily_notifier = None + + @staticmethod + def set_daily_notifier(callback): + """ Replace the daily notification (callback which return a string with message to notify) """ + Server.daily_notifier = callback + + @staticmethod + def default_daily_notifier(): + """ Return the default message notification """ + message = "\n - Lan Ip : %s\n"%wifi.Station.get_info()[0] + message += " - Wan Ip : %s\n"%Server.context.wan_ip + message += " - Uptime : %s\n"%strings.tostrings(info.uptime()) + message += " - %s : %s\n"%(strings.tostrings(lang.memory_label), strings.tostrings(info.meminfo())) + message += " - %s : %s\n"%(strings.tostrings(lang.flash_label), strings.tostrings(info.flashinfo())) + return message @staticmethod def suspend(): @@ -150,7 +170,7 @@

    Module lib.server.server

    preload : True force the load of page at the start, False the load of page is done a the first http connection (Takes time on first connection) """ Server.context = ServerContext(loop, page_loader, preload, http_port) - logger.syslog(info.sysinfo(display=False)) + logger.syslog(info.sysinfo()) logger.syslog("Version: %s"%strings.tostrings(builddate.date)) from server.periodic import periodic_task @@ -183,11 +203,12 @@

    Module lib.server.server

    wifi.Wifi.wan_disconnected() if forced: - await Server.context.notifier.notify("\n - Lan Ip : %s\n - Wan Ip : %s\n - Uptime : %s\n - %s"%( - wifi.Station.get_info()[0], - Server.context.wan_ip, - info.uptime(), - strings.tostrings(info.flashinfo(mountpoint=sdcard.SdCard.get_mountpoint(), display=False)))) + try: + # pylint:disable=not-callable + message = Server.daily_notifier() + except: + message = Server.default_daily_notifier() + await Server.context.notifier.notify(message) @staticmethod async def synchronize_time(): @@ -234,9 +255,9 @@

    Module lib.server.server

    @staticmethod def is_one_per_day(): """ Indicates if the action must be done on per day """ - date = strings.date_to_bytes()[:14] - if Server.context.one_per_day is None or (date[-2:] == b"12" and date != Server.context.one_per_day): - Server.context.one_per_day = date + date_ = date.date_to_bytes()[:14] + if Server.context.one_per_day is None or (date_[-2:] == b"12" and date_ != Server.context.one_per_day): + Server.context.one_per_day = date_ return True return False @@ -256,9 +277,10 @@

    Module lib.server.server

    # If telnet activated if Server.context.server_config.telnet: - # Load and start telnet - import server.telnet - server.telnet.start() + if support.telnet(): + # Load and start telnet + import server.telnet + server.telnet.start() # If ftp activated if Server.context.server_config.ftp: @@ -364,6 +386,22 @@

    Classes

    slow_speed = [None] tasks = {} context = None + daily_notifier = None + + @staticmethod + def set_daily_notifier(callback): + """ Replace the daily notification (callback which return a string with message to notify) """ + Server.daily_notifier = callback + + @staticmethod + def default_daily_notifier(): + """ Return the default message notification """ + message = "\n - Lan Ip : %s\n"%wifi.Station.get_info()[0] + message += " - Wan Ip : %s\n"%Server.context.wan_ip + message += " - Uptime : %s\n"%strings.tostrings(info.uptime()) + message += " - %s : %s\n"%(strings.tostrings(lang.memory_label), strings.tostrings(info.meminfo())) + message += " - %s : %s\n"%(strings.tostrings(lang.flash_label), strings.tostrings(info.flashinfo())) + return message @staticmethod def suspend(): @@ -432,7 +470,7 @@

    Classes

    preload : True force the load of page at the start, False the load of page is done a the first http connection (Takes time on first connection) """ Server.context = ServerContext(loop, page_loader, preload, http_port) - logger.syslog(info.sysinfo(display=False)) + logger.syslog(info.sysinfo()) logger.syslog("Version: %s"%strings.tostrings(builddate.date)) from server.periodic import periodic_task @@ -465,11 +503,12 @@

    Classes

    wifi.Wifi.wan_disconnected() if forced: - await Server.context.notifier.notify("\n - Lan Ip : %s\n - Wan Ip : %s\n - Uptime : %s\n - %s"%( - wifi.Station.get_info()[0], - Server.context.wan_ip, - info.uptime(), - strings.tostrings(info.flashinfo(mountpoint=sdcard.SdCard.get_mountpoint(), display=False)))) + try: + # pylint:disable=not-callable + message = Server.daily_notifier() + except: + message = Server.default_daily_notifier() + await Server.context.notifier.notify(message) @staticmethod async def synchronize_time(): @@ -516,9 +555,9 @@

    Classes

    @staticmethod def is_one_per_day(): """ Indicates if the action must be done on per day """ - date = strings.date_to_bytes()[:14] - if Server.context.one_per_day is None or (date[-2:] == b"12" and date != Server.context.one_per_day): - Server.context.one_per_day = date + date_ = date.date_to_bytes()[:14] + if Server.context.one_per_day is None or (date_[-2:] == b"12" and date_ != Server.context.one_per_day): + Server.context.one_per_day = date_ return True return False @@ -538,9 +577,10 @@

    Classes

    # If telnet activated if Server.context.server_config.telnet: - # Load and start telnet - import server.telnet - server.telnet.start() + if support.telnet(): + # Load and start telnet + import server.telnet + server.telnet.start() # If ftp activated if Server.context.server_config.ftp: @@ -627,6 +667,10 @@

    Class variables

    +
    var daily_notifier
    +
    +
    +
    var slow_speed
    @@ -642,6 +686,26 @@

    Class variables

    Static methods

    +
    +def default_daily_notifier() +
    +
    +

    Return the default message notification

    +
    + +Expand source code + +
    @staticmethod
    +def default_daily_notifier():
    +        """ Return the default message notification """
    +        message = "\n - Lan Ip : %s\n"%wifi.Station.get_info()[0]
    +        message += " - Wan Ip : %s\n"%Server.context.wan_ip
    +        message += " - Uptime : %s\n"%strings.tostrings(info.uptime())
    +        message += " - %s : %s\n"%(strings.tostrings(lang.memory_label), strings.tostrings(info.meminfo()))
    +        message += " - %s : %s\n"%(strings.tostrings(lang.flash_label), strings.tostrings(info.flashinfo()))
    +        return message
    +
    +
    def init(loop=None, page_loader=None, preload=False, http_port=80)
    @@ -663,7 +727,7 @@

    Static methods

    preload : True force the load of page at the start, False the load of page is done a the first http connection (Takes time on first connection) """ Server.context = ServerContext(loop, page_loader, preload, http_port) - logger.syslog(info.sysinfo(display=False)) + logger.syslog(info.sysinfo()) logger.syslog("Version: %s"%strings.tostrings(builddate.date)) from server.periodic import periodic_task @@ -701,9 +765,9 @@

    Static methods

    @staticmethod
     def is_one_per_day():
             """ Indicates if the action must be done on per day """
    -        date = strings.date_to_bytes()[:14]
    -        if Server.context.one_per_day is None or (date[-2:] == b"12" and date != Server.context.one_per_day):
    -                Server.context.one_per_day = date
    +        date_ = date.date_to_bytes()[:14]
    +        if Server.context.one_per_day is None or (date_[-2:] == b"12" and date_ != Server.context.one_per_day):
    +                Server.context.one_per_day = date_
                     return True
             return False
    @@ -812,6 +876,21 @@

    Static methods

    Server.suspended[0] = False
    +
    +def set_daily_notifier(callback) +
    +
    +

    Replace the daily notification (callback which return a string with message to notify)

    +
    + +Expand source code + +
    @staticmethod
    +def set_daily_notifier(callback):
    +        """ Replace the daily notification (callback which return a string with message to notify) """
    +        Server.daily_notifier = callback
    +
    +
    def slow_down(duration=20)
    @@ -852,9 +931,10 @@

    Static methods

    # If telnet activated if Server.context.server_config.telnet: - # Load and start telnet - import server.telnet - server.telnet.start() + if support.telnet(): + # Load and start telnet + import server.telnet + server.telnet.start() # If ftp activated if Server.context.server_config.ftp: @@ -981,11 +1061,12 @@

    Static methods

    wifi.Wifi.wan_disconnected() if forced: - await Server.context.notifier.notify("\n - Lan Ip : %s\n - Wan Ip : %s\n - Uptime : %s\n - %s"%( - wifi.Station.get_info()[0], - Server.context.wan_ip, - info.uptime(), - strings.tostrings(info.flashinfo(mountpoint=sdcard.SdCard.get_mountpoint(), display=False))))
    + try: + # pylint:disable=not-callable + message = Server.daily_notifier() + except: + message = Server.default_daily_notifier() + await Server.context.notifier.notify(message)
    @@ -1054,7 +1135,10 @@

    Static methods

    self.telnet = True self.wanip = True self.notify = True - self.server_postponed = 7
    + if filesystem.ismicropython(): + self.server_postponed = 7 + else: + self.server_postponed = 1

    Ancestors

      @@ -1084,7 +1168,7 @@

      Ancestors

      self.get_wan_ip_async = None self.wan_ip = None self.server_config = ServerConfig() - self.region_config = lang.RegionConfig() + self.region_config = region.RegionConfig() self.set_date = None self.one_per_day = None self.flushed = False @@ -1117,14 +1201,17 @@

      Index

      • Server

        -
          +
          • context
          • +
          • daily_notifier
          • +
          • default_daily_notifier
          • init
          • is_all_waiting
          • is_one_per_day
          • is_slow
          • manage
          • resume
          • +
          • set_daily_notifier
          • slow_down
          • slow_speed
          • start_server
          • diff --git a/doc/lib/server/sessions.html b/doc/lib/server/sessions.html index 3383ff3..1536d7c 100644 --- a/doc/lib/server/sessions.html +++ b/doc/lib/server/sessions.html @@ -34,7 +34,7 @@

            Module lib.server.sessions

            """ Class used to store http connection sessions, it is useful if you define an user and password, on your site """ import time -from tools import encryption, strings +from tools import encryption, strings, date class Sessions: """ Class manage an http sessions """ @@ -43,7 +43,7 @@

            Module lib.server.sessions

            @staticmethod def create(duration): """ Create new session """ - session = encryption.gethash(strings.date_to_bytes()) + session = encryption.gethash(date.date_to_bytes()) Sessions.sessions.append((session, time.time() + duration)) return session @@ -100,7 +100,7 @@

            Classes

            @staticmethod def create(duration): """ Create new session """ - session = encryption.gethash(strings.date_to_bytes()) + session = encryption.gethash(date.date_to_bytes()) Sessions.sessions.append((session, time.time() + duration)) return session @@ -174,7 +174,7 @@

            Static methods

            @staticmethod
             def create(duration):
                     """ Create new session """
            -        session = encryption.gethash(strings.date_to_bytes())
            +        session = encryption.gethash(date.date_to_bytes())
                     Sessions.sessions.append((session, time.time() + duration))
                     return session
            diff --git a/doc/lib/server/telnet.html b/doc/lib/server/telnet.html index 413d197..f4c94a5 100644 --- a/doc/lib/server/telnet.html +++ b/doc/lib/server/telnet.html @@ -29,6 +29,7 @@

            Module lib.server.telnet

            # Based on https://github.com/cpopp/MicroTelnetServer/blob/master/utelnet/utelnetserver.py
             # Add user login
            +# pylint:disable=consider-using-f-string
             # pylint:disable=consider-using-enumerate
             """ Telnet class """
             import sys
            @@ -95,6 +96,10 @@ 

            Module lib.server.telnet

            result = self.input_buffer self.input_buffer = b"" self.input_char = b"" + # If tabulation sequence detected + elif self.input_char[0] == 0x09: + # Ignore escape sequence + self.input_char = b"" # If line feed detected elif self.input_char[0] == 0x0A: self.input_char = b"" @@ -244,6 +249,7 @@

            Module lib.server.telnet

            def stop(): """ Stop telnet server """ import uos + # pylint:disable=global-variable-not-assigned global server_socket, last_client_socket uos.dupterm(None) if server_socket: @@ -351,6 +357,7 @@

            Functions

            def stop():
                     """ Stop telnet server """
                     import uos
            +        # pylint:disable=global-variable-not-assigned
                     global server_socket, last_client_socket
                     uos.dupterm(None)
                     if server_socket:
            @@ -423,6 +430,10 @@ 

            Classes

            result = self.input_buffer self.input_buffer = b"" self.input_char = b"" + # If tabulation sequence detected + elif self.input_char[0] == 0x09: + # Ignore escape sequence + self.input_char = b"" # If line feed detected elif self.input_char[0] == 0x0A: self.input_char = b"" @@ -568,6 +579,10 @@

            Methods

            result = self.input_buffer self.input_buffer = b"" self.input_char = b"" + # If tabulation sequence detected + elif self.input_char[0] == 0x09: + # Ignore escape sequence + self.input_char = b"" # If line feed detected elif self.input_char[0] == 0x0A: self.input_char = b"" diff --git a/doc/lib/server/timesetting.html b/doc/lib/server/timesetting.html index a2ba62a..4a9629f 100644 --- a/doc/lib/server/timesetting.html +++ b/doc/lib/server/timesetting.html @@ -29,9 +29,9 @@

            Module lib.server.timesetting

            # Distributed under MIT License
             # Copyright (c) 2021 Remi BERTHOLET
            +# pylint:disable=consider-using-f-string
             """ Function which sets the internal clock of the card based on an ntp server """
            -import time
            -from tools import logger,strings,filesystem
            +from tools import logger, strings, date
             
             def get_ntp_time():
                     """ Return the time from a NTP server """
            @@ -58,7 +58,7 @@ 

            Module lib.server.timesetting

            def set_time(currenttime): """ Change the current time """ try: - newtime = strings.local_time(currenttime) + newtime = date.local_time(currenttime) year,month,day,hour,minute,second,weekday,yearday = newtime[:8] import machine @@ -66,31 +66,22 @@

            Module lib.server.timesetting

            except Exception as exc: logger.syslog("Cannot set time '%s'"%exc) -def mktime(t): - """ Portable mktime """ - year,month,day,hour,minute,second,weekday,yearday = t - if filesystem.ismicropython(): - return time.mktime((year, month, day, hour, minute, second, weekday, yearday)) - else: - return time.mktime((year, month, day, hour, minute, second, weekday, yearday, 0)) - - def calc_local_time(currenttime, offsetTime=+1, dst=True): """ Calculate the local time """ - year,month,day,hour,minute,second,weekday,yearday = strings.local_time(currenttime)[:8] + year,month,day,hour,minute,second,weekday,yearday = date.local_time(currenttime)[:8] # Get the day of the last sunday of march - march_end_weekday = strings.local_time(mktime((year, 3, 31, 0, 0, 0, 0, 0)))[6] + march_end_weekday = date.local_time(date.mktime((year, 3, 31, 0, 0, 0, 0, 0)))[6] start_day_dst = 31-((1+march_end_weekday)%7) # Get the day of the last sunday of october - october_end_weekday = strings.local_time(mktime((year,10, 30, 0, 0, 0, 0, 0)))[6] + october_end_weekday = date.local_time(date.mktime((year,10, 30, 0, 0, 0, 0, 0)))[6] end_day_dst = 30-((1+october_end_weekday)%7) - start_DST = mktime((year,3 ,start_day_dst,1,0,0,0,0)) - end_DST = mktime((year,10,end_day_dst ,1,0,0,0,0)) + start_DST = date.mktime((year,3 ,start_day_dst,1,0,0,0,0)) + end_DST = date.mktime((year,10,end_day_dst ,1,0,0,0,0)) - now = mktime((year,month,day,hour,minute,second,weekday,yearday)) + now = date.mktime((year,month,day,hour,minute,second,weekday,yearday)) if dst and now > start_DST and now < end_DST : # we are before last sunday of october return now+(offsetTime*3600)+3600 # DST: UTC+dst*H + 1 @@ -105,7 +96,7 @@

            Module lib.server.timesetting

            if currenttime > 0: set_time(currenttime) if display: - logger.syslog("Date updated : %s"%(strings.date_to_string())) + logger.syslog("Date updated : %s"%(date.date_to_string())) return currenttime return 0
            @@ -128,20 +119,20 @@

            Functions

            def calc_local_time(currenttime, offsetTime=+1, dst=True):
                     """ Calculate the local time """
            -        year,month,day,hour,minute,second,weekday,yearday = strings.local_time(currenttime)[:8]
            +        year,month,day,hour,minute,second,weekday,yearday = date.local_time(currenttime)[:8]
             
                     # Get the day of the last sunday of march
            -        march_end_weekday = strings.local_time(mktime((year, 3, 31, 0, 0, 0, 0, 0)))[6]
            +        march_end_weekday = date.local_time(date.mktime((year, 3, 31, 0, 0, 0, 0, 0)))[6]
                     start_day_dst = 31-((1+march_end_weekday)%7)
             
                     # Get the day of the last sunday of october
            -        october_end_weekday = strings.local_time(mktime((year,10, 30, 0, 0, 0, 0, 0)))[6]
            +        october_end_weekday = date.local_time(date.mktime((year,10, 30, 0, 0, 0, 0, 0)))[6]
                     end_day_dst = 30-((1+october_end_weekday)%7)
             
            -        start_DST = mktime((year,3 ,start_day_dst,1,0,0,0,0))
            -        end_DST   = mktime((year,10,end_day_dst  ,1,0,0,0,0))
            +        start_DST = date.mktime((year,3 ,start_day_dst,1,0,0,0,0))
            +        end_DST   = date.mktime((year,10,end_day_dst  ,1,0,0,0,0))
             
            -        now = mktime((year,month,day,hour,minute,second,weekday,yearday))
            +        now = date.mktime((year,month,day,hour,minute,second,weekday,yearday))
             
                     if dst and now > start_DST and now < end_DST : # we are before last sunday of october
                             return now+(offsetTime*3600)+3600 # DST: UTC+dst*H + 1
            @@ -181,24 +172,6 @@ 

            Functions

            return 0
            -
            -def mktime(t) -
            -
            -

            Portable mktime

            -
            - -Expand source code - -
            def mktime(t):
            -        """ Portable mktime """
            -        year,month,day,hour,minute,second,weekday,yearday = t
            -        if filesystem.ismicropython():
            -                return time.mktime((year, month, day, hour, minute, second, weekday, yearday))
            -        else:
            -                return time.mktime((year, month, day, hour, minute, second, weekday, yearday, 0))
            -
            -
            def set_date(offsetTime=1, dst=True, display=False)
            @@ -216,7 +189,7 @@

            Functions

            if currenttime > 0: set_time(currenttime) if display: - logger.syslog("Date updated : %s"%(strings.date_to_string())) + logger.syslog("Date updated : %s"%(date.date_to_string())) return currenttime return 0
            @@ -233,7 +206,7 @@

            Functions

            def set_time(currenttime):
                     """ Change the current time """
                     try:
            -                newtime = strings.local_time(currenttime)
            +                newtime = date.local_time(currenttime)
                             year,month,day,hour,minute,second,weekday,yearday = newtime[:8]
             
                             import machine
            @@ -262,7 +235,6 @@ 

            Index

            diff --git a/doc/lib/server/user.html b/doc/lib/server/user.html index 60846a4..330ecf3 100644 --- a/doc/lib/server/user.html +++ b/doc/lib/server/user.html @@ -29,6 +29,7 @@

            Module lib.server.user

            # Distributed under MIT License
             # Copyright (c) 2021 Remi BERTHOLET
            +# pylint:disable=consider-using-f-string
             """ Class used to manage a username and a password """
             from tools import logger,jsonconfig,encryption,strings,info
             
            diff --git a/doc/lib/server/wanip.html b/doc/lib/server/wanip.html
            index 00ff0b3..bbe8655 100644
            --- a/doc/lib/server/wanip.html
            +++ b/doc/lib/server/wanip.html
            @@ -50,7 +50,6 @@ 

            Module lib.server.wanip

            req.set_header(b"Accept" ,b"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") req.set_header(b"User-Agent" ,b"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.3 Safari/605.1.15") req.set_header(b"Accept-Language",b"fr-FR,fr;q=0.9") - req.set_header(b"Accept-Encoding",b"gzip, deflate") req.set_header(b"Connection" ,b"keep-alive") await req.send(streamio) response = HttpResponse(streamio) @@ -138,7 +137,6 @@

            Functions

            req.set_header(b"Accept" ,b"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") req.set_header(b"User-Agent" ,b"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.3 Safari/605.1.15") req.set_header(b"Accept-Language",b"fr-FR,fr;q=0.9") - req.set_header(b"Accept-Encoding",b"gzip, deflate") req.set_header(b"Connection" ,b"keep-alive") await req.send(streamio) response = HttpResponse(streamio) diff --git a/doc/lib/shell/editor.html b/doc/lib/shell/editor.html index 3903112..747636d 100644 --- a/doc/lib/shell/editor.html +++ b/doc/lib/shell/editor.html @@ -40,6 +40,9 @@

            Module lib.shell.editor

            # Copyright (c) 2021 Remi BERTHOLET # pylint:disable=multiple-statements # pylint:disable=too-many-lines +# pylint:disable=consider-using-f-string +# pylint:disable=unspecified-encoding + """ Class defining a VT100 text editor. This editor works directly in the board. This allows you to make quick and easy changes directly on the board, without having to use synchronization tools. @@ -374,6 +377,8 @@

            Module lib.shell.editor

            part_line = self.text.get_tab_line(current_line, self.column, self.column+self.width, True) self.write(clear_line) self.colorize(part_line) + elif current_line == count_line: + self.write(clear_line) def colorize_none(self, text): """ No colorization """ @@ -746,13 +751,17 @@

            Module lib.shell.editor

            else: self.cursor_column = 0 - def load(self, filename_): + def getFilename(self): + """ Return the filename """ + return self.filename + + def load(self, filename): """ Load file in the editor """ self.filename = None try: self.lines = [] - self.filename = filename_ - file = open(filename_, "r") + self.filename = filename + file = open(filename, "r") line = file.readline() while line != "": self.lines.append(line.replace("\r\n","\n")) @@ -770,13 +779,15 @@

            Module lib.shell.editor

            logger.syslog(err) self.lines = [""] - def save(self): + def save(self, filename=None): """ Save text in the file """ result = False if self.read_only is False: - if self.filename is not None: + if filename is None: + filename = self.filename + if filename is not None: try: - file = open(self.filename, "w") + file = open(filename, "w") for line in self.lines: file.write(line) file.close() @@ -989,7 +1000,7 @@

            Module lib.shell.editor

            def get_selection(self): """ Get information about selection """ - if self.selection_start: + if self.selection_start is not None and self.selection_end is not None: if self.selection_start[1] > self.selection_end[1]: return self.selection_end, self.selection_start elif self.selection_start[1] < self.selection_end[1]: @@ -1375,7 +1386,7 @@

            Module lib.shell.editor

            def change_case(self, keys=None): """ Change the case of selection """ selection = self.copy_clipboard() - if selection != []: + if len(selection) > 0: self.modified = True selection_start = self.selection_start selection_end = self.selection_end @@ -1660,21 +1671,19 @@

            Module lib.shell.editor

            if len(keys[0]) > 3: # If camflasher mouse selection if keys[0][0:2] == "\x1B[" and keys[0][-1] in ["x","y"]: - if keys[0][-1] == "y": - end = True - else: - + try: + pos = keys[0][2:-1] + line, column = pos.split(";") self.begin_line, self.begin_column = self.view.get_position() - self.hide_selection() - end = False - pos = keys[0][2:-1] - line, column = pos.split(";") - - if end: - self.open_selection() - self.goto(int(line)+self.begin_line,int(column)+self.begin_column, not end) - if end: - self.close_selection() + if keys[0][-1] == "x": + self.goto(int(line)+self.begin_line,int(column)+self.begin_column, True) + self.open_selection() + self.close_selection() + else: + self.goto(int(line)+self.begin_line,int(column)+self.begin_column, False) + self.close_selection() + except Exception as err: + pass class Edit: """ Class which aggregate the View and Text """ @@ -1687,20 +1696,19 @@

            Module lib.shell.editor

            class Editor: """ Class which manage a complete editor """ - def __init__(self, filename_, no_color=False, read_only=False): + def __init__(self, filename, no_color=False, read_only=False): """ Constructor """ self.cfg = EditorConfig() if self.cfg.load(tobytes=False, errorlog=False) is False: self.cfg.save() - self.file = filename_ - self.filename = filesystem.split(filename_)[1] + self.displayed_filename = filesystem.split(filename)[1] if no_color: extension = "" else: - extension = filesystem.splitext(filename_)[1] + extension = filesystem.splitext(filename)[1] self.edit = Edit(self.cfg, read_only=read_only, extension=extension) - self.edit.text.load(filename_) + self.edit.text.load(filename) self.is_refresh_header = True self.find_text = None self.replace_text = None @@ -1710,32 +1718,32 @@

            Module lib.shell.editor

            self.precedent_callback = None self.trace = None if filesystem.ismicropython() is False: - if filename_ == "newfile.py": + if filename == "newfile.py": self.trace = open("key.txt","w") - if (not filesystem.exists(filename_) and read_only is True) or filesystem.isdir(filename_): - print("Cannot open '%s'"%self.filename) + if (not filesystem.exists(filename) and read_only is True) or filesystem.isdir(filename): + print("Cannot open '%s'"%self.displayed_filename) else: try: self.run() except Exception as err: self.edit.view.cls() logger.syslog(err) - print("Failed edit '%s'"%self.filename) + print("Failed edit '%s'"%self.displayed_filename) def refresh_header(self): """ Refresh the header of editor """ if self.is_refresh_header: self.edit.view.move_cursor(0, 0) - filename_ = "\u25C1 File: %s"%(self.filename) + filename = "\u25C1 File: %s"%(self.displayed_filename) if self.edit.text.read_only is False: - filename_ += " (*)" if self.edit.text.modified else "" + filename += " (*)" if self.edit.text.modified else "" end = " Mode: %s "%("Replace" if self.edit.text.replace_mode else "Insert") else: end = " Read only " if self.edit.text.read_only else "" end = "L%d C%d "%(self.edit.text.cursor_line+1, self.edit.view.tab_cursor_column+1) + end + "\u25B7" - header = "\x1B[7m%s%s%s\x1B[m"%(filename_, " "*(self.edit.view.width - len(filename_) - len(end)), end) + header = "\x1B[7m%s%s%s\x1B[m"%(filename, " "*(self.edit.view.width - len(filename) - len(end)), end) self.edit.view.write(header) self.edit.view.move_cursor() self.is_refresh_header = False @@ -1753,16 +1761,16 @@

            Module lib.shell.editor

            self.edit.text.replace_mode = True self.is_refresh_header = True - def save(self, keys=None): + def save(self, keys=None, filename=None): """ Save the file edited """ - self.edit.text.save() + self.edit.text.save(filename) self.is_refresh_header = True def exit(self, keys=None): """ Exit from editor """ self.edit.view.cls() if self.edit.text.modified: - self.edit.view.write("\nSave file '%s' (\x1b[7mY\x1b[m:Yes, \x1b[7mN\x1b[m:No, \x1b[7mEsc\x1b[m:Cancel) : "%self.filename) + self.edit.view.write("\nSave file '%s' (\x1b[7mY\x1b[m:Yes, \x1b[7mN\x1b[m:No, \x1b[7mEsc\x1b[m:Cancel) : "%self.displayed_filename) self.edit.view.flush() while 1: key = terminal.getch() @@ -1905,7 +1913,7 @@

            Module lib.shell.editor

            self.edit.view.flush() startTime = strings.ticks() try: - error_line = useful.run(self.filename) + error_line = useful.run(self.displayed_filename) except KeyboardInterrupt: error_line = None @@ -1929,78 +1937,88 @@

            Module lib.shell.editor

            def run(self): """ Core of the editor """ - self.edit.view.cls() - self.edit.view.get_screen_size() - self.loop = True - self.EDIT_KEYS = self.cfg.key_toggle_mode+self.cfg.key_find+self.cfg.key_replace+self.cfg.key_find_previous+self.cfg.key_find_next+self.cfg.key_exit+self.cfg.key_goto+self.cfg.key_save+self.cfg.key_execute - while(self.loop): - try: - self.refresh() - keys = self.get_key(duration=0.4) - if len(keys[0]) == 0: - self.is_refresh_header = True - self.refresh_header() - keys = self.get_key() - - if keys == ["\x1B[23~"]: - keys = ["\x1B[1;5x"] - if keys == ["\x1B[24~"]: - keys = ["\x1B[5;1y"] - - if self.trace is not None: - for key in keys: - self.trace.write(strings.dump(key, withColor=False) + "\n") - self.trace.flush() - modified = self.edit.text.modified - self.precedent_callback = self.key_callback - self.key_callback = None - - if ord(keys[0][0]) < 0x20: - if keys[0] in self.EDIT_KEYS: - if keys[0] in self.cfg.key_toggle_mode: self.key_callback = self.toggle_mode - elif keys[0] in self.cfg.key_find: self.key_callback = self.find - elif keys[0] in self.cfg.key_replace: self.key_callback = self.replace - elif keys[0] in self.cfg.key_find_previous: self.key_callback = self.find_previous - elif keys[0] in self.cfg.key_find_next: self.key_callback = self.find_next - elif keys[0] in self.cfg.key_exit: self.key_callback = self.exit - elif keys[0] in self.cfg.key_goto: self.key_callback = self.goto - elif keys[0] in self.cfg.key_save: self.key_callback = self.save - elif keys[0] in self.cfg.key_execute: self.key_callback = self.execute - - # If a replacement is in progress and new line pressed - if keys[0] in self.cfg.key_new_line: - if self.precedent_callback is not None: - # The next check is compatible with micropython - if self.precedent_callback.__name__ in [self.find_next.__name__, self.find_previous.__name__, self.replace.__name__, self.replace_current.__name__]: - if self.replace_text is not None: - # Replace current found - self.key_callback = self.replace_current - # If a replacement is in progress and select all pressed - if keys[0] in self.cfg.key_sel_all: - if self.precedent_callback is not None: - # The next check is compatible with micropython - if self.precedent_callback.__name__ in [self.find_next.__name__, self.find_previous.__name__, self.replace.__name__, self.replace_current.__name__]: - if self.replace_text is not None: - # Replace all - self.key_callback = self.replace_all - - if self.key_callback is not None: - self.key_callback(keys) - else: - self.edit.text.treat_key(keys) - if modified != self.edit.text.modified: - self.is_refresh_header = True - except KeyboardInterrupt: - pass - self.edit.view.reset_scroll_region() - self.edit.view.reset() + try: + self.edit.view.cls() + self.edit.view.get_screen_size() + self.loop = True + self.EDIT_KEYS = self.cfg.key_toggle_mode+self.cfg.key_find+self.cfg.key_replace+self.cfg.key_find_previous+self.cfg.key_find_next+self.cfg.key_exit+self.cfg.key_goto+self.cfg.key_save+self.cfg.key_execute + while(self.loop): + try: + self.refresh() + keys = self.get_key(duration=0.4) + if len(keys[0]) == 0: + self.is_refresh_header = True + self.refresh_header() + keys = self.get_key() + + if keys == ["\x1B[23~"]: + keys = ["\x1B[15;5x"] + if keys == ["\x1B[24~"]: + keys = ["\x1B[20;5y"] + + if self.trace is not None: + for key in keys: + self.trace.write(strings.dump(key, withColor=False) + "\n") + self.trace.flush() + modified = self.edit.text.modified + self.precedent_callback = self.key_callback + self.key_callback = None + + if ord(keys[0][0]) < 0x20: + if keys[0] in self.EDIT_KEYS: + if keys[0] in self.cfg.key_toggle_mode: self.key_callback = self.toggle_mode + elif keys[0] in self.cfg.key_find: self.key_callback = self.find + elif keys[0] in self.cfg.key_replace: self.key_callback = self.replace + elif keys[0] in self.cfg.key_find_previous: self.key_callback = self.find_previous + elif keys[0] in self.cfg.key_find_next: self.key_callback = self.find_next + elif keys[0] in self.cfg.key_exit: self.key_callback = self.exit + elif keys[0] in self.cfg.key_goto: self.key_callback = self.goto + elif keys[0] in self.cfg.key_save: self.key_callback = self.save + elif keys[0] in self.cfg.key_execute: self.key_callback = self.execute + + # If a replacement is in progress and new line pressed + if keys[0] in self.cfg.key_new_line: + if self.precedent_callback is not None: + # The next check is compatible with micropython + if self.precedent_callback.__name__ in [self.find_next.__name__, self.find_previous.__name__, self.replace.__name__, self.replace_current.__name__]: + if self.replace_text is not None: + # Replace current found + self.key_callback = self.replace_current + # If a replacement is in progress and select all pressed + if keys[0] in self.cfg.key_sel_all: + if self.precedent_callback is not None: + # The next check is compatible with micropython + if self.precedent_callback.__name__ in [self.find_next.__name__, self.find_previous.__name__, self.replace.__name__, self.replace_current.__name__]: + if self.replace_text is not None: + # Replace all + self.key_callback = self.replace_all + + if self.key_callback is not None: + self.key_callback(keys) + else: + self.edit.text.treat_key(keys) + if modified != self.edit.text.modified: + self.is_refresh_header = True + except KeyboardInterrupt: + pass + self.edit.view.reset_scroll_region() + self.edit.view.reset() + except Exception as err: + print(logger.exception(err)) + filename = self.edit.text.getFilename() + "_backup" + self.save(filename=filename) + print("After the crash, a copy of the file was saved in '%s'"%filename) -if __name__ == "__main__": +def main(): + """ Main function """ if len(sys.argv) > 1: filename = sys.argv[1] else: filename = "newfile.py" - edit = Editor(filename, read_only=False)
            + edit = Editor(filename, read_only=False) + +if __name__ == "__main__": + main()
            @@ -2032,6 +2050,24 @@

            Functions

            return True
            +
            +def main() +
            +
            +

            Main function

            +
            + +Expand source code + +
            def main():
            +        """ Main function """
            +        if len(sys.argv) > 1:
            +                filename = sys.argv[1]
            +        else:
            +                filename = "newfile.py"
            +        edit = Editor(filename, read_only=False)
            +
            +
    @@ -2060,7 +2096,7 @@

    Classes

    class Editor -(filename_, no_color=False, read_only=False) +(filename, no_color=False, read_only=False)

    Class which manage a complete editor

    @@ -2071,20 +2107,19 @@

    Classes

    class Editor:
             """ Class which manage a complete editor """
    -        def __init__(self, filename_, no_color=False, read_only=False):
    +        def __init__(self, filename, no_color=False, read_only=False):
                     """ Constructor """
                     self.cfg = EditorConfig()
                     if self.cfg.load(tobytes=False, errorlog=False) is False:
                             self.cfg.save()
     
    -                self.file = filename_
    -                self.filename = filesystem.split(filename_)[1]
    +                self.displayed_filename = filesystem.split(filename)[1]
                     if no_color:
                             extension = ""
                     else:
    -                        extension = filesystem.splitext(filename_)[1]
    +                        extension = filesystem.splitext(filename)[1]
                     self.edit = Edit(self.cfg, read_only=read_only, extension=extension)
    -                self.edit.text.load(filename_)
    +                self.edit.text.load(filename)
                     self.is_refresh_header = True
                     self.find_text = None
                     self.replace_text = None
    @@ -2094,32 +2129,32 @@ 

    Classes

    self.precedent_callback = None self.trace = None if filesystem.ismicropython() is False: - if filename_ == "newfile.py": + if filename == "newfile.py": self.trace = open("key.txt","w") - if (not filesystem.exists(filename_) and read_only is True) or filesystem.isdir(filename_): - print("Cannot open '%s'"%self.filename) + if (not filesystem.exists(filename) and read_only is True) or filesystem.isdir(filename): + print("Cannot open '%s'"%self.displayed_filename) else: try: self.run() except Exception as err: self.edit.view.cls() logger.syslog(err) - print("Failed edit '%s'"%self.filename) + print("Failed edit '%s'"%self.displayed_filename) def refresh_header(self): """ Refresh the header of editor """ if self.is_refresh_header: self.edit.view.move_cursor(0, 0) - filename_ = "\u25C1 File: %s"%(self.filename) + filename = "\u25C1 File: %s"%(self.displayed_filename) if self.edit.text.read_only is False: - filename_ += " (*)" if self.edit.text.modified else "" + filename += " (*)" if self.edit.text.modified else "" end = " Mode: %s "%("Replace" if self.edit.text.replace_mode else "Insert") else: end = " Read only " if self.edit.text.read_only else "" end = "L%d C%d "%(self.edit.text.cursor_line+1, self.edit.view.tab_cursor_column+1) + end + "\u25B7" - header = "\x1B[7m%s%s%s\x1B[m"%(filename_, " "*(self.edit.view.width - len(filename_) - len(end)), end) + header = "\x1B[7m%s%s%s\x1B[m"%(filename, " "*(self.edit.view.width - len(filename) - len(end)), end) self.edit.view.write(header) self.edit.view.move_cursor() self.is_refresh_header = False @@ -2137,16 +2172,16 @@

    Classes

    self.edit.text.replace_mode = True self.is_refresh_header = True - def save(self, keys=None): + def save(self, keys=None, filename=None): """ Save the file edited """ - self.edit.text.save() + self.edit.text.save(filename) self.is_refresh_header = True def exit(self, keys=None): """ Exit from editor """ self.edit.view.cls() if self.edit.text.modified: - self.edit.view.write("\nSave file '%s' (\x1b[7mY\x1b[m:Yes, \x1b[7mN\x1b[m:No, \x1b[7mEsc\x1b[m:Cancel) : "%self.filename) + self.edit.view.write("\nSave file '%s' (\x1b[7mY\x1b[m:Yes, \x1b[7mN\x1b[m:No, \x1b[7mEsc\x1b[m:Cancel) : "%self.displayed_filename) self.edit.view.flush() while 1: key = terminal.getch() @@ -2289,7 +2324,7 @@

    Classes

    self.edit.view.flush() startTime = strings.ticks() try: - error_line = useful.run(self.filename) + error_line = useful.run(self.displayed_filename) except KeyboardInterrupt: error_line = None @@ -2313,71 +2348,77 @@

    Classes

    def run(self): """ Core of the editor """ - self.edit.view.cls() - self.edit.view.get_screen_size() - self.loop = True - self.EDIT_KEYS = self.cfg.key_toggle_mode+self.cfg.key_find+self.cfg.key_replace+self.cfg.key_find_previous+self.cfg.key_find_next+self.cfg.key_exit+self.cfg.key_goto+self.cfg.key_save+self.cfg.key_execute - while(self.loop): - try: - self.refresh() - keys = self.get_key(duration=0.4) - if len(keys[0]) == 0: - self.is_refresh_header = True - self.refresh_header() - keys = self.get_key() - - if keys == ["\x1B[23~"]: - keys = ["\x1B[1;5x"] - if keys == ["\x1B[24~"]: - keys = ["\x1B[5;1y"] - - if self.trace is not None: - for key in keys: - self.trace.write(strings.dump(key, withColor=False) + "\n") - self.trace.flush() - modified = self.edit.text.modified - self.precedent_callback = self.key_callback - self.key_callback = None - - if ord(keys[0][0]) < 0x20: - if keys[0] in self.EDIT_KEYS: - if keys[0] in self.cfg.key_toggle_mode: self.key_callback = self.toggle_mode - elif keys[0] in self.cfg.key_find: self.key_callback = self.find - elif keys[0] in self.cfg.key_replace: self.key_callback = self.replace - elif keys[0] in self.cfg.key_find_previous: self.key_callback = self.find_previous - elif keys[0] in self.cfg.key_find_next: self.key_callback = self.find_next - elif keys[0] in self.cfg.key_exit: self.key_callback = self.exit - elif keys[0] in self.cfg.key_goto: self.key_callback = self.goto - elif keys[0] in self.cfg.key_save: self.key_callback = self.save - elif keys[0] in self.cfg.key_execute: self.key_callback = self.execute - - # If a replacement is in progress and new line pressed - if keys[0] in self.cfg.key_new_line: - if self.precedent_callback is not None: - # The next check is compatible with micropython - if self.precedent_callback.__name__ in [self.find_next.__name__, self.find_previous.__name__, self.replace.__name__, self.replace_current.__name__]: - if self.replace_text is not None: - # Replace current found - self.key_callback = self.replace_current - # If a replacement is in progress and select all pressed - if keys[0] in self.cfg.key_sel_all: - if self.precedent_callback is not None: - # The next check is compatible with micropython - if self.precedent_callback.__name__ in [self.find_next.__name__, self.find_previous.__name__, self.replace.__name__, self.replace_current.__name__]: - if self.replace_text is not None: - # Replace all - self.key_callback = self.replace_all - - if self.key_callback is not None: - self.key_callback(keys) - else: - self.edit.text.treat_key(keys) - if modified != self.edit.text.modified: - self.is_refresh_header = True - except KeyboardInterrupt: - pass - self.edit.view.reset_scroll_region() - self.edit.view.reset()
    + try: + self.edit.view.cls() + self.edit.view.get_screen_size() + self.loop = True + self.EDIT_KEYS = self.cfg.key_toggle_mode+self.cfg.key_find+self.cfg.key_replace+self.cfg.key_find_previous+self.cfg.key_find_next+self.cfg.key_exit+self.cfg.key_goto+self.cfg.key_save+self.cfg.key_execute + while(self.loop): + try: + self.refresh() + keys = self.get_key(duration=0.4) + if len(keys[0]) == 0: + self.is_refresh_header = True + self.refresh_header() + keys = self.get_key() + + if keys == ["\x1B[23~"]: + keys = ["\x1B[15;5x"] + if keys == ["\x1B[24~"]: + keys = ["\x1B[20;5y"] + + if self.trace is not None: + for key in keys: + self.trace.write(strings.dump(key, withColor=False) + "\n") + self.trace.flush() + modified = self.edit.text.modified + self.precedent_callback = self.key_callback + self.key_callback = None + + if ord(keys[0][0]) < 0x20: + if keys[0] in self.EDIT_KEYS: + if keys[0] in self.cfg.key_toggle_mode: self.key_callback = self.toggle_mode + elif keys[0] in self.cfg.key_find: self.key_callback = self.find + elif keys[0] in self.cfg.key_replace: self.key_callback = self.replace + elif keys[0] in self.cfg.key_find_previous: self.key_callback = self.find_previous + elif keys[0] in self.cfg.key_find_next: self.key_callback = self.find_next + elif keys[0] in self.cfg.key_exit: self.key_callback = self.exit + elif keys[0] in self.cfg.key_goto: self.key_callback = self.goto + elif keys[0] in self.cfg.key_save: self.key_callback = self.save + elif keys[0] in self.cfg.key_execute: self.key_callback = self.execute + + # If a replacement is in progress and new line pressed + if keys[0] in self.cfg.key_new_line: + if self.precedent_callback is not None: + # The next check is compatible with micropython + if self.precedent_callback.__name__ in [self.find_next.__name__, self.find_previous.__name__, self.replace.__name__, self.replace_current.__name__]: + if self.replace_text is not None: + # Replace current found + self.key_callback = self.replace_current + # If a replacement is in progress and select all pressed + if keys[0] in self.cfg.key_sel_all: + if self.precedent_callback is not None: + # The next check is compatible with micropython + if self.precedent_callback.__name__ in [self.find_next.__name__, self.find_previous.__name__, self.replace.__name__, self.replace_current.__name__]: + if self.replace_text is not None: + # Replace all + self.key_callback = self.replace_all + + if self.key_callback is not None: + self.key_callback(keys) + else: + self.edit.text.treat_key(keys) + if modified != self.edit.text.modified: + self.is_refresh_header = True + except KeyboardInterrupt: + pass + self.edit.view.reset_scroll_region() + self.edit.view.reset() + except Exception as err: + print(logger.exception(err)) + filename = self.edit.text.getFilename() + "_backup" + self.save(filename=filename) + print("After the crash, a copy of the file was saved in '%s'"%filename)

    Methods

    @@ -2400,7 +2441,7 @@

    Methods

    self.edit.view.flush() startTime = strings.ticks() try: - error_line = useful.run(self.filename) + error_line = useful.run(self.displayed_filename) except KeyboardInterrupt: error_line = None @@ -2436,7 +2477,7 @@

    Methods

    """ Exit from editor """ self.edit.view.cls() if self.edit.text.modified: - self.edit.view.write("\nSave file '%s' (\x1b[7mY\x1b[m:Yes, \x1b[7mN\x1b[m:No, \x1b[7mEsc\x1b[m:Cancel) : "%self.filename) + self.edit.view.write("\nSave file '%s' (\x1b[7mY\x1b[m:Yes, \x1b[7mN\x1b[m:No, \x1b[7mEsc\x1b[m:Cancel) : "%self.displayed_filename) self.edit.view.flush() while 1: key = terminal.getch() @@ -2644,15 +2685,15 @@

    Methods

    """ Refresh the header of editor """ if self.is_refresh_header: self.edit.view.move_cursor(0, 0) - filename_ = "\u25C1 File: %s"%(self.filename) + filename = "\u25C1 File: %s"%(self.displayed_filename) if self.edit.text.read_only is False: - filename_ += " (*)" if self.edit.text.modified else "" + filename += " (*)" if self.edit.text.modified else "" end = " Mode: %s "%("Replace" if self.edit.text.replace_mode else "Insert") else: end = " Read only " if self.edit.text.read_only else "" end = "L%d C%d "%(self.edit.text.cursor_line+1, self.edit.view.tab_cursor_column+1) + end + "\u25B7" - header = "\x1B[7m%s%s%s\x1B[m"%(filename_, " "*(self.edit.view.width - len(filename_) - len(end)), end) + header = "\x1B[7m%s%s%s\x1B[m"%(filename, " "*(self.edit.view.width - len(filename) - len(end)), end) self.edit.view.write(header) self.edit.view.move_cursor() self.is_refresh_header = False @@ -2723,75 +2764,81 @@

    Methods

    def run(self):
             """ Core of the editor """
    -        self.edit.view.cls()
    -        self.edit.view.get_screen_size()
    -        self.loop = True
    -        self.EDIT_KEYS = self.cfg.key_toggle_mode+self.cfg.key_find+self.cfg.key_replace+self.cfg.key_find_previous+self.cfg.key_find_next+self.cfg.key_exit+self.cfg.key_goto+self.cfg.key_save+self.cfg.key_execute
    -        while(self.loop):
    -                try:
    -                        self.refresh()
    -                        keys = self.get_key(duration=0.4)
    -                        if len(keys[0]) == 0:
    -                                self.is_refresh_header = True
    -                                self.refresh_header()
    -                                keys = self.get_key()
    +        try:
    +                self.edit.view.cls()
    +                self.edit.view.get_screen_size()
    +                self.loop = True
    +                self.EDIT_KEYS = self.cfg.key_toggle_mode+self.cfg.key_find+self.cfg.key_replace+self.cfg.key_find_previous+self.cfg.key_find_next+self.cfg.key_exit+self.cfg.key_goto+self.cfg.key_save+self.cfg.key_execute
    +                while(self.loop):
    +                        try:
    +                                self.refresh()
    +                                keys = self.get_key(duration=0.4)
    +                                if len(keys[0]) == 0:
    +                                        self.is_refresh_header = True
    +                                        self.refresh_header()
    +                                        keys = self.get_key()
     
    -                        if keys == ["\x1B[23~"]:
    -                                keys = ["\x1B[1;5x"]
    -                        if keys == ["\x1B[24~"]:
    -                                keys = ["\x1B[5;1y"]
    -
    -                        if self.trace is not None:
    -                                for key in keys:
    -                                        self.trace.write(strings.dump(key, withColor=False) + "\n")
    -                                        self.trace.flush()
    -                        modified = self.edit.text.modified
    -                        self.precedent_callback = self.key_callback
    -                        self.key_callback = None
    -
    -                        if ord(keys[0][0]) < 0x20:
    -                                if keys[0] in self.EDIT_KEYS:
    -                                        if   keys[0] in self.cfg.key_toggle_mode:    self.key_callback = self.toggle_mode
    -                                        elif keys[0] in self.cfg.key_find:           self.key_callback = self.find
    -                                        elif keys[0] in self.cfg.key_replace:        self.key_callback = self.replace
    -                                        elif keys[0] in self.cfg.key_find_previous:  self.key_callback = self.find_previous
    -                                        elif keys[0] in self.cfg.key_find_next:      self.key_callback = self.find_next
    -                                        elif keys[0] in self.cfg.key_exit:           self.key_callback = self.exit
    -                                        elif keys[0] in self.cfg.key_goto:           self.key_callback = self.goto
    -                                        elif keys[0] in self.cfg.key_save:           self.key_callback = self.save
    -                                        elif keys[0] in self.cfg.key_execute:        self.key_callback = self.execute
    -
    -                        # If a replacement is in progress and new line pressed
    -                        if keys[0] in self.cfg.key_new_line:
    -                                if self.precedent_callback is not None:
    -                                        # The next check is compatible with micropython
    -                                        if self.precedent_callback.__name__ in [self.find_next.__name__, self.find_previous.__name__, self.replace.__name__, self.replace_current.__name__]:
    -                                                if self.replace_text is not None:
    -                                                        # Replace current found
    -                                                        self.key_callback = self.replace_current
    -                        # If a replacement is in progress and select all pressed
    -                        if keys[0] in self.cfg.key_sel_all:
    -                                if self.precedent_callback is not None:
    -                                        # The next check is compatible with micropython
    -                                        if self.precedent_callback.__name__ in [self.find_next.__name__, self.find_previous.__name__, self.replace.__name__, self.replace_current.__name__]:
    -                                                if self.replace_text is not None:
    -                                                        # Replace all
    -                                                        self.key_callback = self.replace_all
    -
    -                        if self.key_callback is not None:
    -                                self.key_callback(keys)
    -                        else:
    -                                self.edit.text.treat_key(keys)
    -                        if modified != self.edit.text.modified:
    -                                self.is_refresh_header = True
    -                except KeyboardInterrupt:
    -                        pass
    -        self.edit.view.reset_scroll_region()
    -        self.edit.view.reset()
    + if keys == ["\x1B[23~"]: + keys = ["\x1B[15;5x"] + if keys == ["\x1B[24~"]: + keys = ["\x1B[20;5y"] + + if self.trace is not None: + for key in keys: + self.trace.write(strings.dump(key, withColor=False) + "\n") + self.trace.flush() + modified = self.edit.text.modified + self.precedent_callback = self.key_callback + self.key_callback = None + + if ord(keys[0][0]) < 0x20: + if keys[0] in self.EDIT_KEYS: + if keys[0] in self.cfg.key_toggle_mode: self.key_callback = self.toggle_mode + elif keys[0] in self.cfg.key_find: self.key_callback = self.find + elif keys[0] in self.cfg.key_replace: self.key_callback = self.replace + elif keys[0] in self.cfg.key_find_previous: self.key_callback = self.find_previous + elif keys[0] in self.cfg.key_find_next: self.key_callback = self.find_next + elif keys[0] in self.cfg.key_exit: self.key_callback = self.exit + elif keys[0] in self.cfg.key_goto: self.key_callback = self.goto + elif keys[0] in self.cfg.key_save: self.key_callback = self.save + elif keys[0] in self.cfg.key_execute: self.key_callback = self.execute + + # If a replacement is in progress and new line pressed + if keys[0] in self.cfg.key_new_line: + if self.precedent_callback is not None: + # The next check is compatible with micropython + if self.precedent_callback.__name__ in [self.find_next.__name__, self.find_previous.__name__, self.replace.__name__, self.replace_current.__name__]: + if self.replace_text is not None: + # Replace current found + self.key_callback = self.replace_current + # If a replacement is in progress and select all pressed + if keys[0] in self.cfg.key_sel_all: + if self.precedent_callback is not None: + # The next check is compatible with micropython + if self.precedent_callback.__name__ in [self.find_next.__name__, self.find_previous.__name__, self.replace.__name__, self.replace_current.__name__]: + if self.replace_text is not None: + # Replace all + self.key_callback = self.replace_all + + if self.key_callback is not None: + self.key_callback(keys) + else: + self.edit.text.treat_key(keys) + if modified != self.edit.text.modified: + self.is_refresh_header = True + except KeyboardInterrupt: + pass + self.edit.view.reset_scroll_region() + self.edit.view.reset() + except Exception as err: + print(logger.exception(err)) + filename = self.edit.text.getFilename() + "_backup" + self.save(filename=filename) + print("After the crash, a copy of the file was saved in '%s'"%filename)
    -def save(self, keys=None) +def save(self, keys=None, filename=None)

    Save the file edited

    @@ -2799,9 +2846,9 @@

    Methods

    Expand source code -
    def save(self, keys=None):
    +
    def save(self, keys=None, filename=None):
             """ Save the file edited """
    -        self.edit.text.save()
    +        self.edit.text.save(filename)
             self.is_refresh_header = True
    @@ -3107,13 +3154,17 @@

    Ancestors

    else: self.cursor_column = 0 - def load(self, filename_): + def getFilename(self): + """ Return the filename """ + return self.filename + + def load(self, filename): """ Load file in the editor """ self.filename = None try: self.lines = [] - self.filename = filename_ - file = open(filename_, "r") + self.filename = filename + file = open(filename, "r") line = file.readline() while line != "": self.lines.append(line.replace("\r\n","\n")) @@ -3131,13 +3182,15 @@

    Ancestors

    logger.syslog(err) self.lines = [""] - def save(self): + def save(self, filename=None): """ Save text in the file """ result = False if self.read_only is False: - if self.filename is not None: + if filename is None: + filename = self.filename + if filename is not None: try: - file = open(self.filename, "w") + file = open(filename, "w") for line in self.lines: file.write(line) file.close() @@ -3350,7 +3403,7 @@

    Ancestors

    def get_selection(self): """ Get information about selection """ - if self.selection_start: + if self.selection_start is not None and self.selection_end is not None: if self.selection_start[1] > self.selection_end[1]: return self.selection_end, self.selection_start elif self.selection_start[1] < self.selection_end[1]: @@ -3736,7 +3789,7 @@

    Ancestors

    def change_case(self, keys=None): """ Change the case of selection """ selection = self.copy_clipboard() - if selection != []: + if len(selection) > 0: self.modified = True selection_start = self.selection_start selection_end = self.selection_end @@ -4021,21 +4074,19 @@

    Ancestors

    if len(keys[0]) > 3: # If camflasher mouse selection if keys[0][0:2] == "\x1B[" and keys[0][-1] in ["x","y"]: - if keys[0][-1] == "y": - end = True - else: - + try: + pos = keys[0][2:-1] + line, column = pos.split(";") self.begin_line, self.begin_column = self.view.get_position() - self.hide_selection() - end = False - pos = keys[0][2:-1] - line, column = pos.split(";") - - if end: - self.open_selection() - self.goto(int(line)+self.begin_line,int(column)+self.begin_column, not end) - if end: - self.close_selection()
    + if keys[0][-1] == "x": + self.goto(int(line)+self.begin_line,int(column)+self.begin_column, True) + self.open_selection() + self.close_selection() + else: + self.goto(int(line)+self.begin_line,int(column)+self.begin_column, False) + self.close_selection() + except Exception as err: + pass

    Methods

    @@ -4186,7 +4237,7 @@

    Methods

    def change_case(self, keys=None):
             """ Change the case of selection """
             selection = self.copy_clipboard()
    -        if selection != []:
    +        if len(selection) > 0:
                     self.modified = True
                     selection_start = self.selection_start
                     selection_end   = self.selection_end
    @@ -4636,6 +4687,20 @@ 

    Methods

    return result
    +
    +def getFilename(self) +
    +
    +

    Return the filename

    +
    + +Expand source code + +
    def getFilename(self):
    +        """ Return the filename """
    +        return self.filename
    +
    +
    def get_count_lines(self)
    @@ -4692,7 +4757,7 @@

    Methods

    def get_selection(self):
             """ Get information about selection """
    -        if self.selection_start:
    +        if self.selection_start is not None and self.selection_end is not None:
                     if self.selection_start[1] > self.selection_end[1]:
                             return self.selection_end, self.selection_start
                     elif self.selection_start[1] < self.selection_end[1]:
    @@ -4982,7 +5047,7 @@ 

    Methods

    -def load(self, filename_) +def load(self, filename)

    Load file in the editor

    @@ -4990,13 +5055,13 @@

    Methods

    Expand source code -
    def load(self, filename_):
    +
    def load(self, filename):
             """ Load file in the editor """
             self.filename = None
             try:
                     self.lines = []
    -                self.filename = filename_
    -                file = open(filename_, "r")
    +                self.filename = filename
    +                file = open(filename, "r")
                     line = file.readline()
                     while line != "":
                             self.lines.append(line.replace("\r\n","\n"))
    @@ -5323,7 +5388,7 @@ 

    Methods

    -def save(self) +def save(self, filename=None)

    Save text in the file

    @@ -5331,13 +5396,15 @@

    Methods

    Expand source code -
    def save(self):
    +
    def save(self, filename=None):
             """ Save text in the file """
             result = False
             if self.read_only is False:
    -                if self.filename is not None:
    +                if filename is None:
    +                        filename = self.filename
    +                if filename is not None:
                             try:
    -                                file = open(self.filename, "w")
    +                                file = open(filename, "w")
                                     for line in self.lines:
                                             file.write(line)
                                     file.close()
    @@ -5720,21 +5787,19 @@ 

    Methods

    if len(keys[0]) > 3: # If camflasher mouse selection if keys[0][0:2] == "\x1B[" and keys[0][-1] in ["x","y"]: - if keys[0][-1] == "y": - end = True - else: - + try: + pos = keys[0][2:-1] + line, column = pos.split(";") self.begin_line, self.begin_column = self.view.get_position() - self.hide_selection() - end = False - pos = keys[0][2:-1] - line, column = pos.split(";") - - if end: - self.open_selection() - self.goto(int(line)+self.begin_line,int(column)+self.begin_column, not end) - if end: - self.close_selection()
    + if keys[0][-1] == "x": + self.goto(int(line)+self.begin_line,int(column)+self.begin_column, True) + self.open_selection() + self.close_selection() + else: + self.goto(int(line)+self.begin_line,int(column)+self.begin_column, False) + self.close_selection() + except Exception as err: + pass
    @@ -6035,6 +6100,8 @@

    Methods

    part_line = self.text.get_tab_line(current_line, self.column, self.column+self.width, True) self.write(clear_line) self.colorize(part_line) + elif current_line == count_line: + self.write(clear_line) def colorize_none(self, text): """ No colorization """ @@ -6865,7 +6932,9 @@

    Methods

    else: part_line = self.text.get_tab_line(current_line, self.column, self.column+self.width, True) self.write(clear_line) - self.colorize(part_line)
    + self.colorize(part_line) + elif current_line == count_line: + self.write(clear_line)
    @@ -6915,6 +6984,7 @@

    Index

  • Functions

  • Classes

    @@ -6970,6 +7040,7 @@

    Textend

  • find_next
  • find_previous
  • +
  • getFilename
  • get_count_lines
  • get_cursor_char
  • get_cursor_line
  • diff --git a/doc/lib/shell/editor_py.html b/doc/lib/shell/editor_py.html index 94a107a..4a2f9a1 100644 --- a/doc/lib/shell/editor_py.html +++ b/doc/lib/shell/editor_py.html @@ -29,6 +29,7 @@

    Module lib.shell.editor_py

    # Distributed under MIT License
     # Copyright (c) 2021 Remi BERTHOLET
    +# pylint:disable=consider-iterating-dictionary
     """ Syntax highlight for python in the editor """
     PYTHON_KEYWORDS = b"and as assert break class continue def del elif else except exec finally for from global if import in is lambda None not or pass print raise return try while self as join abs apply bool buffer callable chr cmp coerce compile complex delattr dir dict divmod eval execfile filter float getattr globals hasattr hash hex id input int intern isinstance issubclass len list locals long map max min oct open ord pow range raw_input reduce reload repr round setattr slice str tuple type unichr unicode vars xrange zip with yield True False async await"
     
    @@ -80,10 +81,11 @@ 

    Module lib.shell.editor_py

    pos = -1 # If a keyword start if char in self.lexicon.keys(): - pos = j - state = STATE_KEYWORD - word = charactere - keywords = self.lexicon[char] + if not (0x41 <= previous_char <= 0x5A or 0x61 <= previous_char <= 0x7A or 0x30 <= previous_char <= 0x39 or previous_char == 0x5F): + pos = j + state = STATE_KEYWORD + word = charactere + keywords = self.lexicon[char] # If decimal number started elif 0x31 <= char <= 0x39: if 0x41 <= previous_char <= 0x5A or 0x61 <= previous_char <= 0x7A or 0x30 <= previous_char <= 0x39 or previous_char == 0x5F: @@ -189,7 +191,7 @@

    Module lib.shell.editor_py

    keywords = None # If decimal detected elif state == STATE_DECIMAL: - if char >= 0x30 and char <= 0x39: + if char >= 0x30 and char <= 0x39 or char == 0x5F: word += charactere elif char == 0x2E: if b"." not in word: @@ -396,10 +398,11 @@

    Classes

    pos = -1 # If a keyword start if char in self.lexicon.keys(): - pos = j - state = STATE_KEYWORD - word = charactere - keywords = self.lexicon[char] + if not (0x41 <= previous_char <= 0x5A or 0x61 <= previous_char <= 0x7A or 0x30 <= previous_char <= 0x39 or previous_char == 0x5F): + pos = j + state = STATE_KEYWORD + word = charactere + keywords = self.lexicon[char] # If decimal number started elif 0x31 <= char <= 0x39: if 0x41 <= previous_char <= 0x5A or 0x61 <= previous_char <= 0x7A or 0x30 <= previous_char <= 0x39 or previous_char == 0x5F: @@ -505,7 +508,7 @@

    Classes

    keywords = None # If decimal detected elif state == STATE_DECIMAL: - if char >= 0x30 and char <= 0x39: + if char >= 0x30 and char <= 0x39 or char == 0x5F: word += charactere elif char == 0x2E: if b"." not in word: @@ -693,10 +696,11 @@

    Methods

    pos = -1 # If a keyword start if char in self.lexicon.keys(): - pos = j - state = STATE_KEYWORD - word = charactere - keywords = self.lexicon[char] + if not (0x41 <= previous_char <= 0x5A or 0x61 <= previous_char <= 0x7A or 0x30 <= previous_char <= 0x39 or previous_char == 0x5F): + pos = j + state = STATE_KEYWORD + word = charactere + keywords = self.lexicon[char] # If decimal number started elif 0x31 <= char <= 0x39: if 0x41 <= previous_char <= 0x5A or 0x61 <= previous_char <= 0x7A or 0x30 <= previous_char <= 0x39 or previous_char == 0x5F: @@ -802,7 +806,7 @@

    Methods

    keywords = None # If decimal detected elif state == STATE_DECIMAL: - if char >= 0x30 and char <= 0x39: + if char >= 0x30 and char <= 0x39 or char == 0x5F: word += charactere elif char == 0x2E: if b"." not in word: diff --git a/doc/lib/shell/shell.html b/doc/lib/shell/shell.html index 36e6c1f..2cd37b7 100644 --- a/doc/lib/shell/shell.html +++ b/doc/lib/shell/shell.html @@ -110,6 +110,9 @@

    Module lib.shell.shell

    # Distributed under MIT License
     # Copyright (c) 2021 Remi BERTHOLET
    +# pylint:disable=too-many-lines
    +# pylint:disable=consider-using-f-string
    +# pylint:disable=unspecified-encoding
     """ Class defining a minimalist shell, directly executable on the board.
     We modify directories, list, delete, move files, edit files ...
     The commands are :
    @@ -161,12 +164,13 @@ 

    Module lib.shell.shell

    import os import uos import machine -from tools import useful,logger,sdcard,filesystem,exchange,info,strings,terminal,watchdog +from tools import useful,logger,sdcard,filesystem,exchange,info,strings,terminal,watchdog,lang,date stdout_redirected = None def print_(message, end=None): """ Redirect the print to file """ + # pylint:disable=global-variable-not-assigned global stdout_redirected if stdout_redirected is None: if end is None: @@ -182,6 +186,7 @@

    Module lib.shell.shell

    def get_screen_size(): """ Return the screen size and check if output redirected """ + # pylint:disable=global-variable-not-assigned global stdout_redirected if stdout_redirected is None: height, width = terminal.get_screen_size() @@ -369,7 +374,7 @@

    Module lib.shell.shell

    if fileinfo[0] & 0x4000 == 0x4000: if self.showdir: if self.long: - message = b"%s %s [%s]"%(strings.date_to_bytes(date_),b" "*7,self.purge_path(path)) + message = b"%s %s [%s]"%(date.date_to_bytes(date_),b" "*7,self.purge_path(path)) else: message = b"[%s]"%self.purge_path(path) self.count = print_part(message, self.width, self.height, self.count) @@ -378,7 +383,7 @@

    Module lib.shell.shell

    fileinfo = filesystem.fileinfo(path) date_ = fileinfo[8] size = fileinfo[6] - message = b"%s %s %s"%(strings.date_to_bytes(date_),strings.size_to_bytes(size),self.purge_path(path)) + message = b"%s %s %s"%(date.date_to_bytes(date_),strings.size_to_bytes(size),self.purge_path(path)) else: message = self.purge_path(path) self.count = print_part(message, self.width, self.height, self.count) @@ -429,6 +434,7 @@

    Module lib.shell.shell

    def print_part(message, width, height, count): """ Print a part of text """ + # pylint:disable=global-variable-not-assigned global stdout_redirected if isinstance(message , bytes): message = message.decode("utf8") @@ -542,7 +548,7 @@

    Module lib.shell.shell

    except: print_("Cannot umount sd from '%s'"%mountpoint) -def date(update=False, offsetUTC=+1, noDst=False): +def date_(update=False, offsetUTC=+1, noDst=False): """ Get or set date """ try: from server.timesetting import set_date @@ -555,7 +561,7 @@

    Module lib.shell.shell

    del sys.modules["server.timesetting"] except: pass - print_(strings.date_to_string()) + print_(date.date_to_string()) def setdate(datetime=""): """ Set date and time """ @@ -620,9 +626,14 @@

    Module lib.shell.shell

    """ Deep sleep command """ machine.deepsleep(int(seconds)*1000) +def ligthsleep(seconds=60): + """ Light sleep command """ + machine.lightsleep(int(seconds)*1000) + edit_class = None def edit(file, no_color=False, read_only=False): """ Edit command """ + # pylint:disable=global-variable-not-assigned global edit_class global stdout_redirected if stdout_redirected is None: @@ -654,7 +665,7 @@

    Module lib.shell.shell

    def df(mountpoint = None): """ Display free disk space """ - print_(strings.tostrings(info.flashinfo(mountpoint=mountpoint, display=False))) + print_(strings.tostrings(info.flashinfo(mountpoint=mountpoint))) def gc(): """ Garbage collector command """ @@ -663,7 +674,7 @@

    Module lib.shell.shell

    def uptime(): """ Tell how long the system has been running """ - print_(info.uptime()) + print_(strings.tostrings(info.uptime())) def man(command): """ Man command """ @@ -742,6 +753,7 @@

    Module lib.shell.shell

    def check_cam_flasher(): """ Check if the terminal is CamFlasher """ + # pylint:disable=global-variable-not-assigned global stdout_redirected if stdout_redirected is None: # Request terminal device attribut @@ -823,15 +835,15 @@

    Module lib.shell.shell

    def meminfo(): """ Get memory informations """ - print_(strings.tostrings(info.meminfo(display=False))) + print_(strings.tostrings(b"%s : %s"%(lang.memory_label, info.meminfo()))) def flashinfo(mountpoint=None): """ Get flash informations """ - print_(strings.tostrings(info.flashinfo(mountpoint=mountpoint, display=False))) + print_(strings.tostrings(b"%s : %s"%(lang.flash_info, info.flashinfo(mountpoint=mountpoint)))) def sysinfo(): """ Get system informations """ - print_(strings.tostrings(info.sysinfo(display=False))) + print_(strings.tostrings(info.sysinfo())) def vtcolors(): """ Show all VT100 colors """ @@ -872,6 +884,7 @@

    Module lib.shell.shell

    def get_command(command_name): """ Get a command callback according to the command name """ try: + # pylint:disable=global-variable-not-assigned global shell_commands command = shell_commands[command_name] command_function = command[0] @@ -889,6 +902,7 @@

    Module lib.shell.shell

    def exec_command(args): """ Execute command """ + # pylint:disable=global-variable-not-assigned global stdout_redirected command_name = "" command_function = None @@ -1094,7 +1108,7 @@

    Module lib.shell.shell

    "rm" :[rm ,"file", ("-r","recursive",True),("-f","force",True),("-s","simulate",True)], "ls" :[ls ,"file", ("-r","recursive",True),("-l","long",True)], "ll" :[ll ,"file", ("-r","recursive",True)], - "date" :[date ,"offsetUTC" , ("-u","update",True), ("-n","noDst",True)], + "date" :[date_ ,"offsetUTC" , ("-u","update",True), ("-n","noDst",True)], "setdate" :[setdate ,"datetime" ], "uptime" :[uptime ], "find" :[find ,"file" ], @@ -1112,6 +1126,7 @@

    Module lib.shell.shell

    "flashinfo" :[flashinfo ], "sysinfo" :[sysinfo ], "deepsleep" :[deepsleep ,"seconds" ], + "lightsleep" :[ligthsleep ,"seconds" ], "ping" :[ping ,"host" ], "reboot" :[reboot ], "help" :[help ], @@ -1259,6 +1274,7 @@

    Functions

    def check_cam_flasher():
             """ Check if the terminal is CamFlasher """
    +        # pylint:disable=global-variable-not-assigned
             global stdout_redirected
             if stdout_redirected is None:
                     # Request terminal device attribut
    @@ -1350,8 +1366,8 @@ 

    Functions

    copyfile(src,dst,quiet)
    -
    -def date(update=False, offsetUTC=1, noDst=False) +
    +def date_(update=False, offsetUTC=1, noDst=False)

    Get or set date

    @@ -1359,7 +1375,7 @@

    Functions

    Expand source code -
    def date(update=False, offsetUTC=+1, noDst=False):
    +
    def date_(update=False, offsetUTC=+1, noDst=False):
             """ Get or set date """
             try:
                     from server.timesetting import set_date
    @@ -1372,7 +1388,7 @@ 

    Functions

    del sys.modules["server.timesetting"] except: pass - print_(strings.date_to_string())
    + print_(date.date_to_string())
    @@ -1400,7 +1416,7 @@

    Functions

    def df(mountpoint = None):
             """ Display free disk space """
    -        print_(strings.tostrings(info.flashinfo(mountpoint=mountpoint, display=False)))
    + print_(strings.tostrings(info.flashinfo(mountpoint=mountpoint)))
    @@ -1470,6 +1486,7 @@

    Functions

    def edit(file, no_color=False, read_only=False):
             """ Edit command """
    +        # pylint:disable=global-variable-not-assigned
             global edit_class
             global stdout_redirected
             if stdout_redirected is None:
    @@ -1521,6 +1538,7 @@ 

    Functions

    def exec_command(args):
             """ Execute command """
    +        # pylint:disable=global-variable-not-assigned
             global stdout_redirected
             command_name = ""
             command_function = None
    @@ -1633,7 +1651,7 @@ 

    Functions

    def flashinfo(mountpoint=None):
             """ Get flash informations """
    -        print_(strings.tostrings(info.flashinfo(mountpoint=mountpoint, display=False)))
    + print_(strings.tostrings(b"%s : %s"%(lang.flash_info, info.flashinfo(mountpoint=mountpoint))))
    @@ -1688,6 +1706,7 @@

    Functions

    def get_command(command_name):
             """ Get a command callback according to the command name """
             try:
    +                # pylint:disable=global-variable-not-assigned
                     global shell_commands
                     command = shell_commands[command_name]
                     command_function = command[0]
    @@ -1715,6 +1734,7 @@ 

    Functions

    def get_screen_size():
             """ Return the screen size and check if output redirected """
    +        # pylint:disable=global-variable-not-assigned
             global stdout_redirected
             if stdout_redirected is None:
                     height, width = terminal.get_screen_size()
    @@ -1845,6 +1865,20 @@ 

    Functions

    print_("Not available")
    +
    +def ligthsleep(seconds=60) +
    +
    +

    Light sleep command

    +
    + +Expand source code + +
    def ligthsleep(seconds=60):
    +        """ Light sleep command """
    +        machine.lightsleep(int(seconds)*1000)
    +
    +
    def ll(file='', recursive=False)
    @@ -1923,7 +1957,7 @@

    Functions

    def meminfo():
             """ Get memory informations """
    -        print_(strings.tostrings(info.meminfo(display=False)))
    + print_(strings.tostrings(b"%s : %s"%(lang.memory_label, info.meminfo())))
    @@ -2061,6 +2095,7 @@

    Functions

    def print_(message, end=None):
             """ Redirect the print to file """
    +        # pylint:disable=global-variable-not-assigned
             global stdout_redirected
             if stdout_redirected is None:
                     if end is None:
    @@ -2086,6 +2121,7 @@ 

    Functions

    def print_part(message, width, height, count):
             """ Print a part of text """
    +        # pylint:disable=global-variable-not-assigned
             global stdout_redirected
             if isinstance(message , bytes):
                     message = message.decode("utf8")
    @@ -2391,7 +2427,7 @@ 

    Functions

    def sysinfo():
             """ Get system informations """
    -        print_(strings.tostrings(info.sysinfo(display=False)))
    + print_(strings.tostrings(info.sysinfo()))
    @@ -2467,7 +2503,7 @@

    Functions

    def uptime():
             """ Tell how long the system has been running """
    -        print_(info.uptime())
    + print_(strings.tostrings(info.uptime()))
    @@ -2656,7 +2692,7 @@

    Methods

    if fileinfo[0] & 0x4000 == 0x4000: if self.showdir: if self.long: - message = b"%s %s [%s]"%(strings.date_to_bytes(date_),b" "*7,self.purge_path(path)) + message = b"%s %s [%s]"%(date.date_to_bytes(date_),b" "*7,self.purge_path(path)) else: message = b"[%s]"%self.purge_path(path) self.count = print_part(message, self.width, self.height, self.count) @@ -2665,7 +2701,7 @@

    Methods

    fileinfo = filesystem.fileinfo(path) date_ = fileinfo[8] size = fileinfo[6] - message = b"%s %s %s"%(strings.date_to_bytes(date_),strings.size_to_bytes(size),self.purge_path(path)) + message = b"%s %s %s"%(date.date_to_bytes(date_),strings.size_to_bytes(size),self.purge_path(path)) else: message = self.purge_path(path) self.count = print_part(message, self.width, self.height, self.count) @@ -2712,7 +2748,7 @@

    Methods

    if fileinfo[0] & 0x4000 == 0x4000: if self.showdir: if self.long: - message = b"%s %s [%s]"%(strings.date_to_bytes(date_),b" "*7,self.purge_path(path)) + message = b"%s %s [%s]"%(date.date_to_bytes(date_),b" "*7,self.purge_path(path)) else: message = b"[%s]"%self.purge_path(path) self.count = print_part(message, self.width, self.height, self.count) @@ -2721,7 +2757,7 @@

    Methods

    fileinfo = filesystem.fileinfo(path) date_ = fileinfo[8] size = fileinfo[6] - message = b"%s %s %s"%(strings.date_to_bytes(date_),strings.size_to_bytes(size),self.purge_path(path)) + message = b"%s %s %s"%(date.date_to_bytes(date_),strings.size_to_bytes(size),self.purge_path(path)) else: message = self.purge_path(path) self.count = print_part(message, self.width, self.height, self.count)
    @@ -2766,7 +2802,7 @@

    Index

  • cls
  • copyfile
  • cp
  • -
  • date
  • +
  • date_
  • deepsleep
  • df
  • download
  • @@ -2786,6 +2822,7 @@

    Index

  • help
  • host2ip
  • ip2host
  • +
  • ligthsleep
  • ll
  • ls
  • man
  • diff --git a/doc/lib/tools/archiver.html b/doc/lib/tools/archiver.html index 4af4423..18e623e 100644 --- a/doc/lib/tools/archiver.html +++ b/doc/lib/tools/archiver.html @@ -29,6 +29,7 @@

    Module lib.tools.archiver

    # Distributed under MIT License
     # Copyright (c) 2021 Remi BERTHOLET
    +# pylint:disable=consider-using-f-string
     """ Archiver files functions """
     from tools import logger,filesystem,exchange
     
    diff --git a/doc/lib/tools/awake.html b/doc/lib/tools/awake.html
    index 6dc1c16..05f6ab1 100644
    --- a/doc/lib/tools/awake.html
    +++ b/doc/lib/tools/awake.html
    @@ -29,6 +29,7 @@ 

    Module lib.tools.awake

    # Distributed under MIT License
     # Copyright (c) 2021 Remi BERTHOLET
    +# pylint:disable=consider-using-f-string
     """ Manage the battery """
     import machine
     from tools import jsonconfig,logger
    diff --git a/doc/lib/tools/battery.html b/doc/lib/tools/battery.html
    index 50e2a12..072dd64 100644
    --- a/doc/lib/tools/battery.html
    +++ b/doc/lib/tools/battery.html
    @@ -29,200 +29,224 @@ 

    Module lib.tools.battery

    # Distributed under MIT License
     # Copyright (c) 2021 Remi BERTHOLET
    +# pylint:disable=consider-using-f-string
     """ Manage the battery """
     import uasyncio
     import machine
    -from tools import jsonconfig,logger,tasking
    -
    -try:
    -        BROWNOUT_RESET = machine.BROWNOUT_RESET
    -except:
    -        BROWNOUT_RESET = 6
    -
    -class BatteryConfig(jsonconfig.JsonConfig):
    -        """ Battery configuration """
    -        def __init__(self):
    -                """ Constructor """
    -                jsonconfig.JsonConfig.__init__(self)
    -
    -                # Battery monitoring
    -                self.activated = False # Monitoring status
    -                self.level_gpio    = 12  # Monitoring GPIO
    -                self.full_battery  = 188 # 4.2V mesured with resistor 100k + 47k
    -                self.empty_battery = 158 # 3.6V mesured with resistor 100k + 47k
    -
    -                # Force deep sleep if to many successive brown out reset detected
    -                self.brownout_detection = True
    -                self.brownout_count = 0
    -
    -class Battery:
    -        """ Manage the battery information """
    -        config = None
    -        level = [-2]
    -        refresh = [0]
    -
    -        @staticmethod
    -        def init():
    -                """ Init battery class """
    -                # If config not yet read
    -                if Battery.config is None:
    -                        Battery.config = BatteryConfig()
    -                        # If config failed to read
    -                        if Battery.config.load() is False:
    -                                # Write default config
    -                                Battery.config.save()
    -
    -        @staticmethod
    -        def get_level():
    -                """ Return the battery level between 0% to 100% (0%=3.6V 100%=4.2V).
    -                        For the ESP32CAM with Gpio12, the value can be read only before the open of camera and SD card.
    -                        The voltage always smaller than 1.5V otherwise the card does not boot (JTAG detection I think).
    -                        This GPIO 12 of the ESP32CAM not have a pull up resistor, it is the only one which allows the ADC measurement.
    -                        I had to patch the micropython firmware to be able to read the GPIO 12."""
    -                Battery.init()
    -                # If battery level not yet read at start
    -                if Battery.level[0] == -2:
    -                        level = -1
    -                        try:
    -                                adc = machine.ADC(machine.Pin(Battery.config.level_gpio))
    -                                adc.atten(machine.ADC.ATTN_11DB)
    -                                adc.width(machine.ADC.WIDTH_9BIT)
    -                                count = 3
    -                                val = 0
    -                                for i in range(count):
    -                                        val += adc.read()
    -                                # If battery level pin not connected
    -                                if val < (Battery.config.empty_battery * count) // 2:
    -                                        level = -1
    -                                else:
    -                                        # Compute battery level
    -                                        level = Battery.calc_percent(val/count, Battery.config)
    -                                        if level < 0.:
    -                                                level = 0
    -                                        elif level > 100.:
    -                                                level = 100
    +from tools import jsonconfig,logger,tasking,support
    +
    +if support.battery():
    +        try:
    +                BROWNOUT_RESET = machine.BROWNOUT_RESET
    +        except:
    +                BROWNOUT_RESET = 6
    +
    +        MAX_BROWNOUT_RESET = 32
    +
    +        class BatteryConfig(jsonconfig.JsonConfig):
    +                """ Battery configuration """
    +                def __init__(self):
    +                        """ Constructor """
    +                        jsonconfig.JsonConfig.__init__(self)
    +
    +                        # Battery monitoring
    +                        self.activated = False # Monitoring status
    +                        self.level_gpio    = 12  # Monitoring GPIO
    +                        self.full_battery  = 188 # 4.2V mesured with resistor 100k + 47k
    +                        self.empty_battery = 158 # 3.6V mesured with resistor 100k + 47k
    +
    +                        # Force deep sleep if to many successive brown out reset detected
    +                        self.brownout_detection = True
    +                        self.brownout_count = 0
    +
    +        class Battery:
    +                """ Manage the battery information """
    +                config = None
    +                level = [-2]
    +                refresh = [0]
    +
    +                @staticmethod
    +                def init():
    +                        """ Init battery class """
    +                        # If config not yet read
    +                        if Battery.config is None:
    +                                Battery.config = BatteryConfig()
    +                                # If config failed to read
    +                                if Battery.config.load() is False:
    +                                        # Write default config
    +                                        Battery.config.save()
    +
    +                @staticmethod
    +                def get_level():
    +                        """ Return the battery level between 0% to 100% (0%=3.6V 100%=4.2V).
    +                                For the ESP32CAM with Gpio12, the value can be read only before the open of camera and SD card.
    +                                The voltage always smaller than 1.5V otherwise the card does not boot (JTAG detection I think).
    +                                This GPIO 12 of the ESP32CAM not have a pull up resistor, it is the only one which allows the ADC measurement.
    +                                I had to patch the micropython firmware to be able to read the GPIO 12."""
    +                        Battery.init()
    +                        # If battery level not yet read at start
    +                        if Battery.level[0] == -2:
    +                                level = -1
    +                                try:
    +                                        adc = machine.ADC(machine.Pin(Battery.config.level_gpio))
    +                                        adc.atten(machine.ADC.ATTN_11DB)
    +                                        adc.width(machine.ADC.WIDTH_9BIT)
    +                                        count = 3
    +                                        val = 0
    +                                        for i in range(count):
    +                                                val += adc.read()
    +                                        # If battery level pin not connected
    +                                        if val < (Battery.config.empty_battery * count) // 2:
    +                                                level = -1
                                             else:
    -                                                level = int(level)
    -                                logger.syslog("Battery level %d %% (%d)"%(level, int(val/count)))
    -                        except Exception as err:
    -                                logger.syslog(err,"Cannot read battery status")
    -                        Battery.level[0] = level
    -                return Battery.level[0]
    -
    -        @staticmethod
    -        def is_activated():
    -                """ Indicates if the battery management activated """
    -                Battery.init()
    -                return Battery.config.activated
    -
    -        @staticmethod
    -        def calc_percent(x, config):
    -                """ Calc the percentage of battery according to the configuration """
    -                x1 = config.full_battery
    -                y1 = 100
    -                x2 = config.empty_battery
    -                y2 = 0
    -
    -                a = (y1 - y2)/(x1 - x2)
    -                b = y1 - (a * x1)
    -                y = a*x + b
    -                return y
    -
    -        @staticmethod
    -        def protect():
    -                """ Protect the battery """
    -                Battery.init()
    -                Battery.keep_reset_cause()
    -                if Battery.manage_level() or Battery.is_too_many_brownout():
    -                        logger.syslog("Sleep infinite")
    -                        machine.deepsleep()
    -
    -        @staticmethod
    -        def manage_level():
    -                """ Checks if the battery level is sufficient.
    -                        If the battery is too low, we enter indefinite deep sleep to protect the battery """
    -                deepsleep = False
    -                Battery.init()
    -                if Battery.config.activated:
    -                        # Can only be done once at boot before start the camera and sd card
    -                        battery_level = Battery.get_level()
    -
    -                        # If the battery is too low
    -                        if battery_level > 5 or battery_level < 0:
    -                                battery_protect = False
    -                        else:
    -                                battery_protect = True
    -
    -                        # Case the battery has not enough current and must be protected
    -                        if battery_protect:
    -                                deepsleep = True
    -                                logger.syslog("Battery too low %d %%"%battery_level)
    -                return deepsleep
    -
    -        @staticmethod
    -        def keep_reset_cause():
    -                """ Keep reset cause """
    -                causes = {
    -                        machine.PWRON_RESET     : "Power on",
    -                        machine.HARD_RESET      : "Hard",
    -                        machine.WDT_RESET       : "Watch dog",
    -                        machine.DEEPSLEEP_RESET : "Deep sleep",
    -                        machine.SOFT_RESET      : "Soft",
    -                        BROWNOUT_RESET          : "Brownout",
    -                }.setdefault(machine.reset_cause(), "%d"%machine.reset_cause())
    -                logger.syslog(" ")
    -                logger.syslog("%s Start %s"%('-'*10,'-'*10), display=False)
    -                logger.syslog("%s reset"%causes)
    -
    -        @staticmethod
    -        def is_too_many_brownout():
    -                """ Checks the number of brownout reset """
    -                deepsleep = False
    -
    -                if Battery.config.is_changed():
    -                        Battery.config.load()
    -
    -                if Battery.config.brownout_detection:
    -                        # If the reset can probably due to insufficient battery
    -                        if machine.reset_cause() == BROWNOUT_RESET:
    -                                Battery.config.brownout_count += 1
    -                        else:
    -                                Battery.config.brownout_count = 0
    +                                                # Compute battery level
    +                                                level = Battery.calc_percent(val/count, Battery.config)
    +                                                if level < 0.:
    +                                                        level = 0
    +                                                elif level > 100.:
    +                                                        level = 100
    +                                                else:
    +                                                        level = int(level)
    +                                        logger.syslog("Battery level %d %% (%d)"%(level, int(val/count)))
    +                                except Exception as err:
    +                                        logger.syslog(err,"Cannot read battery status")
    +                                Battery.level[0] = level
    +                        return Battery.level[0]
    +
    +                @staticmethod
    +                def is_activated():
    +                        """ Indicates if the battery management activated """
    +                        Battery.init()
    +                        return Battery.config.activated
    +
    +                @staticmethod
    +                def calc_percent(x, config):
    +                        """ Calc the percentage of battery according to the configuration """
    +                        x1 = config.full_battery
    +                        y1 = 100
    +                        x2 = config.empty_battery
    +                        y2 = 0
    +
    +                        a = (y1 - y2)/(x1 - x2)
    +                        b = y1 - (a * x1)
    +                        y = a*x + b
    +                        return y
    +
    +
    +                @staticmethod
    +                def protect():
    +                        """ Protect the battery """
    +                        Battery.init()
    +                        Battery.keep_reset_cause()
    +                        if Battery.manage_level() or Battery.is_too_many_brownout():
    +                                # Too many brownout reset
    +                                # Slow deepsleep during 1 hour
    +                                if Battery.config.brownout_count < MAX_BROWNOUT_RESET + 60:
    +                                        logger.syslog("Sleep 1 minute")
    +                                        machine.deepsleep(600*1000)
    +                                # Slow deepsleep during one day
    +                                elif Battery.config.brownout_count < MAX_BROWNOUT_RESET + 60 + 24:
    +                                        logger.syslog("Sleep 1 hour")
    +                                        machine.deepsleep(3600*1000)
    +                                # Slow deepsleep during three days
    +                                elif Battery.config.brownout_count < MAX_BROWNOUT_RESET + 60 + 24 + 8:
    +                                        logger.syslog("Sleep 3 hours")
    +                                        machine.deepsleep(3*3600*1000)
    +                                # Slow deepsleep during one week
    +                                elif Battery.config.brownout_count < MAX_BROWNOUT_RESET + 60 + 24 + 8 + 7:
    +                                        logger.syslog("Sleep 24 hours")
    +                                        machine.deepsleep(24*3600*1000)
    +                                # Deepsleep infinite
    +                                else:
    +                                        logger.syslog("Sleep infinite")
    +                                        machine.deepsleep()
    +
    +
    +                @staticmethod
    +                def manage_level():
    +                        """ Checks if the battery level is sufficient.
    +                                If the battery is too low, we enter indefinite deep sleep to protect the battery """
    +                        deepsleep = False
    +                        Battery.init()
    +                        if Battery.config.activated:
    +                                # Can only be done once at boot before start the camera and sd card
    +                                battery_level = Battery.get_level()
    +
    +                                # If the battery is too low
    +                                if battery_level > 5 or battery_level < 0:
    +                                        battery_protect = False
    +                                else:
    +                                        battery_protect = True
    +
    +                                # Case the battery has not enough current and must be protected
    +                                if battery_protect:
    +                                        deepsleep = True
    +                                        logger.syslog("Battery too low %d %%"%battery_level)
    +                        return deepsleep
    +
    +                @staticmethod
    +                def keep_reset_cause():
    +                        """ Keep reset cause """
    +                        causes = {
    +                                machine.PWRON_RESET     : "Power on",
    +                                machine.HARD_RESET      : "Hard",
    +                                machine.WDT_RESET       : "Watch dog",
    +                                machine.DEEPSLEEP_RESET : "Deep sleep",
    +                                machine.SOFT_RESET      : "Soft",
    +                                BROWNOUT_RESET          : "Brownout",
    +                        }.setdefault(machine.reset_cause(), "%d"%machine.reset_cause())
    +                        logger.syslog("%s Start %s"%('-'*10,'-'*10), display=False)
    +                        logger.syslog("%s reset"%causes)
    +
    +                @staticmethod
    +                def is_too_many_brownout():
    +                        """ Checks the number of brownout reset """
    +                        deepsleep = False
     
    -                        Battery.config.save()
    +                        if Battery.config.is_changed():
    +                                Battery.config.load()
     
    -                        # if the number of consecutive brownout resets is too high
    -                        if Battery.config.brownout_count > 32:
    -                                # Battery too low, save the battery status
    -                                logger.syslog("Too many successive brownout reset")
    -                                deepsleep = True
    -                return deepsleep
    +                        if Battery.config.brownout_detection:
    +                                # If the reset can probably due to insufficient battery
    +                                if machine.reset_cause() == BROWNOUT_RESET:
    +                                        Battery.config.brownout_count += 1
    +                                else:
    +                                        Battery.config.brownout_count = 0
     
    -        @staticmethod
    -        def reset_brownout():
    -                """ Reset brownout counter if wifi connected """
    -                if Battery.config is not None:
    -                        if Battery.config.brownout_count > 0:
    -                                Battery.config.brownout_count = 0
                                     Battery.config.save()
     
    -        @staticmethod
    -        async def periodic():
    -                """ Internal periodic task """
    -                if Battery.config is not None:
    -                        if Battery.refresh[0] % 10 == 0:
    -                                if Battery.config.is_changed():
    -                                        Battery.config.load()
    -                        Battery.refresh[0] += 1
    -                else:
    -                        Battery.protect()
    -                await uasyncio.sleep(11)
    -                return True
    -
    -        @staticmethod
    -        async def periodic_task():
    -                """ Execute periodic treatment """
    -                await tasking.task_monitoring(Battery.periodic)
    + # if the number of consecutive brownout resets is too high + if Battery.config.brownout_count > MAX_BROWNOUT_RESET: + # Battery too low, save the battery status + logger.syslog("Too many successive brownout reset %d"%Battery.config.brownout_count) + deepsleep = True + return deepsleep + + @staticmethod + def reset_brownout(): + """ Reset brownout counter if wifi connected """ + if Battery.config is not None: + if Battery.config.brownout_count > 0: + Battery.config.brownout_count = 0 + Battery.config.save() + + @staticmethod + async def periodic(): + """ Internal periodic task """ + if Battery.config is not None: + if Battery.refresh[0] % 10 == 0: + if Battery.config.is_changed(): + Battery.config.load() + Battery.refresh[0] += 1 + else: + Battery.protect() + await uasyncio.sleep(11) + return True + + @staticmethod + async def periodic_task(): + """ Execute periodic treatment """ + await tasking.task_monitoring(Battery.periodic)
    @@ -316,14 +340,35 @@

    Classes

    y = a*x + b return y + @staticmethod def protect(): """ Protect the battery """ Battery.init() Battery.keep_reset_cause() if Battery.manage_level() or Battery.is_too_many_brownout(): - logger.syslog("Sleep infinite") - machine.deepsleep() + # Too many brownout reset + # Slow deepsleep during 1 hour + if Battery.config.brownout_count < MAX_BROWNOUT_RESET + 60: + logger.syslog("Sleep 1 minute") + machine.deepsleep(600*1000) + # Slow deepsleep during one day + elif Battery.config.brownout_count < MAX_BROWNOUT_RESET + 60 + 24: + logger.syslog("Sleep 1 hour") + machine.deepsleep(3600*1000) + # Slow deepsleep during three days + elif Battery.config.brownout_count < MAX_BROWNOUT_RESET + 60 + 24 + 8: + logger.syslog("Sleep 3 hours") + machine.deepsleep(3*3600*1000) + # Slow deepsleep during one week + elif Battery.config.brownout_count < MAX_BROWNOUT_RESET + 60 + 24 + 8 + 7: + logger.syslog("Sleep 24 hours") + machine.deepsleep(24*3600*1000) + # Deepsleep infinite + else: + logger.syslog("Sleep infinite") + machine.deepsleep() + @staticmethod def manage_level(): @@ -358,7 +403,6 @@

    Classes

    machine.SOFT_RESET : "Soft", BROWNOUT_RESET : "Brownout", }.setdefault(machine.reset_cause(), "%d"%machine.reset_cause()) - logger.syslog(" ") logger.syslog("%s Start %s"%('-'*10,'-'*10), display=False) logger.syslog("%s reset"%causes) @@ -380,9 +424,9 @@

    Classes

    Battery.config.save() # if the number of consecutive brownout resets is too high - if Battery.config.brownout_count > 32: + if Battery.config.brownout_count > MAX_BROWNOUT_RESET: # Battery too low, save the battery status - logger.syslog("Too many successive brownout reset") + logger.syslog("Too many successive brownout reset %d"%Battery.config.brownout_count) deepsleep = True return deepsleep @@ -567,9 +611,9 @@

    Static methods

    Battery.config.save() # if the number of consecutive brownout resets is too high - if Battery.config.brownout_count > 32: + if Battery.config.brownout_count > MAX_BROWNOUT_RESET: # Battery too low, save the battery status - logger.syslog("Too many successive brownout reset") + logger.syslog("Too many successive brownout reset %d"%Battery.config.brownout_count) deepsleep = True return deepsleep
    @@ -594,7 +638,6 @@

    Static methods

    machine.SOFT_RESET : "Soft", BROWNOUT_RESET : "Brownout", }.setdefault(machine.reset_cause(), "%d"%machine.reset_cause()) - logger.syslog(" ") logger.syslog("%s Start %s"%('-'*10,'-'*10), display=False) logger.syslog("%s reset"%causes)
    @@ -685,8 +728,27 @@

    Static methods

    Battery.init() Battery.keep_reset_cause() if Battery.manage_level() or Battery.is_too_many_brownout(): - logger.syslog("Sleep infinite") - machine.deepsleep()
    + # Too many brownout reset + # Slow deepsleep during 1 hour + if Battery.config.brownout_count < MAX_BROWNOUT_RESET + 60: + logger.syslog("Sleep 1 minute") + machine.deepsleep(600*1000) + # Slow deepsleep during one day + elif Battery.config.brownout_count < MAX_BROWNOUT_RESET + 60 + 24: + logger.syslog("Sleep 1 hour") + machine.deepsleep(3600*1000) + # Slow deepsleep during three days + elif Battery.config.brownout_count < MAX_BROWNOUT_RESET + 60 + 24 + 8: + logger.syslog("Sleep 3 hours") + machine.deepsleep(3*3600*1000) + # Slow deepsleep during one week + elif Battery.config.brownout_count < MAX_BROWNOUT_RESET + 60 + 24 + 8 + 7: + logger.syslog("Sleep 24 hours") + machine.deepsleep(24*3600*1000) + # Deepsleep infinite + else: + logger.syslog("Sleep infinite") + machine.deepsleep()
    diff --git a/doc/lib/tools/builddate.html b/doc/lib/tools/builddate.html index d7478a4..7dfeced 100644 --- a/doc/lib/tools/builddate.html +++ b/doc/lib/tools/builddate.html @@ -5,7 +5,7 @@ lib.tools.builddate API documentation - + @@ -22,11 +22,13 @@

    Module lib.tools.builddate

    +

    Build date

    Expand source code -
    date=b'2022/07/16  11:18:01'
    +
    ''' Build date '''
    +date=b'2023/01/22  16:34:03'
    diff --git a/doc/lib/tools/date.html b/doc/lib/tools/date.html new file mode 100644 index 0000000..ce170b8 --- /dev/null +++ b/doc/lib/tools/date.html @@ -0,0 +1,384 @@ + + + + + + +lib.tools.date API documentation + + + + + + + + + + + +
    +
    +
    +

    Module lib.tools.date

    +
    +
    +

    Date and time utilities

    +
    + +Expand source code + +
    # Distributed under MIT License
    +# Copyright (c) 2021 Remi BERTHOLET
    +# pylint:disable=consider-using-f-string
    +""" Date and time utilities """
    +import sys
    +import time
    +
    +def local_time(date_=None):
    +        """ Safe local time, it return 2000/1/1 00:00:00 if date can be extracted """
    +        try:
    +                year,month,day,hour,minute,second,weekday,yearday = time.localtime(date_)[:8]
    +        except:
    +                year,month,day,hour,minute,second,weekday,yearday = 2000,1,1,0,0,0,0,0
    +        return year,month,day,hour,minute,second,weekday,yearday
    +
    +def date_to_string(date_ = None):
    +        """ Get a string with the current date """
    +        return date_to_bytes(date_).decode("utf8")
    +
    +def date_to_bytes(date_ = None):
    +        """ Get a bytes with the current date """
    +        year,month,day,hour,minute,second = local_time(date_)[:6]
    +        return b"%04d/%02d/%02d  %02d:%02d:%02d"%(year,month,day,hour,minute,second)
    +
    +def date_ms_to_string():
    +        """ Get a string with the current date with ms """
    +        current = time.time_ns()
    +        ms = (current // 1_000_000)%1000
    +        current //= 1_000_000_000
    +        year,month,day,hour,minute,second = local_time(current)[:6]
    +        return "%04d/%02d/%02d %02d:%02d:%02d.%03d"%(year,month,day,hour,minute,second,ms)
    +
    +def mktime(t):
    +        """ Portable mktime """
    +        year,month,day,hour,minute,second,weekday,yearday = t
    +        if sys.implementation.name == "micropython":
    +                result = time.mktime((year, month, day, hour, minute, second, weekday, yearday))
    +        else:
    +                result = time.mktime((year, month, day, hour, minute, second, weekday, yearday, 0))
    +        return result
    +
    +def html_to_date(date_, separator=b"-"):
    +        """ Convert html date into time integer """
    +        result = 0
    +        try:
    +                year, month, day = date_.split(separator)
    +                year  = int( year.lstrip(b"0"))
    +                month = int(month.lstrip(b"0"))
    +                day   = int(  day.lstrip(b"0"))
    +                result = mktime((year, month, day, 1,0,0, 0,0))
    +        except:
    +                result = (time.time() // 86400) * 86400
    +        return result
    +
    +def date_to_html(date_ = None, separator=b"-"):
    +        """ Get a html date with the current date """
    +        year,month,day = local_time(date_)[:3]
    +        return b"%04d%s%02d%s%02d"%(year,separator,month,separator,day)
    +
    +
    +def html_to_time(time_, separator=b":"):
    +        """ Convert html time string into time integer """
    +        result = 0
    +        try:
    +                hour, minute, second = time_.split(separator)
    +                hour   = int(  hour.lstrip(b"0"))
    +                minute = int(minute.lstrip(b"0"))
    +                second = int(second.lstrip(b"0"))
    +                result = hour * 3600 + minute * 60 * second
    +        except:
    +                try:
    +                        hour, minute = time_.split(separator)
    +                        hour   = int(  hour.lstrip(b"0"))
    +                        minute = int(minute.lstrip(b"0"))
    +                        result = hour * 3600 + minute * 60
    +                except:
    +                        pass
    +        return result
    +
    +def time_to_html(t = None, seconds=False):
    +        """ Convert time into date bytes """
    +        if t is None:
    +                t = time.time()
    +        t %= 86400
    +        result =  b"%02d:%02d:%02d"%(t//3600, (t%3600)//60, t%60)
    +        if seconds is False:
    +                result = result[:-3]
    +        return result
    +
    +def date_to_filename(date_ = None):
    +        """ Get a filename with a date """
    +        filename = date_to_string(date_)
    +        filename = filename.replace("  "," ")
    +        filename = filename.replace(" ","_")
    +        filename = filename.replace("/","-")
    +        filename = filename.replace(":","-")
    +        return filename
    +
    +def date_to_path(date_=None):
    +        """ Get a path with year/month/day/hour """
    +        year,month,day,hour,minute = local_time(date_)[:5]
    +        return b"%04d/%02d/%02d/%02dh%02d"%(year,month,day,hour,minute)
    +
    +
    +
    +
    +
    +
    +
    +

    Functions

    +
    +
    +def date_ms_to_string() +
    +
    +

    Get a string with the current date with ms

    +
    + +Expand source code + +
    def date_ms_to_string():
    +        """ Get a string with the current date with ms """
    +        current = time.time_ns()
    +        ms = (current // 1_000_000)%1000
    +        current //= 1_000_000_000
    +        year,month,day,hour,minute,second = local_time(current)[:6]
    +        return "%04d/%02d/%02d %02d:%02d:%02d.%03d"%(year,month,day,hour,minute,second,ms)
    +
    +
    +
    +def date_to_bytes(date_=None) +
    +
    +

    Get a bytes with the current date

    +
    + +Expand source code + +
    def date_to_bytes(date_ = None):
    +        """ Get a bytes with the current date """
    +        year,month,day,hour,minute,second = local_time(date_)[:6]
    +        return b"%04d/%02d/%02d  %02d:%02d:%02d"%(year,month,day,hour,minute,second)
    +
    +
    +
    +def date_to_filename(date_=None) +
    +
    +

    Get a filename with a date

    +
    + +Expand source code + +
    def date_to_filename(date_ = None):
    +        """ Get a filename with a date """
    +        filename = date_to_string(date_)
    +        filename = filename.replace("  "," ")
    +        filename = filename.replace(" ","_")
    +        filename = filename.replace("/","-")
    +        filename = filename.replace(":","-")
    +        return filename
    +
    +
    +
    +def date_to_html(date_=None, separator=b'-') +
    +
    +

    Get a html date with the current date

    +
    + +Expand source code + +
    def date_to_html(date_ = None, separator=b"-"):
    +        """ Get a html date with the current date """
    +        year,month,day = local_time(date_)[:3]
    +        return b"%04d%s%02d%s%02d"%(year,separator,month,separator,day)
    +
    +
    +
    +def date_to_path(date_=None) +
    +
    +

    Get a path with year/month/day/hour

    +
    + +Expand source code + +
    def date_to_path(date_=None):
    +        """ Get a path with year/month/day/hour """
    +        year,month,day,hour,minute = local_time(date_)[:5]
    +        return b"%04d/%02d/%02d/%02dh%02d"%(year,month,day,hour,minute)
    +
    +
    +
    +def date_to_string(date_=None) +
    +
    +

    Get a string with the current date

    +
    + +Expand source code + +
    def date_to_string(date_ = None):
    +        """ Get a string with the current date """
    +        return date_to_bytes(date_).decode("utf8")
    +
    +
    +
    +def html_to_date(date_, separator=b'-') +
    +
    +

    Convert html date into time integer

    +
    + +Expand source code + +
    def html_to_date(date_, separator=b"-"):
    +        """ Convert html date into time integer """
    +        result = 0
    +        try:
    +                year, month, day = date_.split(separator)
    +                year  = int( year.lstrip(b"0"))
    +                month = int(month.lstrip(b"0"))
    +                day   = int(  day.lstrip(b"0"))
    +                result = mktime((year, month, day, 1,0,0, 0,0))
    +        except:
    +                result = (time.time() // 86400) * 86400
    +        return result
    +
    +
    +
    +def html_to_time(time_, separator=b':') +
    +
    +

    Convert html time string into time integer

    +
    + +Expand source code + +
    def html_to_time(time_, separator=b":"):
    +        """ Convert html time string into time integer """
    +        result = 0
    +        try:
    +                hour, minute, second = time_.split(separator)
    +                hour   = int(  hour.lstrip(b"0"))
    +                minute = int(minute.lstrip(b"0"))
    +                second = int(second.lstrip(b"0"))
    +                result = hour * 3600 + minute * 60 * second
    +        except:
    +                try:
    +                        hour, minute = time_.split(separator)
    +                        hour   = int(  hour.lstrip(b"0"))
    +                        minute = int(minute.lstrip(b"0"))
    +                        result = hour * 3600 + minute * 60
    +                except:
    +                        pass
    +        return result
    +
    +
    +
    +def local_time(date_=None) +
    +
    +

    Safe local time, it return 2000/1/1 00:00:00 if date can be extracted

    +
    + +Expand source code + +
    def local_time(date_=None):
    +        """ Safe local time, it return 2000/1/1 00:00:00 if date can be extracted """
    +        try:
    +                year,month,day,hour,minute,second,weekday,yearday = time.localtime(date_)[:8]
    +        except:
    +                year,month,day,hour,minute,second,weekday,yearday = 2000,1,1,0,0,0,0,0
    +        return year,month,day,hour,minute,second,weekday,yearday
    +
    +
    +
    +def mktime(t) +
    +
    +

    Portable mktime

    +
    + +Expand source code + +
    def mktime(t):
    +        """ Portable mktime """
    +        year,month,day,hour,minute,second,weekday,yearday = t
    +        if sys.implementation.name == "micropython":
    +                result = time.mktime((year, month, day, hour, minute, second, weekday, yearday))
    +        else:
    +                result = time.mktime((year, month, day, hour, minute, second, weekday, yearday, 0))
    +        return result
    +
    +
    +
    +def time_to_html(t=None, seconds=False) +
    +
    +

    Convert time into date bytes

    +
    + +Expand source code + +
    def time_to_html(t = None, seconds=False):
    +        """ Convert time into date bytes """
    +        if t is None:
    +                t = time.time()
    +        t %= 86400
    +        result =  b"%02d:%02d:%02d"%(t//3600, (t%3600)//60, t%60)
    +        if seconds is False:
    +                result = result[:-3]
    +        return result
    +
    +
    +
    +
    +
    +
    +
    + +
    + + + \ No newline at end of file diff --git a/doc/lib/tools/exchange.html b/doc/lib/tools/exchange.html index 388e43f..7ac4b43 100644 --- a/doc/lib/tools/exchange.html +++ b/doc/lib/tools/exchange.html @@ -29,6 +29,7 @@

    Module lib.tools.exchange

    # Distributed under MIT License
     # Copyright (c) 2021 Remi BERTHOLET
    +# pylint:disable=consider-using-f-string
     """ Classes for exchanging files between the device and the computer """
     import time
     import os
    @@ -36,9 +37,9 @@ 

    Module lib.tools.exchange

    import binascii try: import filesystem - import strings + import date except: - from tools import filesystem, strings + from tools import filesystem, date if filesystem.ismicropython(): # pylint:disable=import-error import micropython @@ -145,8 +146,8 @@

    Module lib.tools.exchange

    def read_second(self, byte): """ Read second """ if self.second.read_byte(byte) is not None: - date = [self.year.get(), self.month.get(), self.day.get(), self.hour.get(), self.minute.get(), self.second.get(), 0, 0, 0] - self.value = time.mktime(tuple(date)) + date_ = [self.year.get(), self.month.get(), self.day.get(), self.hour.get(), self.minute.get(), self.second.get(), 0, 0, 0] + self.value = time.mktime(tuple(date_)) return self.value class FilenameReader(Reader): @@ -465,7 +466,7 @@

    Module lib.tools.exchange

    out_file.write(b"# %s\x0D\x0A"%filename_.encode("utf8")) # Send the file date - year,month,day,hour,minute,second,_,_ = strings.local_time(filesystem.filetime(filename))[:8] + year,month,day,hour,minute,second,_,_ = date.local_time(filesystem.filetime(filename))[:8] out_file.write(b"# %04d/%02d/%02d %02d:%02d:%02d\x0D\x0A"%(year,month,day,hour,minute,second)) # Send the file size @@ -879,8 +880,8 @@

    Inherited members

    def read_second(self, byte): """ Read second """ if self.second.read_byte(byte) is not None: - date = [self.year.get(), self.month.get(), self.day.get(), self.hour.get(), self.minute.get(), self.second.get(), 0, 0, 0] - self.value = time.mktime(tuple(date)) + date_ = [self.year.get(), self.month.get(), self.day.get(), self.hour.get(), self.minute.get(), self.second.get(), 0, 0, 0] + self.value = time.mktime(tuple(date_)) return self.value

    Ancestors

    @@ -961,8 +962,8 @@

    Methods

    def read_second(self, byte):
             """ Read second """
             if self.second.read_byte(byte) is not None:
    -                date = [self.year.get(), self.month.get(), self.day.get(), self.hour.get(), self.minute.get(), self.second.get(), 0, 0, 0]
    -                self.value = time.mktime(tuple(date))
    +                date_ = [self.year.get(), self.month.get(), self.day.get(), self.hour.get(), self.minute.get(), self.second.get(), 0, 0, 0]
    +                self.value = time.mktime(tuple(date_))
                     return self.value
    @@ -1495,7 +1496,7 @@

    Methods

    out_file.write(b"# %s\x0D\x0A"%filename_.encode("utf8")) # Send the file date - year,month,day,hour,minute,second,_,_ = strings.local_time(filesystem.filetime(filename))[:8] + year,month,day,hour,minute,second,_,_ = date.local_time(filesystem.filetime(filename))[:8] out_file.write(b"# %04d/%02d/%02d %02d:%02d:%02d\x0D\x0A"%(year,month,day,hour,minute,second)) # Send the file size @@ -1586,7 +1587,7 @@

    Methods

    out_file.write(b"# %s\x0D\x0A"%filename_.encode("utf8")) # Send the file date - year,month,day,hour,minute,second,_,_ = strings.local_time(filesystem.filetime(filename))[:8] + year,month,day,hour,minute,second,_,_ = date.local_time(filesystem.filetime(filename))[:8] out_file.write(b"# %04d/%02d/%02d %02d:%02d:%02d\x0D\x0A"%(year,month,day,hour,minute,second)) # Send the file size diff --git a/doc/lib/tools/filesystem.html b/doc/lib/tools/filesystem.html index 0a21384..a1df4e9 100644 --- a/doc/lib/tools/filesystem.html +++ b/doc/lib/tools/filesystem.html @@ -39,6 +39,7 @@

    Module lib.tools.filesystem

    from tools import fnmatch try: + import uasyncio import uos except: pass @@ -56,15 +57,15 @@

    Module lib.tools.filesystem

    pass return False -previousFileInfo = [] +previous_file_info = [] def fileinfo(path): """ Get the file informations """ - global previousFileInfo - if len(previousFileInfo) == 0: - previousFileInfo = [path, os.stat(path)] - elif previousFileInfo[0] != path: - previousFileInfo = [path, os.stat(path)] - return previousFileInfo[1] + global previous_file_info + if len(previous_file_info) == 0: + previous_file_info = [path, os.stat(path)] + elif previous_file_info[0] != path: + previous_file_info = [path, os.stat(path)] + return previous_file_info[1] def isdir(path): """ Indicates if the path is a directory """ @@ -98,18 +99,18 @@

    Module lib.tools.filesystem

    sep='\\' altsep = '/' extsep = '.' - sepIndex = p.rfind(sep) + sep_index = p.rfind(sep) if altsep: altsepIndex = p.rfind(altsep) - sepIndex = max(sepIndex, altsepIndex) - - dotIndex = p.rfind(extsep) - if dotIndex > sepIndex: - filenameIndex = sepIndex + 1 - while filenameIndex < dotIndex: - if p[filenameIndex:filenameIndex+1] != extsep: - return p[:dotIndex], p[dotIndex:] - filenameIndex += 1 + sep_index = max(sep_index, altsepIndex) + + dot_index = p.rfind(extsep) + if dot_index > sep_index: + filename_index = sep_index + 1 + while filename_index < dot_index: + if p[filename_index:filename_index+1] != extsep: + return p[:dot_index], p[dot_index:] + filename_index += 1 return p, p[:0] def split(p): @@ -208,15 +209,19 @@

    Module lib.tools.filesystem

    if path == "": path = "." if path is not None and pattern is not None: - for file in os.listdir(path): + for file_info in uos.ilistdir(path): + name = file_info[0] + typ = file_info[1] if path != "": - filename = path + "/" + file + filename = path + "/" + name else: - filename = file + filename = name if sys.platform != "win32": filename = filename.replace("//","/") filename = filename.replace("//","/") - if isdir(filename): + + # if directory + if typ & 0xF000 == 0x4000: if displayer: displayer.show(filename) else: @@ -226,12 +231,51 @@

    Module lib.tools.filesystem

    filenames += fils directories += dirs else: - if fnmatch.fnmatch(file, pattern): + if fnmatch.fnmatch(name, pattern): + if displayer: + displayer.show(filename) + filenames = [""] + else: + filenames.append(filename) + return directories, filenames + +async def ascandir(path, pattern, recursive, displayer=None): + """ Asynchronous scan recursively a directory """ + filenames = [] + directories = [] + if path == "": + path = "." + if path is not None and pattern is not None: + for file_info in uos.ilistdir(path): + name = file_info[0] + typ = file_info[1] + if path != "": + filename = path + "/" + name + else: + filename = name + if sys.platform != "win32": + filename = filename.replace("//","/") + filename = filename.replace("//","/") + + # if directory + if typ & 0xF000 == 0x4000: + if displayer: + displayer.show(filename) + else: + directories.append(filename) + if recursive: + dirs,fils = await ascandir(filename, pattern, recursive, displayer) + filenames += fils + directories += dirs + else: + if fnmatch.fnmatch(name, pattern): if displayer: displayer.show(filename) filenames = [""] else: filenames.append(filename) + if ismicropython(): + await uasyncio.sleep_ms(3) return directories, filenames def prefix(files): @@ -381,6 +425,55 @@

    Functions

    return cwd
    +
    +async def ascandir(path, pattern, recursive, displayer=None) +
    +
    +

    Asynchronous scan recursively a directory

    +
    + +Expand source code + +
    async def ascandir(path, pattern, recursive, displayer=None):
    +        """ Asynchronous scan recursively a directory """
    +        filenames   = []
    +        directories = []
    +        if path == "":
    +                path = "."
    +        if path is not None and pattern is not None:
    +                for file_info in uos.ilistdir(path):
    +                        name = file_info[0]
    +                        typ  = file_info[1]
    +                        if path != "":
    +                                filename = path + "/" + name
    +                        else:
    +                                filename = name
    +                        if sys.platform != "win32":
    +                                filename = filename.replace("//","/")
    +                                filename = filename.replace("//","/")
    +
    +                        # if directory
    +                        if typ & 0xF000 == 0x4000:
    +                                if displayer:
    +                                        displayer.show(filename)
    +                                else:
    +                                        directories.append(filename)
    +                                if recursive:
    +                                        dirs,fils = await ascandir(filename, pattern, recursive, displayer)
    +                                        filenames += fils
    +                                        directories += dirs
    +                        else:
    +                                if fnmatch.fnmatch(name, pattern):
    +                                        if displayer:
    +                                                displayer.show(filename)
    +                                                filenames = [""]
    +                                        else:
    +                                                filenames.append(filename)
    +                if ismicropython():
    +                        await uasyncio.sleep_ms(3)
    +        return directories, filenames
    +
    +
    def exists(filename)
    @@ -415,12 +508,12 @@

    Functions

    def fileinfo(path):
             """ Get the file informations """
    -        global previousFileInfo
    -        if len(previousFileInfo) == 0:
    -                previousFileInfo = [path, os.stat(path)]
    -        elif previousFileInfo[0] != path:
    -                previousFileInfo = [path, os.stat(path)]
    -        return previousFileInfo[1]
    + global previous_file_info + if len(previous_file_info) == 0: + previous_file_info = [path, os.stat(path)] + elif previous_file_info[0] != path: + previous_file_info = [path, os.stat(path)] + return previous_file_info[1]
    @@ -685,15 +778,19 @@

    Functions

    if path == "": path = "." if path is not None and pattern is not None: - for file in os.listdir(path): + for file_info in uos.ilistdir(path): + name = file_info[0] + typ = file_info[1] if path != "": - filename = path + "/" + file + filename = path + "/" + name else: - filename = file + filename = name if sys.platform != "win32": filename = filename.replace("//","/") filename = filename.replace("//","/") - if isdir(filename): + + # if directory + if typ & 0xF000 == 0x4000: if displayer: displayer.show(filename) else: @@ -703,7 +800,7 @@

    Functions

    filenames += fils directories += dirs else: - if fnmatch.fnmatch(file, pattern): + if fnmatch.fnmatch(name, pattern): if displayer: displayer.show(filename) filenames = [""] @@ -745,18 +842,18 @@

    Functions

    sep='\\' altsep = '/' extsep = '.' - sepIndex = p.rfind(sep) + sep_index = p.rfind(sep) if altsep: altsepIndex = p.rfind(altsep) - sepIndex = max(sepIndex, altsepIndex) - - dotIndex = p.rfind(extsep) - if dotIndex > sepIndex: - filenameIndex = sepIndex + 1 - while filenameIndex < dotIndex: - if p[filenameIndex:filenameIndex+1] != extsep: - return p[:dotIndex], p[dotIndex:] - filenameIndex += 1 + sep_index = max(sep_index, altsepIndex) + + dot_index = p.rfind(extsep) + if dot_index > sep_index: + filename_index = sep_index + 1 + while filename_index < dot_index: + if p[filename_index:filename_index+1] != extsep: + return p[:dot_index], p[dot_index:] + filename_index += 1 return p, p[:0]
    @@ -780,6 +877,7 @@

    Index

    • abspath
    • abspathbytes
    • +
    • ascandir
    • exists
    • fileinfo
    • filesize
    • diff --git a/doc/lib/tools/fnmatch.html b/doc/lib/tools/fnmatch.html index d861382..3958af8 100644 --- a/doc/lib/tools/fnmatch.html +++ b/doc/lib/tools/fnmatch.html @@ -31,6 +31,8 @@

      Module lib.tools.fnmatch

      """  Unix filename pattern matching.
       Extracted from cpython fnmatch.py """
      +# pylint:disable=consider-using-f-string
      +
       import re
       
       previous_pat = [None, None]
      diff --git a/doc/lib/tools/index.html b/doc/lib/tools/index.html
      index 06202b2..a66216f 100644
      --- a/doc/lib/tools/index.html
      +++ b/doc/lib/tools/index.html
      @@ -49,7 +49,11 @@ 

      Sub-modules

      lib.tools.builddate
      -
      +

      Build date

      +
      +
      lib.tools.date
      +
      +

      Date and time utilities

      lib.tools.encryption
      @@ -98,6 +102,10 @@

      Sub-modules

      Logger and exception functions

      +
      lib.tools.region
      +
      +

      Language selected and regional time

      +
      lib.tools.sdcard

      Sdcard management class

      @@ -106,6 +114,10 @@

      Sub-modules

      Strings utilities

      +
      lib.tools.support
      +
      +

      List the supported configuration according to the hardware

      +
      lib.tools.system

      System utilities functions

      @@ -152,6 +164,7 @@

      Index

    • lib.tools.awake
    • lib.tools.battery
    • lib.tools.builddate
    • +
    • lib.tools.date
    • lib.tools.encryption
    • lib.tools.exchange
    • lib.tools.filesystem
    • @@ -163,8 +176,10 @@

      Index

    • lib.tools.lang_french
    • lib.tools.linearfunction
    • lib.tools.logger
    • +
    • lib.tools.region
    • lib.tools.sdcard
    • lib.tools.strings
    • +
    • lib.tools.support
    • lib.tools.system
    • lib.tools.tasking
    • lib.tools.terminal
    • diff --git a/doc/lib/tools/info.html b/doc/lib/tools/info.html index 5189a41..05f6095 100644 --- a/doc/lib/tools/info.html +++ b/doc/lib/tools/info.html @@ -29,11 +29,13 @@

      Module lib.tools.info

      # Distributed under MIT License
       # Copyright (c) 2021 Remi BERTHOLET
      +# pylint:disable=consider-using-f-string
       """ Device informations """
       import sys
       import time
       import os
      -from tools import strings,filesystem
      +from tools import strings,filesystem,lang,date
      +
       try:
               import machine
       except:
      @@ -60,7 +62,7 @@ 

      Module lib.tools.info

      else: return True -def meminfo(display=True): +def meminfo(): """ Get memory informations """ import gc try: @@ -68,51 +70,69 @@

      Module lib.tools.info

      alloc = gc.mem_alloc() free = gc.mem_free() total = alloc+free - result = b"Memory : alloc=%s free=%s total=%s used=%-3.2f%%"%( + result = lang.memory_info%( strings.size_to_bytes(alloc, 1), strings.size_to_bytes(free, 1), strings.size_to_bytes(total, 1), 100-(free*100/total)) - if display: - print(strings.tostrings(result)) - else: - return result except: - return b"Mem unavailable" + result = lang.no_information + return result -def flashinfo(mountpoint=None, display=True): +def flash_size(mountpoint=None): """ Get flash informations """ - try: - import uos - if mountpoint is None: - mountpoint = os.getcwd() - status = uos.statvfs(mountpoint) + import uos + if mountpoint is None: + mountpoint = os.getcwd() + status = uos.statvfs(mountpoint) + if filesystem.ismicropython(): free = status[0]*status[3] if free < 0: free = 0 total = status[1]*status[2] alloc = total - free - result = b"Disk %s : alloc=%s free=%s total=%s used=%-3.2f%%"%(strings.tobytes(mountpoint), - strings.size_to_string(alloc, 1), - strings.size_to_string(free, 1), - strings.size_to_string(total, 1), - 100-(free*100/total)) - if display: - print(strings.tostrings(result)) - else: - return result + else: + free = status.f_bavail * status.f_frsize + total = status.f_blocks * status.f_frsize + alloc = (status.f_blocks - status.f_bfree) * status.f_frsize + return total, alloc, free + + +def flashinfo(mountpoint=None): + """ Get flash informations """ + try: + total, alloc, free = flash_size(mountpoint=mountpoint) except: - return b"Flash unavailable" + alloc = 1 + free = 1 + total = alloc+free + percent = 100-(free*100/total) + total = strings.size_to_bytes(total, 1) + free = strings.size_to_bytes(free, 1) + alloc = strings.size_to_bytes(alloc, 1) + if mountpoint is None: + mountpoint = b"/" + else: + mountpoint = strings.tobytes(mountpoint) + result = lang.flash_info%(mountpoint, alloc, free, total, percent) + return result -def sysinfo(display=True, text=""): - """ Get system informations """ +def deviceinfo(): + """ Get device informations """ try: - result = b"Device : %s%s %dMhz\nTime : %s\n%s\n%s"%(text, sys.platform, machine.freq()//1000000, strings.date_to_bytes(), meminfo(False), flashinfo("/",False)) - if display: - print(strings.tostrings(result)) - else: - return result + return b"%s %dMhz"%(strings.tobytes(sys.platform), machine.freq()//1000000) except: + return b"Device information unavailable" + +def sysinfo(): + """ Get system informations """ + try: + result = b"Device : %s\n"%(deviceinfo()) + result += b"Time : %s\n"%(date.date_to_bytes()) + result += b"%s : %s\n"%(lang.memory_label, meminfo()) + result += b"%s : %s"%(lang.flash_label, flashinfo("/")) + return result + except Exception as err: return b"Sysinfo not available" up_last=None @@ -137,34 +157,41 @@

      Module lib.tools.info

      up += up_total return up -def uptime(text="days"): +def uptime(text=b"days"): """ Tell how long the system has been running """ - up = uptime_sec() - seconds = (up)%60 - mins = (up/60)%60 - hours = (up/3600)%24 - days = (up/86400) - return "%d %s, %d:%02d:%02d"%(days, strings.tostrings(text),hours,mins,seconds) + try: + up = uptime_sec() + seconds = (up)%60 + mins = (up/60)%60 + hours = (up/3600)%24 + days = (up/86400) + return b"%d %s, %d:%02d:%02d"%(days, text,hours,mins,seconds) + except: + return lang.no_information _last_activity = 0 def get_last_activity(): """ Get the last activity from user """ + # pylint:disable=global-variable-not-assigned global _last_activity return _last_activity def set_last_activity(): """ Set the last activity from user """ + # pylint:disable=global-variable-not-assigned global _last_activity _last_activity = uptime_sec() _issues_counter = 0 def increase_issues_counter(): """ Increases a issue counter, that may require a reboot if there are too many""" + # pylint:disable=global-variable-not-assigned global _issues_counter _issues_counter += 1 def get_issues_counter(): """ Return the value of the issues counter """ + # pylint:disable=global-variable-not-assigned global _issues_counter return _issues_counter
      @@ -176,8 +203,25 @@

      Module lib.tools.info

      Functions

      -
      -def flashinfo(mountpoint=None, display=True) +
      +def deviceinfo() +
      +
      +

      Get device informations

      +
      + +Expand source code + +
      def deviceinfo():
      +        """ Get device informations """
      +        try:
      +                return b"%s %dMhz"%(strings.tobytes(sys.platform), machine.freq()//1000000)
      +        except:
      +                return b"Device information unavailable"
      +
      +
      +
      +def flash_size(mountpoint=None)

      Get flash informations

      @@ -185,29 +229,52 @@

      Functions

      Expand source code -
      def flashinfo(mountpoint=None, display=True):
      +
      def flash_size(mountpoint=None):
               """ Get flash informations """
      -        try:
      -                import uos
      -                if mountpoint is None:
      -                        mountpoint = os.getcwd()
      -                status = uos.statvfs(mountpoint)
      +        import uos
      +        if mountpoint is None:
      +                mountpoint = os.getcwd()
      +        status = uos.statvfs(mountpoint)
      +        if filesystem.ismicropython():
                       free  = status[0]*status[3]
                       if free < 0:
                               free = 0
                       total = status[1]*status[2]
                       alloc  = total - free
      -                result = b"Disk %s : alloc=%s free=%s total=%s used=%-3.2f%%"%(strings.tobytes(mountpoint),
      -                        strings.size_to_string(alloc, 1),
      -                        strings.size_to_string(free,  1),
      -                        strings.size_to_string(total, 1),
      -                        100-(free*100/total))
      -                if display:
      -                        print(strings.tostrings(result))
      -                else:
      -                        return result
      +        else:
      +                free = status.f_bavail * status.f_frsize
      +                total = status.f_blocks * status.f_frsize
      +                alloc = (status.f_blocks - status.f_bfree) * status.f_frsize
      +        return total, alloc, free
      + +
      +
      +def flashinfo(mountpoint=None) +
      +
      +

      Get flash informations

      +
      + +Expand source code + +
      def flashinfo(mountpoint=None):
      +        """ Get flash informations """
      +        try:
      +                total, alloc, free = flash_size(mountpoint=mountpoint)
               except:
      -                return b"Flash unavailable"
      + alloc = 1 + free = 1 + total = alloc+free + percent = 100-(free*100/total) + total = strings.size_to_bytes(total, 1) + free = strings.size_to_bytes(free, 1) + alloc = strings.size_to_bytes(alloc, 1) + if mountpoint is None: + mountpoint = b"/" + else: + mountpoint = strings.tobytes(mountpoint) + result = lang.flash_info%(mountpoint, alloc, free, total, percent) + return result
      @@ -221,6 +288,7 @@

      Functions

      def get_issues_counter():
               """ Return the value of the issues counter """
      +        # pylint:disable=global-variable-not-assigned
               global _issues_counter
               return _issues_counter
      @@ -236,6 +304,7 @@

      Functions

      def get_last_activity():
               """ Get the last activity from user """
      +        # pylint:disable=global-variable-not-assigned
               global _last_activity
               return _last_activity
      @@ -251,6 +320,7 @@

      Functions

      def increase_issues_counter():
               """ Increases a issue counter, that may require a reboot if there are too many"""
      +        # pylint:disable=global-variable-not-assigned
               global _issues_counter
               _issues_counter += 1
      @@ -277,7 +347,7 @@

      Functions

      -def meminfo(display=True) +def meminfo()

      Get memory informations

      @@ -285,7 +355,7 @@

      Functions

      Expand source code -
      def meminfo(display=True):
      +
      def meminfo():
               """ Get memory informations """
               import gc
               try:
      @@ -293,17 +363,14 @@ 

      Functions

      alloc = gc.mem_alloc() free = gc.mem_free() total = alloc+free - result = b"Memory : alloc=%s free=%s total=%s used=%-3.2f%%"%( + result = lang.memory_info%( strings.size_to_bytes(alloc, 1), strings.size_to_bytes(free, 1), strings.size_to_bytes(total, 1), 100-(free*100/total)) - if display: - print(strings.tostrings(result)) - else: - return result except: - return b"Mem unavailable"
      + result = lang.no_information + return result
      @@ -317,12 +384,13 @@

      Functions

      def set_last_activity():
               """ Set the last activity from user """
      +        # pylint:disable=global-variable-not-assigned
               global _last_activity
               _last_activity = uptime_sec()
      -def sysinfo(display=True, text='') +def sysinfo()

      Get system informations

      @@ -330,15 +398,15 @@

      Functions

      Expand source code -
      def sysinfo(display=True, text=""):
      +
      def sysinfo():
               """ Get system informations """
               try:
      -                result = b"Device : %s%s %dMhz\nTime   : %s\n%s\n%s"%(text, sys.platform, machine.freq()//1000000, strings.date_to_bytes(), meminfo(False), flashinfo("/",False))
      -                if display:
      -                        print(strings.tostrings(result))
      -                else:
      -                        return result
      -        except:
      +                result  = b"Device : %s\n"%(deviceinfo())
      +                result += b"Time   : %s\n"%(date.date_to_bytes())
      +                result += b"%s : %s\n"%(lang.memory_label, meminfo())
      +                result += b"%s : %s"%(lang.flash_label, flashinfo("/"))
      +                return result
      +        except Exception as err:
                       return b"Sysinfo not available"
      @@ -363,7 +431,7 @@

      Functions

      -def uptime(text='days') +def uptime(text=b'days')

      Tell how long the system has been running

      @@ -371,14 +439,17 @@

      Functions

      Expand source code -
      def uptime(text="days"):
      +
      def uptime(text=b"days"):
               """ Tell how long the system has been running """
      -        up = uptime_sec()
      -        seconds = (up)%60
      -        mins    = (up/60)%60
      -        hours   = (up/3600)%24
      -        days    = (up/86400)
      -        return "%d %s, %d:%02d:%02d"%(days, strings.tostrings(text),hours,mins,seconds)
      + try: + up = uptime_sec() + seconds = (up)%60 + mins = (up/60)%60 + hours = (up/3600)%24 + days = (up/86400) + return b"%d %s, %d:%02d:%02d"%(days, text,hours,mins,seconds) + except: + return lang.no_information
      @@ -428,6 +499,8 @@

      Index

    • Functions

        +
      • deviceinfo
      • +
      • flash_size
      • flashinfo
      • get_issues_counter
      • get_last_activity
      • diff --git a/doc/lib/tools/jsonconfig.html b/doc/lib/tools/jsonconfig.html index a26937a..aaaeda2 100644 --- a/doc/lib/tools/jsonconfig.html +++ b/doc/lib/tools/jsonconfig.html @@ -33,6 +33,7 @@

        Module lib.tools.jsonconfig

        # Distributed under MIT License
         # Copyright (c) 2021 Remi BERTHOLET
        +# pylint:disable=consider-using-f-string
         """ Functions for managing the json configuration.
         All configuration classes end the name with the word Config.
         For each of these classes, a json file with the same name is stored in the config directory of the board. """
        @@ -44,11 +45,12 @@ 

        Module lib.tools.jsonconfig

        import uos except: import os as uos + try: - from tools import logger,strings,filesystem -except: + from tools import logger,strings,filesystem,date +except Exception as err: # pylint:disable=multiple-imports - import logger,strings,filesystem + import logger,strings,filesystem,date self_config = None @@ -72,7 +74,7 @@

        Module lib.tools.jsonconfig

        file, filename = self.open(file=file, read_write="w", part_filename=part_filename) data = self.__dict__.copy() del data["modification_date"] - json.dump(strings.tostrings(data),file) + json.dump(strings.tostrings(data),file,separators=(',', ':')) file.close() self.modification_date = uos.stat(filename)[8] return True @@ -84,7 +86,7 @@

        Module lib.tools.jsonconfig

        """ Convert the configuration to string """ data = self.__dict__.copy() del data["modification_date"] - return json.dumps(strings.tostrings(data)) + return json.dumps(strings.tostrings(data),separators=(',', ':')) def get_pathname(self, part_filename=""): """ Get the configuration filename according to the class name """ @@ -112,6 +114,7 @@

        Module lib.tools.jsonconfig

        def open(self, file=None, read_write="r", part_filename=""): """ Create or open configuration file """ + # pylint:disable=unspecified-encoding filename = file if filesystem.exists(self.config_root()) is False: filesystem.makedir(self.config_root()) @@ -122,7 +125,7 @@

        Module lib.tools.jsonconfig

        file = open(filename, read_write) return file, filename - def update(self, params): + def update(self, params, show_error=True): """ Update object with html request params """ global self_config if b"name" in params and b"value" in params and len(params) == 2: @@ -131,6 +134,7 @@

        Module lib.tools.jsonconfig

        else: setmany = True self_config = self + for name in self.__dict__.keys(): # Case of web input is missing when bool is false if type(self.__dict__[name]) == type(True): @@ -154,13 +158,32 @@

        Module lib.tools.jsonconfig

        if setmany: params[name] = False # Case of web input is integer but string with number received - elif type(self.__dict__[name]) == type(0) or type(self.__dict__[name]) == type(0.): + elif type(self.__dict__[name]) == type(0): name = strings.tobytes(name) if name in params: try: params[name] = int(params[name]) + except: + if b"date" in name: + try: + params[name] = date.html_to_date(params[name]) + except: + params[name] = 0 + elif b"time" in name: + try: + params[name] = date.html_to_time(params[name]) + except: + params[name] = 0 + else: + params[name] = 0 + elif type(self.__dict__[name]) == type(0.): + name = strings.tobytes(name) + if name in params: + try: + params[name] = float(params[name]) except: params[name] = 0 + result = True for name, value in params.items(): execval = strings.tostrings(name) @@ -169,7 +192,9 @@

        Module lib.tools.jsonconfig

        # pylint: disable=exec-used exec("a = self_config.%s"%execval) existing = True - except: + except Exception as err: + if "'NoneType' object" in str(err): + result = None existing = False if existing: @@ -177,16 +202,17 @@

        Module lib.tools.jsonconfig

        # pylint: disable=exec-used exec(execval) else: - if name != b"action": + if name != b"action" and show_error and result is not None: print("%s.%s not existing"%(self.__class__.__name__, strings.tostrings(name))) except Exception as err: logger.syslog(err, "Error on %s"%(execval)) result = False - del self_config + self_config = None return result def load(self, file = None, part_filename="", tobytes=True, errorlog=True): """ Load object with the file specified """ + filename = "" try: filename = self.get_pathname(strings.tofilename(part_filename)) file, filename = self.open(file=file, read_write="r", part_filename=part_filename) @@ -267,7 +293,7 @@

        Classes

        file, filename = self.open(file=file, read_write="w", part_filename=part_filename) data = self.__dict__.copy() del data["modification_date"] - json.dump(strings.tostrings(data),file) + json.dump(strings.tostrings(data),file,separators=(',', ':')) file.close() self.modification_date = uos.stat(filename)[8] return True @@ -279,7 +305,7 @@

        Classes

        """ Convert the configuration to string """ data = self.__dict__.copy() del data["modification_date"] - return json.dumps(strings.tostrings(data)) + return json.dumps(strings.tostrings(data),separators=(',', ':')) def get_pathname(self, part_filename=""): """ Get the configuration filename according to the class name """ @@ -307,6 +333,7 @@

        Classes

        def open(self, file=None, read_write="r", part_filename=""): """ Create or open configuration file """ + # pylint:disable=unspecified-encoding filename = file if filesystem.exists(self.config_root()) is False: filesystem.makedir(self.config_root()) @@ -317,7 +344,7 @@

        Classes

        file = open(filename, read_write) return file, filename - def update(self, params): + def update(self, params, show_error=True): """ Update object with html request params """ global self_config if b"name" in params and b"value" in params and len(params) == 2: @@ -326,6 +353,7 @@

        Classes

        else: setmany = True self_config = self + for name in self.__dict__.keys(): # Case of web input is missing when bool is false if type(self.__dict__[name]) == type(True): @@ -349,13 +377,32 @@

        Classes

        if setmany: params[name] = False # Case of web input is integer but string with number received - elif type(self.__dict__[name]) == type(0) or type(self.__dict__[name]) == type(0.): + elif type(self.__dict__[name]) == type(0): name = strings.tobytes(name) if name in params: try: params[name] = int(params[name]) + except: + if b"date" in name: + try: + params[name] = date.html_to_date(params[name]) + except: + params[name] = 0 + elif b"time" in name: + try: + params[name] = date.html_to_time(params[name]) + except: + params[name] = 0 + else: + params[name] = 0 + elif type(self.__dict__[name]) == type(0.): + name = strings.tobytes(name) + if name in params: + try: + params[name] = float(params[name]) except: params[name] = 0 + result = True for name, value in params.items(): execval = strings.tostrings(name) @@ -364,7 +411,9 @@

        Classes

        # pylint: disable=exec-used exec("a = self_config.%s"%execval) existing = True - except: + except Exception as err: + if "'NoneType' object" in str(err): + result = None existing = False if existing: @@ -372,16 +421,17 @@

        Classes

        # pylint: disable=exec-used exec(execval) else: - if name != b"action": + if name != b"action" and show_error and result is not None: print("%s.%s not existing"%(self.__class__.__name__, strings.tostrings(name))) except Exception as err: logger.syslog(err, "Error on %s"%(execval)) result = False - del self_config + self_config = None return result def load(self, file = None, part_filename="", tobytes=True, errorlog=True): """ Load object with the file specified """ + filename = "" try: filename = self.get_pathname(strings.tofilename(part_filename)) file, filename = self.open(file=file, read_write="r", part_filename=part_filename) @@ -555,6 +605,7 @@

        Methods

        def load(self, file = None, part_filename="", tobytes=True, errorlog=True):
                 """ Load object with the file specified """
        +        filename = ""
                 try:
                         filename = self.get_pathname(strings.tofilename(part_filename))
                         file, filename = self.open(file=file, read_write="r", part_filename=part_filename)
        @@ -588,6 +639,7 @@ 

        Methods

        def open(self, file=None, read_write="r", part_filename=""):
                 """ Create or open configuration file """
        +        # pylint:disable=unspecified-encoding
                 filename = file
                 if filesystem.exists(self.config_root()) is False:
                         filesystem.makedir(self.config_root())
        @@ -615,7 +667,7 @@ 

        Methods

        file, filename = self.open(file=file, read_write="w", part_filename=part_filename) data = self.__dict__.copy() del data["modification_date"] - json.dump(strings.tostrings(data),file) + json.dump(strings.tostrings(data),file,separators=(',', ':')) file.close() self.modification_date = uos.stat(filename)[8] return True @@ -637,11 +689,11 @@

        Methods

        """ Convert the configuration to string """ data = self.__dict__.copy() del data["modification_date"] - return json.dumps(strings.tostrings(data))
        + return json.dumps(strings.tostrings(data),separators=(',', ':'))
        -def update(self, params) +def update(self, params, show_error=True)

        Update object with html request params

        @@ -649,7 +701,7 @@

        Methods

        Expand source code -
        def update(self, params):
        +
        def update(self, params, show_error=True):
                 """ Update object with html request params """
                 global self_config
                 if b"name" in params and b"value" in params and len(params) == 2:
        @@ -658,6 +710,7 @@ 

        Methods

        else: setmany = True self_config = self + for name in self.__dict__.keys(): # Case of web input is missing when bool is false if type(self.__dict__[name]) == type(True): @@ -681,13 +734,32 @@

        Methods

        if setmany: params[name] = False # Case of web input is integer but string with number received - elif type(self.__dict__[name]) == type(0) or type(self.__dict__[name]) == type(0.): + elif type(self.__dict__[name]) == type(0): name = strings.tobytes(name) if name in params: try: params[name] = int(params[name]) + except: + if b"date" in name: + try: + params[name] = date.html_to_date(params[name]) + except: + params[name] = 0 + elif b"time" in name: + try: + params[name] = date.html_to_time(params[name]) + except: + params[name] = 0 + else: + params[name] = 0 + elif type(self.__dict__[name]) == type(0.): + name = strings.tobytes(name) + if name in params: + try: + params[name] = float(params[name]) except: params[name] = 0 + result = True for name, value in params.items(): execval = strings.tostrings(name) @@ -696,7 +768,9 @@

        Methods

        # pylint: disable=exec-used exec("a = self_config.%s"%execval) existing = True - except: + except Exception as err: + if "'NoneType' object" in str(err): + result = None existing = False if existing: @@ -704,12 +778,12 @@

        Methods

        # pylint: disable=exec-used exec(execval) else: - if name != b"action": + if name != b"action" and show_error and result is not None: print("%s.%s not existing"%(self.__class__.__name__, strings.tostrings(name))) except Exception as err: logger.syslog(err, "Error on %s"%(execval)) result = False - del self_config + self_config = None return result
        diff --git a/doc/lib/tools/lang.html b/doc/lib/tools/lang.html index 2c26c88..be98fac 100644 --- a/doc/lib/tools/lang.html +++ b/doc/lib/tools/lang.html @@ -29,40 +29,36 @@

        Module lib.tools.lang

        # Distributed under MIT License
         # Copyright (c) 2021 Remi BERTHOLET
        +# pylint:disable=consider-using-f-string
         """ Language selected and regional time """
        -from tools.jsonconfig import *
        -
        -region_config = None
        -
        -class RegionConfig(JsonConfig):
        -        """ Language selected and regional time """
        -        def __init__(self):
        -                """ Constructor """
        -                JsonConfig.__init__(self)
        -                self.lang        = b"english"
        -                self.offset_time  = 1
        -                self.dst         = True
        -                self.current_time = 0
        -
        -        @staticmethod
        -        def get():
        -                """ Return region configuration """
        -                global region_config
        -                if region_config is None:
        -                        region_config = RegionConfig()
        -                        if region_config.load() is False:
        -                                region_config.save()
        -
        -                if region_config.is_changed():
        -                        region_config.load()
        -                return region_config
        +import time
        +import sys
        +from tools import region, strings, logger
         
         try:
        -        exec(b"from tools.lang_%s import *"%RegionConfig.get().lang)
        +        exec(b"from tools.lang_%s import *"%region.RegionConfig.get().lang)
        +        logger.syslog("Select lang : %s"%strings.tostrings(region.RegionConfig.get().lang))
         except Exception as err:
        -        from tools import logger
                 logger.syslog(err)
        -        from tools.lang_english import *
        + from tools.lang_english import * + +def translate_date(current_date, with_day=True): + """ Return the date formated according to the lang """ + # pylint:disable=undefined-variable + # pylint:disable=possibly-unused-variable + # pylint:disable=used-before-assignment + # pylint:disable=global-variable-not-assigned + year, month, day, _, _, _, weekday = time.localtime(current_date)[:7] + global weekdays, months, date_format, month_format + weekday = weekdays[weekday] + month = months[month-1] + info = {"weekday":weekday,"month":month,"day":day,"year":year} + if sys.implementation.name == "micropython": + return date_format % info + if with_day: + return date_format % strings.tobytes(info) + else: + return month_format % strings.tobytes(info)
    @@ -70,75 +66,39 @@

    Module lib.tools.lang

    -
    -
    -

    Classes

    -
    -
    -class RegionConfig -
    -
    -

    Language selected and regional time

    -

    Constructor

    -
    - -Expand source code - -
    class RegionConfig(JsonConfig):
    -        """ Language selected and regional time """
    -        def __init__(self):
    -                """ Constructor """
    -                JsonConfig.__init__(self)
    -                self.lang        = b"english"
    -                self.offset_time  = 1
    -                self.dst         = True
    -                self.current_time = 0
    -
    -        @staticmethod
    -        def get():
    -                """ Return region configuration """
    -                global region_config
    -                if region_config is None:
    -                        region_config = RegionConfig()
    -                        if region_config.load() is False:
    -                                region_config.save()
    -
    -                if region_config.is_changed():
    -                        region_config.load()
    -                return region_config
    -
    -

    Ancestors

    -
      -
    • tools.jsonconfig.JsonConfig
    • -
    -

    Static methods

    +

    Functions

    -
    -def get() +
    +def translate_date(current_date, with_day=True)
    -

    Return region configuration

    +

    Return the date formated according to the lang

    Expand source code -
    @staticmethod
    -def get():
    -        """ Return region configuration """
    -        global region_config
    -        if region_config is None:
    -                region_config = RegionConfig()
    -                if region_config.load() is False:
    -                        region_config.save()
    -
    -        if region_config.is_changed():
    -                region_config.load()
    -        return region_config
    +
    def translate_date(current_date, with_day=True):
    +        """ Return the date formated according to the lang """
    +        # pylint:disable=undefined-variable
    +        # pylint:disable=possibly-unused-variable
    +        # pylint:disable=used-before-assignment
    +        # pylint:disable=global-variable-not-assigned
    +        year, month, day, _, _, _, weekday = time.localtime(current_date)[:7]
    +        global weekdays, months, date_format, month_format
    +        weekday = weekdays[weekday]
    +        month   = months[month-1]
    +        info = {"weekday":weekday,"month":month,"day":day,"year":year}
    +        if sys.implementation.name == "micropython":
    +                return date_format % info
    +        if with_day:
    +                return date_format % strings.tobytes(info)
    +        else:
    +                return month_format % strings.tobytes(info)
    -
    -
    +
    +
    diff --git a/doc/lib/tools/lang_french.html b/doc/lib/tools/lang_french.html index 1ee8f40..6dbc92f 100644 --- a/doc/lib/tools/lang_french.html +++ b/doc/lib/tools/lang_french.html @@ -97,7 +97,7 @@

    Module lib.tools.lang_french

    pushover_token =b"API Jeton" enter_pushover_token =b"Entrer le jeton API de pushover" see_pushover_website =b"Voir le site web pushover" -historic_not_available =b"Pas encore disponible, attendre 5 minutes" +historic_not_available =b"Pas encore disponible, ressayez plus tard" last_motion_detections =b"Derni\xC3\xA8res d\xC3\xA9tections de mouvement" convert_ip_address =b"Convertir les adresses ip en noms DNS" smartphone_d =b"Smartphone %d" @@ -133,7 +133,7 @@

    Module lib.tools.lang_french

    lt =b"&lt;-" gt =b"-&gt;" forget =b"Oublier" -set_default =b"Mettre par d\xC3\xA9faut" +set_default =b"Par d\xC3\xA9faut" modify =b"Modifier" days =b"jours" wifi_configuration =b"Configuration wifi" @@ -164,14 +164,12 @@

    Module lib.tools.lang_french

    unavailable =b"Non disponible" date =b"Date" uptime =b"Dur\xC3\xA9e de fonctionnement" -platform =b"Platforme" -frequency =b"Fr\xC3\xA9quence" build_date =b"Date de g\xC3\xA9n\xC3\xA9ration du firmware" -memory_free =b"M\xC3\xA9moire libre" -memory_allocated =b"M\xC3\xA9moire allou\xC3\xA9e" -memory_total =b"M\xC3\xA9moire totale" -flash_user =b"Taille flash utilisateur" -flash_size =b"Taille flash totale" +device_label =b"Platforme" +memory_label =b"M\xC3\xA9moire" +memory_info =b"allou\xC3\xA9e=%s libre=%s total=%s utilis\xC3\xA9e=%-3.2f%%" +flash_label =b"Flash " +flash_info =b"disque='%s' allou\xC3\xA9e=%s libre=%s total=%s utilis\xC3\xA9e=%-3.2f%%" change_password =b"Utilisateur et mot de passe" logout =b"D\xC3\xA9connexion" modify_password =b"Modifier mot de passe" @@ -205,7 +203,38 @@

    Module lib.tools.lang_french

    saturation =b"Saturation" hmirror =b"Mirroir horizontal" vflip =b"Rotation verticale" -flash_level =b"Luminosit\xC3\xA9 du flash"
    +flash_level =b"Luminosit\xC3\xA9 du flash" +signal_strength =b"Force signal wifi" +signal_excellent =b"Excellent" +signal_very_good =b"Tr\xC3\xA9s bon" +signal_good =b"Bon" +signal_low =b"Faible" +signal_very_low =b"Tr\xC3\xA9s faible" +signal_no =b"Aucun signal" +no_information =b"Aucune information" +monday =b"lundi" +tuesday =b"mardi" +wednesday =b"mercredi" +thursday =b"jeudi" +friday =b"vendredi" +saturday =b"samedi" +sunday =b"dimanche" +january =b"janvier" +february =b"f\xC3\xA9vrier" +march =b"mars" +april =b"avril" +may =b"mai" +june =b"juin" +july =b"juillet" +august =b"aout" +september =b"septembre" +october =b"octobre" +november =b"novembre" +december =b"d\xC3\xA9cembre" +date_format =b"%(weekday)s %(day)d %(month)s %(year)d" +month_format =b"%(month)s %(year)d" +weekdays = [monday,tuesday,wednesday,thursday,friday,saturday,sunday] +months = [january,february,march,april,may,june,july,august,september,october,november,december]
    diff --git a/doc/lib/tools/logger.html b/doc/lib/tools/logger.html index a5d7eb4..2face6c 100644 --- a/doc/lib/tools/logger.html +++ b/doc/lib/tools/logger.html @@ -29,11 +29,12 @@

    Module lib.tools.logger

    # Distributed under MIT License
     # Copyright (c) 2021 Remi BERTHOLET
    +# pylint:disable=consider-using-f-string
     """ Logger and exception functions """
     import sys
     import io
     try:
    -        from tools import filesystem, strings
    +        from tools import filesystem, strings, date
     except:
             import filesystem
             import strings
    @@ -55,7 +56,7 @@ 

    Module lib.tools.logger

    text = file.getvalue() return text -def syslog(err, msg="", display=True): +def syslog(err, msg="", display=True, write=True): """ Log the error in syslog.log file """ if isinstance(err, Exception): err = exception(err) @@ -68,11 +69,13 @@

    Module lib.tools.logger

    result = "%s%s"%(strings.tostrings(msg),strings.tostrings(err)) - log(result) + if write: + log(result) return result def log(msg): """ Log message in syslog.log file without printing """ + # pylint:disable=unspecified-encoding try: filename = "syslog.log" if filesystem.ismicropython(): @@ -89,7 +92,7 @@

    Module lib.tools.logger

    filesystem.rename(filename ,filename + ".1") log_file = open(filename,"a") - log_file.write(strings.date_ms_to_string() + " %s\n"%(msg)) + log_file.write(date.date_ms_to_string() + " %s\n"%(msg)) log_file.flush() log_file.close() except: @@ -167,6 +170,7 @@

    Functions

    def log(msg):
             """ Log message in syslog.log file without printing """
    +        # pylint:disable=unspecified-encoding
             try:
                     filename = "syslog.log"
                     if filesystem.ismicropython():
    @@ -183,7 +187,7 @@ 

    Functions

    filesystem.rename(filename ,filename + ".1") log_file = open(filename,"a") - log_file.write(strings.date_ms_to_string() + " %s\n"%(msg)) + log_file.write(date.date_ms_to_string() + " %s\n"%(msg)) log_file.flush() log_file.close() except: @@ -191,7 +195,7 @@

    Functions

    -def syslog(err, msg='', display=True) +def syslog(err, msg='', display=True, write=True)

    Log the error in syslog.log file

    @@ -199,7 +203,7 @@

    Functions

    Expand source code -
    def syslog(err, msg="", display=True):
    +
    def syslog(err, msg="", display=True, write=True):
             """ Log the error in syslog.log file """
             if isinstance(err, Exception):
                     err = exception(err)
    @@ -212,7 +216,8 @@ 

    Functions

    result = "%s%s"%(strings.tostrings(msg),strings.tostrings(err)) - log(result) + if write: + log(result) return result
    diff --git a/doc/lib/tools/region.html b/doc/lib/tools/region.html new file mode 100644 index 0000000..6e29c0c --- /dev/null +++ b/doc/lib/tools/region.html @@ -0,0 +1,162 @@ + + + + + + +lib.tools.region API documentation + + + + + + + + + + + +
    +
    +
    +

    Module lib.tools.region

    +
    +
    +

    Language selected and regional time

    +
    + +Expand source code + +
    # Distributed under MIT License
    +# Copyright (c) 2021 Remi BERTHOLET
    +""" Language selected and regional time """
    +from tools import jsonconfig
    +
    +region_config = None
    +
    +class RegionConfig(jsonconfig.JsonConfig):
    +        """ Language selected and regional time """
    +        def __init__(self):
    +                """ Constructor """
    +                jsonconfig.JsonConfig.__init__(self)
    +                self.lang        = b"english"
    +                self.offset_time  = 1
    +                self.dst         = True
    +                self.current_time = 0
    +
    +        @staticmethod
    +        def get():
    +                """ Return region configuration """
    +                global region_config
    +                if region_config is None:
    +                        region_config = RegionConfig()
    +                        if region_config.load() is False:
    +                                region_config.save()
    +                if region_config.is_changed():
    +                        region_config.load()
    +                return region_config
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    Classes

    +
    +
    +class RegionConfig +
    +
    +

    Language selected and regional time

    +

    Constructor

    +
    + +Expand source code + +
    class RegionConfig(jsonconfig.JsonConfig):
    +        """ Language selected and regional time """
    +        def __init__(self):
    +                """ Constructor """
    +                jsonconfig.JsonConfig.__init__(self)
    +                self.lang        = b"english"
    +                self.offset_time  = 1
    +                self.dst         = True
    +                self.current_time = 0
    +
    +        @staticmethod
    +        def get():
    +                """ Return region configuration """
    +                global region_config
    +                if region_config is None:
    +                        region_config = RegionConfig()
    +                        if region_config.load() is False:
    +                                region_config.save()
    +                if region_config.is_changed():
    +                        region_config.load()
    +                return region_config
    +
    +

    Ancestors

    +
      +
    • tools.jsonconfig.JsonConfig
    • +
    +

    Static methods

    +
    +
    +def get() +
    +
    +

    Return region configuration

    +
    + +Expand source code + +
    @staticmethod
    +def get():
    +        """ Return region configuration """
    +        global region_config
    +        if region_config is None:
    +                region_config = RegionConfig()
    +                if region_config.load() is False:
    +                        region_config.save()
    +        if region_config.is_changed():
    +                region_config.load()
    +        return region_config
    +
    +
    +
    +
    +
    +
    +
    + +
    + + + \ No newline at end of file diff --git a/doc/lib/tools/sdcard.html b/doc/lib/tools/sdcard.html index 6507f82..829d3df 100644 --- a/doc/lib/tools/sdcard.html +++ b/doc/lib/tools/sdcard.html @@ -29,6 +29,8 @@

    Module lib.tools.sdcard

    # Distributed under MIT License
     # Copyright (c) 2021 Remi BERTHOLET
    +# pylint:disable=consider-using-f-string
    +
     """ Sdcard management class """
     try:
             import machine
    @@ -63,7 +65,10 @@ 

    Module lib.tools.sdcard

    """ Return the maximal size of sdcard """ if SdCard.is_mounted(): status = uos.statvfs(SdCard.get_mountpoint()) - return status[1]*status[2] + if filesystem.ismicropython(): + return status[1]*status[2] + else: + return status.f_frsize*status.f_blocks else: return 0 @@ -72,7 +77,10 @@

    Module lib.tools.sdcard

    """ Return the free size of sdcard """ if SdCard.is_mounted(): status = uos.statvfs(SdCard.get_mountpoint()) - return status[0]*status[3] + if filesystem.ismicropython(): + return status[0]*status[3] + else: + return status.f_bsize * status.f_bfree else: return 0 @@ -100,14 +108,18 @@

    Module lib.tools.sdcard

    result = False if SdCard.is_mounted() is False: try: + # pylint:disable=redefined-outer-name + import os import machine sd = machine.SDCard(**(SdCard.slot[0])) if fs== "FAT": + # pylint:disable=no-member os.VfsFat.mkfs(sd) os.VfsFat(sd) result = True elif fs== "LFS": + # pylint:disable=no-member os.VfsLfs2.mkfs(sd) os.VfsLfs2(sd) result = True @@ -222,6 +234,7 @@

    Module lib.tools.sdcard

    logger.syslog(err) break try: + # pylint:disable=unspecified-encoding result = open(filepath,mode) break except OSError as err: @@ -326,7 +339,10 @@

    Classes

    """ Return the maximal size of sdcard """ if SdCard.is_mounted(): status = uos.statvfs(SdCard.get_mountpoint()) - return status[1]*status[2] + if filesystem.ismicropython(): + return status[1]*status[2] + else: + return status.f_frsize*status.f_blocks else: return 0 @@ -335,7 +351,10 @@

    Classes

    """ Return the free size of sdcard """ if SdCard.is_mounted(): status = uos.statvfs(SdCard.get_mountpoint()) - return status[0]*status[3] + if filesystem.ismicropython(): + return status[0]*status[3] + else: + return status.f_bsize * status.f_bfree else: return 0 @@ -363,14 +382,18 @@

    Classes

    result = False if SdCard.is_mounted() is False: try: + # pylint:disable=redefined-outer-name + import os import machine sd = machine.SDCard(**(SdCard.slot[0])) if fs== "FAT": + # pylint:disable=no-member os.VfsFat.mkfs(sd) os.VfsFat(sd) result = True elif fs== "LFS": + # pylint:disable=no-member os.VfsLfs2.mkfs(sd) os.VfsLfs2(sd) result = True @@ -485,6 +508,7 @@

    Classes

    logger.syslog(err) break try: + # pylint:disable=unspecified-encoding result = open(filepath,mode) break except OSError as err: @@ -607,6 +631,7 @@

    Static methods

    logger.syslog(err) break try: + # pylint:disable=unspecified-encoding result = open(filepath,mode) break except OSError as err: @@ -631,14 +656,18 @@

    Static methods

    result = False if SdCard.is_mounted() is False: try: + # pylint:disable=redefined-outer-name + import os import machine sd = machine.SDCard(**(SdCard.slot[0])) if fs== "FAT": + # pylint:disable=no-member os.VfsFat.mkfs(sd) os.VfsFat(sd) result = True elif fs== "LFS": + # pylint:disable=no-member os.VfsLfs2.mkfs(sd) os.VfsLfs2(sd) result = True @@ -661,7 +690,10 @@

    Static methods

    """ Return the free size of sdcard """ if SdCard.is_mounted(): status = uos.statvfs(SdCard.get_mountpoint()) - return status[0]*status[3] + if filesystem.ismicropython(): + return status[0]*status[3] + else: + return status.f_bsize * status.f_bfree else: return 0
    @@ -680,7 +712,10 @@

    Static methods

    """ Return the maximal size of sdcard """ if SdCard.is_mounted(): status = uos.statvfs(SdCard.get_mountpoint()) - return status[1]*status[2] + if filesystem.ismicropython(): + return status[1]*status[2] + else: + return status.f_frsize*status.f_blocks else: return 0
    diff --git a/doc/lib/tools/strings.html b/doc/lib/tools/strings.html index 1a9aefa..486937e 100644 --- a/doc/lib/tools/strings.html +++ b/doc/lib/tools/strings.html @@ -29,46 +29,11 @@

    Module lib.tools.strings

    # Distributed under MIT License
     # Copyright (c) 2021 Remi BERTHOLET
    +# pylint:disable=consider-using-f-string
     """ Strings utilities """
     import binascii
     import time
     
    -def local_time(date=None):
    -        """ Safe local time, it return 2000/1/1 00:00:00 if date can be extracted """
    -        try:
    -                year,month,day,hour,minute,second,weekday,yearday = time.localtime(date)[:8]
    -        except:
    -                year,month,day,hour,minute,second,weekday,yearday = 2000,1,1,0,0,0,0,0
    -        return year,month,day,hour,minute,second,weekday,yearday
    -
    -def date_to_string(date = None):
    -        """ Get a string with the current date """
    -        return date_to_bytes(date).decode("utf8")
    -
    -def date_to_bytes(date = None):
    -        """ Get a bytes with the current date """
    -        year,month,day,hour,minute,second,weekday,yearday = local_time(date)[:8]
    -        return b"%04d/%02d/%02d  %02d:%02d:%02d"%(year,month,day,hour,minute,second)
    -
    -def date_ms_to_string():
    -        """ Get a string with the current date with ms """
    -        ms = (time.time_ns() // 1000000)%1000
    -        year,month,day,hour,minute,second,weekday,yearday = local_time(None)[:8]
    -        return "%04d/%02d/%02d %02d:%02d:%02d.%03d"%(year,month,day,hour,minute,second,ms)
    -
    -def date_to_filename(date = None):
    -        """ Get a filename with a date """
    -        filename = date_to_string(date)
    -        filename = filename.replace("  "," ")
    -        filename = filename.replace(" ","_")
    -        filename = filename.replace("/","-")
    -        filename = filename.replace(":","-")
    -        return filename
    -
    -def date_to_path(date=None):
    -        """ Get a path with year/month/day/hour """
    -        year,month,day,hour,minute,second,weekday,yearday = local_time(date)[:8]
    -        return b"%04d/%02d/%02d/%02dh%02d"%(year,month,day,hour,minute)
     
     def size_to_string(size, largeur=6):
             """ Convert a size in a string with k, m, g, t..."""
    @@ -378,85 +343,6 @@ 

    Functions

    return hash_ % 65536
    -
    -def date_ms_to_string() -
    -
    -

    Get a string with the current date with ms

    -
    - -Expand source code - -
    def date_ms_to_string():
    -        """ Get a string with the current date with ms """
    -        ms = (time.time_ns() // 1000000)%1000
    -        year,month,day,hour,minute,second,weekday,yearday = local_time(None)[:8]
    -        return "%04d/%02d/%02d %02d:%02d:%02d.%03d"%(year,month,day,hour,minute,second,ms)
    -
    -
    -
    -def date_to_bytes(date=None) -
    -
    -

    Get a bytes with the current date

    -
    - -Expand source code - -
    def date_to_bytes(date = None):
    -        """ Get a bytes with the current date """
    -        year,month,day,hour,minute,second,weekday,yearday = local_time(date)[:8]
    -        return b"%04d/%02d/%02d  %02d:%02d:%02d"%(year,month,day,hour,minute,second)
    -
    -
    -
    -def date_to_filename(date=None) -
    -
    -

    Get a filename with a date

    -
    - -Expand source code - -
    def date_to_filename(date = None):
    -        """ Get a filename with a date """
    -        filename = date_to_string(date)
    -        filename = filename.replace("  "," ")
    -        filename = filename.replace(" ","_")
    -        filename = filename.replace("/","-")
    -        filename = filename.replace(":","-")
    -        return filename
    -
    -
    -
    -def date_to_path(date=None) -
    -
    -

    Get a path with year/month/day/hour

    -
    - -Expand source code - -
    def date_to_path(date=None):
    -        """ Get a path with year/month/day/hour """
    -        year,month,day,hour,minute,second,weekday,yearday = local_time(date)[:8]
    -        return b"%04d/%02d/%02d/%02dh%02d"%(year,month,day,hour,minute)
    -
    -
    -
    -def date_to_string(date=None) -
    -
    -

    Get a string with the current date

    -
    - -Expand source code - -
    def date_to_string(date = None):
    -        """ Get a string with the current date """
    -        return date_to_bytes(date).decode("utf8")
    -
    -
    def dump(buff, withColor=True)
    @@ -754,24 +640,6 @@

    Functions

    return False
    -
    -def local_time(date=None) -
    -
    -

    Safe local time, it return 2000/1/1 00:00:00 if date can be extracted

    -
    - -Expand source code - -
    def local_time(date=None):
    -        """ Safe local time, it return 2000/1/1 00:00:00 if date can be extracted """
    -        try:
    -                year,month,day,hour,minute,second,weekday,yearday = time.localtime(date)[:8]
    -        except:
    -                year,month,day,hour,minute,second,weekday,yearday = 2000,1,1,0,0,0,0,0
    -        return year,month,day,hour,minute,second,weekday,yearday
    -
    -
    def size_to_bytes(size, largeur=6)
    @@ -935,11 +803,6 @@

    Index

  • Functions

    • compute_hash
    • -
    • date_ms_to_string
    • -
    • date_to_bytes
    • -
    • date_to_filename
    • -
    • date_to_path
    • -
    • date_to_string
    • dump
    • dump_line
    • get_length_utf8
    • @@ -952,7 +815,6 @@

      Index

    • ispunctuation
    • isspace
    • isupper
    • -
    • local_time
    • size_to_bytes
    • size_to_string
    • ticks
    • diff --git a/doc/lib/tools/support.html b/doc/lib/tools/support.html new file mode 100644 index 0000000..c9ca0f8 --- /dev/null +++ b/doc/lib/tools/support.html @@ -0,0 +1,161 @@ + + + + + + +lib.tools.support API documentation + + + + + + + + + + + +
      +
      +
      +

      Module lib.tools.support

      +
      +
      +

      List the supported configuration according to the hardware

      +
      + +Expand source code + +
      """ List the supported configuration according to the hardware """
      +
      +import sys
      +
      +def telnet():
      +        """ Indicates if telnet is supported by this hardware """
      +        if sys.platform == "rp2":
      +                return False
      +        return True
      +
      +def hostname():
      +        """ Indicates if hostname can be used by this hardware """
      +        if sys.platform == "rp2":
      +                return False
      +        return True
      +
      +def static_ip_accesspoint():
      +        """ Indicates if static ip on access point can be used by this hardware """
      +        if sys.platform == "rp2":
      +                return False
      +        return True
      +
      +def battery():
      +        """ Indicates if battery setup supported """
      +        if sys.platform == "rp2":
      +                return False
      +        return True
      +
      +
      +
      +
      +
      +
      +
      +

      Functions

      +
      +
      +def battery() +
      +
      +

      Indicates if battery setup supported

      +
      + +Expand source code + +
      def battery():
      +        """ Indicates if battery setup supported """
      +        if sys.platform == "rp2":
      +                return False
      +        return True
      +
      +
      +
      +def hostname() +
      +
      +

      Indicates if hostname can be used by this hardware

      +
      + +Expand source code + +
      def hostname():
      +        """ Indicates if hostname can be used by this hardware """
      +        if sys.platform == "rp2":
      +                return False
      +        return True
      +
      +
      +
      +def static_ip_accesspoint() +
      +
      +

      Indicates if static ip on access point can be used by this hardware

      +
      + +Expand source code + +
      def static_ip_accesspoint():
      +        """ Indicates if static ip on access point can be used by this hardware """
      +        if sys.platform == "rp2":
      +                return False
      +        return True
      +
      +
      +
      +def telnet() +
      +
      +

      Indicates if telnet is supported by this hardware

      +
      + +Expand source code + +
      def telnet():
      +        """ Indicates if telnet is supported by this hardware """
      +        if sys.platform == "rp2":
      +                return False
      +        return True
      +
      +
      +
      +
      +
      +
      +
      + +
      + + + \ No newline at end of file diff --git a/doc/lib/tools/system.html b/doc/lib/tools/system.html index 719ef7b..c756845 100644 --- a/doc/lib/tools/system.html +++ b/doc/lib/tools/system.html @@ -36,8 +36,8 @@

      Module lib.tools.system

      def reboot(message="Reboot"): """ Reboot command """ logger.syslog(message) - from tools import lang - region_config = lang.RegionConfig() + from tools import region + region_config = region.RegionConfig() if region_config.load(): region_config.current_time = time.time() + 8 region_config.save() @@ -73,8 +73,8 @@

      Functions

      def reboot(message="Reboot"):
               """ Reboot command """
               logger.syslog(message)
      -        from tools import lang
      -        region_config = lang.RegionConfig()
      +        from tools import region
      +        region_config = region.RegionConfig()
               if region_config.load():
                       region_config.current_time = time.time() + 8
                       region_config.save()
      diff --git a/doc/lib/tools/tasking.html b/doc/lib/tools/tasking.html
      index 98928c5..686d288 100644
      --- a/doc/lib/tools/tasking.html
      +++ b/doc/lib/tools/tasking.html
      @@ -29,9 +29,10 @@ 

      Module lib.tools.tasking

      # Distributed under MIT License
       # Copyright (c) 2021 Remi BERTHOLET
      +# pylint:disable=consider-using-f-string
       """ Miscellaneous utility functions """
       import machine
      -from tools import strings,logger,system,watchdog
      +from tools import lang,strings,logger,system,watchdog, info
       
       class Inactivity:
               """ Class to manage inactivity timer """
      @@ -60,7 +61,7 @@ 

      Module lib.tools.tasking

      self.stop() self.start() -async def task_monitoring(task): +async def task_monitoring(task, *args, **params): """ Check if task crash, log message and reboot if it too frequent """ import uasyncio retry = 0 @@ -71,10 +72,10 @@

      Module lib.tools.tasking

      while retry < max_retry: try: while True: - if await task(): + if await task(*args, **params): retry = 0 except MemoryError as err: - lastError = logger.syslog(err, "Memory error") + lastError = logger.syslog(err, "Memory error, %s"%strings.tostrings(info.meminfo())) from gc import collect collect() memory_error_count += 1 @@ -95,7 +96,6 @@

      Module lib.tools.tasking

      config = ServerConfig() config.load() - from tools import lang await Notifier.notify(lang.reboot_after_many%strings.tobytes(lastError), enabled=config.notify) finally: system.reboot()
      @@ -109,7 +109,7 @@

      Module lib.tools.tasking

      Functions

      -async def task_monitoring(task) +async def task_monitoring(task, *args, **params)

      Check if task crash, log message and reboot if it too frequent

      @@ -117,7 +117,7 @@

      Functions

      Expand source code -
      async def task_monitoring(task):
      +
      async def task_monitoring(task, *args, **params):
               """ Check if task crash, log message and reboot if it too frequent """
               import uasyncio
               retry = 0
      @@ -128,10 +128,10 @@ 

      Functions

      while retry < max_retry: try: while True: - if await task(): + if await task(*args, **params): retry = 0 except MemoryError as err: - lastError = logger.syslog(err, "Memory error") + lastError = logger.syslog(err, "Memory error, %s"%strings.tostrings(info.meminfo())) from gc import collect collect() memory_error_count += 1 @@ -152,7 +152,6 @@

      Functions

      config = ServerConfig() config.load() - from tools import lang await Notifier.notify(lang.reboot_after_many%strings.tobytes(lastError), enabled=config.notify) finally: system.reboot()
      diff --git a/doc/lib/tools/terminal.html b/doc/lib/tools/terminal.html index 5f95466..8bb50c7 100644 --- a/doc/lib/tools/terminal.html +++ b/doc/lib/tools/terminal.html @@ -34,7 +34,7 @@

      Module lib.tools.terminal

      import os import select try: - from tools import filesystem, strings,watchdog + from tools import filesystem, strings, watchdog except: # pylint:disable=multiple-imports import strings,filesystem @@ -88,6 +88,45 @@

      Module lib.tools.terminal

      sys.stdin.buffer.read(1) else: break +elif sys.platform == "win32": + def getch(raw = True, duration=MAXINT, interchar=0.01): + """ Wait a key pressed on keyboard and return it """ + return read_keyboard(duration, raw, get_char) + def kbhit(duration=0.001): + """ Indicates if a key is pressed or not """ + return read_keyboard(duration, True, test_char) + def get_char(stdins): + """ Get character """ + if stdins != []: + return stdins[0].read() + return b"" + def test_char(stdins): + """ Test a character """ + if stdins != []: + return True + return False + def read_keyboard(duration=0.001, raw=True, callback=None): + """ Read keyboard on os/x, linux or windows""" + # pylint:disable=import-error + # pylint:disable=unreachable + import msvcrt + import time + start = time.process_time() + end = start + duration + result = b"" + while True: + break + if msvcrt.kbhit(): + result = msvcrt.getch() + break + current = time.process_time() + if time.process_time() > end: + break + else: + time.sleep(duration/100) + return result + def kbflush(duration=0.5): + """ Flush all keys not yet read """ else: def getch(raw = True, duration=MAXINT, interchar=0.01): """ Wait a key pressed on keyboard and return it """ @@ -110,7 +149,7 @@

      Module lib.tools.terminal

      return False def read_keyboard(duration=0.001, raw=True, callback=None): - """ Read keyboard on os/x, linux or windows""" + """ Read keyboard on os/x, linux """ import tty import termios import fcntl @@ -174,6 +213,7 @@

      Module lib.tools.terminal

      def get_screen_size(force=False): """ Get the VT100 screen size """ + # pylint:disable=global-variable-not-assigned global screen_size, refresh_size refresh_size += 1 if refresh_size == 5: @@ -219,6 +259,7 @@

      Functions

      def get_screen_size(force=False):
               """ Get the VT100 screen size """
      +        # pylint:disable=global-variable-not-assigned
               global screen_size, refresh_size
               refresh_size += 1
               if refresh_size == 5:
      @@ -275,13 +316,13 @@ 

      Functions

      def read_keyboard(duration=0.001, raw=True, callback=None)
  • -

    Read keyboard on os/x, linux or windows

    +

    Read keyboard on os/x, linux

    Expand source code
    def read_keyboard(duration=0.001, raw=True, callback=None):
    -        """ Read keyboard on os/x, linux or windows"""
    +        """ Read keyboard on os/x, linux """
             import tty
             import termios
             import fcntl
    diff --git a/doc/lib/tools/useful.html b/doc/lib/tools/useful.html
    index 44d472e..4ec71eb 100644
    --- a/doc/lib/tools/useful.html
    +++ b/doc/lib/tools/useful.html
    @@ -29,6 +29,7 @@ 

    Module lib.tools.useful

    # Distributed under MIT License
     # Copyright (c) 2021 Remi BERTHOLET
    +# pylint:disable=consider-using-f-string
     """ Miscellaneous utility functions """
     import sys
     import io
    diff --git a/doc/lib/video/video.html b/doc/lib/video/video.html
    index cd8e16f..c4e887e 100644
    --- a/doc/lib/video/video.html
    +++ b/doc/lib/video/video.html
    @@ -35,6 +35,7 @@ 

    Module lib.video.video

    # Distributed under MIT License
     # Copyright (c) 2021 Remi BERTHOLET
    +# pylint:disable=consider-using-f-string
     """ Class to manage the camera of the ESP32CAM.
     This requires the modified firmware.
     I added in the firmware the possibility of detecting movements,
    diff --git a/doc/lib/webpage/awakepage.html b/doc/lib/webpage/awakepage.html
    index 106c3b5..24589fd 100644
    --- a/doc/lib/webpage/awakepage.html
    +++ b/doc/lib/webpage/awakepage.html
    @@ -42,12 +42,13 @@ 

    Module lib.webpage.awakepage

    disabled, action, submit = manage_default_button(request, config) page = main_frame(request, response, args, lang.gpio_wake_up, - Switch(text=lang.activated, name=b"activated", checked=config.activated, disabled=disabled),Br(), - Edit(text=lang.gpio_used_wake_up, name=b"wake_up_gpio", placeholder=lang.gpio_connected_to_pir, pattern=b"[0-9]*[0-9]", value=b"%d"%config.wake_up_gpio, disabled=disabled), - Edit(text=lang.awake_duration_in, name=b"awake_duration", placeholder=lang.duration_awake, pattern=b"[0-9]*[0-9]", value=b"%d"%config.awake_duration, disabled=disabled), - Edit(text=lang.sleep_duration_in, name=b"sleep_duration", placeholder=lang.duration_sleep, pattern=b"[0-9]*[0-9]", value=b"%d"%config.sleep_duration, disabled=disabled), - Br(), - submit) + Form([ + Switch(text=lang.activated, name=b"activated", checked=config.activated, disabled=disabled), + Edit(text=lang.gpio_used_wake_up, name=b"wake_up_gpio", placeholder=lang.gpio_connected_to_pir, pattern=b"[0-9]*[0-9]", value=b"%d"%config.wake_up_gpio, disabled=disabled), + Edit(text=lang.awake_duration_in, name=b"awake_duration", placeholder=lang.duration_awake, pattern=b"[0-9]*[0-9]", value=b"%d"%config.awake_duration, disabled=disabled), + Edit(text=lang.sleep_duration_in, name=b"sleep_duration", placeholder=lang.duration_sleep, pattern=b"[0-9]*[0-9]", value=b"%d"%config.sleep_duration, disabled=disabled), + submit + ])) await response.send_page(page)
    @@ -74,12 +75,13 @@

    Functions

    disabled, action, submit = manage_default_button(request, config) page = main_frame(request, response, args, lang.gpio_wake_up, - Switch(text=lang.activated, name=b"activated", checked=config.activated, disabled=disabled),Br(), - Edit(text=lang.gpio_used_wake_up, name=b"wake_up_gpio", placeholder=lang.gpio_connected_to_pir, pattern=b"[0-9]*[0-9]", value=b"%d"%config.wake_up_gpio, disabled=disabled), - Edit(text=lang.awake_duration_in, name=b"awake_duration", placeholder=lang.duration_awake, pattern=b"[0-9]*[0-9]", value=b"%d"%config.awake_duration, disabled=disabled), - Edit(text=lang.sleep_duration_in, name=b"sleep_duration", placeholder=lang.duration_sleep, pattern=b"[0-9]*[0-9]", value=b"%d"%config.sleep_duration, disabled=disabled), - Br(), - submit) + Form([ + Switch(text=lang.activated, name=b"activated", checked=config.activated, disabled=disabled), + Edit(text=lang.gpio_used_wake_up, name=b"wake_up_gpio", placeholder=lang.gpio_connected_to_pir, pattern=b"[0-9]*[0-9]", value=b"%d"%config.wake_up_gpio, disabled=disabled), + Edit(text=lang.awake_duration_in, name=b"awake_duration", placeholder=lang.duration_awake, pattern=b"[0-9]*[0-9]", value=b"%d"%config.awake_duration, disabled=disabled), + Edit(text=lang.sleep_duration_in, name=b"sleep_duration", placeholder=lang.duration_sleep, pattern=b"[0-9]*[0-9]", value=b"%d"%config.sleep_duration, disabled=disabled), + submit + ])) await response.send_page(page)
    diff --git a/doc/lib/webpage/batterypage.html b/doc/lib/webpage/batterypage.html index c717e08..fdeeb54 100644 --- a/doc/lib/webpage/batterypage.html +++ b/doc/lib/webpage/batterypage.html @@ -43,14 +43,14 @@

    Module lib.webpage.batterypage

    disabled, action, submit = manage_default_button(request, config) page = main_frame(request, response, args, lang.battery_management, - Switch(text=lang.activated, name=b"activated", checked=config.activated, disabled=disabled),Br(), - Edit(text=lang.gpio_used_battery, name=b"level_gpio", placeholder=lang.gpio_connected_to_battery, pattern=b"[0-9]*[0-9]", value=b"%d"%config.level_gpio, disabled=disabled), - Edit(text=lang.gpio_value_for_full, name=b"full_battery", placeholder=lang.gpio_adc_value_full, pattern=b"[0-9]*[0-9]", value=b"%d"%config.full_battery, disabled=disabled), - Edit(text=lang.gpio_value_for_empty, name=b"empty_battery", placeholder=lang.gpio_adc_value_empty, pattern=b"[0-9]*[0-9]", value=b"%d"%config.empty_battery, disabled=disabled), - Br(), - Switch(text=lang.force_a_deep, name=b"brownout_detection", checked=config.brownout_detection, disabled=disabled), - Br(), - submit) + Form([ + Switch(text=lang.activated, name=b"activated", checked=config.activated, disabled=disabled), + Edit(text=lang.gpio_used_battery, name=b"level_gpio", placeholder=lang.gpio_connected_to_battery, pattern=b"[0-9]*[0-9]", value=b"%d"%config.level_gpio, disabled=disabled), + Edit(text=lang.gpio_value_for_full, name=b"full_battery", placeholder=lang.gpio_adc_value_full, pattern=b"[0-9]*[0-9]", value=b"%d"%config.full_battery, disabled=disabled), + Edit(text=lang.gpio_value_for_empty, name=b"empty_battery", placeholder=lang.gpio_adc_value_empty, pattern=b"[0-9]*[0-9]", value=b"%d"%config.empty_battery, disabled=disabled), + Switch(text=lang.force_a_deep, name=b"brownout_detection", checked=config.brownout_detection, disabled=disabled), + submit + ])) await response.send_page(page)
    @@ -77,14 +77,14 @@

    Functions

    disabled, action, submit = manage_default_button(request, config) page = main_frame(request, response, args, lang.battery_management, - Switch(text=lang.activated, name=b"activated", checked=config.activated, disabled=disabled),Br(), - Edit(text=lang.gpio_used_battery, name=b"level_gpio", placeholder=lang.gpio_connected_to_battery, pattern=b"[0-9]*[0-9]", value=b"%d"%config.level_gpio, disabled=disabled), - Edit(text=lang.gpio_value_for_full, name=b"full_battery", placeholder=lang.gpio_adc_value_full, pattern=b"[0-9]*[0-9]", value=b"%d"%config.full_battery, disabled=disabled), - Edit(text=lang.gpio_value_for_empty, name=b"empty_battery", placeholder=lang.gpio_adc_value_empty, pattern=b"[0-9]*[0-9]", value=b"%d"%config.empty_battery, disabled=disabled), - Br(), - Switch(text=lang.force_a_deep, name=b"brownout_detection", checked=config.brownout_detection, disabled=disabled), - Br(), - submit) + Form([ + Switch(text=lang.activated, name=b"activated", checked=config.activated, disabled=disabled), + Edit(text=lang.gpio_used_battery, name=b"level_gpio", placeholder=lang.gpio_connected_to_battery, pattern=b"[0-9]*[0-9]", value=b"%d"%config.level_gpio, disabled=disabled), + Edit(text=lang.gpio_value_for_full, name=b"full_battery", placeholder=lang.gpio_adc_value_full, pattern=b"[0-9]*[0-9]", value=b"%d"%config.full_battery, disabled=disabled), + Edit(text=lang.gpio_value_for_empty, name=b"empty_battery", placeholder=lang.gpio_adc_value_empty, pattern=b"[0-9]*[0-9]", value=b"%d"%config.empty_battery, disabled=disabled), + Switch(text=lang.force_a_deep, name=b"brownout_detection", checked=config.brownout_detection, disabled=disabled), + submit + ])) await response.send_page(page)
    diff --git a/doc/lib/webpage/camerapage.html b/doc/lib/webpage/camerapage.html index b1ed6e6..1830c78 100644 --- a/doc/lib/webpage/camerapage.html +++ b/doc/lib/webpage/camerapage.html @@ -48,6 +48,7 @@

    Module lib.webpage.camerapage

    for size in [b"1600x1200",b"1280x1024",b"1024x768",b"800x600",b"640x480",b"400x296",b"320x240",b"240x176",b"160x120" ]: framesizes.append(Option(value=size, text=size, selected= True if config.framesize == size else False)) page = main_frame(request, response, args, lang.camera, + Form([ Streaming.get_html(request), ComboCmd(framesizes, text=lang.resolution, path=b"camera/configure", name=b"framesize"), SliderCmd( text=lang.quality , path=b"camera/configure", name=b"quality", min=b"10", max=b"63", step=b"1", value=b"%d"%config.quality), @@ -56,7 +57,8 @@

    Module lib.webpage.camerapage

    # SliderCmd( text=lang.saturation, path=b"camera/configure", name=b"saturation", min=b"-2", max=b"2" , step=b"1", value=b"%d"%config.saturation), SliderCmd( text=lang.flash_level, path=b"camera/configure", name=b"flash_level", min=b"0" , max=b"256", step=b"1", value=b"%d"%config.flash_level), SwitchCmd( text=lang.hmirror , path=b"camera/configure", name=b"hmirror" , checked=config.hmirror), - SwitchCmd( text=lang.vflip , path=b"camera/configure", name=b"vflip" , checked=config.vflip)) + SwitchCmd( text=lang.vflip , path=b"camera/configure", name=b"vflip" , checked=config.vflip) + ])) await response.send_page(page) @HttpServer.add_route(b'/camera/configure') @@ -84,12 +86,14 @@

    Module lib.webpage.camerapage

    if command in [b"on",b"off"]: config.save() - alert = Br(), Br(), AlertError(text=lang.taken_into_account) + alert = AlertError(text=lang.taken_into_account) else: alert = None page = main_frame(request, response, args, lang.camera_onoff, - Submit(text=lang.camera_off if config.activated else lang.camera_on, name=b"action", value=b"off" if config.activated else b"on" ), alert) + Form([ + Submit(text=lang.camera_off if config.activated else lang.camera_on, name=b"action", value=b"off" if config.activated else b"on" ), alert + ])) await response.send_page(page) @@ -144,12 +148,14 @@

    Functions

    if command in [b"on",b"off"]: config.save() - alert = Br(), Br(), AlertError(text=lang.taken_into_account) + alert = AlertError(text=lang.taken_into_account) else: alert = None page = main_frame(request, response, args, lang.camera_onoff, - Submit(text=lang.camera_off if config.activated else lang.camera_on, name=b"action", value=b"off" if config.activated else b"on" ), alert) + Form([ + Submit(text=lang.camera_off if config.activated else lang.camera_on, name=b"action", value=b"off" if config.activated else b"on" ), alert + ])) await response.send_page(page) @@ -172,6 +178,7 @@

    Functions

    for size in [b"1600x1200",b"1280x1024",b"1024x768",b"800x600",b"640x480",b"400x296",b"320x240",b"240x176",b"160x120" ]: framesizes.append(Option(value=size, text=size, selected= True if config.framesize == size else False)) page = main_frame(request, response, args, lang.camera, + Form([ Streaming.get_html(request), ComboCmd(framesizes, text=lang.resolution, path=b"camera/configure", name=b"framesize"), SliderCmd( text=lang.quality , path=b"camera/configure", name=b"quality", min=b"10", max=b"63", step=b"1", value=b"%d"%config.quality), @@ -180,7 +187,8 @@

    Functions

    # SliderCmd( text=lang.saturation, path=b"camera/configure", name=b"saturation", min=b"-2", max=b"2" , step=b"1", value=b"%d"%config.saturation), SliderCmd( text=lang.flash_level, path=b"camera/configure", name=b"flash_level", min=b"0" , max=b"256", step=b"1", value=b"%d"%config.flash_level), SwitchCmd( text=lang.hmirror , path=b"camera/configure", name=b"hmirror" , checked=config.hmirror), - SwitchCmd( text=lang.vflip , path=b"camera/configure", name=b"vflip" , checked=config.vflip)) + SwitchCmd( text=lang.vflip , path=b"camera/configure", name=b"vflip" , checked=config.vflip) + ])) await response.send_page(page) diff --git a/doc/lib/webpage/historicpage.html b/doc/lib/webpage/historicpage.html index 7ccb911..f621f21 100644 --- a/doc/lib/webpage/historicpage.html +++ b/doc/lib/webpage/historicpage.html @@ -30,496 +30,402 @@

    Module lib.webpage.historicpage

    # Distributed under MIT License
     # Copyright (c) 2021 Remi BERTHOLET
     """ Function define the web page to view recent motion detection """
    -from server.httpserver import HttpServer
    -from server.server     import Server
    -from htmltemplate      import *
    -from webpage.mainpage  import main_frame
    -from motion            import Historic
    -from video             import Camera
    -from tools             import lang,info, strings
    +# pylint:disable=anomalous-unicode-escape-in-string
    +from server.httpserver     import HttpServer
    +from server.server         import Server
    +from htmltemplate          import *
    +from webpage.mainpage      import main_frame
    +from webpage.streamingpage import Streaming
    +from motion                import Historic
    +from video                 import Camera
    +from tools                 import lang,info, strings
    +
    +def get_days_pagination(last_days, request):
    +        """ Get the pagination html part of days """
    +        current_day = b""
    +        last_days = list(last_days)
    +
    +        if len(last_days) > 0:
    +                last_days.sort()
    +                last_days.reverse()
    +
    +                pages = []
    +
    +                day_id = int(request.params.get(b"day_id",0))
    +                max_days = 5
    +                if len(last_days) > max_days:
    +                        if day_id - 2 < 0:
    +                                begin_day = 0
    +                                end_day   = len(last_days) if len(last_days) < max_days else max_days
    +                        elif day_id + 3 > len(last_days):
    +                                end_day   = len(last_days)
    +                                begin_day = 0 if len(last_days) < max_days else len(last_days) - max_days
    +                        else:
    +                                begin_day = day_id - max_days // 2
    +                                end_day   = day_id + (max_days//2 + 1)
    +
    +                        if begin_day > 0:
    +                                pages.append(PageItem(text=b"...", class_=b"", href=b"/historic?day_id=%d"%(begin_day-1)))
    +                else:
    +                        begin_day = 0
    +                        end_day = len(last_days)
    +
    +                i = begin_day
    +                for day in last_days[begin_day:end_day]:
    +                        pages.append(PageItem(text=day[8:10],
    +                                class_=b"",
    +                                active=b"active" if day_id == i else b"",
    +                                href=b"/historic?day_id=%d"%i if day_id != i else b""))
    +                        if day_id == i:
    +                                current_day = day
    +                        i += 1
    +
    +                if len(last_days) > max_days:
    +                        if end_day < len(last_days):
    +                                pages.append(PageItem(text=b"...", class_=b"", href=b"/historic?day_id=%d"%(end_day)))
    +
    +                pagination_begin = Pagination(pages, class_=b"pagination-sm", id=b"pagination_begin")
    +                pagination_end   = Pagination(pages, class_=b"pagination-sm", id=b"pagination_end")
    +                result = pagination_begin, pagination_end,current_day
    +        else:
    +                result = None,None,current_day
    +        return result
    +
     
     @HttpServer.add_route(b'/historic', menu=lang.menu_motion, item=lang.item_historic, available=info.iscamera() and Camera.is_activated())
     async def historic(request, response, args):
             """ Historic motion detection page """
    +        Streaming.stop()
             Historic.get_root()
    -        if len(request.params) == 0:
    -                detailled = False
    -        else:
    -                detailled = True
    -        pageContent = [\
    -                Tag(b"""
    -                <script type='text/javascript'>
    -
    -                document.onkeydown = check_key;
    -                window.onload = load_historic;
    -
    -                var historic = null;
    -                var images = [];
    -                var current_id = 0;
    -                var last_id = 0;
    -                var previousId = 0;
    -                var historic_request = new XMLHttpRequest();
    -                var image_request    = new XMLHttpRequest();
    -
    -                setInterval(show, 200);
    -
    -                const MOTION_FILENAME =0;
    -                const MOTION_WIDTH    =1;
    -                const MOTION_HEIGHT   =2;
    -                const MOTION_DIFFS    =3;
    -                const MOTION_SQUAREX  =4;
    -                const MOTION_SQUAREY  =5;
    -
    -                function download(fileUrl) 
    -                {
    -                        var a = document.createElement("a");
    -                        a.href = fileUrl;
    -                        filename = fileUrl.split("/").pop();
    -                        a.setAttribute("download", filename);
    -                        a.click();
    -                }
    -
    -                function download_motion()
    -                {
    -                        download("/historic/download/" + historic[current_id][MOTION_FILENAME]);
    -                }
    -
    -                function load_historic()
    -                {
    -                        var canvas = document.getElementById('motion'); 
    -                        canvas.addEventListener("mousedown", function (e) { get_click_position(canvas, e); });
    -                        historic_request.onreadystatechange = historic_loaded;
    -                        historic_request.open("GET","historic/historic.json",true);
    -                        historic_request.send();
    -                }
    -
    -                function historic_loaded() 
    -                {
    -                        if (historic_request.readyState === XMLHttpRequest.DONE)
    -                        {
    -                                if (historic_request.status === 200)
    -                                {
    -                                        historic = JSON.parse(historic_request.responseText);
    -                                        load_image();
    -                                }
    -                        }
    -                }
    +        last_days = await Historic.get_last_days()
    +        pagination_begin, pagination_end,current_day = get_days_pagination(last_days, request)
     
    -                function load_image()
    -                {
    -                        if (historic.length > 0)
    -                        {
    -                                var motion = historic[last_id];
    -                                image_request.onreadystatechange = image_loaded;
    -                                image_request.open("GET","/historic/images/" + motion[MOTION_FILENAME],true);
    -                                image_request.send();
    -                        }
    -                        else
    +        if pagination_end is not None and pagination_begin is not None:
    +                page_content = [\
    +                        pagination_begin,
    +                        Tag(b"""
    +
    +                        <div class="modal" id="zoom_window">
    +                                <div class="modal-dialog modal-fullscreen">
    +                                        <div class="modal-content">
    +                                                <div class="modal-header">
    +                                                        <button type="button" class="btn " data-bs-dismiss="modal">Close</button>
    +                                                        <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
    +                                                </div>
    +                                                <div class="modal-body" >
    +                                                        <canvas id="zoom_image" class="w-100" data-bs-dismiss="modal"/>
    +                                                </div>
    +                                        </div>
    +                                </div>
    +                        </div>
    +
    +                        <script type='text/javascript'>
    +
    +                        window.onload = load_historic;
    +
    +                        var current_day = '%s';
    +
    +                        var historic = null;
    +                        var last_id = 0;
    +                        var historic_request = new XMLHttpRequest();
    +                        var image_request    = new XMLHttpRequest();
    +
    +                        const MOTION_FILENAME =0;
    +                        const MOTION_WIDTH    =1;
    +                        const MOTION_HEIGHT   =2;
    +                        const MOTION_DIFFS    =3;
    +                        const MOTION_SQUAREX  =4;
    +                        const MOTION_SQUAREY  =5;
    +
    +                        function load_historic()
                             {
    -                                var ctx = document.getElementById('motion').getContext('2d');
    -                                ctx.font = '25px Arial';
    -                                ctx.fillStyle = "black";
    -                                ctx.fillText("%s", 10, 20);
    +                                historic_request.onreadystatechange = historic_loaded;
    +                                historic_request.open("GET","historic/historic.json",true);
    +                                historic_request.send();
                             }
    -                }
     
    -                function image_loaded()
    -                {
    -                        if (image_request.readyState === XMLHttpRequest.DONE)
    +                        function historic_loaded() 
                             {
    -                                if (image_request.status === 200)
    +                                if (historic_request.readyState === XMLHttpRequest.DONE)
                                     {
    -                                        var motion = historic[last_id];
    -                                        var image = new Image();
    -                                        image.id     = last_id;
    -                                        image.src    = 'data:image/jpeg;base64,' + image_request.response;
    -                                        image.width  = motion[MOTION_WIDTH] /15;
    -                                        image.height = motion[MOTION_HEIGHT]/15;
    -                                        image.alt    = get_name(motion[MOTION_FILENAME]);
    -                                        image.title  = get_name(motion[MOTION_FILENAME]);
    -                                        image.style  = "padding: 1px;";
    -                                        image.onclick = e => 
    -                                                {
    -                                                        click_motion(parseInt(e.target.id,10));
    -                                                };
    -                                        images.push(image);
    -                                        document.getElementById('motions').appendChild(image);
    -                                        last_id = last_id + 1;
    -                                        if (last_id < historic.length-1)
    +                                        if (historic_request.status === 200)
                                             {
    -                                                setTimeout(load_image, 1);
    +                                                historic = JSON.parse(historic_request.responseText);
    +                                                select_day();
    +                                                load_image();
                                             }
                                     }
                             }
    -                }
    -
    -                function show()
    -                {
    -                        show_motion(current_id);
    -                }
    -
    -                function show_motion(id)
    -                {
    -                        var motion = historic[id];
    -                        var ctx = document.getElementById('motion').getContext('2d');
    -                        
    -                        var offsetX = 30;
    -                        var offsetY = 30;
    -                        ctx.drawImage(document.getElementById(id), offsetX, offsetY, motion[MOTION_WIDTH], motion[MOTION_HEIGHT]);
    -                        var x;
    -                        var y;
    -
    -                        // Show thumb image selected
    -                        document.getElementById(previousId).style.border = "";
    -                        document.getElementById(id).style.border = "5px solid dodgerblue";
    -                        previousId = id;
    -
    -                        var squarex = motion[MOTION_SQUAREX];
    -                        var squarey = motion[MOTION_SQUAREY];
    -                        var maxx = motion[MOTION_WIDTH] /squarex;
    -                        var maxy = motion[MOTION_HEIGHT]/squarey;
    -                        
    -                        if (%d)
    +
    +                        function load_image()
                             {
    -                                for (y = 0; y < maxy; y ++)
    +                                if (historic.length > 0)
                                     {
    -                                        for (x = 0; x < maxx; x ++)
    -                                        {
    -                                                detection = motion[MOTION_DIFFS][y*maxx + x];
    -                                                if (detection != " ")
    -                                                {
    -                                                        ctx.strokeStyle = "yellow";
    -                                                        ctx.strokeRect(offsetX + (x * squarex + 15),offsetY + (y*squarey +15), squarex-30, squarey-30);
    -                                                }
    -                                        }
    +                                        var motion = historic[last_id];
    +                                        image_request.onreadystatechange = image_loaded;
    +                                        image_request.open("GET","/historic/images/" + motion[MOTION_FILENAME],true);
    +                                        image_request.send();
                                     }
                             }
     
    -                        ctx.strokeStyle = "red";
    -                        for (y = 0; y < maxy; y ++)
    +                        function rtrim(x, characters)
                             {
    -                                for (x = 0; x < maxx; x ++)
    +                                var start = 0;
    +                                var end = x.length - 1;
    +                                while (characters.indexOf(x[end]) >= 0)
                                     {
    -                                        var detection = motion[MOTION_DIFFS][y*maxx + x];
    -                                        if (x >= 1)
    -                                        {
    -                                                var previous = motion[MOTION_DIFFS][y*maxx + x -1];
    -                                                if (previous != detection)
    -                                                {
    -                                                        ctx.beginPath();
    -                                                        ctx.moveTo(offsetX + x*squarex, offsetY + y*squarey);
    -                                                        ctx.lineTo(offsetX + x*squarex, offsetY + y*squarey + squarey);
    -                                                        ctx.stroke();
    -                                                }
    -                                        }
    +                                        end -= 1;
                                     }
    +                                return x.substr(0, end + 1);
                             }
    -                        
    -                        for (x = 0; x < maxx; x ++)
    +
    +                        function ltrim(x, characters) 
                             {
    -                                for (y = 0; y < maxy; y ++)
    +                                var start = 0;
    +                                while (characters.indexOf(x[start]) >= 0)
                                     {
    -                                        var detection = motion[MOTION_DIFFS][y*maxx + x];
    -                                        if (y >= 1)
    -                                        {
    -                                                var previous = motion[MOTION_DIFFS][(y-1)*maxx + x];
    -                                                if (previous != detection)
    -                                                {
    -                                                        ctx.beginPath();
    -                                                        ctx.moveTo(offsetX + x*squarex, offsetY + y*squarey);
    -                                                        ctx.lineTo(offsetX + x*squarex + squarex, offsetY + y*squarey);
    -                                                        ctx.stroke();
    -                                                }
    -                                        }
    +                                        start += 1;
                                     }
    +                                var end = x.length - 1;
    +                                return x.substr(start);
                             }
     
    -                        // Show text image
    -                        ctx.font = '20px monospace';
    -                        ctx.fillStyle = "white";
    -                        ctx.rect(0, offsetY + motion[MOTION_HEIGHT],  motion[MOTION_WIDTH], 100);
    -                        ctx.fill();
    -
    -                        ctx.fillStyle = "black";
    -                        ctx.fillText(get_name(motion[MOTION_FILENAME]),  10, offsetY + motion[MOTION_HEIGHT] + 20);
    -
    -                        // Show arrows
    -                        ctx.fillStyle = 'rgba(255,255,255,10)';
    -                        ctx.font = '30px monospace';
    -                        
    -                        // Previous
    -                        ctx.fillText("\u25C0\uFE0F", 0, offsetY + motion[MOTION_HEIGHT]/2); 
    -                        
    -                        // Next
    -                        ctx.fillText("\u25B6\uFE0F", offsetX + motion[MOTION_WIDTH], offsetY + motion[MOTION_HEIGHT]/2);
    -
    -                        // Previous day
    -                        ctx.fillText("\u23EA",  offsetX + motion[MOTION_WIDTH]/2, 25); 
    -                        
    -                        // Next day
    -                        ctx.fillText("\u23E9", offsetX + motion[MOTION_WIDTH]/2,25+ offsetY + motion[MOTION_HEIGHT]);
    -
    -                        // Begin
    -                        ctx.fillText("\u23EE\uFE0F",0, 25);
    -
    -                        // End
    -                        ctx.fillText("\u23ED\uFE0F", offsetX + motion[MOTION_WIDTH], 25+offsetY + motion[MOTION_HEIGHT]);
    -                }
    -
    -                // Convert the filename into text displayed
    -                function get_name(filename)
    -                {
    -                        filename = filename.split(".")[0];
    -                        lst = filename.split("/");
    -                        filename = lst[lst.length-1];
    -                        filename = filename.replace("D= ","D=");
    -                        spl = filename.split(" ");
    -
    -                        if (spl.length == 3)
    +                        function get_day(id)
                             {
    -                                date = spl[0].split("_")[0];
    -                                hour = spl[0].split("_")[1];
    -
    -                                date = date.replaceAll("-","/") + " " + hour.replaceAll("-",":");
    -                                last = spl[1] + " " + spl[2];
    -                                result = date + " "+ last;
    -                        }
    -                        else
    -                        {
    -                                result = filename;
    -                        }
    -                        return result;
    -                }
    -
    -                function get_date(filename)
    -                {
    -                        return get_name(filename).substring(0,10);
    -                }
    -
    -                function click_motion(id)
    -                {
    -                        current_id = id;
    -                        show_motion(id);
    -                }
    -
    -                function first_motion()
    -                {
    -                        current_id = 0;
    -                        show_motion(current_id);
    -                }
    -
    -                function last_motion()
    -                {
    -                        current_id = last_id-1;
    -                        show_motion(current_id);
    -                }
    -
    -                function next_motion()
    -                {
    -                        if (current_id + 1 < last_id)
    -                        {
    -                                current_id = current_id + 1;
    -                                show_motion(current_id);
    +                                var filename = historic[id][MOTION_FILENAME];
    +                                filename = ltrim(filename, "/");
    +                                filename = filename.split("/");
    +                                return filename[1]+"/"+filename[2]+"/"+filename[3];
                             }
    -                }
     
    -                function previous_motion()
    -                {
    -                        if (current_id > 0)
    +                        function select_day()
                             {
    -                                current_id = current_id -1;
    -                                show_motion(current_id);
    -                        }
    -                }
    -
    -                function next_day_motion()
    -                {
    -                        if (current_id + 1 < last_id)
    -                        {
    -                                var new_id = current_id;
    -
    -                                do
    +                                if (historic.length > 0)
                                     {
    -                                        new_id += 1;
    -                                        if (get_date(historic[new_id][MOTION_FILENAME]) != get_date(historic[current_id][MOTION_FILENAME]))
    +                                        for (i = 0; i < historic.length; i++)
                                             {
    -                                                current_id = new_id;
    -                                                show_motion(current_id);
    -                                                break;
    +                                                if (get_day(i) == current_day)
    +                                                {
    +                                                        last_id = i;
    +                                                        break;
    +                                                }
                                             }
                                     }
    -                                while (new_id + 1 < last_id);
                             }
    -                }
     
    -                function previous_day_motion()
    -                {
    -                        if (current_id > 0)
    +                        function get_quality()
                             {
    -                                var new_id = current_id;
    -
    -                                do
    +                                if (/iPhone|iPad|iPod|Android/i.test(navigator.userAgent))
    +                                {
    +                                        return 1;
    +                                }
    +                                else
                                     {
    -                                        new_id -= 1;
    +                                        return 2;
    +                                }
    +                        }
     
    -                                        if (get_date(historic[new_id][MOTION_FILENAME]) != get_date(historic[current_id][MOTION_FILENAME]))
    +                        function image_loaded()
    +                        {
    +                                if (image_request.readyState === XMLHttpRequest.DONE)
    +                                {
    +                                        if (image_request.status === 200)
                                             {
    -                                                current_id = new_id;
    -
    -                                                do
    +                                                var motion = historic[last_id];
    +
    +                                                var div = document.createElement("div");
    +                                                        div.className = "col-lg-2  pb-1";
    +
    +                                                        var canvas = document.createElement("canvas");
    +                                                                canvas.width     = motion[MOTION_WIDTH ] * get_quality();
    +                                                                canvas.height    = motion[MOTION_HEIGHT] * get_quality();
    +                                                                canvas.id        = last_id;
    +                                                                canvas.className = "w-100";
    +                                                                canvas.setAttribute("data-bs-toggle","modal");
    +                                                                canvas.setAttribute("data-bs-target","#zoom_window");
    +                                                                canvas.onclick = e => 
    +                                                                        {
    +                                                                                var view = document.getElementById('zoom_image');
    +                                                                                var destCtx = view.getContext('2d');
    +                                                                                view.width     = motion[MOTION_WIDTH ] * get_quality();
    +                                                                                view.height    = motion[MOTION_HEIGHT] * get_quality();
    +
    +                                                                                destCtx.drawImage(canvas, 0, 0);
    +                                                                        };
    +
    +                                                        var image = new Image();
    +                                                                image.src        = 'data:image/jpeg;base64,' + image_request.response;
    +                                                                image.onload     = function(){show_motion(canvas.id, image);};
    +
    +                                                        div.appendChild(canvas);
    +
    +                                                if (last_id == 0)
                                                     {
    -                                                        new_id -= 1;
    -                                                        if (get_date(historic[new_id][MOTION_FILENAME]) == get_date(historic[current_id][MOTION_FILENAME]))
    -                                                        {
    -                                                                current_id = new_id;
    -                                                        }
    -                                                        else
    +                                                        document.getElementById('motions').replaceChildren(div);
    +                                                }
    +                                                else
    +                                                {
    +                                                        document.getElementById('motions').appendChild(div);
    +                                                }
    +                                                last_id = last_id + 1;
    +                                                if (last_id < historic.length-1)
    +                                                {
    +                                                        if (get_day(last_id) == current_day)
                                                             {
    -                                                                break;
    +                                                                setTimeout(load_image, 1);
                                                             }
                                                     }
    -                                                while(new_id - 1 >= 0);
    -                                                show_motion(current_id);
    -                                                break;
    +                                        }
    +                                        else 
    +                                        {
    +                                                setTimeout(load_image, 1);
                                             }
                                     }
    -                                while (new_id - 1 >= 0);
                             }
    -                }
    -
    -                function check_key(e)
    -                {
    -                        e = e || window.event;
     
    -                        if (e.keyCode == '38') // up arrow
    +                        function get_difference(motion, x, y)
                             {
    -                                previous_day_motion();
    -                        }
    -                        else if (e.keyCode == '40') // down arrow
    -                        {
    -                                next_day_motion();
    -                        }
    -                        else if (e.keyCode == '37') // left arrow
    -                        {
    -                                previous_motion();
    -                        }
    -                        else if (e.keyCode == '39')// right arrow
    -                        {
    -                                next_motion();
    -                        }
    -                        else if (e.keyCode == '35') // end
    -                        {
    -                        }
    -                        else if (e.keyCode == '36') // home
    -                        {
    -                        }
    -                        else if (e.keyCode == '33') // page up
    -                        {
    -                                first_motion();
    -                        }
    -                        else if (e.keyCode == '34') // page down
    -                        {
    -                                last_motion();
    -                        }
    -                }
    -
    -                function get_click_position(canvas, e)
    -                {
    -                        const rect = canvas.getBoundingClientRect();
    -                        const x = e.clientX - rect.left;
    -                        const y = e.clientY - rect.top;
    -
    -                        // If click on first
    -                        if (x < 100 && y < 100)
    -                        {
    -                                first_motion();
    -                        }
    -                        // If click on last
    -                        else if (x > (rect.width - 100) && y > (rect.height -100))
    -                        {
    -                                last_motion();
    -                        }
    -                        // If the click is in the middle
    -                        else if (x > rect.width/3 && x < (2*rect.width/3) && y > rect.height/3 && y < (2*rect.height/3))
    -                        {
    -                                // Download image
    -                                if (confirm("Download this image ?"))
    +                                var squarex = motion[MOTION_SQUAREX];
    +                                var maxx    = motion[MOTION_WIDTH] /squarex;
    +                                var bitpos  = y*maxx + x;
    +                                if (typeof motion[MOTION_DIFFS] === 'string')
                                     {
    -                                        download_motion();
    +                                        return motion[MOTION_DIFFS][bitpos];
                                     }
    -                        }
    -                        else
    -                        {
    -                                if (x < rect.width / 2)
    +                                else
                                     {
    -                                        if (y < rect.height / 2)
    +                                        var word = parseInt(bitpos/32);
    +                                        var bit  = 31-bitpos%%32;
    +                                        var mask = 1 << bit;
    +                                        var val  = motion[MOTION_DIFFS][word];
    +                                        if (val & mask)
                                             {
    -                                                if (x < y)
    -                                                {
    -                                                        previous_motion();
    -                                                }
    -                                                else
    -                                                {
    -                                                        previous_day_motion();
    -                                                }
    +                                                return "#";
                                             }
                                             else
                                             {
    -                                                if (x < rect.height - y)
    -                                                {
    -                                                        previous_motion();
    -                                                }
    -                                                else
    -                                                {
    -                                                        next_day_motion();
    -                                                }
    +                                                return " ";
                                             }
                                     }
    -                                else
    +                        }
    +
    +                        function show_motion(id, image)
    +                        {
    +                                var x;
    +                                var y;
    +
    +                                var motion = historic[id];
    +                                var canvas = document.getElementById(id);
    +                                var ctx = canvas.getContext('2d');
    +
    +                                var squarex = motion[MOTION_SQUAREX] * get_quality();
    +                                var squarey = motion[MOTION_SQUAREY] * get_quality();
    +                                var maxx = (motion[MOTION_WIDTH] /squarex) * get_quality();
    +                                var maxy = (motion[MOTION_HEIGHT]/squarey) * get_quality();
    +
    +                                ctx.drawImage(image, 0, 0, motion[MOTION_WIDTH ]  , motion[MOTION_HEIGHT], 0, 0, motion[MOTION_WIDTH ] * get_quality(), motion[MOTION_HEIGHT] * get_quality());
    +
    +                                ctx.strokeStyle = "red";
    +                                ctx.lineWidth =  1 * get_quality();
    +                                for (y = 0; y < maxy; y ++)
                                     {
    -                                        if (y < rect.height / 2)
    +                                        for (x = 0; x < maxx; x ++)
                                             {
    -                                                if (rect.width - x < y)
    +                                                var detection = get_difference(motion, x, y);
    +                                                if (x >= 1)
                                                     {
    -                                                        next_motion();
    -                                                }
    -                                                else
    -                                                {
    -                                                        previous_day_motion();
    +                                                        var previous = get_difference(motion, x-1, y);
    +                                                        if (previous != detection)
    +                                                        {
    +                                                                ctx.beginPath();
    +                                                                ctx.moveTo(0 + x*squarex, 0 + y*squarey);
    +                                                                ctx.lineTo(0 + x*squarex, 0 + y*squarey + squarey);
    +                                                                ctx.stroke();
    +                                                        }
                                                     }
                                             }
    -                                        else
    +                                }
    +                                
    +                                for (x = 0; x < maxx; x ++)
    +                                {
    +                                        for (y = 0; y < maxy; y ++)
                                             {
    -                                                if (rect.width - x < rect.height - y)
    +                                                var detection = get_difference(motion, x, y);
    +                                                if (y >= 1)
                                                     {
    -                                                        next_motion();
    -                                                }
    -                                                else
    -                                                {
    -                                                        next_day_motion();
    +                                                        var previous = get_difference(motion, x, y-1);
    +                                                        if (previous != detection)
    +                                                        {
    +                                                                ctx.beginPath();
    +                                                                ctx.moveTo(0 + x*squarex, 0 + y*squarey);
    +                                                                ctx.lineTo(0 + x*squarex + squarex, 0 + y*squarey);
    +                                                                ctx.stroke();
    +                                                        }
                                                     }
                                             }
                                     }
    +
    +
    +                                var font_size = 35 * get_quality();
    +                                ctx.font = font_size + "px monospace";
    +                                var width = ctx.measureText(get_name(motion[MOTION_FILENAME])).width;
    +                                ctx.fillStyle = 'rgba(0,0,0,0.3)';
    +
    +                                ctx.fillRect(0,(motion[MOTION_HEIGHT] * get_quality())-font_size, 
    +                                        width+get_quality(), font_size+10);
    +
    +                                ctx.fillStyle = 'rgba(255,255,255,0.5)';
    +                                ctx.fillText(get_name(motion[MOTION_FILENAME]),  3, (motion[MOTION_HEIGHT] * get_quality() - 5*get_quality() ));
    +                        }
    +
    +                        // Convert the filename into text displayed
    +                        function get_name(filename)
    +                        {
    +                                filename = filename.split(".")[0];
    +                                lst = filename.split("/");
    +                                filename = lst[lst.length-1];
    +                                filename = filename.replace("D= ","D=");
    +                                spl = filename.split(" ");
    +
    +                                if (spl.length == 3)
    +                                {
    +                                        date = spl[0].split("_")[0];
    +                                        hour = spl[0].split("_")[1];
    +
    +                                        date = date.replaceAll("-","/") + " " + hour.replaceAll("-",":");
    +                                        last = spl[1] + " " + spl[2];
    +                                        result = date + " "+ last;
    +                                }
    +                                else
    +                                {
    +                                        result = filename;
    +                                }
    +                                return result;
                             }
    -                }
    -
    -                </script>
    -                <canvas id="motion" width="%d" height="%d" ></canvas>
    -                <br>
    -                <div id="motions"></div>
    -                """%(lang.historic_not_available, detailled, 860,660)),
    -        ]
    -        page = main_frame(request, response, args,lang.last_motion_detections,pageContent)
    +
    +                        function get_date(filename)
    +                        {
    +                                return get_name(filename).substring(0,10);
    +                        }
    +                        </script>
    +                        <div id="motions" class="row"></div>
    +                        """%(current_day)),
    +                        Br(),
    +                        pagination_end
    +                ]
    +        else:
    +                page_content = Tag(b"<span>%s</span>"%lang.historic_not_available)
    +        page = main_frame(request, response, args,lang.last_motion_detections,Form(page_content))
             await response.send_page(page)
     
     @HttpServer.add_route(b'/historic/historic.json', available=info.iscamera() and Camera.is_activated())
     async def historic_json(request, response, args):
             """ Send historic json file """
             Server.slow_down()
    -        if await Historic.locked() is False:
    +        try:
                     await response.send_buffer(b"historic.json", await Historic.get_json())
    -        else:
    -                await response.send_buffer(b"historic.json", b"[]")
    +        except Exception as err:
    +                await response.send_not_found(err)
     
     @HttpServer.add_route(b'/historic/images/.*', available=info.iscamera() and Camera.is_activated())
     async def historic_image(request, response, args):
    @@ -531,7 +437,7 @@ 

    Module lib.webpage.historicpage

    await Historic.acquire() await response.send_file(strings.tostrings(request.path[len("/historic/images/"):]), base64=True) else: - await response.send_error(status=b"404", content=b"Image not found") + await response.send_not_found() finally: if reserved: await Historic.release() @@ -547,7 +453,7 @@

    Module lib.webpage.historicpage

    await Historic.acquire() await response.send_file(strings.tostrings(request.path[len("/historic/download/"):]), base64=False) else: - await response.send_error(status=b"404", content=b"Image not found") + await response.send_not_found() finally: if reserved: await Historic.release() @@ -580,13 +486,74 @@

    Functions

    await Historic.acquire() await response.send_file(strings.tostrings(request.path[len("/historic/download/"):]), base64=False) else: - await response.send_error(status=b"404", content=b"Image not found") + await response.send_not_found() finally: if reserved: await Historic.release() await Camera.unreserve(Historic)
    +
    +def get_days_pagination(last_days, request) +
    +
    +

    Get the pagination html part of days

    +
    + +Expand source code + +
    def get_days_pagination(last_days, request):
    +        """ Get the pagination html part of days """
    +        current_day = b""
    +        last_days = list(last_days)
    +
    +        if len(last_days) > 0:
    +                last_days.sort()
    +                last_days.reverse()
    +
    +                pages = []
    +
    +                day_id = int(request.params.get(b"day_id",0))
    +                max_days = 5
    +                if len(last_days) > max_days:
    +                        if day_id - 2 < 0:
    +                                begin_day = 0
    +                                end_day   = len(last_days) if len(last_days) < max_days else max_days
    +                        elif day_id + 3 > len(last_days):
    +                                end_day   = len(last_days)
    +                                begin_day = 0 if len(last_days) < max_days else len(last_days) - max_days
    +                        else:
    +                                begin_day = day_id - max_days // 2
    +                                end_day   = day_id + (max_days//2 + 1)
    +
    +                        if begin_day > 0:
    +                                pages.append(PageItem(text=b"...", class_=b"", href=b"/historic?day_id=%d"%(begin_day-1)))
    +                else:
    +                        begin_day = 0
    +                        end_day = len(last_days)
    +
    +                i = begin_day
    +                for day in last_days[begin_day:end_day]:
    +                        pages.append(PageItem(text=day[8:10],
    +                                class_=b"",
    +                                active=b"active" if day_id == i else b"",
    +                                href=b"/historic?day_id=%d"%i if day_id != i else b""))
    +                        if day_id == i:
    +                                current_day = day
    +                        i += 1
    +
    +                if len(last_days) > max_days:
    +                        if end_day < len(last_days):
    +                                pages.append(PageItem(text=b"...", class_=b"", href=b"/historic?day_id=%d"%(end_day)))
    +
    +                pagination_begin = Pagination(pages, class_=b"pagination-sm", id=b"pagination_begin")
    +                pagination_end   = Pagination(pages, class_=b"pagination-sm", id=b"pagination_end")
    +                result = pagination_begin, pagination_end,current_day
    +        else:
    +                result = None,None,current_day
    +        return result
    +
    +
    async def historic(request, response, args)
    @@ -599,475 +566,327 @@

    Functions

    @HttpServer.add_route(b'/historic', menu=lang.menu_motion, item=lang.item_historic, available=info.iscamera() and Camera.is_activated())
     async def historic(request, response, args):
             """ Historic motion detection page """
    +        Streaming.stop()
             Historic.get_root()
    -        if len(request.params) == 0:
    -                detailled = False
    -        else:
    -                detailled = True
    -        pageContent = [\
    -                Tag(b"""
    -                <script type='text/javascript'>
    -
    -                document.onkeydown = check_key;
    -                window.onload = load_historic;
    -
    -                var historic = null;
    -                var images = [];
    -                var current_id = 0;
    -                var last_id = 0;
    -                var previousId = 0;
    -                var historic_request = new XMLHttpRequest();
    -                var image_request    = new XMLHttpRequest();
    -
    -                setInterval(show, 200);
    -
    -                const MOTION_FILENAME =0;
    -                const MOTION_WIDTH    =1;
    -                const MOTION_HEIGHT   =2;
    -                const MOTION_DIFFS    =3;
    -                const MOTION_SQUAREX  =4;
    -                const MOTION_SQUAREY  =5;
    -
    -                function download(fileUrl) 
    -                {
    -                        var a = document.createElement("a");
    -                        a.href = fileUrl;
    -                        filename = fileUrl.split("/").pop();
    -                        a.setAttribute("download", filename);
    -                        a.click();
    -                }
    -
    -                function download_motion()
    -                {
    -                        download("/historic/download/" + historic[current_id][MOTION_FILENAME]);
    -                }
    -
    -                function load_historic()
    -                {
    -                        var canvas = document.getElementById('motion'); 
    -                        canvas.addEventListener("mousedown", function (e) { get_click_position(canvas, e); });
    -                        historic_request.onreadystatechange = historic_loaded;
    -                        historic_request.open("GET","historic/historic.json",true);
    -                        historic_request.send();
    -                }
    -
    -                function historic_loaded() 
    -                {
    -                        if (historic_request.readyState === XMLHttpRequest.DONE)
    -                        {
    -                                if (historic_request.status === 200)
    -                                {
    -                                        historic = JSON.parse(historic_request.responseText);
    -                                        load_image();
    -                                }
    -                        }
    -                }
    +        last_days = await Historic.get_last_days()
    +        pagination_begin, pagination_end,current_day = get_days_pagination(last_days, request)
     
    -                function load_image()
    -                {
    -                        if (historic.length > 0)
    -                        {
    -                                var motion = historic[last_id];
    -                                image_request.onreadystatechange = image_loaded;
    -                                image_request.open("GET","/historic/images/" + motion[MOTION_FILENAME],true);
    -                                image_request.send();
    -                        }
    -                        else
    +        if pagination_end is not None and pagination_begin is not None:
    +                page_content = [\
    +                        pagination_begin,
    +                        Tag(b"""
    +
    +                        <div class="modal" id="zoom_window">
    +                                <div class="modal-dialog modal-fullscreen">
    +                                        <div class="modal-content">
    +                                                <div class="modal-header">
    +                                                        <button type="button" class="btn " data-bs-dismiss="modal">Close</button>
    +                                                        <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
    +                                                </div>
    +                                                <div class="modal-body" >
    +                                                        <canvas id="zoom_image" class="w-100" data-bs-dismiss="modal"/>
    +                                                </div>
    +                                        </div>
    +                                </div>
    +                        </div>
    +
    +                        <script type='text/javascript'>
    +
    +                        window.onload = load_historic;
    +
    +                        var current_day = '%s';
    +
    +                        var historic = null;
    +                        var last_id = 0;
    +                        var historic_request = new XMLHttpRequest();
    +                        var image_request    = new XMLHttpRequest();
    +
    +                        const MOTION_FILENAME =0;
    +                        const MOTION_WIDTH    =1;
    +                        const MOTION_HEIGHT   =2;
    +                        const MOTION_DIFFS    =3;
    +                        const MOTION_SQUAREX  =4;
    +                        const MOTION_SQUAREY  =5;
    +
    +                        function load_historic()
                             {
    -                                var ctx = document.getElementById('motion').getContext('2d');
    -                                ctx.font = '25px Arial';
    -                                ctx.fillStyle = "black";
    -                                ctx.fillText("%s", 10, 20);
    +                                historic_request.onreadystatechange = historic_loaded;
    +                                historic_request.open("GET","historic/historic.json",true);
    +                                historic_request.send();
                             }
    -                }
     
    -                function image_loaded()
    -                {
    -                        if (image_request.readyState === XMLHttpRequest.DONE)
    +                        function historic_loaded() 
                             {
    -                                if (image_request.status === 200)
    +                                if (historic_request.readyState === XMLHttpRequest.DONE)
                                     {
    -                                        var motion = historic[last_id];
    -                                        var image = new Image();
    -                                        image.id     = last_id;
    -                                        image.src    = 'data:image/jpeg;base64,' + image_request.response;
    -                                        image.width  = motion[MOTION_WIDTH] /15;
    -                                        image.height = motion[MOTION_HEIGHT]/15;
    -                                        image.alt    = get_name(motion[MOTION_FILENAME]);
    -                                        image.title  = get_name(motion[MOTION_FILENAME]);
    -                                        image.style  = "padding: 1px;";
    -                                        image.onclick = e => 
    -                                                {
    -                                                        click_motion(parseInt(e.target.id,10));
    -                                                };
    -                                        images.push(image);
    -                                        document.getElementById('motions').appendChild(image);
    -                                        last_id = last_id + 1;
    -                                        if (last_id < historic.length-1)
    +                                        if (historic_request.status === 200)
                                             {
    -                                                setTimeout(load_image, 1);
    +                                                historic = JSON.parse(historic_request.responseText);
    +                                                select_day();
    +                                                load_image();
                                             }
                                     }
                             }
    -                }
    -
    -                function show()
    -                {
    -                        show_motion(current_id);
    -                }
    -
    -                function show_motion(id)
    -                {
    -                        var motion = historic[id];
    -                        var ctx = document.getElementById('motion').getContext('2d');
    -                        
    -                        var offsetX = 30;
    -                        var offsetY = 30;
    -                        ctx.drawImage(document.getElementById(id), offsetX, offsetY, motion[MOTION_WIDTH], motion[MOTION_HEIGHT]);
    -                        var x;
    -                        var y;
    -
    -                        // Show thumb image selected
    -                        document.getElementById(previousId).style.border = "";
    -                        document.getElementById(id).style.border = "5px solid dodgerblue";
    -                        previousId = id;
    -
    -                        var squarex = motion[MOTION_SQUAREX];
    -                        var squarey = motion[MOTION_SQUAREY];
    -                        var maxx = motion[MOTION_WIDTH] /squarex;
    -                        var maxy = motion[MOTION_HEIGHT]/squarey;
    -                        
    -                        if (%d)
    +
    +                        function load_image()
                             {
    -                                for (y = 0; y < maxy; y ++)
    +                                if (historic.length > 0)
                                     {
    -                                        for (x = 0; x < maxx; x ++)
    -                                        {
    -                                                detection = motion[MOTION_DIFFS][y*maxx + x];
    -                                                if (detection != " ")
    -                                                {
    -                                                        ctx.strokeStyle = "yellow";
    -                                                        ctx.strokeRect(offsetX + (x * squarex + 15),offsetY + (y*squarey +15), squarex-30, squarey-30);
    -                                                }
    -                                        }
    +                                        var motion = historic[last_id];
    +                                        image_request.onreadystatechange = image_loaded;
    +                                        image_request.open("GET","/historic/images/" + motion[MOTION_FILENAME],true);
    +                                        image_request.send();
                                     }
                             }
     
    -                        ctx.strokeStyle = "red";
    -                        for (y = 0; y < maxy; y ++)
    +                        function rtrim(x, characters)
                             {
    -                                for (x = 0; x < maxx; x ++)
    +                                var start = 0;
    +                                var end = x.length - 1;
    +                                while (characters.indexOf(x[end]) >= 0)
                                     {
    -                                        var detection = motion[MOTION_DIFFS][y*maxx + x];
    -                                        if (x >= 1)
    -                                        {
    -                                                var previous = motion[MOTION_DIFFS][y*maxx + x -1];
    -                                                if (previous != detection)
    -                                                {
    -                                                        ctx.beginPath();
    -                                                        ctx.moveTo(offsetX + x*squarex, offsetY + y*squarey);
    -                                                        ctx.lineTo(offsetX + x*squarex, offsetY + y*squarey + squarey);
    -                                                        ctx.stroke();
    -                                                }
    -                                        }
    +                                        end -= 1;
                                     }
    +                                return x.substr(0, end + 1);
                             }
    -                        
    -                        for (x = 0; x < maxx; x ++)
    +
    +                        function ltrim(x, characters) 
                             {
    -                                for (y = 0; y < maxy; y ++)
    +                                var start = 0;
    +                                while (characters.indexOf(x[start]) >= 0)
                                     {
    -                                        var detection = motion[MOTION_DIFFS][y*maxx + x];
    -                                        if (y >= 1)
    -                                        {
    -                                                var previous = motion[MOTION_DIFFS][(y-1)*maxx + x];
    -                                                if (previous != detection)
    -                                                {
    -                                                        ctx.beginPath();
    -                                                        ctx.moveTo(offsetX + x*squarex, offsetY + y*squarey);
    -                                                        ctx.lineTo(offsetX + x*squarex + squarex, offsetY + y*squarey);
    -                                                        ctx.stroke();
    -                                                }
    -                                        }
    +                                        start += 1;
                                     }
    +                                var end = x.length - 1;
    +                                return x.substr(start);
                             }
     
    -                        // Show text image
    -                        ctx.font = '20px monospace';
    -                        ctx.fillStyle = "white";
    -                        ctx.rect(0, offsetY + motion[MOTION_HEIGHT],  motion[MOTION_WIDTH], 100);
    -                        ctx.fill();
    -
    -                        ctx.fillStyle = "black";
    -                        ctx.fillText(get_name(motion[MOTION_FILENAME]),  10, offsetY + motion[MOTION_HEIGHT] + 20);
    -
    -                        // Show arrows
    -                        ctx.fillStyle = 'rgba(255,255,255,10)';
    -                        ctx.font = '30px monospace';
    -                        
    -                        // Previous
    -                        ctx.fillText("\u25C0\uFE0F", 0, offsetY + motion[MOTION_HEIGHT]/2); 
    -                        
    -                        // Next
    -                        ctx.fillText("\u25B6\uFE0F", offsetX + motion[MOTION_WIDTH], offsetY + motion[MOTION_HEIGHT]/2);
    -
    -                        // Previous day
    -                        ctx.fillText("\u23EA",  offsetX + motion[MOTION_WIDTH]/2, 25); 
    -                        
    -                        // Next day
    -                        ctx.fillText("\u23E9", offsetX + motion[MOTION_WIDTH]/2,25+ offsetY + motion[MOTION_HEIGHT]);
    -
    -                        // Begin
    -                        ctx.fillText("\u23EE\uFE0F",0, 25);
    -
    -                        // End
    -                        ctx.fillText("\u23ED\uFE0F", offsetX + motion[MOTION_WIDTH], 25+offsetY + motion[MOTION_HEIGHT]);
    -                }
    -
    -                // Convert the filename into text displayed
    -                function get_name(filename)
    -                {
    -                        filename = filename.split(".")[0];
    -                        lst = filename.split("/");
    -                        filename = lst[lst.length-1];
    -                        filename = filename.replace("D= ","D=");
    -                        spl = filename.split(" ");
    -
    -                        if (spl.length == 3)
    -                        {
    -                                date = spl[0].split("_")[0];
    -                                hour = spl[0].split("_")[1];
    -
    -                                date = date.replaceAll("-","/") + " " + hour.replaceAll("-",":");
    -                                last = spl[1] + " " + spl[2];
    -                                result = date + " "+ last;
    -                        }
    -                        else
    +                        function get_day(id)
                             {
    -                                result = filename;
    +                                var filename = historic[id][MOTION_FILENAME];
    +                                filename = ltrim(filename, "/");
    +                                filename = filename.split("/");
    +                                return filename[1]+"/"+filename[2]+"/"+filename[3];
                             }
    -                        return result;
    -                }
    -
    -                function get_date(filename)
    -                {
    -                        return get_name(filename).substring(0,10);
    -                }
    -
    -                function click_motion(id)
    -                {
    -                        current_id = id;
    -                        show_motion(id);
    -                }
    -
    -                function first_motion()
    -                {
    -                        current_id = 0;
    -                        show_motion(current_id);
    -                }
    -
    -                function last_motion()
    -                {
    -                        current_id = last_id-1;
    -                        show_motion(current_id);
    -                }
    -
    -                function next_motion()
    -                {
    -                        if (current_id + 1 < last_id)
    -                        {
    -                                current_id = current_id + 1;
    -                                show_motion(current_id);
    -                        }
    -                }
     
    -                function previous_motion()
    -                {
    -                        if (current_id > 0)
    +                        function select_day()
                             {
    -                                current_id = current_id -1;
    -                                show_motion(current_id);
    -                        }
    -                }
    -
    -                function next_day_motion()
    -                {
    -                        if (current_id + 1 < last_id)
    -                        {
    -                                var new_id = current_id;
    -
    -                                do
    +                                if (historic.length > 0)
                                     {
    -                                        new_id += 1;
    -                                        if (get_date(historic[new_id][MOTION_FILENAME]) != get_date(historic[current_id][MOTION_FILENAME]))
    +                                        for (i = 0; i < historic.length; i++)
                                             {
    -                                                current_id = new_id;
    -                                                show_motion(current_id);
    -                                                break;
    +                                                if (get_day(i) == current_day)
    +                                                {
    +                                                        last_id = i;
    +                                                        break;
    +                                                }
                                             }
                                     }
    -                                while (new_id + 1 < last_id);
                             }
    -                }
     
    -                function previous_day_motion()
    -                {
    -                        if (current_id > 0)
    +                        function get_quality()
                             {
    -                                var new_id = current_id;
    -
    -                                do
    +                                if (/iPhone|iPad|iPod|Android/i.test(navigator.userAgent))
    +                                {
    +                                        return 1;
    +                                }
    +                                else
                                     {
    -                                        new_id -= 1;
    +                                        return 2;
    +                                }
    +                        }
     
    -                                        if (get_date(historic[new_id][MOTION_FILENAME]) != get_date(historic[current_id][MOTION_FILENAME]))
    +                        function image_loaded()
    +                        {
    +                                if (image_request.readyState === XMLHttpRequest.DONE)
    +                                {
    +                                        if (image_request.status === 200)
                                             {
    -                                                current_id = new_id;
    -
    -                                                do
    +                                                var motion = historic[last_id];
    +
    +                                                var div = document.createElement("div");
    +                                                        div.className = "col-lg-2  pb-1";
    +
    +                                                        var canvas = document.createElement("canvas");
    +                                                                canvas.width     = motion[MOTION_WIDTH ] * get_quality();
    +                                                                canvas.height    = motion[MOTION_HEIGHT] * get_quality();
    +                                                                canvas.id        = last_id;
    +                                                                canvas.className = "w-100";
    +                                                                canvas.setAttribute("data-bs-toggle","modal");
    +                                                                canvas.setAttribute("data-bs-target","#zoom_window");
    +                                                                canvas.onclick = e => 
    +                                                                        {
    +                                                                                var view = document.getElementById('zoom_image');
    +                                                                                var destCtx = view.getContext('2d');
    +                                                                                view.width     = motion[MOTION_WIDTH ] * get_quality();
    +                                                                                view.height    = motion[MOTION_HEIGHT] * get_quality();
    +
    +                                                                                destCtx.drawImage(canvas, 0, 0);
    +                                                                        };
    +
    +                                                        var image = new Image();
    +                                                                image.src        = 'data:image/jpeg;base64,' + image_request.response;
    +                                                                image.onload     = function(){show_motion(canvas.id, image);};
    +
    +                                                        div.appendChild(canvas);
    +
    +                                                if (last_id == 0)
                                                     {
    -                                                        new_id -= 1;
    -                                                        if (get_date(historic[new_id][MOTION_FILENAME]) == get_date(historic[current_id][MOTION_FILENAME]))
    -                                                        {
    -                                                                current_id = new_id;
    -                                                        }
    -                                                        else
    +                                                        document.getElementById('motions').replaceChildren(div);
    +                                                }
    +                                                else
    +                                                {
    +                                                        document.getElementById('motions').appendChild(div);
    +                                                }
    +                                                last_id = last_id + 1;
    +                                                if (last_id < historic.length-1)
    +                                                {
    +                                                        if (get_day(last_id) == current_day)
                                                             {
    -                                                                break;
    +                                                                setTimeout(load_image, 1);
                                                             }
                                                     }
    -                                                while(new_id - 1 >= 0);
    -                                                show_motion(current_id);
    -                                                break;
    +                                        }
    +                                        else 
    +                                        {
    +                                                setTimeout(load_image, 1);
                                             }
                                     }
    -                                while (new_id - 1 >= 0);
    -                        }
    -                }
    -
    -                function check_key(e)
    -                {
    -                        e = e || window.event;
    -
    -                        if (e.keyCode == '38') // up arrow
    -                        {
    -                                previous_day_motion();
                             }
    -                        else if (e.keyCode == '40') // down arrow
    -                        {
    -                                next_day_motion();
    -                        }
    -                        else if (e.keyCode == '37') // left arrow
    -                        {
    -                                previous_motion();
    -                        }
    -                        else if (e.keyCode == '39')// right arrow
    -                        {
    -                                next_motion();
    -                        }
    -                        else if (e.keyCode == '35') // end
    -                        {
    -                        }
    -                        else if (e.keyCode == '36') // home
    -                        {
    -                        }
    -                        else if (e.keyCode == '33') // page up
    -                        {
    -                                first_motion();
    -                        }
    -                        else if (e.keyCode == '34') // page down
    -                        {
    -                                last_motion();
    -                        }
    -                }
    -
    -                function get_click_position(canvas, e)
    -                {
    -                        const rect = canvas.getBoundingClientRect();
    -                        const x = e.clientX - rect.left;
    -                        const y = e.clientY - rect.top;
     
    -                        // If click on first
    -                        if (x < 100 && y < 100)
    -                        {
    -                                first_motion();
    -                        }
    -                        // If click on last
    -                        else if (x > (rect.width - 100) && y > (rect.height -100))
    -                        {
    -                                last_motion();
    -                        }
    -                        // If the click is in the middle
    -                        else if (x > rect.width/3 && x < (2*rect.width/3) && y > rect.height/3 && y < (2*rect.height/3))
    +                        function get_difference(motion, x, y)
                             {
    -                                // Download image
    -                                if (confirm("Download this image ?"))
    +                                var squarex = motion[MOTION_SQUAREX];
    +                                var maxx    = motion[MOTION_WIDTH] /squarex;
    +                                var bitpos  = y*maxx + x;
    +                                if (typeof motion[MOTION_DIFFS] === 'string')
                                     {
    -                                        download_motion();
    +                                        return motion[MOTION_DIFFS][bitpos];
                                     }
    -                        }
    -                        else
    -                        {
    -                                if (x < rect.width / 2)
    +                                else
                                     {
    -                                        if (y < rect.height / 2)
    +                                        var word = parseInt(bitpos/32);
    +                                        var bit  = 31-bitpos%%32;
    +                                        var mask = 1 << bit;
    +                                        var val  = motion[MOTION_DIFFS][word];
    +                                        if (val & mask)
                                             {
    -                                                if (x < y)
    -                                                {
    -                                                        previous_motion();
    -                                                }
    -                                                else
    -                                                {
    -                                                        previous_day_motion();
    -                                                }
    +                                                return "#";
                                             }
                                             else
                                             {
    -                                                if (x < rect.height - y)
    -                                                {
    -                                                        previous_motion();
    -                                                }
    -                                                else
    -                                                {
    -                                                        next_day_motion();
    -                                                }
    +                                                return " ";
                                             }
                                     }
    -                                else
    +                        }
    +
    +                        function show_motion(id, image)
    +                        {
    +                                var x;
    +                                var y;
    +
    +                                var motion = historic[id];
    +                                var canvas = document.getElementById(id);
    +                                var ctx = canvas.getContext('2d');
    +
    +                                var squarex = motion[MOTION_SQUAREX] * get_quality();
    +                                var squarey = motion[MOTION_SQUAREY] * get_quality();
    +                                var maxx = (motion[MOTION_WIDTH] /squarex) * get_quality();
    +                                var maxy = (motion[MOTION_HEIGHT]/squarey) * get_quality();
    +
    +                                ctx.drawImage(image, 0, 0, motion[MOTION_WIDTH ]  , motion[MOTION_HEIGHT], 0, 0, motion[MOTION_WIDTH ] * get_quality(), motion[MOTION_HEIGHT] * get_quality());
    +
    +                                ctx.strokeStyle = "red";
    +                                ctx.lineWidth =  1 * get_quality();
    +                                for (y = 0; y < maxy; y ++)
                                     {
    -                                        if (y < rect.height / 2)
    +                                        for (x = 0; x < maxx; x ++)
                                             {
    -                                                if (rect.width - x < y)
    -                                                {
    -                                                        next_motion();
    -                                                }
    -                                                else
    +                                                var detection = get_difference(motion, x, y);
    +                                                if (x >= 1)
                                                     {
    -                                                        previous_day_motion();
    +                                                        var previous = get_difference(motion, x-1, y);
    +                                                        if (previous != detection)
    +                                                        {
    +                                                                ctx.beginPath();
    +                                                                ctx.moveTo(0 + x*squarex, 0 + y*squarey);
    +                                                                ctx.lineTo(0 + x*squarex, 0 + y*squarey + squarey);
    +                                                                ctx.stroke();
    +                                                        }
                                                     }
                                             }
    -                                        else
    +                                }
    +                                
    +                                for (x = 0; x < maxx; x ++)
    +                                {
    +                                        for (y = 0; y < maxy; y ++)
                                             {
    -                                                if (rect.width - x < rect.height - y)
    +                                                var detection = get_difference(motion, x, y);
    +                                                if (y >= 1)
                                                     {
    -                                                        next_motion();
    -                                                }
    -                                                else
    -                                                {
    -                                                        next_day_motion();
    +                                                        var previous = get_difference(motion, x, y-1);
    +                                                        if (previous != detection)
    +                                                        {
    +                                                                ctx.beginPath();
    +                                                                ctx.moveTo(0 + x*squarex, 0 + y*squarey);
    +                                                                ctx.lineTo(0 + x*squarex + squarex, 0 + y*squarey);
    +                                                                ctx.stroke();
    +                                                        }
                                                     }
                                             }
                                     }
    +
    +
    +                                var font_size = 35 * get_quality();
    +                                ctx.font = font_size + "px monospace";
    +                                var width = ctx.measureText(get_name(motion[MOTION_FILENAME])).width;
    +                                ctx.fillStyle = 'rgba(0,0,0,0.3)';
    +
    +                                ctx.fillRect(0,(motion[MOTION_HEIGHT] * get_quality())-font_size, 
    +                                        width+get_quality(), font_size+10);
    +
    +                                ctx.fillStyle = 'rgba(255,255,255,0.5)';
    +                                ctx.fillText(get_name(motion[MOTION_FILENAME]),  3, (motion[MOTION_HEIGHT] * get_quality() - 5*get_quality() ));
    +                        }
    +
    +                        // Convert the filename into text displayed
    +                        function get_name(filename)
    +                        {
    +                                filename = filename.split(".")[0];
    +                                lst = filename.split("/");
    +                                filename = lst[lst.length-1];
    +                                filename = filename.replace("D= ","D=");
    +                                spl = filename.split(" ");
    +
    +                                if (spl.length == 3)
    +                                {
    +                                        date = spl[0].split("_")[0];
    +                                        hour = spl[0].split("_")[1];
    +
    +                                        date = date.replaceAll("-","/") + " " + hour.replaceAll("-",":");
    +                                        last = spl[1] + " " + spl[2];
    +                                        result = date + " "+ last;
    +                                }
    +                                else
    +                                {
    +                                        result = filename;
    +                                }
    +                                return result;
                             }
    -                }
    -
    -                </script>
    -                <canvas id="motion" width="%d" height="%d" ></canvas>
    -                <br>
    -                <div id="motions"></div>
    -                """%(lang.historic_not_available, detailled, 860,660)),
    -        ]
    -        page = main_frame(request, response, args,lang.last_motion_detections,pageContent)
    +
    +                        function get_date(filename)
    +                        {
    +                                return get_name(filename).substring(0,10);
    +                        }
    +                        </script>
    +                        <div id="motions" class="row"></div>
    +                        """%(current_day)),
    +                        Br(),
    +                        pagination_end
    +                ]
    +        else:
    +                page_content = Tag(b"<span>%s</span>"%lang.historic_not_available)
    +        page = main_frame(request, response, args,lang.last_motion_detections,Form(page_content))
             await response.send_page(page)
    @@ -1090,7 +909,7 @@

    Functions

    await Historic.acquire() await response.send_file(strings.tostrings(request.path[len("/historic/images/"):]), base64=True) else: - await response.send_error(status=b"404", content=b"Image not found") + await response.send_not_found() finally: if reserved: await Historic.release() @@ -1110,10 +929,10 @@

    Functions

    async def historic_json(request, response, args): """ Send historic json file """ Server.slow_down() - if await Historic.locked() is False: + try: await response.send_buffer(b"historic.json", await Historic.get_json()) - else: - await response.send_buffer(b"historic.json", b"[]") + except Exception as err: + await response.send_not_found(err) @@ -1135,6 +954,7 @@

    Index

  • Functions

  • diff --git a/doc/lib/webpage/systempage.html b/doc/lib/webpage/systempage.html index 820ff8d..e52ea0c 100644 --- a/doc/lib/webpage/systempage.html +++ b/doc/lib/webpage/systempage.html @@ -41,21 +41,30 @@

    Module lib.webpage.systempage

    async def system_page(request, response, args): """ Function define the web page to manage system of the board """ page = main_frame(request, response, args, lang.system_management_s%Station.get_hostname(), - Label(text=lang.configuration ),Br(), - UploadFile(text=lang.upload, path=b"/system/upload_config", alert=lang.configuration_uploaded, accept=b".cfg"), - DownloadFile(text=lang.download, path=b"/system/download_config", filename=b"Config_%s.cfg"%Station.get_hostname()), - - Br(),Br(),Label(text=lang.file_system),Br(), - UploadFile(text=lang.upload, path=b"/system/upload_file_system", alert=lang.upload_in_progress, accept=b".cfs"), - DownloadFile(text=lang.download, path=b"/system/download_file_system", filename=b"FileSystem_%s.cfs"%Station.get_hostname()), - - Br(),Br(),Label(text=lang.syslog),Br(), - DownloadFile(text=lang.download, path=b"/system/download_syslog", filename=b"Syslog_%s.log"%Station.get_hostname()), - - Br(), Br(),Label(text=lang.reboot_device),Br(), - ButtonCmd(text=lang.reboot,path=b"/system/reboot",confirm=lang.confirm_reboot, name=b"reboot")) + Form([ + FormGroup([ + Label(text=lang.configuration ), Br(), + UploadFile(text=lang.upload, path=b"/system/upload_config", alert=lang.configuration_uploaded, accept=b".cfg"), Space(), + DownloadFile(text=lang.download, path=b"/system/download_config", filename=b"Config_%s.cfg"%Station.get_hostname()), + ]), + FormGroup([ + Label(text=lang.file_system), Br(), + UploadFile(text=lang.upload, path=b"/system/upload_file_system", alert=lang.upload_in_progress, accept=b".cfs"), Space(), + DownloadFile(text=lang.download, path=b"/system/download_file_system", filename=b"FileSystem_%s.cfs"%Station.get_hostname()), + ]), + FormGroup([ + Label(text=lang.syslog), Br(), + DownloadFile(text=lang.download, path=b"/system/download_syslog", filename=b"Syslog_%s.log"%Station.get_hostname()), + ]), + FormGroup([ + Label(text=lang.reboot_device), Br(), + ButtonCmd(text=lang.reboot,path=b"/system/reboot",confirm=lang.confirm_reboot, name=b"reboot") + ]) + ]) + ) await response.send_page(page) + @HttpServer.add_route(b'/system/upload_config') async def upload_config(request, response, args): """ Upload configuration """ @@ -200,19 +209,27 @@

    Functions

    async def system_page(request, response, args): """ Function define the web page to manage system of the board """ page = main_frame(request, response, args, lang.system_management_s%Station.get_hostname(), - Label(text=lang.configuration ),Br(), - UploadFile(text=lang.upload, path=b"/system/upload_config", alert=lang.configuration_uploaded, accept=b".cfg"), - DownloadFile(text=lang.download, path=b"/system/download_config", filename=b"Config_%s.cfg"%Station.get_hostname()), - - Br(),Br(),Label(text=lang.file_system),Br(), - UploadFile(text=lang.upload, path=b"/system/upload_file_system", alert=lang.upload_in_progress, accept=b".cfs"), - DownloadFile(text=lang.download, path=b"/system/download_file_system", filename=b"FileSystem_%s.cfs"%Station.get_hostname()), - - Br(),Br(),Label(text=lang.syslog),Br(), - DownloadFile(text=lang.download, path=b"/system/download_syslog", filename=b"Syslog_%s.log"%Station.get_hostname()), - - Br(), Br(),Label(text=lang.reboot_device),Br(), - ButtonCmd(text=lang.reboot,path=b"/system/reboot",confirm=lang.confirm_reboot, name=b"reboot")) + Form([ + FormGroup([ + Label(text=lang.configuration ), Br(), + UploadFile(text=lang.upload, path=b"/system/upload_config", alert=lang.configuration_uploaded, accept=b".cfg"), Space(), + DownloadFile(text=lang.download, path=b"/system/download_config", filename=b"Config_%s.cfg"%Station.get_hostname()), + ]), + FormGroup([ + Label(text=lang.file_system), Br(), + UploadFile(text=lang.upload, path=b"/system/upload_file_system", alert=lang.upload_in_progress, accept=b".cfs"), Space(), + DownloadFile(text=lang.download, path=b"/system/download_file_system", filename=b"FileSystem_%s.cfs"%Station.get_hostname()), + ]), + FormGroup([ + Label(text=lang.syslog), Br(), + DownloadFile(text=lang.download, path=b"/system/download_syslog", filename=b"Syslog_%s.log"%Station.get_hostname()), + ]), + FormGroup([ + Label(text=lang.reboot_device), Br(), + ButtonCmd(text=lang.reboot,path=b"/system/reboot",confirm=lang.confirm_reboot, name=b"reboot") + ]) + ]) + ) await response.send_page(page) diff --git a/doc/lib/webpage/wifipage.html b/doc/lib/webpage/wifipage.html index 84ab12b..58cc077 100644 --- a/doc/lib/webpage/wifipage.html +++ b/doc/lib/webpage/wifipage.html @@ -34,7 +34,7 @@

    Module lib.webpage.wifipage

    from htmltemplate.htmlclasses import * from webpage.mainpage import main_frame, manage_default_button import wifi -from tools import lang,strings,logger +from tools import lang,strings,logger,support def static_ip_html(config, disabled): """ Html to get static ip """ @@ -114,10 +114,10 @@

    Module lib.webpage.wifipage

    pass # If the save button clicked elif action == b"save": - network.update(request.params) + network.update(request.params, show_error=False) network.save() network = select_network(0, True) - config.update(request.params) + config.update(request.params, show_error=False) config.default = network.ssid config.save() forced = b"none" @@ -146,11 +146,11 @@

    Module lib.webpage.wifipage

    if action in [b"previous",b"next",b"change",b"forget",b"modify",b"default"]: submit = \ - Submit (text=lang.save , name=b"action", value=b"save" , style=b"margin-right:0.5em"), \ - Submit (text=lang.lt , name=b"action", value=b"previous", style=b"margin-right:0.5em"), \ - Submit (text=lang.gt , name=b"action", value=b"next" , style=b"margin-right:0.5em"), \ - Submit (text=lang.forget, name=b"action", value=b"forget" , style=b"margin-right:0.5em"), \ - Submit (text=lang.set_default,name=b"action", value=b"default" , style=b"margin-right:0.5em"), \ + Submit (text=lang.save , name=b"action", value=b"save" ), Space(), \ + Submit (text=lang.lt , name=b"action", value=b"previous"), Space(), \ + Submit (text=lang.gt , name=b"action", value=b"next" ), Space(), \ + Submit (text=lang.forget, name=b"action", value=b"forget" ), Space(), \ + Submit (text=lang.set_default,name=b"action", value=b"default" ), Space(), \ Input ( name=b"forced", value=forced , type=b"hidden"), \ Input ( name=b"current", value=current , type=b"hidden") @@ -162,26 +162,27 @@

    Module lib.webpage.wifipage

    submit = Submit(text=lang.modify, name=b"action", value=b"modify") page = main_frame(request, response, args, lang.wifi_configuration, - Switch(text=lang.activated, name=b"activated", checked=config.activated, disabled=disabled),Br(), - Edit(text=lang.hostname , name=b"hostname", placeholder=lang.hostname_not_available, pattern=patternDns, value=config.hostname, disabled=disabled), - Switch(text=lang.fallback_to_the, name=b"fallback", checked=config.fallback, disabled=disabled),Br(), - Card( - [ - CardHeader(text= lang.wifi if strings.tostrings(network.ssid) != strings.tostrings(config.default) else lang.wifi_default), - CardBody([ - ComboBox(ssids, text=lang.ssid, placeholder=lang.enter_ssid, name=b"ssid", value=network.ssid, disabled=disabled), - Edit(text=lang.password, name=b"wifi_password", placeholder=lang.enter_password, type=b"password",value=network.wifi_password, disabled=disabled), - ]) - ]),Br(), - Card( - [ - CardHeader([\ - Switch(text=lang.dynamic_ip, checked=dynamic, name=b"dynamic", onchange=b"this.form.submit()", disabled=disabled)]), - CardBody([\ - None if dynamic else static_ip_html(network, disabled)]) - ]), - Br(), - submit) + Form([ + Switch(text=lang.activated, name=b"activated", checked=config.activated, disabled=disabled), + Edit(text=lang.hostname , name=b"hostname", placeholder=lang.hostname_not_available, pattern=patternDns, value=config.hostname, disabled=disabled) if support.hostname() else None, + Switch(text=lang.fallback_to_the, name=b"fallback", checked=config.fallback, disabled=disabled), + Card( + [ + CardHeader(text= lang.wifi if strings.tostrings(network.ssid) != strings.tostrings(config.default) else lang.wifi_default), + CardBody([ + ComboBox(ssids, text=lang.ssid, placeholder=lang.enter_ssid, name=b"ssid", value=network.ssid, disabled=disabled), + Edit(text=lang.password, name=b"wifi_password", placeholder=lang.enter_password, type=b"password",value=network.wifi_password, disabled=disabled), + ]) + ]), + Card( + [ + CardHeader([\ + Switch(text=lang.dynamic_ip, spacer=b"mb-0", checked=dynamic, name=b"dynamic", event=b'onchange="this.form.submit()"', disabled=disabled)]), + None if dynamic else CardBody([\ + static_ip_html(network, disabled)]) + ]), + submit + ])) await response.send_page(page) @HttpServer.add_route(b'/accesspoint', menu=lang.menu_network, item=lang.item_access_point) @@ -201,22 +202,25 @@

    Module lib.webpage.wifipage

    # pylint: disable=missing-parentheses-for-call-in-test # pylint: disable=using-constant-test page = main_frame(request, response, args, lang.access_point_configuration if access_point else b"Wifi configuration", - Switch(text=lang.activated, name=b"activated", checked=config.activated, disabled=disabled),Br(), - Card( - [ - CardHeader(text=lang.wifi), - CardBody([ - Edit(text=lang.ssid, placeholder=lang.enter_ssid, name=b"ssid", value=config.ssid, disabled=disabled), - Edit(text=lang.password, name=b"wifi_password",type=b"password", placeholder=lang.enter_password, value=config.wifi_password, disabled=disabled), - Select(authmodes,text=lang.authentication_mode,name=b"authmode", disabled=disabled), + Form([ + Switch(text=lang.activated, name=b"activated", checked=config.activated, disabled=disabled), + Card( + [ + CardHeader(text=lang.wifi), + CardBody([ + Edit(text=lang.ssid, placeholder=lang.enter_ssid, name=b"ssid", value=config.ssid, disabled=disabled), + Edit(text=lang.password, name=b"wifi_password",type=b"password", placeholder=lang.enter_password, value=config.wifi_password, disabled=disabled), + Label(text=lang.authentication_mode), + Select(authmodes,name=b"authmode", disabled=disabled), + ]), ]), - ]),Br(), - Card( - [ - CardHeader(text=lang.static_ip), - CardBody(static_ip_html(config, disabled)) - ]),Br(), - submit) + Card( + [ + CardHeader(text=lang.static_ip), + CardBody(static_ip_html(config, disabled)) + ]) if support.static_ip_accesspoint() else None , + submit + ])) await response.send_page(page) @@ -253,22 +257,25 @@

    Functions

    # pylint: disable=missing-parentheses-for-call-in-test # pylint: disable=using-constant-test page = main_frame(request, response, args, lang.access_point_configuration if access_point else b"Wifi configuration", - Switch(text=lang.activated, name=b"activated", checked=config.activated, disabled=disabled),Br(), - Card( - [ - CardHeader(text=lang.wifi), - CardBody([ - Edit(text=lang.ssid, placeholder=lang.enter_ssid, name=b"ssid", value=config.ssid, disabled=disabled), - Edit(text=lang.password, name=b"wifi_password",type=b"password", placeholder=lang.enter_password, value=config.wifi_password, disabled=disabled), - Select(authmodes,text=lang.authentication_mode,name=b"authmode", disabled=disabled), + Form([ + Switch(text=lang.activated, name=b"activated", checked=config.activated, disabled=disabled), + Card( + [ + CardHeader(text=lang.wifi), + CardBody([ + Edit(text=lang.ssid, placeholder=lang.enter_ssid, name=b"ssid", value=config.ssid, disabled=disabled), + Edit(text=lang.password, name=b"wifi_password",type=b"password", placeholder=lang.enter_password, value=config.wifi_password, disabled=disabled), + Label(text=lang.authentication_mode), + Select(authmodes,name=b"authmode", disabled=disabled), + ]), ]), - ]),Br(), - Card( - [ - CardHeader(text=lang.static_ip), - CardBody(static_ip_html(config, disabled)) - ]),Br(), - submit) + Card( + [ + CardHeader(text=lang.static_ip), + CardBody(static_ip_html(config, disabled)) + ]) if support.static_ip_accesspoint() else None , + submit + ])) await response.send_page(page) @@ -375,10 +382,10 @@

    Functions

    pass # If the save button clicked elif action == b"save": - network.update(request.params) + network.update(request.params, show_error=False) network.save() network = select_network(0, True) - config.update(request.params) + config.update(request.params, show_error=False) config.default = network.ssid config.save() forced = b"none" @@ -407,11 +414,11 @@

    Functions

    if action in [b"previous",b"next",b"change",b"forget",b"modify",b"default"]: submit = \ - Submit (text=lang.save , name=b"action", value=b"save" , style=b"margin-right:0.5em"), \ - Submit (text=lang.lt , name=b"action", value=b"previous", style=b"margin-right:0.5em"), \ - Submit (text=lang.gt , name=b"action", value=b"next" , style=b"margin-right:0.5em"), \ - Submit (text=lang.forget, name=b"action", value=b"forget" , style=b"margin-right:0.5em"), \ - Submit (text=lang.set_default,name=b"action", value=b"default" , style=b"margin-right:0.5em"), \ + Submit (text=lang.save , name=b"action", value=b"save" ), Space(), \ + Submit (text=lang.lt , name=b"action", value=b"previous"), Space(), \ + Submit (text=lang.gt , name=b"action", value=b"next" ), Space(), \ + Submit (text=lang.forget, name=b"action", value=b"forget" ), Space(), \ + Submit (text=lang.set_default,name=b"action", value=b"default" ), Space(), \ Input ( name=b"forced", value=forced , type=b"hidden"), \ Input ( name=b"current", value=current , type=b"hidden") @@ -423,26 +430,27 @@

    Functions

    submit = Submit(text=lang.modify, name=b"action", value=b"modify") page = main_frame(request, response, args, lang.wifi_configuration, - Switch(text=lang.activated, name=b"activated", checked=config.activated, disabled=disabled),Br(), - Edit(text=lang.hostname , name=b"hostname", placeholder=lang.hostname_not_available, pattern=patternDns, value=config.hostname, disabled=disabled), - Switch(text=lang.fallback_to_the, name=b"fallback", checked=config.fallback, disabled=disabled),Br(), - Card( - [ - CardHeader(text= lang.wifi if strings.tostrings(network.ssid) != strings.tostrings(config.default) else lang.wifi_default), - CardBody([ - ComboBox(ssids, text=lang.ssid, placeholder=lang.enter_ssid, name=b"ssid", value=network.ssid, disabled=disabled), - Edit(text=lang.password, name=b"wifi_password", placeholder=lang.enter_password, type=b"password",value=network.wifi_password, disabled=disabled), - ]) - ]),Br(), - Card( - [ - CardHeader([\ - Switch(text=lang.dynamic_ip, checked=dynamic, name=b"dynamic", onchange=b"this.form.submit()", disabled=disabled)]), - CardBody([\ - None if dynamic else static_ip_html(network, disabled)]) - ]), - Br(), - submit) + Form([ + Switch(text=lang.activated, name=b"activated", checked=config.activated, disabled=disabled), + Edit(text=lang.hostname , name=b"hostname", placeholder=lang.hostname_not_available, pattern=patternDns, value=config.hostname, disabled=disabled) if support.hostname() else None, + Switch(text=lang.fallback_to_the, name=b"fallback", checked=config.fallback, disabled=disabled), + Card( + [ + CardHeader(text= lang.wifi if strings.tostrings(network.ssid) != strings.tostrings(config.default) else lang.wifi_default), + CardBody([ + ComboBox(ssids, text=lang.ssid, placeholder=lang.enter_ssid, name=b"ssid", value=network.ssid, disabled=disabled), + Edit(text=lang.password, name=b"wifi_password", placeholder=lang.enter_password, type=b"password",value=network.wifi_password, disabled=disabled), + ]) + ]), + Card( + [ + CardHeader([\ + Switch(text=lang.dynamic_ip, spacer=b"mb-0", checked=dynamic, name=b"dynamic", event=b'onchange="this.form.submit()"', disabled=disabled)]), + None if dynamic else CardBody([\ + static_ip_html(network, disabled)]) + ]), + submit + ])) await response.send_page(page) diff --git a/doc/lib/wifi/accesspoint.html b/doc/lib/wifi/accesspoint.html index d3714af..8fb2706 100644 --- a/doc/lib/wifi/accesspoint.html +++ b/doc/lib/wifi/accesspoint.html @@ -29,7 +29,9 @@

    Module lib.wifi.accesspoint

    # Distributed under MIT License
     # Copyright (c) 2021 Remi BERTHOLET
    +# pylint:disable=consider-using-f-string
     """ Classes used to manage the wifi access point """
    +import sys
     from wifi import hostname, ip
     from tools import jsonconfig,strings,logger
     
    @@ -71,22 +73,28 @@ 

    Module lib.wifi.accesspoint

    @staticmethod def open(ssid=None, password=None, authmode=None): """ Open access point """ - from wifi import AUTHMODE + from wifi import AUTHMODE, AUTHMODE_DEFAULT # pylint:disable=multiple-statements if ssid is not None: AccessPoint.config.ssid = strings.tobytes(ssid) if password is not None: AccessPoint.config.wifi_password = strings.tobytes(password) if authmode is not None: AccessPoint.config.authmode = strings.tobytes(authmode) - authmode = 3 + authmode = AUTHMODE_DEFAULT for authmode_num, authmode_name in AUTHMODE.items(): if authmode_name == AccessPoint.config.authmode: authmode = authmode_num break - AccessPoint.wlan.active(True) # IMPORTANT : Activate before configure + + if sys.platform != "rp2": + AccessPoint.wlan.active(True) # IMPORTANT : For esp32 activate before configure + AccessPoint.wlan.config(\ essid = strings.tostrings(AccessPoint.config.ssid), password = strings.tostrings(AccessPoint.config.wifi_password), - authmode = authmode) + security = authmode) + + if sys.platform == "rp2": + AccessPoint.wlan.active(True) # IMPORTANT : For esp32 activate before configure @staticmethod def reload_config(): @@ -152,11 +160,12 @@

    Module lib.wifi.accesspoint

    AccessPoint.config.netmask != b"" and \ AccessPoint.config.gateway != b"" and \ AccessPoint.config.dns != b"": - AccessPoint.wlan.ifconfig(( - strings.tostrings(AccessPoint.config.ip_address), - strings.tostrings(AccessPoint.config.netmask), - strings.tostrings(AccessPoint.config.gateway), - strings.tostrings(AccessPoint.config.dns))) + if sys.platform != "rp2": + AccessPoint.wlan.ifconfig(( + strings.tostrings(AccessPoint.config.ip_address), + strings.tostrings(AccessPoint.config.netmask), + strings.tostrings(AccessPoint.config.gateway), + strings.tostrings(AccessPoint.config.dns))) except Exception as err: logger.syslog(err, msg="Cannot configure wifi access point") @@ -173,7 +182,6 @@

    Module lib.wifi.accesspoint

    AccessPoint.wlan = WLAN(AP_IF) AccessPoint.configure() AccessPoint.open() - AccessPoint.configure() print(repr(AccessPoint.config)) result = True else: @@ -226,22 +234,28 @@

    Classes

    @staticmethod def open(ssid=None, password=None, authmode=None): """ Open access point """ - from wifi import AUTHMODE + from wifi import AUTHMODE, AUTHMODE_DEFAULT # pylint:disable=multiple-statements if ssid is not None: AccessPoint.config.ssid = strings.tobytes(ssid) if password is not None: AccessPoint.config.wifi_password = strings.tobytes(password) if authmode is not None: AccessPoint.config.authmode = strings.tobytes(authmode) - authmode = 3 + authmode = AUTHMODE_DEFAULT for authmode_num, authmode_name in AUTHMODE.items(): if authmode_name == AccessPoint.config.authmode: authmode = authmode_num break - AccessPoint.wlan.active(True) # IMPORTANT : Activate before configure + + if sys.platform != "rp2": + AccessPoint.wlan.active(True) # IMPORTANT : For esp32 activate before configure + AccessPoint.wlan.config(\ essid = strings.tostrings(AccessPoint.config.ssid), password = strings.tostrings(AccessPoint.config.wifi_password), - authmode = authmode) + security = authmode) + + if sys.platform == "rp2": + AccessPoint.wlan.active(True) # IMPORTANT : For esp32 activate before configure @staticmethod def reload_config(): @@ -307,11 +321,12 @@

    Classes

    AccessPoint.config.netmask != b"" and \ AccessPoint.config.gateway != b"" and \ AccessPoint.config.dns != b"": - AccessPoint.wlan.ifconfig(( - strings.tostrings(AccessPoint.config.ip_address), - strings.tostrings(AccessPoint.config.netmask), - strings.tostrings(AccessPoint.config.gateway), - strings.tostrings(AccessPoint.config.dns))) + if sys.platform != "rp2": + AccessPoint.wlan.ifconfig(( + strings.tostrings(AccessPoint.config.ip_address), + strings.tostrings(AccessPoint.config.netmask), + strings.tostrings(AccessPoint.config.gateway), + strings.tostrings(AccessPoint.config.dns))) except Exception as err: logger.syslog(err, msg="Cannot configure wifi access point") @@ -328,7 +343,6 @@

    Classes

    AccessPoint.wlan = WLAN(AP_IF) AccessPoint.configure() AccessPoint.open() - AccessPoint.configure() print(repr(AccessPoint.config)) result = True else: @@ -415,11 +429,12 @@

    Static methods

    AccessPoint.config.netmask != b"" and \ AccessPoint.config.gateway != b"" and \ AccessPoint.config.dns != b"": - AccessPoint.wlan.ifconfig(( - strings.tostrings(AccessPoint.config.ip_address), - strings.tostrings(AccessPoint.config.netmask), - strings.tostrings(AccessPoint.config.gateway), - strings.tostrings(AccessPoint.config.dns))) + if sys.platform != "rp2": + AccessPoint.wlan.ifconfig(( + strings.tostrings(AccessPoint.config.ip_address), + strings.tostrings(AccessPoint.config.netmask), + strings.tostrings(AccessPoint.config.gateway), + strings.tostrings(AccessPoint.config.dns))) except Exception as err: logger.syslog(err, msg="Cannot configure wifi access point")
    @@ -504,22 +519,28 @@

    Static methods

    @staticmethod
     def open(ssid=None, password=None, authmode=None):
             """ Open access point """
    -        from wifi import AUTHMODE
    +        from wifi import AUTHMODE, AUTHMODE_DEFAULT
             # pylint:disable=multiple-statements
             if ssid     is not None: AccessPoint.config.ssid         = strings.tobytes(ssid)
             if password is not None: AccessPoint.config.wifi_password = strings.tobytes(password)
             if authmode is not None: AccessPoint.config.authmode     = strings.tobytes(authmode)
     
    -        authmode = 3
    +        authmode = AUTHMODE_DEFAULT
             for authmode_num, authmode_name in AUTHMODE.items():
                     if authmode_name == AccessPoint.config.authmode:
                             authmode = authmode_num
                             break
    -        AccessPoint.wlan.active(True) # IMPORTANT : Activate before configure
    +
    +        if sys.platform != "rp2":
    +                AccessPoint.wlan.active(True) # IMPORTANT : For esp32 activate before configure
    +
             AccessPoint.wlan.config(\
                     essid    = strings.tostrings(AccessPoint.config.ssid),
                     password = strings.tostrings(AccessPoint.config.wifi_password),
    -                authmode = authmode)
    + security = authmode) + + if sys.platform == "rp2": + AccessPoint.wlan.active(True) # IMPORTANT : For esp32 activate before configure
    @@ -569,7 +590,6 @@

    Static methods

    AccessPoint.wlan = WLAN(AP_IF) AccessPoint.configure() AccessPoint.open() - AccessPoint.configure() print(repr(AccessPoint.config)) result = True else: diff --git a/doc/lib/wifi/hostname.html b/doc/lib/wifi/hostname.html index 5f8b7c9..39e5471 100644 --- a/doc/lib/wifi/hostname.html +++ b/doc/lib/wifi/hostname.html @@ -29,6 +29,7 @@

    Module lib.wifi.hostname

    # Distributed under MIT License
     # Copyright (c) 2021 Remi BERTHOLET
    +# pylint:disable=consider-using-f-string
     """ Manage the host name """
     from tools import strings
     
    diff --git a/doc/lib/wifi/index.html b/doc/lib/wifi/index.html
    index e4c8de6..b445196 100644
    --- a/doc/lib/wifi/index.html
    +++ b/doc/lib/wifi/index.html
    @@ -30,14 +30,18 @@ 

    Module lib.wifi

    # Distributed under MIT License
     # Copyright (c) 2021 Remi BERTHOLET
     """ Class to manage wifi and access point """
    -# from wifi.hostname import *
    +import sys
     from wifi.accesspoint import *
     from wifi.station import *
     from wifi.wifi import *
     from wifi.ip import *
     
    -
    -AUTHMODE = {0: b"open", 1: b"WEP", 2: b"WPA-PSK", 3: b"WPA2-PSK", 4: b"WPA/WPA2-PSK"}
    +if sys.platform == "rp2": + AUTHMODE = {0: b"open", 0x00200002: b"WPA-PSK", 0x00400004: b"WPA2-PSK", 0x00400006: b"WPA/WPA2-PSK"} + AUTHMODE_DEFAULT = 0x00400004 +else: + AUTHMODE = {0: b"open", 1: b"WEP", 2: b"WPA-PSK", 3: b"WPA2-PSK", 4: b"WPA/WPA2-PSK"} + AUTHMODE_DEFAULT = 3
    diff --git a/doc/lib/wifi/station.html b/doc/lib/wifi/station.html index c234593..750e3df 100644 --- a/doc/lib/wifi/station.html +++ b/doc/lib/wifi/station.html @@ -29,20 +29,21 @@

    Module lib.wifi.station

    # Distributed under MIT License
     # Copyright (c) 2021 Remi BERTHOLET
    +# pylint:disable=consider-using-f-string
     """ Classes used to manage the wifi station """
     import time
     from wifi import ip, hostname
     import uasyncio
    -from tools import jsonconfig,strings,logger
    +from tools import jsonconfig,strings,logger,lang
     
     class NetworkConfig(jsonconfig.JsonConfig):
             """ Wifi station configuration class """
             def __init__(self):
                     """ Constructor """
                     jsonconfig.JsonConfig.__init__(self)
    -                self.wifi_password  = b""
    +                self.wifi_password = b""
                     self.ssid          = b""
    -                self.ip_address     = b""
    +                self.ip_address    = b""
                     self.netmask       = b""
                     self.gateway       = b""
                     self.dns           = b""
    @@ -121,7 +122,7 @@ 

    Module lib.wifi.station

    retry = 0 while not Station.wlan.isconnected() and retry < max_retry: await uasyncio.sleep(1) - logger.syslog (" %-2d/%d wait connection to %s"%(retry+1, max_retry, strings.tostrings(network.ssid))) + logger.syslog (" %-2d/%d wait connection to %s (wifi=%s)"%(retry+1, max_retry, strings.tostrings(network.ssid), strings.tostrings(Station.get_signal_strength_bytes()))) retry += 1 if Station.wlan.isconnected() is False: @@ -161,7 +162,10 @@

    Module lib.wifi.station

    # If ip is dynamic if network.dynamic is True: if len(Station.get_hostname()) > 0: - Station.wlan.config(dhcp_hostname= strings.tostrings(Station.get_hostname())) + try: + Station.wlan.config(hostname= strings.tostrings(Station.get_hostname())) + except: + pass else: try: Station.wlan.ifconfig((strings.tostrings(network.ip_address),strings.tostrings(network.netmask),strings.tostrings(network.gateway),strings.tostrings(network.dns))) @@ -169,9 +173,9 @@

    Module lib.wifi.station

    logger.syslog(err, msg="Cannot configure wifi station") try: network.ip_address = strings.tobytes(Station.wlan.ifconfig()[0]) - network.netmask = strings.tobytes(Station.wlan.ifconfig()[1]) - network.gateway = strings.tobytes(Station.wlan.ifconfig()[2]) - network.dns = strings.tobytes(Station.wlan.ifconfig()[3]) + network.netmask = strings.tobytes(Station.wlan.ifconfig()[1]) + network.gateway = strings.tobytes(Station.wlan.ifconfig()[2]) + network.dns = strings.tobytes(Station.wlan.ifconfig()[3]) except Exception as err: logger.syslog(err, msg="Cannot get ip station") @@ -211,9 +215,45 @@

    Module lib.wifi.station

    Station.last_scan[0] = time.time() except Exception as err: logger.syslog("No network found") - return Station.other_networks + @staticmethod + def get_signal_strength(): + """ Get the signal strength """ + result = None + if Station.wlan: + try: + rssi = Station.wlan.status("rssi") + if rssi > -60: + result = 5 + elif rssi > -70: + result = 4 + elif rssi > -80: + result = 3 + elif rssi > -90: + result = 2 + elif rssi > -100: + result = 1 + else: + result = 0 + except: + pass + return result + + @staticmethod + def get_signal_strength_bytes(): + """ Get the signal strength into string """ + signal_strength = { + 5 : lang.signal_excellent, + 4 : lang.signal_very_good, + 3 : lang.signal_good , + 2 : lang.signal_low , + 1 : lang.signal_very_low , + 0 : lang.signal_no , + None : lang.no_information + }[Station.get_signal_strength()] + return signal_strength + @staticmethod def is_activated(): """ Indicates if the wifi station is configured to be activated """ @@ -371,9 +411,9 @@

    Classes

    def __init__(self): """ Constructor """ jsonconfig.JsonConfig.__init__(self) - self.wifi_password = b"" + self.wifi_password = b"" self.ssid = b"" - self.ip_address = b"" + self.ip_address = b"" self.netmask = b"" self.gateway = b"" self.dns = b"" @@ -494,7 +534,7 @@

    Methods

    retry = 0 while not Station.wlan.isconnected() and retry < max_retry: await uasyncio.sleep(1) - logger.syslog (" %-2d/%d wait connection to %s"%(retry+1, max_retry, strings.tostrings(network.ssid))) + logger.syslog (" %-2d/%d wait connection to %s (wifi=%s)"%(retry+1, max_retry, strings.tostrings(network.ssid), strings.tostrings(Station.get_signal_strength_bytes()))) retry += 1 if Station.wlan.isconnected() is False: @@ -534,7 +574,10 @@

    Methods

    # If ip is dynamic if network.dynamic is True: if len(Station.get_hostname()) > 0: - Station.wlan.config(dhcp_hostname= strings.tostrings(Station.get_hostname())) + try: + Station.wlan.config(hostname= strings.tostrings(Station.get_hostname())) + except: + pass else: try: Station.wlan.ifconfig((strings.tostrings(network.ip_address),strings.tostrings(network.netmask),strings.tostrings(network.gateway),strings.tostrings(network.dns))) @@ -542,9 +585,9 @@

    Methods

    logger.syslog(err, msg="Cannot configure wifi station") try: network.ip_address = strings.tobytes(Station.wlan.ifconfig()[0]) - network.netmask = strings.tobytes(Station.wlan.ifconfig()[1]) - network.gateway = strings.tobytes(Station.wlan.ifconfig()[2]) - network.dns = strings.tobytes(Station.wlan.ifconfig()[3]) + network.netmask = strings.tobytes(Station.wlan.ifconfig()[1]) + network.gateway = strings.tobytes(Station.wlan.ifconfig()[2]) + network.dns = strings.tobytes(Station.wlan.ifconfig()[3]) except Exception as err: logger.syslog(err, msg="Cannot get ip station") @@ -584,9 +627,45 @@

    Methods

    Station.last_scan[0] = time.time() except Exception as err: logger.syslog("No network found") - return Station.other_networks + @staticmethod + def get_signal_strength(): + """ Get the signal strength """ + result = None + if Station.wlan: + try: + rssi = Station.wlan.status("rssi") + if rssi > -60: + result = 5 + elif rssi > -70: + result = 4 + elif rssi > -80: + result = 3 + elif rssi > -90: + result = 2 + elif rssi > -100: + result = 1 + else: + result = 0 + except: + pass + return result + + @staticmethod + def get_signal_strength_bytes(): + """ Get the signal strength into string """ + signal_strength = { + 5 : lang.signal_excellent, + 4 : lang.signal_very_good, + 3 : lang.signal_good , + 2 : lang.signal_low , + 1 : lang.signal_very_low , + 0 : lang.signal_no , + None : lang.no_information + }[Station.get_signal_strength()] + return signal_strength + @staticmethod def is_activated(): """ Indicates if the wifi station is configured to be activated """ @@ -793,7 +872,10 @@

    Static methods

    # If ip is dynamic if network.dynamic is True: if len(Station.get_hostname()) > 0: - Station.wlan.config(dhcp_hostname= strings.tostrings(Station.get_hostname())) + try: + Station.wlan.config(hostname= strings.tostrings(Station.get_hostname())) + except: + pass else: try: Station.wlan.ifconfig((strings.tostrings(network.ip_address),strings.tostrings(network.netmask),strings.tostrings(network.gateway),strings.tostrings(network.dns))) @@ -801,9 +883,9 @@

    Static methods

    logger.syslog(err, msg="Cannot configure wifi station") try: network.ip_address = strings.tobytes(Station.wlan.ifconfig()[0]) - network.netmask = strings.tobytes(Station.wlan.ifconfig()[1]) - network.gateway = strings.tobytes(Station.wlan.ifconfig()[2]) - network.dns = strings.tobytes(Station.wlan.ifconfig()[3]) + network.netmask = strings.tobytes(Station.wlan.ifconfig()[1]) + network.gateway = strings.tobytes(Station.wlan.ifconfig()[2]) + network.dns = strings.tobytes(Station.wlan.ifconfig()[3]) except Exception as err: logger.syslog(err, msg="Cannot get ip station")
    @@ -829,7 +911,7 @@

    Static methods

    retry = 0 while not Station.wlan.isconnected() and retry < max_retry: await uasyncio.sleep(1) - logger.syslog (" %-2d/%d wait connection to %s"%(retry+1, max_retry, strings.tostrings(network.ssid))) + logger.syslog (" %-2d/%d wait connection to %s (wifi=%s)"%(retry+1, max_retry, strings.tostrings(network.ssid), strings.tostrings(Station.get_signal_strength_bytes()))) retry += 1 if Station.wlan.isconnected() is False: @@ -894,6 +976,63 @@

    Static methods

    return ("","","","")
    +
    +def get_signal_strength() +
    +
    +

    Get the signal strength

    +
    + +Expand source code + +
    @staticmethod
    +def get_signal_strength():
    +        """ Get the signal strength """
    +        result = None
    +        if Station.wlan:
    +                try:
    +                        rssi = Station.wlan.status("rssi")
    +                        if rssi > -60:
    +                                result = 5
    +                        elif rssi > -70:
    +                                result = 4
    +                        elif rssi > -80:
    +                                result = 3
    +                        elif rssi > -90:
    +                                result = 2
    +                        elif rssi > -100:
    +                                result = 1
    +                        else:
    +                                result = 0
    +                except:
    +                        pass
    +        return result
    +
    +
    +
    +def get_signal_strength_bytes() +
    +
    +

    Get the signal strength into string

    +
    + +Expand source code + +
    @staticmethod
    +def get_signal_strength_bytes():
    +        """ Get the signal strength into string """
    +        signal_strength = {
    +                        5    : lang.signal_excellent,
    +                        4    : lang.signal_very_good,
    +                        3    : lang.signal_good     ,
    +                        2    : lang.signal_low      ,
    +                        1    : lang.signal_very_low ,
    +                        0    : lang.signal_no       ,
    +                        None : lang.no_information
    +                }[Station.get_signal_strength()]
    +        return signal_strength
    +
    +
    def init()
    @@ -1046,7 +1185,6 @@

    Static methods

    Station.last_scan[0] = time.time() except Exception as err: logger.syslog("No network found") - return Station.other_networks @@ -1233,7 +1371,7 @@

    Station

    -