1+ import random
2+ import datetime
3+ import uuid
4+ import time
5+ import logging
6+ import paho .mqtt .client as mqtt
7+ from kaa_client import KaaClient , CommandsDto , ConfigurationStatusResponseDto
8+ from paho .mqtt .client import MQTTMessage
9+
10+ class ElectricMeter (object ):
11+ def __init__ (self , client : KaaClient ):
12+ self .kaa_client = client
13+ self .lines = ["l1" , "l2" , "l3" ]
14+ self .time = time .time ()
15+ self .l1 = 1
16+ self .l2 = 1
17+ self .l3 = 1
18+ self .consumed_power = random .randint (991 , 110000 )
19+ self .config = {
20+ "overdriveThreshold" : 240 ,
21+ "powerThreshold" : None
22+ }
23+ self .simulateOverdrive = False
24+
25+ def toggle (self , status : bool ) -> None :
26+ self .l1 = status
27+ self .l2 = status
28+ self .l3 = status
29+
30+ @staticmethod
31+ def get_device_metadata ():
32+ return {
33+ "model" : "Qubino Smart Meter 3-Phase" ,
34+ "lon" : "-0.0395317" ,
35+ "lat" : "51.5025671" ,
36+ "lastMaintenance" : f"{ datetime .datetime .now ()} " ,
37+ "type" : "electric meter" ,
38+ "serial" : f"{ uuid .uuid4 ()} " ,
39+ "Address" : "Dock Hill Ave, Rotherhithe, London SE16 6AX, United Kingdom"
40+ }
41+
42+ def _is_overdrive (self , voltage_total : int ) -> bool :
43+ return bool (self .config ["overdriveThreshold" ] and voltage_total > self .config ["overdriveThreshold" ] * 3 )
44+
45+ def get_voltage (self ):
46+ if self .simulateOverdrive is True :
47+ return random .randint (251 , 300 )
48+ else :
49+ return random .randint (215 , 230 )
50+
51+ def get_current (self , line ):
52+ if getattr (self , line ):
53+ return random .randint (8 , 10 ) * getattr (self , line )
54+ else :
55+ return 0
56+
57+ @staticmethod
58+ def get_power (voltage , current ):
59+ return voltage * current
60+
61+ @staticmethod
62+ def get_current_reactive (current_active ):
63+ return current_active / (random .randint (1 , 10 ))
64+
65+ def get_consumed_power (self , total_power ):
66+ time_delta = (time .time () - self .time ) / 3600
67+ self .consumed_power = self .consumed_power + time_delta * total_power
68+ return self .consumed_power
69+
70+ def get_power_status (self ):
71+ if self .l1 and self .l2 and self .l3 :
72+ return 1
73+ return 0
74+
75+ """
76+ Generates telemetry data sample.
77+ """
78+ def get_data_sample (self ):
79+ data = {}
80+ for line in self .lines :
81+ data [f"voltage_{ line } " ] = self .get_voltage ()
82+ voltage_total = sum ([data [f"voltage_{ line } " ] for line in self .lines ])
83+ if self ._is_overdrive (voltage_total ):
84+ for line in self .lines :
85+ setattr (self , line , 0 )
86+ data [f"status_{ line } " ] = 0
87+ for line in self .lines :
88+ data [f"current_{ line } _active" ] = self .get_current (line )
89+ data [f"current_{ line } _reactive" ] = self .get_current_reactive (data [f"current_{ line } _active" ])
90+ data [f"current_{ line } _total" ] = data [f"current_{ line } _active" ] + data [f"current_{ line } _reactive" ]
91+ data [f"power_{ line } _active" ] = self .get_power (data [f"voltage_{ line } " ], data [f"current_{ line } _active" ])
92+ data [f"power_{ line } _reactive" ] = self .get_power (data [f"voltage_{ line } " ], data [f"current_{ line } _reactive" ])
93+ data [f"power_{ line } _total" ] = self .get_power (data [f"voltage_{ line } " ], data [f"current_{ line } _total" ])
94+ data [f"status_{ line } " ] = getattr (self , line )
95+
96+ data ["current_total" ] = sum ([data [f"current_{ line } _total" ] for line in self .lines ])
97+ data ["power_total" ] = sum ([data [f"power_{ line } _total" ] for line in self .lines ])
98+ data ["power_active" ] = sum ([data [f"power_{ line } _active" ] for line in self .lines ])
99+ data ["power_reactive" ] = sum ([data [f"power_{ line } _reactive" ] for line in self .lines ])
100+ data ["consumed_power" ] = self .get_consumed_power (data ["power_total" ])
101+ data ["power_status" ] = self .get_power_status ()
102+ data ["voltage_total" ] = voltage_total
103+
104+ return data
105+
106+ """
107+ command type: electric_meter_toggle
108+ example of payloads:
109+ Turn off all lines
110+ {
111+ "l1": 0,
112+ "l2": 0,
113+ "l3": 0
114+ }
115+ Turn on the Line 1
116+ {
117+ "l1": 1
118+ }
119+ """
120+ def electric_meter_handler (self , client : mqtt .Client , userdata , message : MQTTMessage ):
121+ command_payload = str (message .payload .decode ('utf-8' ))
122+ logging .info (f"Received command: [{ message .topic } ] []" )
123+ response_topic = KaaClient .get_command_response_topic (message , "electric_meter_toggle" )
124+ commands = CommandsDto (command_payload ).get_command_responses ()
125+ for command in commands :
126+ try :
127+ for key in command .payload .keys ():
128+ setattr (self , key , int (command .payload [key ]))
129+ command .status_code = 200
130+ command .payload [key ] = "status changed"
131+ except Exception as error :
132+ logging .error (f"Invalid command payload [{ error } ] [{ command .to_dict ()} ]" )
133+ client .publish (response_topic , self .kaa_client .compose_commands_result (commands ))
134+
135+
136+ """
137+ Command to simulate overdrive
138+ Send command type `electric_meter_overdrive` to turn on the overdrive.
139+ Send payload { "off": true } to switch to normal mode.
140+ """
141+ def electric_meter_overdrive_handler (self , client : mqtt .Client , userdata , message : MQTTMessage ):
142+ command_payload = str (message .payload .decode ('utf-8' ))
143+ logging .info (f"Received command: [{ message .topic } ] []" )
144+ response_topic = KaaClient .get_command_response_topic (message , "electric_meter_overdrive" )
145+ commands = CommandsDto (command_payload ).get_command_responses ()
146+ last_command = commands [- 1 ].to_dict ()
147+ for command in commands :
148+ command .status_code = 200
149+ command .reasonPhrase = "Ok"
150+ if last_command and "off" in last_command ["payload" ]:
151+ self .simulateOverdrive = False
152+ else :
153+ self .simulateOverdrive = True
154+ client .publish (response_topic , self .kaa_client .compose_commands_result (commands ))
155+
156+ """
157+ Handle value `overdriveThreshold` published from configuration.
158+ When avarage voltage is less then threshold value simulator will turn off all the lines
159+ """
160+ def configuration_handler (self , client : mqtt .Client , userdata , message ):
161+ configuration_payload = str (message .payload .decode ('utf-8' ))
162+ logging .info ("Received configuration" )
163+ if configuration_payload :
164+ self .config = ConfigurationStatusResponseDto (configuration_payload ).to_dict ()["config" ]
165+
0 commit comments