Rewrite logging function to log to seq and console; Add function to change SQL driver depends on os; Add requirements.exe
This commit is contained in:
parent
fc3aeb9a14
commit
7447dd8e43
@ -1,38 +1,29 @@
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import socket
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List, Tuple
|
||||
|
||||
import pyodbc
|
||||
import seqlog
|
||||
import snap7
|
||||
from dotenv import load_dotenv
|
||||
from snap7.util.getters import get_lreal, get_ulint
|
||||
|
||||
load_dotenv()
|
||||
|
||||
# Determine the correct driver based on OS
|
||||
SQL_DRIVER = "ODBC Driver 18 for SQL Server" if platform.system() == "Linux" else "SQL Server"
|
||||
# Configuration
|
||||
CONN_STR = (
|
||||
f"Driver={{SQL Server}};"
|
||||
f"Driver={{{SQL_DRIVER}}};"
|
||||
f"Server={os.getenv('DB_SERVER')};"
|
||||
f"Database={os.getenv('DB_NAME')};"
|
||||
f"UID={os.getenv('DB_USER')};"
|
||||
f"PWD={os.getenv('DB_PASSWORD')};"
|
||||
"TrustServerCertificate=yes;"
|
||||
)
|
||||
|
||||
# Logger setup
|
||||
root_logger = logging.getLogger()
|
||||
# Logger setup
|
||||
seq_logger = seqlog.log_to_seq(
|
||||
server_url=os.getenv("SEQ_URL"),
|
||||
api_key=os.getenv("SEQ_API_KEY"),
|
||||
level=logging.INFO,
|
||||
support_extra_properties=True,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class PlcConfig:
|
||||
"""
|
||||
@ -53,10 +44,13 @@ class PlcConfig:
|
||||
id: int
|
||||
ip: str
|
||||
db_number: int
|
||||
is_enabled: bool
|
||||
air_offset: int
|
||||
energy_offset: int
|
||||
runstatus_offset: int
|
||||
is_enabled: bool
|
||||
location: str
|
||||
last_energy_read: float
|
||||
last_air_read: float
|
||||
runstop_status_offset: int
|
||||
|
||||
|
||||
@dataclass
|
||||
@ -79,7 +73,7 @@ class DatabaseManager:
|
||||
with pyodbc.connect(self.conn_str) as conn:
|
||||
with conn.cursor() as cursor:
|
||||
cursor.execute("""
|
||||
SELECT Id, Ip, DbNumber, AirDbOffset, EnergyDbOffset, RunStatusDbOffset,IsEnable
|
||||
SELECT Id, Ip, DbNumber, IsEnable, AirDbOffset, EnergyDbOffset, Location, LastEnergyRead, LastAirRead, RunStopStatusDbOffset
|
||||
FROM sch.Plc
|
||||
WHERE IsEnable = 1
|
||||
""")
|
||||
@ -145,9 +139,9 @@ class PlcManager:
|
||||
try:
|
||||
plc.connect(config.ip, 0, 1)
|
||||
db_data = plc.db_read(
|
||||
config.db_number, config.air_offset, config.energy_offset + 10
|
||||
config.db_number, 0, config.runstop_status_offset
|
||||
) # Read up to energy offset + 8 bytes
|
||||
air_value = get_ulint(db_data, config.air_offset)
|
||||
air_value = get_lreal(db_data, config.air_offset)
|
||||
# TODO if (air_value < prev_air_val): update in plc
|
||||
energy_value = get_lreal(db_data, config.energy_offset)
|
||||
stauts_value = getattr(db_data, config.runstatus_offset)
|
||||
|
37
logger_setup.py
Normal file
37
logger_setup.py
Normal file
@ -0,0 +1,37 @@
|
||||
import logging
|
||||
import os
|
||||
import json
|
||||
import seqlog
|
||||
|
||||
|
||||
class CustomFormatter(logging.Formatter):
|
||||
def format(self, record):
|
||||
if hasattr(record, "extra"):
|
||||
extra_formatted = json.dumps(record.extra, indent=2)
|
||||
return f"{self.formatTime(record)} [{record.levelname}] {record.getMessage()}\nExtra Data: {extra_formatted}"
|
||||
return f"{self.formatTime(record)} [{record.levelname}] {record.getMessage()}"
|
||||
|
||||
|
||||
def setup_logger():
|
||||
logger = logging.getLogger("IndustrialEnergyTracker")
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
# Console handler with custom formatting
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setFormatter(CustomFormatter())
|
||||
logger.addHandler(console_handler)
|
||||
|
||||
# SEQ logging if configured with optimized batch settings
|
||||
seq_url = os.getenv("SEQ_URL")
|
||||
seq_api_key = os.getenv("SEQ_API_KEY")
|
||||
|
||||
if seq_url and seq_api_key:
|
||||
seqlog.log_to_seq(
|
||||
server_url=seq_url,
|
||||
api_key=seq_api_key,
|
||||
level=logging.DEBUG,
|
||||
batch_size=50, # Increased batch size
|
||||
auto_flush_timeout=10, # Increased flush timeout to 10 seconds
|
||||
)
|
||||
|
||||
return logger
|
60
main.py
60
main.py
@ -1,21 +1,44 @@
|
||||
import os
|
||||
import time
|
||||
from datetime import datetime
|
||||
import pprint
|
||||
|
||||
from energy_monitor import CONN_STR, DatabaseManager, PlcManager, root_logger
|
||||
from energy_monitor import CONN_STR, DatabaseManager, PlcManager
|
||||
from logger_setup import setup_logger
|
||||
|
||||
# Globals
|
||||
DEVICE_DELAY = 1 # Delay between device data fetches
|
||||
POOL_RATE = 2 # 2s polling rate reading of NextRead value from database
|
||||
FALLBACK_INTERVAL = 3600 # 1h interval in case db read issue
|
||||
FALLBACK_INTERVAL = 20 # 1h = 3600s interval in case db read issue
|
||||
|
||||
|
||||
# Logger setup
|
||||
logger = setup_logger()
|
||||
|
||||
|
||||
def process_plc_devices(db_manager: DatabaseManager, plc_manager: PlcManager):
|
||||
plc_configs = db_manager.get_plc_configs()
|
||||
|
||||
# If debug print out plc_configs to logs
|
||||
logger.debug(
|
||||
"Retrieved PLC configurations",
|
||||
extra={
|
||||
"plc_configs": [
|
||||
{
|
||||
"id": plc.id,
|
||||
"ip": plc.ip,
|
||||
"location": plc.location,
|
||||
"is_enabled": plc.is_enabled,
|
||||
"db_number": plc.db_number,
|
||||
}
|
||||
for plc in plc_configs
|
||||
],
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
},
|
||||
)
|
||||
for plc_config in plc_configs:
|
||||
try:
|
||||
if not plc_manager.check_connection(plc_config.ip):
|
||||
root_logger.error(
|
||||
logger.error(
|
||||
"PLC connection failed",
|
||||
extra={
|
||||
"PlcId": plc_config.id,
|
||||
@ -28,7 +51,7 @@ def process_plc_devices(db_manager: DatabaseManager, plc_manager: PlcManager):
|
||||
air_value, energy_value = plc_manager.read_plc_data(plc_config)
|
||||
db_manager.save_energy_data(plc_config.id, energy_value, air_value, True)
|
||||
|
||||
root_logger.info(
|
||||
logger.info(
|
||||
"✅ Data successfully read and saved",
|
||||
extra={
|
||||
"PlcId": plc_config.id,
|
||||
@ -42,7 +65,7 @@ def process_plc_devices(db_manager: DatabaseManager, plc_manager: PlcManager):
|
||||
time.sleep(DEVICE_DELAY) # n-second delay between devices
|
||||
|
||||
except Exception as e:
|
||||
root_logger.error(
|
||||
logger.error(
|
||||
"Error processing PLC",
|
||||
extra={
|
||||
"PlcId": plc_config.id,
|
||||
@ -54,6 +77,23 @@ def process_plc_devices(db_manager: DatabaseManager, plc_manager: PlcManager):
|
||||
|
||||
|
||||
def main():
|
||||
pprint.pp(
|
||||
{
|
||||
"SQL_Config": {
|
||||
"Server": os.getenv("DB_SERVER"),
|
||||
"Database": os.getenv("DB_NAME"),
|
||||
"User": os.getenv("DB_USER"),
|
||||
# Masking password for security
|
||||
"Password": "****",
|
||||
},
|
||||
"SEQ_Config": {
|
||||
"URL": os.getenv("SEQ_URL"),
|
||||
# Masking API key for security
|
||||
"API_Key": "****",
|
||||
},
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
}
|
||||
)
|
||||
db_manager = DatabaseManager(CONN_STR)
|
||||
plc_manager = PlcManager()
|
||||
|
||||
@ -68,7 +108,13 @@ def main():
|
||||
time.sleep(POOL_RATE)
|
||||
|
||||
except Exception as e:
|
||||
root_logger.error(
|
||||
pprint.pp(
|
||||
{
|
||||
"error": str(e),
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
}
|
||||
)
|
||||
logger.error(
|
||||
"Main loop error! Falling back to 1h interval.",
|
||||
extra={"error": str(e), "timestamp": datetime.now().isoformat()},
|
||||
)
|
||||
|
14
requirements.txt
Normal file
14
requirements.txt
Normal file
@ -0,0 +1,14 @@
|
||||
# requirements.txt
|
||||
|
||||
certifi==2024.8.30
|
||||
charset-normalizer==3.4.0
|
||||
idna==3.10
|
||||
pyodbc==5.2.0
|
||||
python-dateutil==2.9.0.post0
|
||||
python-dotenv==1.0.1
|
||||
python-snap7==2.0.2
|
||||
PyYAML==6.0.2
|
||||
requests==2.32.3
|
||||
seqlog==0.3.31
|
||||
six==1.16.0
|
||||
urllib3==2.2.3
|
Loading…
x
Reference in New Issue
Block a user