arthexis 0.1.13__py3-none-any.whl → 0.1.14__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of arthexis might be problematic. Click here for more details.

Files changed (107) hide show
  1. {arthexis-0.1.13.dist-info → arthexis-0.1.14.dist-info}/METADATA +222 -221
  2. arthexis-0.1.14.dist-info/RECORD +109 -0
  3. {arthexis-0.1.13.dist-info → arthexis-0.1.14.dist-info}/licenses/LICENSE +674 -674
  4. config/__init__.py +5 -5
  5. config/active_app.py +15 -15
  6. config/asgi.py +43 -43
  7. config/auth_app.py +7 -7
  8. config/celery.py +32 -32
  9. config/context_processors.py +67 -69
  10. config/horologia_app.py +7 -7
  11. config/loadenv.py +11 -11
  12. config/logging.py +59 -48
  13. config/middleware.py +25 -25
  14. config/offline.py +49 -49
  15. config/settings.py +691 -682
  16. config/settings_helpers.py +109 -109
  17. config/urls.py +171 -166
  18. config/wsgi.py +17 -17
  19. core/admin.py +3771 -2809
  20. core/admin_history.py +50 -50
  21. core/admindocs.py +151 -151
  22. core/apps.py +356 -272
  23. core/auto_upgrade.py +57 -57
  24. core/backends.py +265 -236
  25. core/changelog.py +342 -0
  26. core/entity.py +133 -133
  27. core/environment.py +61 -61
  28. core/fields.py +168 -168
  29. core/form_fields.py +75 -75
  30. core/github_helper.py +188 -25
  31. core/github_issues.py +178 -172
  32. core/github_repos.py +72 -0
  33. core/lcd_screen.py +78 -78
  34. core/liveupdate.py +25 -25
  35. core/log_paths.py +100 -100
  36. core/mailer.py +85 -85
  37. core/middleware.py +91 -91
  38. core/models.py +3609 -2795
  39. core/notifications.py +105 -105
  40. core/public_wifi.py +267 -227
  41. core/reference_utils.py +108 -108
  42. core/release.py +721 -368
  43. core/rfid_import_export.py +113 -0
  44. core/sigil_builder.py +149 -149
  45. core/sigil_context.py +20 -20
  46. core/sigil_resolver.py +315 -315
  47. core/system.py +752 -493
  48. core/tasks.py +408 -394
  49. core/temp_passwords.py +181 -181
  50. core/test_system_info.py +186 -139
  51. core/tests.py +2095 -1521
  52. core/tests_liveupdate.py +17 -17
  53. core/urls.py +11 -11
  54. core/user_data.py +641 -633
  55. core/views.py +2175 -1417
  56. core/widgets.py +213 -94
  57. core/workgroup_urls.py +17 -17
  58. core/workgroup_views.py +94 -94
  59. nodes/admin.py +1720 -1161
  60. nodes/apps.py +87 -85
  61. nodes/backends.py +160 -160
  62. nodes/dns.py +203 -203
  63. nodes/feature_checks.py +133 -133
  64. nodes/lcd.py +165 -165
  65. nodes/models.py +1737 -1597
  66. nodes/reports.py +411 -411
  67. nodes/rfid_sync.py +195 -0
  68. nodes/signals.py +18 -0
  69. nodes/tasks.py +46 -46
  70. nodes/tests.py +3810 -3116
  71. nodes/urls.py +15 -14
  72. nodes/utils.py +121 -105
  73. nodes/views.py +683 -619
  74. ocpp/admin.py +948 -948
  75. ocpp/apps.py +25 -25
  76. ocpp/consumers.py +1565 -1459
  77. ocpp/evcs.py +844 -844
  78. ocpp/evcs_discovery.py +158 -158
  79. ocpp/models.py +917 -917
  80. ocpp/reference_utils.py +42 -42
  81. ocpp/routing.py +11 -11
  82. ocpp/simulator.py +745 -745
  83. ocpp/status_display.py +26 -26
  84. ocpp/store.py +601 -541
  85. ocpp/tasks.py +31 -31
  86. ocpp/test_export_import.py +130 -130
  87. ocpp/test_rfid.py +913 -702
  88. ocpp/tests.py +4445 -4094
  89. ocpp/transactions_io.py +189 -189
  90. ocpp/urls.py +50 -50
  91. ocpp/views.py +1479 -1251
  92. pages/admin.py +708 -539
  93. pages/apps.py +10 -10
  94. pages/checks.py +40 -40
  95. pages/context_processors.py +127 -119
  96. pages/defaults.py +13 -13
  97. pages/forms.py +198 -198
  98. pages/middleware.py +205 -153
  99. pages/models.py +607 -426
  100. pages/tests.py +2612 -2200
  101. pages/urls.py +25 -25
  102. pages/utils.py +12 -12
  103. pages/views.py +1165 -1128
  104. arthexis-0.1.13.dist-info/RECORD +0 -105
  105. nodes/actions.py +0 -70
  106. {arthexis-0.1.13.dist-info → arthexis-0.1.14.dist-info}/WHEEL +0 -0
  107. {arthexis-0.1.13.dist-info → arthexis-0.1.14.dist-info}/top_level.txt +0 -0
nodes/lcd.py CHANGED
@@ -1,165 +1,165 @@
1
- """Minimal driver for PCF8574/PCF8574A I2C LCD1602 displays.
2
-
3
- The implementation is adapted from the example provided in the
4
- instructions. It is intentionally lightweight and only implements the
5
- operations required for this project: initialisation, clearing the
6
- screen and writing text to a specific position.
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- import subprocess
12
- import time
13
- from dataclasses import dataclass
14
-
15
- try: # pragma: no cover - hardware dependent
16
- import smbus # type: ignore
17
- except Exception: # pragma: no cover - missing dependency
18
- try: # pragma: no cover - hardware dependent
19
- import smbus2 as smbus # type: ignore
20
- except Exception: # pragma: no cover - missing dependency
21
- smbus = None # type: ignore
22
-
23
- SMBUS_HINT = (
24
- "smbus module not found. Enable the I2C interface and install the dependencies.\n"
25
- "For Debian/Ubuntu run: sudo apt-get install i2c-tools python3-smbus\n"
26
- "Within the virtualenv: pip install smbus2"
27
- )
28
-
29
-
30
- class LCDUnavailableError(RuntimeError):
31
- """Raised when the LCD cannot be initialised."""
32
-
33
-
34
- @dataclass
35
- class _BusWrapper:
36
- """Wrapper around :class:`smbus.SMBus` to allow mocking in tests."""
37
-
38
- channel: int
39
-
40
- def write_byte(
41
- self, addr: int, data: int
42
- ) -> None: # pragma: no cover - thin wrapper
43
- if smbus is None:
44
- raise LCDUnavailableError(SMBUS_HINT)
45
- bus = smbus.SMBus(self.channel)
46
- bus.write_byte(addr, data)
47
- bus.close()
48
-
49
-
50
- class CharLCD1602:
51
- """Minimal driver for PCF8574/PCF8574A I2C backpack (LCD1602)."""
52
-
53
- def __init__(self, bus: _BusWrapper | None = None) -> None:
54
- if smbus is None: # pragma: no cover - hardware dependent
55
- raise LCDUnavailableError(SMBUS_HINT)
56
- self.bus = bus or _BusWrapper(1)
57
- self.BLEN = 1
58
- self.PCF8574_address = 0x27
59
- self.PCF8574A_address = 0x3F
60
- self.LCD_ADDR = self.PCF8574_address
61
-
62
- def _write_word(self, addr: int, data: int) -> None:
63
- if self.BLEN:
64
- data |= 0x08
65
- else:
66
- data &= 0xF7
67
- self.bus.write_byte(addr, data)
68
-
69
- def _pulse_enable(self, data: int) -> None:
70
- self._write_word(self.LCD_ADDR, data | 0x04)
71
- time.sleep(0.0005)
72
- self._write_word(self.LCD_ADDR, data & ~0x04)
73
- time.sleep(0.0001)
74
-
75
- def send_command(self, cmd: int) -> None:
76
- high = cmd & 0xF0
77
- low = (cmd << 4) & 0xF0
78
- self._write_word(self.LCD_ADDR, high)
79
- self._pulse_enable(high)
80
- self._write_word(self.LCD_ADDR, low)
81
- self._pulse_enable(low)
82
- # Give the LCD time to process the command to avoid garbled output.
83
- time.sleep(0.001)
84
-
85
- def send_data(self, data: int) -> None:
86
- high = (data & 0xF0) | 0x01
87
- low = ((data << 4) & 0xF0) | 0x01
88
- self._write_word(self.LCD_ADDR, high)
89
- self._pulse_enable(high)
90
- self._write_word(self.LCD_ADDR, low)
91
- self._pulse_enable(low)
92
- # Allow the LCD controller to catch up between data writes.
93
- time.sleep(0.001)
94
-
95
- def i2c_scan(self) -> list[str]: # pragma: no cover - requires hardware
96
- """Return a list of detected I2C addresses.
97
-
98
- The implementation relies on the external ``i2cdetect`` command. On
99
- systems where ``i2c-tools`` is not installed or the command cannot be
100
- executed (e.g. insufficient permissions), the function returns an empty
101
- list so callers can fall back to a sensible default address.
102
- """
103
-
104
- try:
105
- output = subprocess.check_output(["i2cdetect", "-y", "1"], text=True)
106
- except Exception: # pragma: no cover - depends on environment
107
- return []
108
-
109
- addresses: list[str] = []
110
- for line in output.splitlines()[1:]:
111
- parts = line.split()
112
- for token in parts[1:]:
113
- if token != "--":
114
- addresses.append(token)
115
- return addresses
116
-
117
- def init_lcd(self, addr: int | None = None, bl: int = 1) -> None:
118
- self.BLEN = 1 if bl else 0
119
- if addr is None:
120
- try:
121
- found = self.i2c_scan()
122
- except Exception: # pragma: no cover - i2c detection issues
123
- found = []
124
- if "3f" in found or "3F" in found:
125
- self.LCD_ADDR = self.PCF8574A_address
126
- else:
127
- # Default to the common PCF8574 address (0x27) when detection
128
- # fails or returns no recognised addresses. This mirrors the
129
- # behaviour prior to introducing automatic address detection and
130
- # prevents the display from remaining uninitialised on systems
131
- # without ``i2c-tools``.
132
- self.LCD_ADDR = self.PCF8574_address
133
- else:
134
- self.LCD_ADDR = addr
135
-
136
- time.sleep(0.05)
137
- self.send_command(0x33)
138
- self.send_command(0x32)
139
- self.send_command(0x28)
140
- self.send_command(0x0C)
141
- self.send_command(0x06)
142
- self.clear()
143
- self._write_word(self.LCD_ADDR, 0x00)
144
-
145
- def clear(self) -> None:
146
- self.send_command(0x01)
147
- time.sleep(0.002)
148
-
149
- def reset(self) -> None:
150
- """Re-run the initialisation sequence to recover the display."""
151
- self.init_lcd(addr=self.LCD_ADDR, bl=self.BLEN)
152
-
153
- def set_backlight(
154
- self, on: bool = True
155
- ) -> None: # pragma: no cover - hardware dependent
156
- self.BLEN = 1 if on else 0
157
- self._write_word(self.LCD_ADDR, 0x00)
158
-
159
- def write(self, x: int, y: int, s: str) -> None:
160
- x = max(0, min(15, int(x)))
161
- y = 0 if int(y) <= 0 else 1
162
- addr = 0x80 + 0x40 * y + x
163
- self.send_command(addr)
164
- for ch in str(s):
165
- self.send_data(ord(ch))
1
+ """Minimal driver for PCF8574/PCF8574A I2C LCD1602 displays.
2
+
3
+ The implementation is adapted from the example provided in the
4
+ instructions. It is intentionally lightweight and only implements the
5
+ operations required for this project: initialisation, clearing the
6
+ screen and writing text to a specific position.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import subprocess
12
+ import time
13
+ from dataclasses import dataclass
14
+
15
+ try: # pragma: no cover - hardware dependent
16
+ import smbus # type: ignore
17
+ except Exception: # pragma: no cover - missing dependency
18
+ try: # pragma: no cover - hardware dependent
19
+ import smbus2 as smbus # type: ignore
20
+ except Exception: # pragma: no cover - missing dependency
21
+ smbus = None # type: ignore
22
+
23
+ SMBUS_HINT = (
24
+ "smbus module not found. Enable the I2C interface and install the dependencies.\n"
25
+ "For Debian/Ubuntu run: sudo apt-get install i2c-tools python3-smbus\n"
26
+ "Within the virtualenv: pip install smbus2"
27
+ )
28
+
29
+
30
+ class LCDUnavailableError(RuntimeError):
31
+ """Raised when the LCD cannot be initialised."""
32
+
33
+
34
+ @dataclass
35
+ class _BusWrapper:
36
+ """Wrapper around :class:`smbus.SMBus` to allow mocking in tests."""
37
+
38
+ channel: int
39
+
40
+ def write_byte(
41
+ self, addr: int, data: int
42
+ ) -> None: # pragma: no cover - thin wrapper
43
+ if smbus is None:
44
+ raise LCDUnavailableError(SMBUS_HINT)
45
+ bus = smbus.SMBus(self.channel)
46
+ bus.write_byte(addr, data)
47
+ bus.close()
48
+
49
+
50
+ class CharLCD1602:
51
+ """Minimal driver for PCF8574/PCF8574A I2C backpack (LCD1602)."""
52
+
53
+ def __init__(self, bus: _BusWrapper | None = None) -> None:
54
+ if smbus is None: # pragma: no cover - hardware dependent
55
+ raise LCDUnavailableError(SMBUS_HINT)
56
+ self.bus = bus or _BusWrapper(1)
57
+ self.BLEN = 1
58
+ self.PCF8574_address = 0x27
59
+ self.PCF8574A_address = 0x3F
60
+ self.LCD_ADDR = self.PCF8574_address
61
+
62
+ def _write_word(self, addr: int, data: int) -> None:
63
+ if self.BLEN:
64
+ data |= 0x08
65
+ else:
66
+ data &= 0xF7
67
+ self.bus.write_byte(addr, data)
68
+
69
+ def _pulse_enable(self, data: int) -> None:
70
+ self._write_word(self.LCD_ADDR, data | 0x04)
71
+ time.sleep(0.0005)
72
+ self._write_word(self.LCD_ADDR, data & ~0x04)
73
+ time.sleep(0.0001)
74
+
75
+ def send_command(self, cmd: int) -> None:
76
+ high = cmd & 0xF0
77
+ low = (cmd << 4) & 0xF0
78
+ self._write_word(self.LCD_ADDR, high)
79
+ self._pulse_enable(high)
80
+ self._write_word(self.LCD_ADDR, low)
81
+ self._pulse_enable(low)
82
+ # Give the LCD time to process the command to avoid garbled output.
83
+ time.sleep(0.001)
84
+
85
+ def send_data(self, data: int) -> None:
86
+ high = (data & 0xF0) | 0x01
87
+ low = ((data << 4) & 0xF0) | 0x01
88
+ self._write_word(self.LCD_ADDR, high)
89
+ self._pulse_enable(high)
90
+ self._write_word(self.LCD_ADDR, low)
91
+ self._pulse_enable(low)
92
+ # Allow the LCD controller to catch up between data writes.
93
+ time.sleep(0.001)
94
+
95
+ def i2c_scan(self) -> list[str]: # pragma: no cover - requires hardware
96
+ """Return a list of detected I2C addresses.
97
+
98
+ The implementation relies on the external ``i2cdetect`` command. On
99
+ systems where ``i2c-tools`` is not installed or the command cannot be
100
+ executed (e.g. insufficient permissions), the function returns an empty
101
+ list so callers can fall back to a sensible default address.
102
+ """
103
+
104
+ try:
105
+ output = subprocess.check_output(["i2cdetect", "-y", "1"], text=True)
106
+ except Exception: # pragma: no cover - depends on environment
107
+ return []
108
+
109
+ addresses: list[str] = []
110
+ for line in output.splitlines()[1:]:
111
+ parts = line.split()
112
+ for token in parts[1:]:
113
+ if token != "--":
114
+ addresses.append(token)
115
+ return addresses
116
+
117
+ def init_lcd(self, addr: int | None = None, bl: int = 1) -> None:
118
+ self.BLEN = 1 if bl else 0
119
+ if addr is None:
120
+ try:
121
+ found = self.i2c_scan()
122
+ except Exception: # pragma: no cover - i2c detection issues
123
+ found = []
124
+ if "3f" in found or "3F" in found:
125
+ self.LCD_ADDR = self.PCF8574A_address
126
+ else:
127
+ # Default to the common PCF8574 address (0x27) when detection
128
+ # fails or returns no recognised addresses. This mirrors the
129
+ # behaviour prior to introducing automatic address detection and
130
+ # prevents the display from remaining uninitialised on systems
131
+ # without ``i2c-tools``.
132
+ self.LCD_ADDR = self.PCF8574_address
133
+ else:
134
+ self.LCD_ADDR = addr
135
+
136
+ time.sleep(0.05)
137
+ self.send_command(0x33)
138
+ self.send_command(0x32)
139
+ self.send_command(0x28)
140
+ self.send_command(0x0C)
141
+ self.send_command(0x06)
142
+ self.clear()
143
+ self._write_word(self.LCD_ADDR, 0x00)
144
+
145
+ def clear(self) -> None:
146
+ self.send_command(0x01)
147
+ time.sleep(0.002)
148
+
149
+ def reset(self) -> None:
150
+ """Re-run the initialisation sequence to recover the display."""
151
+ self.init_lcd(addr=self.LCD_ADDR, bl=self.BLEN)
152
+
153
+ def set_backlight(
154
+ self, on: bool = True
155
+ ) -> None: # pragma: no cover - hardware dependent
156
+ self.BLEN = 1 if on else 0
157
+ self._write_word(self.LCD_ADDR, 0x00)
158
+
159
+ def write(self, x: int, y: int, s: str) -> None:
160
+ x = max(0, min(15, int(x)))
161
+ y = 0 if int(y) <= 0 else 1
162
+ addr = 0x80 + 0x40 * y + x
163
+ self.send_command(addr)
164
+ for ch in str(s):
165
+ self.send_data(ord(ch))