import { Client, SubscriptionCallback, ActiveSubscription, AdsSymbolContainer, AdsClientSettings } from 'ads-client'; import { Server, Socket } from 'socket.io'; import { L33tADSPLC, L33tMemDB } from '.'; interface PLCConfig { localAmsNetId: string; localAdsPort: number; targetAmsNetId: string; targetAdsPort: number; routerAddress: string; routerTcpPort: number; } interface ConfigFile { agents: Record; plcs: Record; tasks: Record; } export class PlcManager { initAll = async ( memoryDB: L33tMemDB, io: Server ) => { // Process each PLC configuration for (const [plcId, plcConfig] of Object.entries(memoryDB.plcs)) { try { // Create ADS Socket Handler with the PLC configuration const handler = new AdsSocketHandler(io, { localAmsNetId: plcConfig.localAmsNetId, localAdsPort: plcConfig.localAdsPort, targetAmsNetId: plcConfig.targetAmsNetId, targetAdsPort: plcConfig.targetAdsPort, routerAddress: plcConfig.routerAddress, routerTcpPort: plcConfig.routerTcpPort }); // Initialize the handler console.log("after initttt") // Create and configure the PLC instance // TODO remove handler and merge in plc class ? // const plcInstance = new L33tADSPLC(); handler.plc = plcConfig; plcConfig.setHandler(handler); await handler.init(); // plcConfig.description = `PLC ${plcId}`; // plcConfig.driver = "ads-client@beta"; // Get and process symbols // plcConfig.symbols = await handler.getSymbols(); // console.log("get SYMBB ", await handler.getSymbols()) // restore bounded variables plcConfig.updateBoundVariables(plcConfig.boundVariables) // Object.entries(plcInstance.symbols).forEach(element => { // if (element[1].name.startsWith(".")) { // console.log(`Symbol found for ${plcId}:`, element[1].name); // } // }); // Store the PLC instance in both the memoryDB and our return object // memoryDB.plcs[plcId].symbols = plcInstance.symbols; // memoryDB.plcs[plcId] = plcInstance; } catch (error) { console.error(`Failed to initialize PLC ${plcId}:`, error); // Continue with other PLCs even if one fails } } } registerHandler = (io:Server, socket:Socket, memoryDB:L33tMemDB):undefined => { // socket.on('l33t_write_symbol', async ( data: { plcId: string, name: string, value:any}) => { // memoryDB.plcs[data.plcId].writeVariable(data.name, data.value) // }) socket.on('l33t_read_symbols', async (data: { plcId: string }) => { try { // memoryDB.plcs // const symbols = await this.getSymbols(); // socket.emit('l33t_read_symbols_response', { // plcId: data.plcId, // symbols // }); } catch (error) { console.error('Error reading symbols:', error); socket.emit('l33t_read_symbols_response', { plcId: data.plcId, error: error instanceof Error ? error.message : 'Unknown error' }); } }); socket.on("l33t_ads_update_bound_variables", async (data: any) => { console.log("l33t_ads_update_bound_variables", data ) const { plcId, boundVariables } = data; if (memoryDB.plcs[plcId]) { // Update the PLC's bound variables const updatedPlc: L33tADSPLC = memoryDB.plcs[plcId]; updatedPlc.boundVariables = boundVariables memoryDB.plcs[plcId] = updatedPlc; await updatedPlc.updateBoundVariables(boundVariables) // Emit the updated PLC data to all connected clients io.emit("l33t_ads_plc_update", updatedPlc.toJSON()); console.log(`Updated bound variables for PLC ${plcId}:`, boundVariables); } else { console.error(`PLC with ID ${plcId} not found in memoryDB`); } }); } } export class AdsSocketHandler { public client: Client; private io: Server; private subscriptions: Map> = new Map(); public plc!: L33tADSPLC; constructor(io: Server, adsConfig: AdsClientSettings) { this.io = io; // adsConfig.allowHalfOpen = true // adsConfig.rawClient = true this.client = new Client(adsConfig); } async init() { try { // connect this.client.on('connect', state => { this.plc.connected = true }) this.client.on('disconnect', state => { this.plc.connected = false }) this.client.on("connectionLost", () => { this.plc.connected = false }) await this.client.connect(); console.log('Connected to ADS client'); // socketio listeneres console.log("SETUP SOCKE" ) // this.setupSocketListeners(); } catch (error) { console.error('Failed to connect to ADS client:', error); this.plc.connected = false } } // private setupSocketListeners() { // this.io.on('connection', (socket: Socket) => { // console.log('New client connected'); // socket.on('l33t_ads', async (method: string, ...args: any[]) => { // console.log("LLE ADS") // try { // switch (method) { // case 'getSymbols': // const symbols = await this.getSymbols(); // socket.emit('l33t_ads_response', { method, data: symbols }); // break; // case 'readValue': // if (args.length > 0) { // const value = await this.readValue(args[0]); // socket.emit('l33t_ads_response', { method, data: value }); // } // break; // case 'writeValue': // if (args.length > 1) { // await this.writeValue(args[0], args[1]); // socket.emit('l33t_ads_response', { method, success: true }); // } // break; // case 'subscribe': // if (args.length > 0) { // await this.subscribe(args[0]); // socket.emit('l33t_ads_response', { method, success: true }); // } // break; // case 'unsubscribe': // if (args.length > 0) { // await this.unsubscribe(args[0]); // socket.emit('l33t_ads_response', { method, success: true }); // } // break; // default: // socket.emit('l33t_ads_response', { method, error: 'Unknown method' }); // } // } catch (error: any) { // console.error(`Error in ${method}:`, error); // socket.emit('l33t_ads_response', { method, error: error.message }); // } // }); // socket.on('disconnect', () => { // console.log('Client disconnected'); // // this.unsubscribeAll(socket); // }); // }); // } public async getSymbols(): Promise { return await this.client.getSymbols(); } public async readValue(variableName: string): Promise<{ value: T }> { const res = await this.client.readValue(variableName); return { value: res.value }; } public async writeValue(variableName: string, value: any): Promise { await this.client.writeValue(variableName, value); } public async subscribe(variableName: string): Promise { if (this.subscriptions.has(variableName)) { console.log(`Already subscribed to ${variableName}`); return; } const callback: SubscriptionCallback = (data, info) => { if (this.plc) { this.plc.values[info.settings.target.toString()] = data.value // this.io.emit('l33t_ads_update_value', ) console.log("emit") const timestamp = info.latestData?.timestamp.getTime(); this.io.emit('l33t_ads_callback', {time: timestamp, target: info.settings.target.toString(), value: data.value}); } }; const subscription = await this.client.subscribe({ target: variableName, callback }); this.subscriptions.set(variableName, subscription); console.log(`Subscribed to ${variableName}`); } public async unsubscribe(variableName: string): Promise { const subscription = this.subscriptions.get(variableName); if (subscription) { await this.client.unsubscribe(subscription); this.subscriptions.delete(variableName); console.log(`Unsubscribed from ${variableName}`); } } private async unsubscribeAll(socket: Socket): Promise { for (const [variableName, subscription] of this.subscriptions.entries()) { await this.client.unsubscribe(subscription); console.log(`Unsubscribed from ${variableName} due to client disconnect`); } this.subscriptions.clear(); } public async addBoundVariables(newVariables: string[]): Promise { // Subscribe to new variables for (const variableName of newVariables) { if (!this.subscriptions.has(variableName)) { console.log("SUBSCRIBE ", variableName) try { await this.subscribe(variableName); } catch(e) { console.error("Could not bind var ", variableName) } } } console.log('added ' + newVariables); } public async updateBoundVariables(boundVariables: string[]): Promise { const currentVariables = new Set(this.subscriptions.keys()); const newVariables = new Set(boundVariables); // Unsubscribe from variables that are no longer in the list for (const variableName of currentVariables) { if (!newVariables.has(variableName)) { await this.unsubscribe(variableName); } } // Subscribe to new variables for (const variableName of newVariables) { if (!currentVariables.has(variableName)) { console.log("SUBSCRIBE ", variableName) try { await this.subscribe(variableName); } catch(e) { console.error("Could not bind var ", variableName) } } } console.log('Updated bound variables'); } }