1414
1515from asyncua import Server , ua
1616
17+ import synnax as sy
18+ from examples .simulators .device_sim import DeviceSim
19+ from synnax import opcua
20+
1721# Configuration constants
1822ARRAY_COUNT = 5
19- ARRAY_SIZE = 5
23+ DEFAULT_ARRAY_SIZE = 5
2024FLOAT_COUNT = 5
2125BOOL_COUNT = 5
22- RATE = 50 # Hz
26+ DEFAULT_RATE = 50 # Hz
2327BOOL_OFFSET = 0.2 # seconds between each boolean transition
2428
2529# Error injection configuration
2933
3034
3135# Initialization Functions
32- async def create_array_variables (myobj , idx ):
36+ async def create_array_variables (myobj , idx , array_size : int = DEFAULT_ARRAY_SIZE ):
3337 """Create array variables with initial values."""
3438 arrays = []
3539 for i in range (ARRAY_COUNT ):
36- initial_values = [float (j + i ) for j in range (ARRAY_SIZE )]
40+ initial_values = [float (j + i ) for j in range (array_size )]
3741 arr = await myobj .add_variable (
3842 idx , f"my_array_{ i } " , initial_values , ua .VariantType .Float
3943 )
40- await arr .write_array_dimensions ([ARRAY_SIZE ])
44+ await arr .write_array_dimensions ([array_size ])
4145 arrays .append (arr )
4246 return arrays
4347
4448
45- async def create_time_array (myobj , idx ):
49+ async def create_time_array (myobj , idx , array_size : int = DEFAULT_ARRAY_SIZE ):
4650 """Create timestamp array variable."""
4751 now = datetime .datetime .now (datetime .timezone .utc )
4852 initial_times = [
49- now + datetime .timedelta (milliseconds = j ) for j in range (ARRAY_SIZE )
53+ now + datetime .timedelta (milliseconds = j ) for j in range (array_size )
5054 ]
5155 mytimearray = await myobj .add_variable (
5256 idx , "my_time_array" , initial_times , ua .VariantType .DateTime
5357 )
5458 await mytimearray .set_writable ()
55- await mytimearray .write_array_dimensions ([ARRAY_SIZE ])
59+ await mytimearray .write_array_dimensions ([array_size ])
5660 return mytimearray
5761
5862
@@ -116,7 +120,7 @@ def generate_sinewave_values(timestamps, start_ref):
116120
117121
118122# Update Functions
119- def inject_error (values ):
123+ def inject_error (values , array_size : int = DEFAULT_ARRAY_SIZE ):
120124 """
121125 Generate corrupted array data for error injection.
122126 """
@@ -129,7 +133,7 @@ def inject_error(values):
129133
130134 # Array smaller than expected
131135 elif error_chance < 0.667 :
132- size = random .randint (1 , ARRAY_SIZE - 2 )
136+ size = random .randint (1 , max ( 2 , array_size - 2 ) )
133137 return values [:size ]
134138
135139 # Array larger than expected
@@ -178,18 +182,24 @@ async def update_bools(bools, elapsed):
178182 await bool_var .set_value (square_wave , varianttype = ua .VariantType .Boolean )
179183
180184
181- async def run_server () -> None :
185+ async def run_server (
186+ endpoint : str = "" ,
187+ rate : sy .Rate = DEFAULT_RATE * sy .Rate .HZ ,
188+ array_size : int = DEFAULT_ARRAY_SIZE ,
189+ ) -> None :
182190 # Initialize server
183191 server = Server ()
184192 await server .init ()
185- server .set_endpoint ("opc.tcp://127.0.0.1:4841/freeopcua/server/" )
193+ if not endpoint :
194+ endpoint = OPCUASim .endpoint
195+ server .set_endpoint (endpoint )
186196 uri = "http://examples.freeopcua.github.io"
187197 idx = await server .register_namespace (uri )
188198
189199 # Create OPC UA object and variables
190200 myobj = await server .nodes .objects .add_object (idx , "MyObject" )
191- arrays = await create_array_variables (myobj , idx )
192- mytimearray = await create_time_array (myobj , idx )
201+ arrays = await create_array_variables (myobj , idx , array_size )
202+ mytimearray = await create_time_array (myobj , idx , array_size )
193203 floats = await create_float_variables (myobj , idx )
194204 bools = await create_bool_variables (myobj , idx )
195205 commands = await create_command_variables (myobj , idx )
@@ -224,7 +234,7 @@ async def run_server() -> None:
224234 elapsed = (start - start_ref ).total_seconds ()
225235
226236 # Generate data
227- timestamps = generate_timestamps (start , RATE , ARRAY_SIZE )
237+ timestamps = generate_timestamps (start , rate , array_size )
228238 sinewave_values = generate_sinewave_values (timestamps , start_ref )
229239
230240 # Update all variables
@@ -237,7 +247,38 @@ async def run_server() -> None:
237247 duration = (
238248 datetime .datetime .now (datetime .timezone .utc ) - start
239249 ).total_seconds ()
240- await asyncio .sleep ((1 / RATE ) - duration )
250+ await asyncio .sleep (max (0 , (1 / rate ) - duration ))
251+
252+
253+ class OPCUASim (DeviceSim ):
254+ """OPC UA device simulator on port 4841."""
255+
256+ description = "OPC UA simulator on port 4841"
257+ host = "127.0.0.1"
258+ port = 4841
259+ device_name = "OPC UA Test Server"
260+ endpoint = f"opc.tcp://{ host } :{ port } /freeopcua/server/"
261+
262+ def __init__ (
263+ self ,
264+ array_size : int = DEFAULT_ARRAY_SIZE ,
265+ rate : sy .Rate = 50 * sy .Rate .HZ ,
266+ verbose : bool = False ,
267+ ):
268+ super ().__init__ (rate = rate , verbose = verbose )
269+ self .array_size = array_size
270+
271+ async def _run_server (self ) -> None :
272+ await run_server (self .endpoint , self .rate , self .array_size )
273+
274+ @staticmethod
275+ def create_device (rack_key : int ) -> opcua .Device :
276+ return opcua .Device (
277+ endpoint = OPCUASim .endpoint ,
278+ name = OPCUASim .device_name ,
279+ location = OPCUASim .endpoint ,
280+ rack = rack_key ,
281+ )
241282
242283
243284if __name__ == "__main__" :
0 commit comments