fixed FTP port support to NL43 configuration and client

This commit is contained in:
serversdwn
2026-01-14 01:44:53 +00:00
parent 2cb96a7a1c
commit 3d445daf1f
4 changed files with 113 additions and 47 deletions

View File

@@ -11,9 +11,10 @@ class NL43Config(Base):
unit_id = Column(String, primary_key=True, index=True) unit_id = Column(String, primary_key=True, index=True)
host = Column(String, default="127.0.0.1") host = Column(String, default="127.0.0.1")
tcp_port = Column(Integer, default=80) # NL43 TCP control port (via RX55) tcp_port = Column(Integer, default=2255) # NL43 TCP control port (standard: 2255)
tcp_enabled = Column(Boolean, default=True) tcp_enabled = Column(Boolean, default=True)
ftp_enabled = Column(Boolean, default=False) ftp_enabled = Column(Boolean, default=False)
ftp_port = Column(Integer, default=21) # FTP port (standard: 21)
ftp_username = Column(String, nullable=True) # FTP login username ftp_username = Column(String, nullable=True) # FTP login username
ftp_password = Column(String, nullable=True) # FTP login password ftp_password = Column(String, nullable=True) # FTP login password
web_enabled = Column(Boolean, default=False) web_enabled = Column(Boolean, default=False)

View File

@@ -21,6 +21,7 @@ router = APIRouter(prefix="/api/nl43", tags=["nl43"])
class ConfigPayload(BaseModel): class ConfigPayload(BaseModel):
host: str | None = None host: str | None = None
tcp_port: int | None = None tcp_port: int | None = None
ftp_port: int | None = None
tcp_enabled: bool | None = None tcp_enabled: bool | None = None
ftp_enabled: bool | None = None ftp_enabled: bool | None = None
ftp_username: str | None = None ftp_username: str | None = None
@@ -44,7 +45,7 @@ class ConfigPayload(BaseModel):
raise ValueError("Host must be a valid IP address or hostname") raise ValueError("Host must be a valid IP address or hostname")
return v return v
@field_validator("tcp_port") @field_validator("tcp_port", "ftp_port")
@classmethod @classmethod
def validate_port(cls, v): def validate_port(cls, v):
if v is None: if v is None:
@@ -65,8 +66,11 @@ def get_config(unit_id: str, db: Session = Depends(get_db)):
"unit_id": unit_id, "unit_id": unit_id,
"host": cfg.host, "host": cfg.host,
"tcp_port": cfg.tcp_port, "tcp_port": cfg.tcp_port,
"ftp_port": cfg.ftp_port,
"tcp_enabled": cfg.tcp_enabled, "tcp_enabled": cfg.tcp_enabled,
"ftp_enabled": cfg.ftp_enabled, "ftp_enabled": cfg.ftp_enabled,
"ftp_username": cfg.ftp_username,
"ftp_password": cfg.ftp_password,
"web_enabled": cfg.web_enabled, "web_enabled": cfg.web_enabled,
}, },
} }
@@ -83,6 +87,8 @@ def upsert_config(unit_id: str, payload: ConfigPayload, db: Session = Depends(ge
cfg.host = payload.host cfg.host = payload.host
if payload.tcp_port is not None: if payload.tcp_port is not None:
cfg.tcp_port = payload.tcp_port cfg.tcp_port = payload.tcp_port
if payload.ftp_port is not None:
cfg.ftp_port = payload.ftp_port
if payload.tcp_enabled is not None: if payload.tcp_enabled is not None:
cfg.tcp_enabled = payload.tcp_enabled cfg.tcp_enabled = payload.tcp_enabled
if payload.ftp_enabled is not None: if payload.ftp_enabled is not None:
@@ -192,7 +198,7 @@ async def start_measurement(unit_id: str, db: Session = Depends(get_db)):
if not cfg.tcp_enabled: if not cfg.tcp_enabled:
raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") raise HTTPException(status_code=403, detail="TCP communication is disabled for this device")
client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password, ftp_port=cfg.ftp_port or 21)
try: try:
await client.start() await client.start()
logger.info(f"Started measurement on unit {unit_id}") logger.info(f"Started measurement on unit {unit_id}")
@@ -235,7 +241,7 @@ async def stop_measurement(unit_id: str, db: Session = Depends(get_db)):
if not cfg.tcp_enabled: if not cfg.tcp_enabled:
raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") raise HTTPException(status_code=403, detail="TCP communication is disabled for this device")
client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password, ftp_port=cfg.ftp_port or 21)
try: try:
await client.stop() await client.stop()
logger.info(f"Stopped measurement on unit {unit_id}") logger.info(f"Stopped measurement on unit {unit_id}")
@@ -267,7 +273,7 @@ async def manual_store(unit_id: str, db: Session = Depends(get_db)):
if not cfg.tcp_enabled: if not cfg.tcp_enabled:
raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") raise HTTPException(status_code=403, detail="TCP communication is disabled for this device")
client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password, ftp_port=cfg.ftp_port or 21)
try: try:
await client.manual_store() await client.manual_store()
logger.info(f"Manual store executed on unit {unit_id}") logger.info(f"Manual store executed on unit {unit_id}")
@@ -293,7 +299,7 @@ async def pause_measurement(unit_id: str, db: Session = Depends(get_db)):
if not cfg.tcp_enabled: if not cfg.tcp_enabled:
raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") raise HTTPException(status_code=403, detail="TCP communication is disabled for this device")
client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password, ftp_port=cfg.ftp_port or 21)
try: try:
await client.pause() await client.pause()
logger.info(f"Paused measurement on unit {unit_id}") logger.info(f"Paused measurement on unit {unit_id}")
@@ -313,7 +319,7 @@ async def resume_measurement(unit_id: str, db: Session = Depends(get_db)):
if not cfg.tcp_enabled: if not cfg.tcp_enabled:
raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") raise HTTPException(status_code=403, detail="TCP communication is disabled for this device")
client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password, ftp_port=cfg.ftp_port or 21)
try: try:
await client.resume() await client.resume()
logger.info(f"Resumed measurement on unit {unit_id}") logger.info(f"Resumed measurement on unit {unit_id}")
@@ -333,7 +339,7 @@ async def reset_measurement(unit_id: str, db: Session = Depends(get_db)):
if not cfg.tcp_enabled: if not cfg.tcp_enabled:
raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") raise HTTPException(status_code=403, detail="TCP communication is disabled for this device")
client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password, ftp_port=cfg.ftp_port or 21)
try: try:
await client.reset() await client.reset()
logger.info(f"Reset measurement data on unit {unit_id}") logger.info(f"Reset measurement data on unit {unit_id}")
@@ -353,7 +359,7 @@ async def get_measurement_state(unit_id: str, db: Session = Depends(get_db)):
if not cfg.tcp_enabled: if not cfg.tcp_enabled:
raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") raise HTTPException(status_code=403, detail="TCP communication is disabled for this device")
client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password, ftp_port=cfg.ftp_port or 21)
try: try:
state = await client.get_measurement_state() state = await client.get_measurement_state()
is_measuring = state == "Start" is_measuring = state == "Start"
@@ -377,7 +383,7 @@ async def sleep_device(unit_id: str, db: Session = Depends(get_db)):
if not cfg.tcp_enabled: if not cfg.tcp_enabled:
raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") raise HTTPException(status_code=403, detail="TCP communication is disabled for this device")
client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password, ftp_port=cfg.ftp_port or 21)
try: try:
await client.sleep() await client.sleep()
logger.info(f"Put device {unit_id} to sleep") logger.info(f"Put device {unit_id} to sleep")
@@ -397,7 +403,7 @@ async def wake_device(unit_id: str, db: Session = Depends(get_db)):
if not cfg.tcp_enabled: if not cfg.tcp_enabled:
raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") raise HTTPException(status_code=403, detail="TCP communication is disabled for this device")
client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password, ftp_port=cfg.ftp_port or 21)
try: try:
await client.wake() await client.wake()
logger.info(f"Woke device {unit_id} from sleep") logger.info(f"Woke device {unit_id} from sleep")
@@ -417,7 +423,7 @@ async def get_sleep_status(unit_id: str, db: Session = Depends(get_db)):
if not cfg.tcp_enabled: if not cfg.tcp_enabled:
raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") raise HTTPException(status_code=403, detail="TCP communication is disabled for this device")
client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password, ftp_port=cfg.ftp_port or 21)
try: try:
status = await client.get_sleep_status() status = await client.get_sleep_status()
return {"status": "ok", "sleep_status": status} return {"status": "ok", "sleep_status": status}
@@ -436,7 +442,7 @@ async def get_battery(unit_id: str, db: Session = Depends(get_db)):
if not cfg.tcp_enabled: if not cfg.tcp_enabled:
raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") raise HTTPException(status_code=403, detail="TCP communication is disabled for this device")
client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password, ftp_port=cfg.ftp_port or 21)
try: try:
level = await client.get_battery_level() level = await client.get_battery_level()
return {"status": "ok", "battery_level": level} return {"status": "ok", "battery_level": level}
@@ -455,7 +461,7 @@ async def get_clock(unit_id: str, db: Session = Depends(get_db)):
if not cfg.tcp_enabled: if not cfg.tcp_enabled:
raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") raise HTTPException(status_code=403, detail="TCP communication is disabled for this device")
client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password, ftp_port=cfg.ftp_port or 21)
try: try:
clock = await client.get_clock() clock = await client.get_clock()
return {"status": "ok", "clock": clock} return {"status": "ok", "clock": clock}
@@ -478,7 +484,7 @@ async def set_clock(unit_id: str, payload: ClockPayload, db: Session = Depends(g
if not cfg.tcp_enabled: if not cfg.tcp_enabled:
raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") raise HTTPException(status_code=403, detail="TCP communication is disabled for this device")
client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password, ftp_port=cfg.ftp_port or 21)
try: try:
await client.set_clock(payload.datetime) await client.set_clock(payload.datetime)
return {"status": "ok", "message": f"Clock set to {payload.datetime}"} return {"status": "ok", "message": f"Clock set to {payload.datetime}"}
@@ -502,7 +508,7 @@ async def get_frequency_weighting(unit_id: str, channel: str = "Main", db: Sessi
if not cfg.tcp_enabled: if not cfg.tcp_enabled:
raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") raise HTTPException(status_code=403, detail="TCP communication is disabled for this device")
client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password, ftp_port=cfg.ftp_port or 21)
try: try:
weighting = await client.get_frequency_weighting(channel) weighting = await client.get_frequency_weighting(channel)
return {"status": "ok", "frequency_weighting": weighting, "channel": channel} return {"status": "ok", "frequency_weighting": weighting, "channel": channel}
@@ -521,7 +527,7 @@ async def set_frequency_weighting(unit_id: str, payload: WeightingPayload, db: S
if not cfg.tcp_enabled: if not cfg.tcp_enabled:
raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") raise HTTPException(status_code=403, detail="TCP communication is disabled for this device")
client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password, ftp_port=cfg.ftp_port or 21)
try: try:
await client.set_frequency_weighting(payload.weighting, payload.channel) await client.set_frequency_weighting(payload.weighting, payload.channel)
return {"status": "ok", "message": f"Frequency weighting set to {payload.weighting} on {payload.channel}"} return {"status": "ok", "message": f"Frequency weighting set to {payload.weighting} on {payload.channel}"}
@@ -540,7 +546,7 @@ async def get_time_weighting(unit_id: str, channel: str = "Main", db: Session =
if not cfg.tcp_enabled: if not cfg.tcp_enabled:
raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") raise HTTPException(status_code=403, detail="TCP communication is disabled for this device")
client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password, ftp_port=cfg.ftp_port or 21)
try: try:
weighting = await client.get_time_weighting(channel) weighting = await client.get_time_weighting(channel)
return {"status": "ok", "time_weighting": weighting, "channel": channel} return {"status": "ok", "time_weighting": weighting, "channel": channel}
@@ -559,7 +565,7 @@ async def set_time_weighting(unit_id: str, payload: WeightingPayload, db: Sessio
if not cfg.tcp_enabled: if not cfg.tcp_enabled:
raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") raise HTTPException(status_code=403, detail="TCP communication is disabled for this device")
client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password, ftp_port=cfg.ftp_port or 21)
try: try:
await client.set_time_weighting(payload.weighting, payload.channel) await client.set_time_weighting(payload.weighting, payload.channel)
return {"status": "ok", "message": f"Time weighting set to {payload.weighting} on {payload.channel}"} return {"status": "ok", "message": f"Time weighting set to {payload.weighting} on {payload.channel}"}
@@ -577,7 +583,7 @@ async def live_status(unit_id: str, db: Session = Depends(get_db)):
if not cfg.tcp_enabled: if not cfg.tcp_enabled:
raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") raise HTTPException(status_code=403, detail="TCP communication is disabled for this device")
client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password, ftp_port=cfg.ftp_port or 21)
try: try:
snap = await client.request_dod() snap = await client.request_dod()
snap.unit_id = unit_id snap.unit_id = unit_id
@@ -622,7 +628,7 @@ async def get_results(unit_id: str, db: Session = Depends(get_db)):
if not cfg.tcp_enabled: if not cfg.tcp_enabled:
raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") raise HTTPException(status_code=403, detail="TCP communication is disabled for this device")
client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password, ftp_port=cfg.ftp_port or 21)
try: try:
results = await client.request_dlc() results = await client.request_dlc()
logger.info(f"Retrieved measurement results for unit {unit_id}") logger.info(f"Retrieved measurement results for unit {unit_id}")
@@ -669,7 +675,7 @@ async def stream_live(websocket: WebSocket, unit_id: str):
return return
# Create client and define callback # Create client and define callback
client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password, ftp_port=cfg.ftp_port or 21)
async def send_snapshot(snap): async def send_snapshot(snap):
"""Callback that sends each snapshot to the WebSocket client.""" """Callback that sends each snapshot to the WebSocket client."""
@@ -746,7 +752,7 @@ async def enable_ftp(unit_id: str, db: Session = Depends(get_db)):
if not cfg.tcp_enabled: if not cfg.tcp_enabled:
raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") raise HTTPException(status_code=403, detail="TCP communication is disabled for this device")
client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password, ftp_port=cfg.ftp_port or 21)
try: try:
await client.enable_ftp() await client.enable_ftp()
logger.info(f"Enabled FTP on unit {unit_id}") logger.info(f"Enabled FTP on unit {unit_id}")
@@ -766,7 +772,7 @@ async def disable_ftp(unit_id: str, db: Session = Depends(get_db)):
if not cfg.tcp_enabled: if not cfg.tcp_enabled:
raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") raise HTTPException(status_code=403, detail="TCP communication is disabled for this device")
client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password, ftp_port=cfg.ftp_port or 21)
try: try:
await client.disable_ftp() await client.disable_ftp()
logger.info(f"Disabled FTP on unit {unit_id}") logger.info(f"Disabled FTP on unit {unit_id}")
@@ -786,7 +792,7 @@ async def get_ftp_status(unit_id: str, db: Session = Depends(get_db)):
if not cfg.tcp_enabled: if not cfg.tcp_enabled:
raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") raise HTTPException(status_code=403, detail="TCP communication is disabled for this device")
client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password, ftp_port=cfg.ftp_port or 21)
try: try:
status = await client.get_ftp_status() status = await client.get_ftp_status()
return {"status": "ok", "ftp_enabled": status.lower() == "on", "ftp_status": status} return {"status": "ok", "ftp_enabled": status.lower() == "on", "ftp_status": status}
@@ -810,7 +816,7 @@ async def get_latest_measurement_time(unit_id: str, db: Session = Depends(get_db
if not cfg.ftp_enabled: if not cfg.ftp_enabled:
raise HTTPException(status_code=403, detail="FTP is disabled for this device") raise HTTPException(status_code=403, detail="FTP is disabled for this device")
client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password, ftp_port=cfg.ftp_port or 21)
try: try:
# List directories in the NL-43 folder # List directories in the NL-43 folder
items = await client.list_ftp_files("/NL-43") items = await client.list_ftp_files("/NL-43")
@@ -864,7 +870,7 @@ async def get_all_settings(unit_id: str, db: Session = Depends(get_db)):
if not cfg.tcp_enabled: if not cfg.tcp_enabled:
raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") raise HTTPException(status_code=403, detail="TCP communication is disabled for this device")
client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password, ftp_port=cfg.ftp_port or 21)
try: try:
settings = await client.get_all_settings() settings = await client.get_all_settings()
logger.info(f"Retrieved all settings for unit {unit_id}") logger.info(f"Retrieved all settings for unit {unit_id}")
@@ -892,7 +898,7 @@ async def list_ftp_files(unit_id: str, path: str = "/", db: Session = Depends(ge
if not cfg: if not cfg:
raise HTTPException(status_code=404, detail="NL43 config not found") raise HTTPException(status_code=404, detail="NL43 config not found")
client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password, ftp_port=cfg.ftp_port or 21)
try: try:
files = await client.list_ftp_files(path) files = await client.list_ftp_files(path)
return {"status": "ok", "path": path, "files": files, "count": len(files)} return {"status": "ok", "path": path, "files": files, "count": len(files)}
@@ -937,7 +943,7 @@ async def download_ftp_file(unit_id: str, payload: DownloadRequest, db: Session
local_path = os.path.join(download_dir, filename) local_path = os.path.join(download_dir, filename)
client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password, ftp_port=cfg.ftp_port or 21)
try: try:
await client.download_ftp_file(payload.remote_path, local_path) await client.download_ftp_file(payload.remote_path, local_path)
logger.info(f"Downloaded {payload.remote_path} from {unit_id} to {local_path}") logger.info(f"Downloaded {payload.remote_path} from {unit_id} to {local_path}")
@@ -968,7 +974,7 @@ async def get_measurement_time(unit_id: str, db: Session = Depends(get_db)):
if not cfg.tcp_enabled: if not cfg.tcp_enabled:
raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") raise HTTPException(status_code=403, detail="TCP communication is disabled for this device")
client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password, ftp_port=cfg.ftp_port or 21)
try: try:
preset = await client.get_measurement_time() preset = await client.get_measurement_time()
return {"status": "ok", "measurement_time": preset} return {"status": "ok", "measurement_time": preset}
@@ -987,7 +993,7 @@ async def set_measurement_time(unit_id: str, payload: TimingPayload, db: Session
if not cfg.tcp_enabled: if not cfg.tcp_enabled:
raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") raise HTTPException(status_code=403, detail="TCP communication is disabled for this device")
client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password, ftp_port=cfg.ftp_port or 21)
try: try:
await client.set_measurement_time(payload.preset) await client.set_measurement_time(payload.preset)
return {"status": "ok", "message": f"Measurement time set to {payload.preset}"} return {"status": "ok", "message": f"Measurement time set to {payload.preset}"}
@@ -1006,7 +1012,7 @@ async def get_leq_interval(unit_id: str, db: Session = Depends(get_db)):
if not cfg.tcp_enabled: if not cfg.tcp_enabled:
raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") raise HTTPException(status_code=403, detail="TCP communication is disabled for this device")
client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password, ftp_port=cfg.ftp_port or 21)
try: try:
preset = await client.get_leq_interval() preset = await client.get_leq_interval()
return {"status": "ok", "leq_interval": preset} return {"status": "ok", "leq_interval": preset}
@@ -1025,7 +1031,7 @@ async def set_leq_interval(unit_id: str, payload: TimingPayload, db: Session = D
if not cfg.tcp_enabled: if not cfg.tcp_enabled:
raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") raise HTTPException(status_code=403, detail="TCP communication is disabled for this device")
client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password, ftp_port=cfg.ftp_port or 21)
try: try:
await client.set_leq_interval(payload.preset) await client.set_leq_interval(payload.preset)
return {"status": "ok", "message": f"Leq interval set to {payload.preset}"} return {"status": "ok", "message": f"Leq interval set to {payload.preset}"}
@@ -1044,7 +1050,7 @@ async def get_lp_interval(unit_id: str, db: Session = Depends(get_db)):
if not cfg.tcp_enabled: if not cfg.tcp_enabled:
raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") raise HTTPException(status_code=403, detail="TCP communication is disabled for this device")
client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password, ftp_port=cfg.ftp_port or 21)
try: try:
preset = await client.get_lp_interval() preset = await client.get_lp_interval()
return {"status": "ok", "lp_interval": preset} return {"status": "ok", "lp_interval": preset}
@@ -1063,7 +1069,7 @@ async def set_lp_interval(unit_id: str, payload: TimingPayload, db: Session = De
if not cfg.tcp_enabled: if not cfg.tcp_enabled:
raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") raise HTTPException(status_code=403, detail="TCP communication is disabled for this device")
client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password, ftp_port=cfg.ftp_port or 21)
try: try:
await client.set_lp_interval(payload.preset) await client.set_lp_interval(payload.preset)
return {"status": "ok", "message": f"Lp interval set to {payload.preset}"} return {"status": "ok", "message": f"Lp interval set to {payload.preset}"}
@@ -1082,7 +1088,7 @@ async def get_index_number(unit_id: str, db: Session = Depends(get_db)):
if not cfg.tcp_enabled: if not cfg.tcp_enabled:
raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") raise HTTPException(status_code=403, detail="TCP communication is disabled for this device")
client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password, ftp_port=cfg.ftp_port or 21)
try: try:
index = await client.get_index_number() index = await client.get_index_number()
return {"status": "ok", "index_number": index} return {"status": "ok", "index_number": index}
@@ -1101,7 +1107,7 @@ async def set_index_number(unit_id: str, payload: IndexPayload, db: Session = De
if not cfg.tcp_enabled: if not cfg.tcp_enabled:
raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") raise HTTPException(status_code=403, detail="TCP communication is disabled for this device")
client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password, ftp_port=cfg.ftp_port or 21)
try: try:
await client.set_index_number(payload.index) await client.set_index_number(payload.index)
return {"status": "ok", "message": f"Index number set to {payload.index:04d}"} return {"status": "ok", "message": f"Index number set to {payload.index:04d}"}
@@ -1129,7 +1135,7 @@ async def check_overwrite_status(unit_id: str, db: Session = Depends(get_db)):
if not cfg.tcp_enabled: if not cfg.tcp_enabled:
raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") raise HTTPException(status_code=403, detail="TCP communication is disabled for this device")
client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password, ftp_port=cfg.ftp_port or 21)
try: try:
overwrite_status = await client.get_overwrite_status() overwrite_status = await client.get_overwrite_status()
will_overwrite = overwrite_status == "Exist" will_overwrite = overwrite_status == "Exist"
@@ -1154,7 +1160,7 @@ async def get_all_settings(unit_id: str, db: Session = Depends(get_db)):
if not cfg.tcp_enabled: if not cfg.tcp_enabled:
raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") raise HTTPException(status_code=403, detail="TCP communication is disabled for this device")
client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password, ftp_port=cfg.ftp_port or 21)
try: try:
settings = await client.get_all_settings() settings = await client.get_all_settings()
return {"status": "ok", "settings": settings} return {"status": "ok", "settings": settings}

View File

@@ -105,12 +105,13 @@ _rate_limit_lock = asyncio.Lock()
class NL43Client: class NL43Client:
def __init__(self, host: str, port: int, timeout: float = 5.0, ftp_username: str = None, ftp_password: str = None): def __init__(self, host: str, port: int, timeout: float = 5.0, ftp_username: str = None, ftp_password: str = None, ftp_port: int = 21):
self.host = host self.host = host
self.port = port self.port = port
self.timeout = timeout self.timeout = timeout
self.ftp_username = ftp_username or "anonymous" self.ftp_username = ftp_username or "USER"
self.ftp_password = ftp_password or "" self.ftp_password = ftp_password or "0000"
self.ftp_port = ftp_port
self.device_key = f"{host}:{port}" self.device_key = f"{host}:{port}"
async def _enforce_rate_limit(self): async def _enforce_rate_limit(self):
@@ -717,14 +718,17 @@ class NL43Client:
logger.info(f"Listing FTP files on {self.device_key} at {remote_path}") logger.info(f"Listing FTP files on {self.device_key} at {remote_path}")
def _list_ftp_sync(): def _list_ftp_sync():
"""Synchronous FTP listing using ftplib (supports active mode).""" """Synchronous FTP listing using ftplib for NL-43 devices."""
ftp = FTP() ftp = FTP()
ftp.set_debuglevel(0) ftp.set_debuglevel(2) # Enable FTP debugging
try: try:
# Connect and login # Connect and login
ftp.connect(self.host, 21, timeout=10) logger.info(f"Connecting to FTP server at {self.host}:{self.ftp_port}")
ftp.connect(self.host, self.ftp_port, timeout=10)
logger.info(f"Logging in with username: {self.ftp_username}")
ftp.login(self.ftp_username, self.ftp_password) ftp.login(self.ftp_username, self.ftp_password)
ftp.set_pasv(False) # Force active mode ftp.set_pasv(False) # Use active mode (required for NL-43 devices)
logger.info("FTP connection established in active mode")
# Change to target directory # Change to target directory
if remote_path != "/": if remote_path != "/":
@@ -824,7 +828,7 @@ class NL43Client:
ftp.set_debuglevel(0) ftp.set_debuglevel(0)
try: try:
# Connect and login # Connect and login
ftp.connect(self.host, 21, timeout=10) ftp.connect(self.host, self.ftp_port, timeout=10)
ftp.login(self.ftp_username, self.ftp_password) ftp.login(self.ftp_username, self.ftp_password)
ftp.set_pasv(False) # Force active mode ftp.set_pasv(False) # Force active mode

55
migrate_add_ftp_port.py Normal file
View File

@@ -0,0 +1,55 @@
#!/usr/bin/env python3
"""
Migration script to add ftp_port column to nl43_config table.
Usage:
python migrate_add_ftp_port.py
"""
import sqlite3
import sys
from pathlib import Path
def migrate():
db_path = Path("data/slmm.db")
if not db_path.exists():
print(f"❌ Database not found at {db_path}")
print(" Run this script from the slmm directory")
return False
try:
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Check if column already exists
cursor.execute("PRAGMA table_info(nl43_config)")
columns = [row[1] for row in cursor.fetchall()]
if "ftp_port" in columns:
print("✓ ftp_port column already exists")
conn.close()
return True
print("Adding ftp_port column to nl43_config table...")
# Add the ftp_port column with default value of 21
cursor.execute("""
ALTER TABLE nl43_config
ADD COLUMN ftp_port INTEGER DEFAULT 21
""")
conn.commit()
print("✓ Migration completed successfully")
print(" Added ftp_port column (default: 21)")
conn.close()
return True
except Exception as e:
print(f"❌ Migration failed: {e}")
return False
if __name__ == "__main__":
success = migrate()
sys.exit(0 if success else 1)