Files
encoach_backend_new_v2/odoo/addons/iot_drivers/main.py
Yamen Ahmad 3e83d8d7d5 feat: add complete EnCoach backend — Odoo 19 + all addons
Includes:
- Odoo 19 framework (odoo/)
- 27 custom EnCoach addons (new_project/custom_addons/)
  - encoach_core, encoach_api, encoach_lms_api, encoach_adaptive_api
  - encoach_exam, encoach_taxonomy, encoach_adaptive, encoach_assignment
  - encoach_ai, encoach_ai_grading, encoach_ai_generation, encoach_ai_media
  - encoach_courseware, encoach_communication, encoach_subscription
  - encoach_notification, encoach_approval, encoach_branding
  - encoach_classroom, encoach_registration, encoach_stats
  - encoach_faq, encoach_ticket, encoach_training, encoach_resources
  - encoach_adaptive_ai, encoach_sis
- 21 OpenEduCat Enterprise modules (new_project/enterprise-19/)
- 14 OpenEduCat Community modules (new_project/openeducat_erp-19.0/)
- Configuration: odoo.conf, requirements.txt, scripts
- 200+ REST API endpoints with JWT authentication
- SRS and test documentation

Made-with: Cursor
2026-04-01 17:10:04 +04:00

183 lines
7.0 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
import requests
import schedule
import subprocess
from threading import Thread
import time
from odoo.addons.iot_drivers.tools import certificate, helpers, upgrade, wifi
from odoo.addons.iot_drivers.tools.system import IS_RPI
from odoo.addons.iot_drivers.websocket_client import WebsocketClient
if IS_RPI:
from dbus.mainloop.glib import DBusGMainLoop
DBusGMainLoop(set_as_default=True) # Must be started from main thread
_logger = logging.getLogger(__name__)
drivers = []
interfaces = {}
iot_devices = {}
unsupported_devices = {}
class Manager(Thread):
ws_channel = ""
def __init__(self):
super().__init__(daemon=True)
self.identifier = helpers.get_identifier()
self.domain = self._get_domain()
self.version = helpers.get_version(detailed_version=True)
self.previous_iot_devices = {}
self.previous_unsupported_devices = {}
def _get_domain(self):
"""
Get the iot box domain based on the IP address and subject.
"""
subject = helpers.get_conf('subject')
ip_addr = helpers.get_ip()
if subject and ip_addr:
return ip_addr.replace('.', '-') + subject.strip('*')
return ip_addr or '127.0.0.1'
def _get_changes_to_send(self):
"""
Check if the IoT Box information has changed since the last time it was sent.
Returns True if any tracked property has changed.
"""
changed = False
current_devices = set(iot_devices.keys()) | set(unsupported_devices.keys())
previous_devices = set(self.previous_iot_devices.keys()) | set(self.previous_unsupported_devices.keys())
if current_devices != previous_devices:
self.previous_iot_devices = iot_devices.copy()
self.previous_unsupported_devices = unsupported_devices.copy()
changed = True
# IP address change
new_domain = self._get_domain()
if self.domain != new_domain:
self.domain = new_domain
changed = True
# Version change
new_version = helpers.get_version(detailed_version=True)
if self.version != new_version:
self.version = new_version
changed = True
return changed
@helpers.require_db
def _send_all_devices(self, server_url=None):
"""This method send IoT Box and devices information to Odoo database
As the server can be down or not started yet (in case of local testing),
we retry to send the data several times with a delay between each attempt.
:param server_url: URL of the Odoo server (provided by decorator).
"""
iot_box = {
'identifier': self.identifier,
'mac': helpers.get_mac_address(),
'ip': self.domain,
'token': helpers.get_token(),
'version': self.version,
}
devices_list = {}
for device in self.previous_iot_devices.values():
identifier = device.device_identifier
devices_list[identifier] = {
'name': device.device_name,
'type': device.device_type,
'manufacturer': device.device_manufacturer,
'connection': device.device_connection,
'subtype': device.device_subtype if device.device_type == 'printer' else '',
}
devices_list.update(self.previous_unsupported_devices)
delay = .5
max_retries = 5
for attempt in range(1, max_retries + 1):
try:
response = requests.post(
server_url + "/iot/setup",
json={'params': {'iot_box': iot_box, 'devices': devices_list}},
timeout=5,
)
response.raise_for_status()
data = response.json()
self.ws_channel = data.get('result', '')
break # Success, exit the retry loop
except requests.exceptions.RequestException:
if attempt < max_retries:
_logger.warning(
'Could not reach configured server to send all IoT devices, retrying in %s seconds (%d/%d attempts)',
delay, attempt, max_retries, exc_info=True
)
time.sleep(delay)
else:
_logger.exception('Could not reach configured server to send all IoT devices after %d attempts.', max_retries)
except ValueError:
_logger.exception('Could not load JSON data: Received data is not valid JSON.\nContent:\n%s', response.content)
break
def run(self):
"""Thread that will load interfaces and drivers and contact the odoo server
with the updates. It will also reconnect to the Wi-Fi if the connection is lost.
"""
if IS_RPI:
# ensure that the root filesystem is writable retro compatibility (TODO: remove this in 19.0)
subprocess.run(["sudo", "mount", "-o", "remount,rw", "/"], check=False)
subprocess.run(["sudo", "mount", "-o", "remount,rw", "/root_bypass_ramdisks/"], check=False)
wifi.reconnect(helpers.get_conf('wifi_ssid'), helpers.get_conf('wifi_password'))
helpers.start_nginx_server()
_logger.info("IoT Box Image version: %s", helpers.get_version(detailed_version=True))
upgrade.check_git_branch()
if IS_RPI and helpers.get_odoo_server_url():
helpers.generate_password()
certificate.ensure_validity()
# We first add the IoT Box to the connected DB because IoT handlers cannot be downloaded if
# the identifier of the Box is not found in the DB. So add the Box to the DB.
self._send_all_devices()
helpers.download_iot_handlers()
helpers.load_iot_handlers()
for interface in interfaces.values():
interface().start()
# Set scheduled actions
schedule.every().day.at("00:00").do(certificate.ensure_validity)
schedule.every().day.at("00:00").do(helpers.reset_log_level)
schedule.every().day.at("00:00").do(upgrade.check_git_branch)
# Set up the websocket connection
ws_client = WebsocketClient(self.ws_channel)
if ws_client:
ws_client.start()
# Check every 3 seconds if the list of connected devices has changed and send the updated
# list to the connected DB.
while 1:
try:
if self._get_changes_to_send():
self._send_all_devices()
if IS_RPI and helpers.get_ip() != '10.11.12.1':
wifi.reconnect(helpers.get_conf('wifi_ssid'), helpers.get_conf('wifi_password'))
time.sleep(3)
schedule.run_pending()
except Exception:
# No matter what goes wrong, the Manager loop needs to keep running
_logger.exception("Manager loop unexpected error")
manager = Manager()
manager.start()