Passed
Push — master ( f8b1cd...9ee003 )
by
unknown
10:23 queued 16s
created

core.point.PointCollection.__init__()   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nop 1
1
import falcon
2
from datetime import datetime, timedelta
3
import mysql.connector
4
import simplejson as json
5
import redis
6
from core.useractivity import user_logger, admin_control
7
import config
8
from decimal import Decimal
9
10
11
PV_GRID_POINT_COLUMNS = [
12
    "power_point_id",
13
    "total_active_power_point_id",
14
    "active_power_a_point_id",
15
    "active_power_b_point_id",
16
    "active_power_c_point_id",
17
    "total_reactive_power_point_id",
18
    "reactive_power_a_point_id",
19
    "reactive_power_b_point_id",
20
    "reactive_power_c_point_id",
21
    "total_apparent_power_point_id",
22
    "apparent_power_a_point_id",
23
    "apparent_power_b_point_id",
24
    "apparent_power_c_point_id",
25
    "total_power_factor_point_id",
26
    "active_energy_import_point_id",
27
    "active_energy_export_point_id",
28
    "active_energy_net_point_id",
29
]
30
31
PV_LOAD_POINT_COLUMNS = list(PV_GRID_POINT_COLUMNS)
32
33
_PV_INVERTOR_BASE_COLUMNS = [
34
    "invertor_state_point_id",
35
    "communication_state_point_id",
36
    "total_energy_point_id",
37
    "today_energy_point_id",
38
    "efficiency_point_id",
39
    "temperature_point_id",
40
    "power_factor_point_id",
41
    "active_power_point_id",
42
    "reactive_power_point_id",
43
    "frequency_point_id",
44
    "uab_point_id",
45
    "ubc_point_id",
46
    "uca_point_id",
47
    "ua_point_id",
48
    "ub_point_id",
49
    "uc_point_id",
50
    "ia_point_id",
51
    "ib_point_id",
52
    "ic_point_id",
53
]
54
_PV_INVERTOR_PV_COLUMNS = [f"pv{i}_{suffix}_point_id" for i in range(1, 29) for suffix in ("u", "i")]
55
_PV_INVERTOR_MPPT_COLUMNS = ["mppt_total_energy_point_id", "mppt_power_point_id"] + [f"mppt_{i}_energy_point_id" for i in range(1, 11)]
56
PV_INVERTOR_POINT_COLUMNS = (
57
    _PV_INVERTOR_BASE_COLUMNS
58
    + _PV_INVERTOR_PV_COLUMNS
59
    + _PV_INVERTOR_MPPT_COLUMNS
60
    + ["startup_time_point_id", "shutdown_time_point_id"]
61
)
62
63
POINT_RELATION_CHECKS = [
64
    ("tbl_microgrids_grids_points", ("point_id",), "API.THERE_IS_RELATION_WITH_MICROGRIDS_GRIDS_POINTS"),
65
    ("tbl_microgrids_heatpumps_points", ("point_id",), "API.THERE_IS_RELATION_WITH_MICROGRIDS_HEATPUMPS_POINTS"),
66
    ("tbl_microgrids_loads_points", ("point_id",), "API.THERE_IS_RELATION_WITH_MICROGRIDS_LOADS_POINTS"),
67
    ("tbl_microgrids_pvs_points", ("point_id",), "API.THERE_IS_RELATION_WITH_MICROGRIDS_PVS_POINTS"),
68
    ("tbl_photovoltaic_power_stations", ("latitude_point_id", "longitude_point_id"), "API.THERE_IS_RELATION_WITH_PHOTOVOLTAIC_POWER_STATIONS"),
69
    ("tbl_photovoltaic_power_stations_grids", tuple(PV_GRID_POINT_COLUMNS), "API.THERE_IS_RELATION_WITH_PHOTOVOLTAIC_POWER_STATIONS_GRIDS"),
70
    ("tbl_photovoltaic_power_stations_grids_points", ("point_id",), "API.THERE_IS_RELATION_WITH_PHOTOVOLTAIC_POWER_STATIONS_GRIDS"),
71
    ("tbl_photovoltaic_power_stations_invertors", tuple(PV_INVERTOR_POINT_COLUMNS), "API.THERE_IS_RELATION_WITH_PHOTOVOLTAIC_POWER_STATIONS_INVERTORS"),
72
    ("tbl_photovoltaic_power_stations_invertors_points", ("point_id",), "API.THERE_IS_RELATION_WITH_PHOTOVOLTAIC_POWER_STATIONS_INVERTORS"),
73
    ("tbl_photovoltaic_power_stations_loads", tuple(PV_LOAD_POINT_COLUMNS), "API.THERE_IS_RELATION_WITH_PHOTOVOLTAIC_POWER_STATIONS_LOADS"),
74
    ("tbl_photovoltaic_power_stations_loads_points", ("point_id",), "API.THERE_IS_RELATION_WITH_PHOTOVOLTAIC_POWER_STATIONS_LOADS"),
75
    ("tbl_points_set_values", ("point_id",), "API.THERE_IS_RELATION_WITH_POINTS_SET_VALUES"),
76
    ("tbl_power_integrators", ("power_point_id", "result_point_id"), "API.THERE_IS_RELATION_WITH_POWER_INTEGRATORS"),
77
    ("tbl_virtual_power_plants_microgrids", ("point_id", "virtual_power_plant_id", "microgrid_id"), "API.THERE_IS_RELATION_WITH_VIRTUAL_POWER_PLANTS_MICROGRIDS"),
78
    ("tbl_wind_farms", ("latitude_point_id", "longitude_point_id"), "API.THERE_IS_RELATION_WITH_WIND_FARMS"),
79
    ("tbl_meters_points", ("point_id",), "API.THERE_IS_RELATION_WITH_METERS"),
80
    ("tbl_sensors_points", ("point_id",), "API.THERE_IS_RELATION_WITH_SENSORS"),
81
    ("tbl_shopfloors_points", ("point_id",), "API.THERE_IS_RELATION_WITH_SHOPFLOORS"),
82
    ("tbl_stores_points", ("point_id",), "API.THERE_IS_RELATION_WITH_STORES"),
83
    ("tbl_spaces_points", ("point_id",), "API.THERE_IS_RELATION_WITH_SPACES"),
84
    ("tbl_tenants_points", ("point_id",), "API.THERE_IS_RELATION_WITH_TENANTS"),
85
    ("tbl_equipments_parameters", ("point_id",), "API.THERE_IS_RELATION_WITH_EQUIPMENT_PARAMETERS"),
86
    ("tbl_combined_equipments_parameters", ("point_id",), "API.THERE_IS_RELATION_WITH_COMBINED_EQUIPMENT_PARAMETERS"),
87
    ("tbl_distribution_circuits_points", ("point_id",), "API.THERE_IS_RELATION_WITH_DISTRIBUTION_CIRCUITS_POINTS"),
88
    ("tbl_heat_integrators", ("high_temperature_point_id", "low_temperature_point_id", "flow_point_id", "result_point_id"), "API.THERE_IS_RELATION_WITH_HEAT_INTEGRATORS"),
89
    ("tbl_microgrids_batteries", ("battery_state_point_id", "soc_point_id", "power_point_id"), "API.THERE_IS_RELATION_WITH_MICROGRIDS_BATTERIES"),
90
    ("tbl_microgrids_power_conversion_systems", ("run_state_point_id", "today_charge_energy_point_id", "today_discharge_energy_point_id", "total_charge_energy_point_id", "total_discharge_energy_point_id"), "API.THERE_IS_RELATION_WITH_MICROGRIDS_POWER_CONVERSION_SYSTEMS"),
91
    ("tbl_microgrids_evchargers", ("power_point_id",), "API.THERE_IS_RELATION_WITH_MICROGRIDS_EVCHARGERS"),
92
    ("tbl_microgrids_evchargers_points", ("point_id",), "API.THERE_IS_RELATION_WITH_MICROGRIDS_EVCHARGERS_POINTS"),
93
    ("tbl_microgrids_generators", ("power_point_id",), "API.THERE_IS_RELATION_WITH_MICROGRIDS_GENERATORS"),
94
    ("tbl_microgrids_generators_points", ("point_id",), "API.THERE_IS_RELATION_WITH_MICROGRIDS_GENERATORS_POINTS"),
95
    ("tbl_microgrids_grids", ("power_point_id",), "API.THERE_IS_RELATION_WITH_MICROGRIDS_GRIDS"),
96
    ("tbl_microgrids_heatpumps", ("power_point_id",), "API.THERE_IS_RELATION_WITH_MICROGRIDS_HEATPUMPS"),
97
    ("tbl_microgrids_loads", ("power_point_id",), "API.THERE_IS_RELATION_WITH_MICROGRIDS_LOADS"),
98
    ("tbl_microgrids_photovoltaics", ("power_point_id",), "API.THERE_IS_RELATION_WITH_MICROGRIDS_PHOTOVOLTAICS"),
99
    ("tbl_microgrids", ("latitude_point_id", "longitude_point_id"), "API.THERE_IS_RELATION_WITH_MICROGRIDS"),
100
    ("tbl_microgrids_bmses_points", ("point_id",), "API.THERE_IS_RELATION_WITH_MICROGRIDS_BMSES_POINTS"),
101
    ("tbl_microgrids_pcses_points", ("point_id",), "API.THERE_IS_RELATION_WITH_MICROGRIDS_PCSES_POINTS"),
102
    ("tbl_virtual_power_plants", ("balancing_price_point_id",), "API.THERE_IS_RELATION_WITH_VIRTUAL_POWER_PLANTS"),
103
    ("tbl_energy_storage_containers_batteries", ("battery_state_point_id", "soc_point_id", "power_point_id"), "API.THERE_IS_RELATION_WITH_ENERGY_STORAGE_CONTAINERS_BATTERIES"),
104
    ("tbl_energy_storage_containers_grids", ("power_point_id",), "API.THERE_IS_RELATION_WITH_ENERGY_STORAGE_CONTAINERS_GRIDS"),
105
    ("tbl_energy_storage_containers_grids_points", ("point_id",), "API.THERE_IS_RELATION_WITH_ENERGY_STORAGE_CONTAINERS_GRIDS_POINTS"),
106
    ("tbl_energy_storage_containers_bmses_points", ("point_id",), "API.THERE_IS_RELATION_WITH_ENERGY_STORAGE_CONTAINERS_BMSES_POINTS"),
107
    ("tbl_energy_storage_containers_dcdcs_points", ("point_id",), "API.THERE_IS_RELATION_WITH_ENERGY_STORAGE_CONTAINERS_DCDCS"),
108
    ("tbl_energy_storage_containers_firecontrols_points", ("point_id",), "API.THERE_IS_RELATION_WITH_ENERGY_STORAGE_CONTAINERS_FIRECONTROLS"),
109
    ("tbl_energy_storage_containers_hvacs_points", ("point_id",), "API.THERE_IS_RELATION_WITH_ENERGY_STORAGE_CONTAINERS_HVACS"),
110
    ("tbl_energy_storage_containers_loads", ("power_point_id",), "API.THERE_IS_RELATION_WITH_ENERGY_STORAGE_CONTAINERS_LOADS"),
111
    ("tbl_energy_storage_containers_loads_points", ("point_id",), "API.THERE_IS_RELATION_WITH_ENERGY_STORAGE_CONTAINERS_LOADS"),
112
    ("tbl_energy_storage_containers_power_conversion_systems", ("run_state_point_id",), "API.THERE_IS_RELATION_WITH_ENERGY_STORAGE_CONTAINERS_POWER_CONVERSION_SYSTEMS"),
113
    ("tbl_energy_storage_containers_pcses_points", ("point_id",), "API.THERE_IS_RELATION_WITH_ENERGY_STORAGE_CONTAINERS_POWER_CONVERSION_SYSTEMS"),
114
    ("tbl_energy_storage_containers_stses_points", ("point_id",), "API.THERE_IS_RELATION_WITH_ENERGY_STORAGE_CONTAINERS_STSES"),
115
    ("tbl_energy_storage_power_stations", ("latitude_point_id", "longitude_point_id"), "API.THERE_IS_RELATION_WITH_ENERGY_STORAGE_POWER_STATIONS"),
116
    ("tbl_fuel_integrators", ("power_point_id", "result_point_id"), "API.THERE_IS_RELATION_WITH_FUEL_INTEGRATORS"),
117
    ("tbl_meters_points", ("point_id",), "API.THERE_IS_RELATION_WITH_METERS_POINTS"),
118
    ("tbl_sensors_points", ("point_id",), "API.THERE_IS_RELATION_WITH_SENSORS_POINTS"),
119
    ("tbl_shopfloors_points", ("point_id",), "API.THERE_IS_RELATION_WITH_SHOPFLOORS_POINTS"),
120
    ("tbl_spaces_points", ("point_id",), "API.THERE_IS_RELATION_WITH_SPACES_POINTS"),
121
    ("tbl_stores_points", ("point_id",), "API.THERE_IS_RELATION_WITH_STORES_POINTS"),
122
    ("tbl_tenants_points", ("point_id",), "API.THERE_IS_RELATION_WITH_TENANTS_POINTS"),
123
]
124
125
126 View Code Duplication
def clear_point_cache(point_id=None):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
127
    """
128
    Clear point-related cache after data modification
129
130
    Args:
131
        point_id: Point ID (optional, for specific point cache)
132
    """
133
    # Check if Redis is enabled
134
    if not config.redis.get('is_enabled', False):
135
        return
136
137
    redis_client = None
138
    try:
139
        redis_client = redis.Redis(
140
            host=config.redis['host'],
141
            port=config.redis['port'],
142
            password=config.redis['password'] if config.redis['password'] else None,
143
            db=config.redis['db'],
144
            decode_responses=True,
145
            socket_connect_timeout=2,
146
            socket_timeout=2
147
        )
148
        redis_client.ping()
149
150
        # Clear point list cache
151
        list_cache_key = 'point:list'
152
        redis_client.delete(list_cache_key)
153
154
        # Clear specific point item cache if point_id is provided
155
        if point_id:
156
            item_cache_key = f'point:item:{point_id}'
157
            redis_client.delete(item_cache_key)
158
            export_cache_key = f'point:export:{point_id}'
159
            redis_client.delete(export_cache_key)
160
161
    except Exception:
162
        # If cache clear fails, ignore and continue
163
        pass
164
165
166
class PointCollection:
167
    """
168
    Point Collection Resource
169
170
    This class handles CRUD operations for point collection.
171
    It provides endpoints for listing all points and creating new points.
172
    Points represent data collection points in the energy management system.
173
    """
174
    def __init__(self):
175
        """Initialize PointCollection"""
176
        pass
177
178
    @staticmethod
179
    def on_options(req, resp):
180
        """Handle OPTIONS requests for CORS preflight"""
181
        _ = req
182
        resp.status = falcon.HTTP_200
183
184
    @staticmethod
185
    def on_get(req, resp):
186
        """
187
        Handle GET requests to retrieve all points
188
189
        Returns a list of all points with their metadata including:
190
        - Point ID, name, and UUID
191
        - Data source information
192
        - Object type and object ID
193
        - Description and other attributes
194
195
        Args:
196
            req: Falcon request object
197
            resp: Falcon response object
198
        """
199
        admin_control(req)
200
201
        # Redis cache key
202
        cache_key = 'point:list'
203
        cache_expire = 28800  # 8 hours in seconds (long-term cache)
204
205
        # Try to get from Redis cache (only if Redis is enabled)
206
        redis_client = None
207
        if config.redis.get('is_enabled', False):
208
            try:
209
                redis_client = redis.Redis(
210
                    host=config.redis['host'],
211
                    port=config.redis['port'],
212
                    password=config.redis['password'] if config.redis['password'] else None,
213
                    db=config.redis['db'],
214
                    decode_responses=True,
215
                    socket_connect_timeout=2,
216
                    socket_timeout=2
217
                )
218
                redis_client.ping()
219
                cached_result = redis_client.get(cache_key)
220
                if cached_result:
221
                    resp.text = cached_result
222
                    return
223
            except Exception:
224
                # If Redis connection fails, continue to database query
225
                pass
226
227
        # Cache miss or Redis error - query database
228
        cnx = mysql.connector.connect(**config.myems_system_db)
229
        cursor = cnx.cursor()
230
231
        query = (" SELECT id, name, uuid "
232
                 " FROM tbl_data_sources ")
233
        cursor.execute(query)
234
        rows_data_sources = cursor.fetchall()
235
236
        data_source_dict = dict()
237
        if rows_data_sources is not None and len(rows_data_sources) > 0:
238
            for row in rows_data_sources:
239
                data_source_dict[row[0]] = {"id": row[0],
240
                                            "name": row[1],
241
                                            "uuid": row[2]}
242
243
        query = (" SELECT id, name, data_source_id, object_type, units, "
244
                 "        high_limit, low_limit, higher_limit, lower_limit, ratio, offset_constant, "
245
                 "        is_trend, is_virtual, address, description, faults, definitions "
246
                 " FROM tbl_points ")
247
        cursor.execute(query)
248
        rows = cursor.fetchall()
249
        cursor.close()
250
        cnx.close()
251
252
        result = list()
253 View Code Duplication
        if rows is not None and len(rows) > 0:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
254
            for row in rows:
255
                meta_result = {"id": row[0],
256
                               "name": row[1],
257
                               "data_source": data_source_dict.get(row[2], None),
258
                               "object_type": row[3],
259
                               "units": row[4],
260
                               "high_limit": row[5],
261
                               "low_limit": row[6],
262
                               "higher_limit": row[7],
263
                               "lower_limit": row[8],
264
                               "ratio": Decimal(row[9]),
265
                               "offset_constant": Decimal(row[10]),
266
                               "is_trend": bool(row[11]),
267
                               "is_virtual": bool(row[12]),
268
                               "address": row[13],
269
                               "description": row[14],
270
                               "faults": row[15],
271
                               "definitions": row[16]}
272
                result.append(meta_result)
273
274
        # Store result in Redis cache
275
        result_json = json.dumps(result)
276
        if redis_client:
277
            try:
278
                redis_client.setex(cache_key, cache_expire, result_json)
279
            except Exception:
280
                # If cache set fails, ignore and continue
281
                pass
282
283
        resp.text = result_json
284
285
    @staticmethod
286
    @user_logger
287
    def on_post(req, resp):
288
        """Handles POST requests"""
289
        admin_control(req)
290
        try:
291
            raw_json = req.stream.read().decode('utf-8')
292
        except UnicodeDecodeError as ex:
293
            print("Failed to decode request")
294
            raise falcon.HTTPError(status=falcon.HTTP_400,
295
                                   title='API.BAD_REQUEST',
296
                                   description='API.INVALID_ENCODING')
297
        except Exception as ex:
298
            print("Unexpected error reading request stream")
299
            raise falcon.HTTPError(status=falcon.HTTP_400,
300
                                   title='API.BAD_REQUEST',
301
                                   description='API.FAILED_TO_READ_REQUEST_STREAM')
302
303
        new_values = json.loads(raw_json)
304
305
        if 'name' not in new_values['data'].keys() or \
306
                not isinstance(new_values['data']['name'], str) or \
307
                len(str.strip(new_values['data']['name'])) == 0:
308
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
309
                                   description='API.INVALID_POINT_NAME')
310
        name = str.strip(new_values['data']['name'])
311
312
        if 'data_source_id' not in new_values['data'].keys() or \
313
                not isinstance(new_values['data']['data_source_id'], int) or \
314
                new_values['data']['data_source_id'] <= 0:
315
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
316
                                   description='API.INVALID_DATA_SOURCE_ID')
317
        data_source_id = new_values['data']['data_source_id']
318
319
        if 'object_type' not in new_values['data'].keys() \
320
                or str.strip(new_values['data']['object_type']) not in \
321
                ('ENERGY_VALUE', 'ANALOG_VALUE', 'DIGITAL_VALUE', 'TEXT_VALUE'):
322
            raise falcon.HTTPError(status=falcon.HTTP_400,
323
                                   title='API.BAD_REQUEST',
324
                                   description='API.INVALID_OBJECT_TYPE')
325
        object_type = str.strip(new_values['data']['object_type'])
326
327
        if 'units' not in new_values['data'].keys() or \
328
                not isinstance(new_values['data']['units'], str) or \
329
                len(str.strip(new_values['data']['units'])) == 0:
330
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
331
                                   description='API.INVALID_UNITS')
332
        units = str.strip(new_values['data']['units'])
333
334
        if 'high_limit' not in new_values['data'].keys() or \
335
                not (isinstance(new_values['data']['high_limit'], float) or
336
                     isinstance(new_values['data']['high_limit'], int)):
337
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
338
                                   description='API.INVALID_HIGH_LIMIT_VALUE')
339
        high_limit = new_values['data']['high_limit']
340
341
        if 'low_limit' not in new_values['data'].keys() or \
342
                not (isinstance(new_values['data']['low_limit'], float) or
343
                     isinstance(new_values['data']['low_limit'], int)):
344
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
345
                                   description='API.INVALID_LOW_LIMIT_VALUE')
346
        low_limit = new_values['data']['low_limit']
347
348
        # the higher_limit is optional
349
        if 'higher_limit' not in new_values['data'].keys() or \
350
                new_values['data']['higher_limit'] is None:
351
            higher_limit = None
352
        elif not (isinstance(new_values['data']['higher_limit'], float) or
353
                  isinstance(new_values['data']['higher_limit'], int)):
354
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
355
                                   description='API.INVALID_HIGHER_LIMIT_VALUE')
356
        else:
357
            higher_limit = new_values['data']['higher_limit']
358
359
        # the lower_limit is optional
360
        if 'lower_limit' not in new_values['data'].keys() or \
361
                new_values['data']['lower_limit'] is None:
362
            lower_limit = None
363
        elif not (isinstance(new_values['data']['lower_limit'], float) or
364
                  isinstance(new_values['data']['lower_limit'], int)):
365
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
366
                                   description='API.INVALID_LOWER_LIMIT_VALUE')
367
        else:
368
            lower_limit = new_values['data']['lower_limit']
369
370
        if 'ratio' not in new_values['data'].keys() or \
371
                not (isinstance(new_values['data']['ratio'], float) or
372
                     isinstance(new_values['data']['ratio'], int)):
373
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
374
                                   description='API.INVALID_RATIO_VALUE')
375
        ratio = new_values['data']['ratio']
376
377
        if 'offset_constant' not in new_values['data'].keys() or \
378
                not (isinstance(new_values['data']['offset_constant'], float) or
379
                     isinstance(new_values['data']['offset_constant'], int)):
380
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
381
                                   description='API.INVALID_OFFSET_CONSTANT_VALUE')
382
        offset_constant = new_values['data']['offset_constant']
383
384
        if 'is_trend' not in new_values['data'].keys() or \
385
                not isinstance(new_values['data']['is_trend'], bool):
386
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
387
                                   description='API.INVALID_IS_TREND_VALUE')
388
        is_trend = new_values['data']['is_trend']
389
390
        if 'is_virtual' not in new_values['data'].keys() or \
391
                not isinstance(new_values['data']['is_virtual'], bool):
392
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
393
                                   description='API.INVALID_IS_VIRTUAL_VALUE')
394
        if new_values['data']['is_virtual'] is True and object_type == 'DIGITAL_VALUE':
395
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
396
                                   description='API.VIRTUAL_POINT_CAN_NOT_BE_DIGITAL_VALUE')
397
        if new_values['data']['is_virtual'] is True and object_type == 'TEXT_VALUE':
398
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
399
                                   description='API.VIRTUAL_POINT_CAN_NOT_BE_TEXT_VALUE')
400
        is_virtual = new_values['data']['is_virtual']
401
402
        if 'address' not in new_values['data'].keys() or \
403
                not isinstance(new_values['data']['address'], str) or \
404
                len(str.strip(new_values['data']['address'])) == 0:
405
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
406
                                   description='API.INVALID_ADDRESS')
407
        address = str.strip(new_values['data']['address'])
408
409
        if 'description' in new_values['data'].keys() and \
410
                new_values['data']['description'] is not None and \
411
                len(str(new_values['data']['description'])) > 0:
412
            description = str.strip(new_values['data']['description'])
413
        else:
414
            description = None
415
416
        if 'faults' in new_values['data'].keys() and \
417
                new_values['data']['faults'] is not None and \
418
                len(str(new_values['data']['faults'])) > 0:
419
            faults = str.strip(new_values['data']['faults'])
420
        else:
421
            faults = None
422
423
        if 'definitions' in new_values['data'].keys() and \
424
                new_values['data']['definitions'] is not None and \
425
                len(str(new_values['data']['definitions'])) > 0:
426
            definitions = str.strip(new_values['data']['definitions'])
427
        else:
428
            definitions = None
429
430
        cnx = mysql.connector.connect(**config.myems_system_db)
431
        cursor = cnx.cursor()
432
433
        cursor.execute(" SELECT name "
434
                       " FROM tbl_points "
435
                       " WHERE name = %s AND data_source_id = %s ", (name, data_source_id))
436
        if cursor.fetchone() is not None:
437
            cursor.close()
438
            cnx.close()
439
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
440
                                   description='API.POINT_NAME_IS_ALREADY_IN_USE')
441
442
        cursor.execute(" SELECT name "
443
                       " FROM tbl_data_sources "
444
                       " WHERE id = %s ", (data_source_id,))
445
        if cursor.fetchone() is None:
446
            cursor.close()
447
            cnx.close()
448
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
449
                                   description='API.INVALID_DATA_SOURCE_ID')
450
451
        add_value = (" INSERT INTO tbl_points (name, data_source_id, object_type, units, "
452
                     "                         high_limit, low_limit, higher_limit, lower_limit, ratio, "
453
                     "                         offset_constant, is_trend, is_virtual, address, description, faults, "
454
                     "                         definitions) "
455
                     " VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) ")
456
        cursor.execute(add_value, (name,
457
                                   data_source_id,
458
                                   object_type,
459
                                   units,
460
                                   high_limit,
461
                                   low_limit,
462
                                   higher_limit,
463
                                   lower_limit,
464
                                   ratio,
465
                                   offset_constant,
466
                                   is_trend,
467
                                   is_virtual,
468
                                   address,
469
                                   description,
470
                                   faults,
471
                                   definitions))
472
        new_id = cursor.lastrowid
473
        cnx.commit()
474
        cursor.close()
475
        cnx.close()
476
477
        # Clear cache after creating new point
478
        clear_point_cache()
479
480
        resp.status = falcon.HTTP_201
481
        resp.location = '/points/' + str(new_id)
482
483
484
class PointItem:
485
    def __init__(self):
486
        pass
487
488
    @staticmethod
489
    def on_options(req, resp, id_):
490
        _ = req
491
        resp.status = falcon.HTTP_200
492
        _ = id_
493
494 View Code Duplication
    @staticmethod
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
495
    def on_get(req, resp, id_):
496
        """Handles GET requests"""
497
        admin_control(req)
498
        if not id_.isdigit() or int(id_) <= 0:
499
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
500
                                   description='API.INVALID_POINT_ID')
501
502
        # Redis cache key
503
        cache_key = f'point:item:{id_}'
504
        cache_expire = 28800  # 8 hours in seconds (long-term cache)
505
506
        # Try to get from Redis cache (only if Redis is enabled)
507
        redis_client = None
508
        if config.redis.get('is_enabled', False):
509
            try:
510
                redis_client = redis.Redis(
511
                    host=config.redis['host'],
512
                    port=config.redis['port'],
513
                    password=config.redis['password'] if config.redis['password'] else None,
514
                    db=config.redis['db'],
515
                    decode_responses=True,
516
                    socket_connect_timeout=2,
517
                    socket_timeout=2
518
                )
519
                redis_client.ping()
520
                cached_result = redis_client.get(cache_key)
521
                if cached_result:
522
                    resp.text = cached_result
523
                    return
524
            except Exception:
525
                # If Redis connection fails, continue to database query
526
                pass
527
528
        # Cache miss or Redis error - query database
529
        cnx = mysql.connector.connect(**config.myems_system_db)
530
        cursor = cnx.cursor()
531
532
        query = (" SELECT id, name, uuid "
533
                 " FROM tbl_data_sources ")
534
        cursor.execute(query)
535
        rows_data_sources = cursor.fetchall()
536
537
        data_source_dict = dict()
538
        if rows_data_sources is not None and len(rows_data_sources) > 0:
539
            for row in rows_data_sources:
540
                data_source_dict[row[0]] = {"id": row[0],
541
                                            "name": row[1],
542
                                            "uuid": row[2]}
543
544
        query = (" SELECT id, name, data_source_id, object_type, units, "
545
                 "        high_limit, low_limit, higher_limit, lower_limit, ratio, offset_constant, "
546
                 "        is_trend, is_virtual, address, description, faults, definitions "
547
                 " FROM tbl_points "
548
                 " WHERE id = %s ")
549
        cursor.execute(query, (id_,))
550
        row = cursor.fetchone()
551
        cursor.close()
552
        cnx.close()
553
        if row is None:
554
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
555
                                   description='API.POINT_NOT_FOUND')
556
557
        result = {"id": row[0],
558
                  "name": row[1],
559
                  "data_source": data_source_dict.get(row[2], None),
560
                  "object_type": row[3],
561
                  "units": row[4],
562
                  "high_limit": row[5],
563
                  "low_limit": row[6],
564
                  "higher_limit": row[7],
565
                  "lower_limit": row[8],
566
                  "ratio": Decimal(row[9]),
567
                  "offset_constant": Decimal(row[10]),
568
                  "is_trend": bool(row[11]),
569
                  "is_virtual": bool(row[12]),
570
                  "address": row[13],
571
                  "description": row[14],
572
                  "faults": row[15],
573
                  "definitions": row[16]}
574
575
        # Store result in Redis cache
576
        result_json = json.dumps(result)
577
        if redis_client:
578
            try:
579
                redis_client.setex(cache_key, cache_expire, result_json)
580
            except Exception:
581
                # If cache set fails, ignore and continue
582
                pass
583
584
        resp.text = result_json
585
586
    @staticmethod
587
    @user_logger
588
    def on_delete(req, resp, id_):
589
        admin_control(req)
590
        if not id_.isdigit() or int(id_) <= 0:
591
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
592
                                   description='API.INVALID_POINT_ID')
593
594
        cnx = mysql.connector.connect(**config.myems_system_db)
595
        cursor = cnx.cursor()
596
597
        cursor.execute(" SELECT name "
598
                       " FROM tbl_points "
599
                       " WHERE id = %s ", (id_,))
600
        if cursor.fetchone() is None:
601
            cursor.close()
602
            cnx.close()
603
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
604
                                   description='API.POINT_NOT_FOUND')
605
606
        point_id = int(id_)
607
608
        def _raise_relation_error(description):
609
            cursor.close()
610
            cnx.close()
611
            raise falcon.HTTPError(status=falcon.HTTP_400,
612
                                   title='API.BAD_REQUEST',
613
                                   description=description)
614
615
        for table_name, columns, error_description in POINT_RELATION_CHECKS:
616
            for column in columns:
617
                try:
618
                    cursor.execute(
619
                        f" SELECT 1 FROM {table_name} WHERE {column} = %s LIMIT 1 ",
620
                        (point_id,))
621
                except mysql.connector.Error:
622
                    continue
623
                if cursor.fetchone() is not None:
624
                    _raise_relation_error(error_description)
625
626
        cursor.execute(" DELETE FROM tbl_points WHERE id = %s ", (id_,))
627
        cnx.commit()
628
629
        cursor.close()
630
        cnx.close()
631
632
        # Clear cache after deleting point
633
        clear_point_cache(point_id)
634
635
        resp.status = falcon.HTTP_204
636
637
    @staticmethod
638
    @user_logger
639
    def on_put(req, resp, id_):
640
        """Handles PUT requests"""
641
        admin_control(req)
642
        try:
643
            raw_json = req.stream.read().decode('utf-8')
644
        except UnicodeDecodeError as ex:
645
            print("Failed to decode request")
646
            raise falcon.HTTPError(status=falcon.HTTP_400,
647
                                   title='API.BAD_REQUEST',
648
                                   description='API.INVALID_ENCODING')
649
        except Exception as ex:
650
            print("Unexpected error reading request stream")
651
            raise falcon.HTTPError(status=falcon.HTTP_400,
652
                                   title='API.BAD_REQUEST',
653
                                   description='API.FAILED_TO_READ_REQUEST_STREAM')
654
655
        if not id_.isdigit() or int(id_) <= 0:
656
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
657
                                   description='API.INVALID_POINT_ID')
658
659
        new_values = json.loads(raw_json)
660
661
        if 'name' not in new_values['data'].keys() or \
662
                not isinstance(new_values['data']['name'], str) or \
663
                len(str.strip(new_values['data']['name'])) == 0:
664
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
665
                                   description='API.INVALID_POINT_NAME')
666
        name = str.strip(new_values['data']['name'])
667
668
        if 'data_source_id' not in new_values['data'].keys() or \
669
                not isinstance(new_values['data']['data_source_id'], int) or \
670
                new_values['data']['data_source_id'] <= 0:
671
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
672
                                   description='API.INVALID_DATA_SOURCE_ID')
673
        data_source_id = new_values['data']['data_source_id']
674
675
        if 'object_type' not in new_values['data'].keys() \
676
                or str.strip(new_values['data']['object_type']) not in \
677
                ('ENERGY_VALUE', 'ANALOG_VALUE', 'DIGITAL_VALUE', 'TEXT_VALUE'):
678
            raise falcon.HTTPError(status=falcon.HTTP_400,
679
                                   title='API.BAD_REQUEST',
680
                                   description='API.INVALID_OBJECT_TYPE')
681
        object_type = str.strip(new_values['data']['object_type'])
682
683
        if 'units' not in new_values['data'].keys() or \
684
                not isinstance(new_values['data']['units'], str) or \
685
                len(str.strip(new_values['data']['units'])) == 0:
686
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
687
                                   description='API.INVALID_UNITS')
688
        units = str.strip(new_values['data']['units'])
689
690
        if 'high_limit' not in new_values['data'].keys() or \
691
                not (isinstance(new_values['data']['high_limit'], float) or
692
                     isinstance(new_values['data']['high_limit'], int)):
693
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
694
                                   description='API.INVALID_HIGH_LIMIT_VALUE')
695
        high_limit = new_values['data']['high_limit']
696
697
        if 'low_limit' not in new_values['data'].keys() or \
698
                not (isinstance(new_values['data']['low_limit'], float) or
699
                     isinstance(new_values['data']['low_limit'], int)):
700
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
701
                                   description='API.INVALID_LOW_LIMIT_VALUE')
702
        low_limit = new_values['data']['low_limit']
703
704
        # the higher_limit is optional
705
        if 'higher_limit' not in new_values['data'].keys() or \
706
                new_values['data']['higher_limit'] is None:
707
            higher_limit = None
708
        elif not (isinstance(new_values['data']['higher_limit'], float) or
709
                  isinstance(new_values['data']['higher_limit'], int)):
710
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
711
                                   description='API.INVALID_HIGHER_LIMIT_VALUE')
712
        else:
713
            higher_limit = new_values['data']['higher_limit']
714
715
        # the lower_limit is optional
716
        if 'lower_limit' not in new_values['data'].keys() or \
717
                new_values['data']['lower_limit'] is None:
718
            lower_limit = None
719
        elif not (isinstance(new_values['data']['lower_limit'], float) or
720
                  isinstance(new_values['data']['lower_limit'], int)):
721
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
722
                                   description='API.INVALID_LOWER_LIMIT_VALUE')
723
        else:
724
            lower_limit = new_values['data']['lower_limit']
725
726
        if 'ratio' not in new_values['data'].keys() or \
727
                not (isinstance(new_values['data']['ratio'], float) or
728
                     isinstance(new_values['data']['ratio'], int)):
729
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
730
                                   description='API.INVALID_RATIO_VALUE')
731
        ratio = new_values['data']['ratio']
732
733
        if 'offset_constant' not in new_values['data'].keys() or \
734
                not (isinstance(new_values['data']['offset_constant'], float) or
735
                     isinstance(new_values['data']['offset_constant'], int)):
736
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
737
                                   description='API.INVALID_OFFSET_CONSTANT_VALUE')
738
        offset_constant = new_values['data']['offset_constant']
739
740
        if 'is_trend' not in new_values['data'].keys() or \
741
                not isinstance(new_values['data']['is_trend'], bool):
742
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
743
                                   description='API.INVALID_IS_TREND_VALUE')
744
        is_trend = new_values['data']['is_trend']
745
746
        if 'is_virtual' not in new_values['data'].keys() or \
747
                not isinstance(new_values['data']['is_virtual'], bool):
748
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
749
                                   description='API.INVALID_IS_VIRTUAL_VALUE')
750
        if new_values['data']['is_virtual'] is True and object_type == 'DIGITAL_VALUE':
751
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
752
                                   description='API.VIRTUAL_POINT_CAN_NOT_BE_DIGITAL_VALUE')
753
        if new_values['data']['is_virtual'] is True and object_type == 'TEXT_VALUE':
754
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
755
                                   description='API.VIRTUAL_POINT_CAN_NOT_BE_TEXT_VALUE')
756
        is_virtual = new_values['data']['is_virtual']
757
758
        if 'address' not in new_values['data'].keys() or \
759
                not isinstance(new_values['data']['address'], str) or \
760
                len(str.strip(new_values['data']['address'])) == 0:
761
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
762
                                   description='API.INVALID_ADDRESS')
763
        address = str.strip(new_values['data']['address'])
764
765
        if 'description' in new_values['data'].keys() and \
766
                new_values['data']['description'] is not None and \
767
                len(str(new_values['data']['description'])) > 0:
768
            description = str.strip(new_values['data']['description'])
769
        else:
770
            description = None
771
772
        if 'faults' in new_values['data'].keys() and \
773
                new_values['data']['faults'] is not None and \
774
                len(str(new_values['data']['faults'])) > 0:
775
            faults = str.strip(new_values['data']['faults'])
776
        else:
777
            faults = None
778
779
        if 'definitions' in new_values['data'].keys() and \
780
                new_values['data']['definitions'] is not None and \
781
                len(str(new_values['data']['definitions'])) > 0:
782
            definitions = str.strip(new_values['data']['definitions'])
783
        else:
784
            definitions = None
785
        cnx = mysql.connector.connect(**config.myems_system_db)
786
        cursor = cnx.cursor()
787
788
        cursor.execute(" SELECT name "
789
                       " FROM tbl_points "
790
                       " WHERE id = %s ", (id_,))
791
        if cursor.fetchone() is None:
792
            cursor.close()
793
            cnx.close()
794
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
795
                                   description='API.POINT_NOT_FOUND')
796
797
        cursor.execute(" SELECT name "
798
                       " FROM tbl_data_sources "
799
                       " WHERE id = %s ", (data_source_id,))
800
        if cursor.fetchone() is None:
801
            cursor.close()
802
            cnx.close()
803
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
804
                                   description='API.INVALID_DATA_SOURCE_ID')
805
806
        cursor.execute(" SELECT name "
807
                       " FROM tbl_points "
808
                       " WHERE name = %s AND data_source_id = %s AND id != %s ", (name, data_source_id, id_))
809
        if cursor.fetchone() is not None:
810
            cursor.close()
811
            cnx.close()
812
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
813
                                   description='API.POINT_NAME_IS_ALREADY_IN_USE')
814
815
        update_row = (" UPDATE tbl_points "
816
                      " SET name = %s, data_source_id = %s, "
817
                      "     object_type = %s, units = %s, "
818
                      "     high_limit = %s, low_limit = %s, higher_limit = %s, lower_limit = %s, ratio = %s, "
819
                      "     offset_constant = %s, is_trend = %s, is_virtual = %s, address = %s, description = %s, "
820
                      "     faults = %s, definitions = %s "
821
                      " WHERE id = %s ")
822
        cursor.execute(update_row, (name,
823
                                    data_source_id,
824
                                    object_type,
825
                                    units,
826
                                    high_limit,
827
                                    low_limit,
828
                                    higher_limit,
829
                                    lower_limit,
830
                                    ratio,
831
                                    offset_constant,
832
                                    is_trend,
833
                                    is_virtual,
834
                                    address,
835
                                    description,
836
                                    faults,
837
                                    definitions,
838
                                    id_,))
839
        cnx.commit()
840
841
        cursor.close()
842
        cnx.close()
843
844
        # Clear cache after updating point
845
        clear_point_cache(int(id_))
846
847
        resp.status = falcon.HTTP_200
848
849
850
class PointLimit:
851
    def __init__(self):
852
        pass
853
854
    @staticmethod
855
    def on_options(req, resp, id_):
856
        _ = req
857
        resp.status = falcon.HTTP_200
858
        _ = id_
859
860
    @staticmethod
861
    @user_logger
862
    def on_put(req, resp, id_):
863
        """Handles PUT requests"""
864
        admin_control(req)
865
        try:
866
            raw_json = req.stream.read().decode('utf-8')
867
        except UnicodeDecodeError as ex:
868
            print("Failed to decode request")
869
            raise falcon.HTTPError(status=falcon.HTTP_400,
870
                                   title='API.BAD_REQUEST',
871
                                   description='API.INVALID_ENCODING')
872
        except Exception as ex:
873
            print("Unexpected error reading request stream")
874
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.EXCEPTION', description=str(ex))
875
876
        if not id_.isdigit() or int(id_) <= 0:
877
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
878
                                   description='API.INVALID_POINT_ID')
879
880
        new_values = json.loads(raw_json)
881
882
        if 'high_limit' not in new_values['data'].keys() or \
883
                not (isinstance(new_values['data']['high_limit'], float) or
884
                     isinstance(new_values['data']['high_limit'], int)):
885
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
886
                                   description='API.INVALID_HIGH_LIMIT_VALUE')
887
        high_limit = new_values['data']['high_limit']
888
889
        if 'low_limit' not in new_values['data'].keys() or \
890
                not (isinstance(new_values['data']['low_limit'], float) or
891
                     isinstance(new_values['data']['low_limit'], int)):
892
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
893
                                   description='API.INVALID_LOW_LIMIT_VALUE')
894
        low_limit = new_values['data']['low_limit']
895
896
        if 'higher_limit' not in new_values['data'].keys() or \
897
                not (isinstance(new_values['data']['higher_limit'], float) or
898
                     isinstance(new_values['data']['higher_limit'], int)):
899
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
900
                                   description='API.INVALID_HIGHER_LIMIT_VALUE')
901
        higher_limit = new_values['data']['higher_limit']
902
903
        if 'lower_limit' not in new_values['data'].keys() or \
904
                not (isinstance(new_values['data']['lower_limit'], float) or
905
                     isinstance(new_values['data']['lower_limit'], int)):
906
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
907
                                   description='API.INVALID_LOWER_LIMIT_VALUE')
908
        lower_limit = new_values['data']['lower_limit']
909
910
        cnx = mysql.connector.connect(**config.myems_system_db)
911
        cursor = cnx.cursor()
912
913
        cursor.execute(" SELECT name "
914
                       " FROM tbl_points "
915
                       " WHERE id = %s ", (id_,))
916
        if cursor.fetchone() is None:
917
            cursor.close()
918
            cnx.close()
919
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
920
                                   description='API.POINT_NOT_FOUND')
921
922
        update_row = (" UPDATE tbl_points "
923
                      " SET  high_limit = %s, low_limit = %s, higher_limit = %s, lower_limit = %s "
924
                      " WHERE id = %s ")
925
        cursor.execute(update_row, (high_limit,
926
                                    low_limit,
927
                                    higher_limit,
928
                                    lower_limit,
929
                                    id_,))
930
        cnx.commit()
931
932
        cursor.close()
933
        cnx.close()
934
935
        # Clear cache after updating point limits
936
        clear_point_cache(int(id_))
937
938
        resp.status = falcon.HTTP_200
939
940
941
class PointSetValue:
942
    def __init__(self):
943
        pass
944
945
    @staticmethod
946
    def on_options(req, resp, id_):
947
        _ = req
948
        resp.status = falcon.HTTP_200
949
        _ = id_
950
951
    @staticmethod
952
    @user_logger
953
    def on_put(req, resp, id_):
954
        """Handles PUT requests"""
955
        admin_control(req)
956
        try:
957
            raw_json = req.stream.read().decode('utf-8')
958
        except UnicodeDecodeError as ex:
959
            print("Failed to decode request")
960
            raise falcon.HTTPError(status=falcon.HTTP_400,
961
                                   title='API.BAD_REQUEST',
962
                                   description='API.INVALID_ENCODING')
963
        except Exception as ex:
964
            print("Unexpected error reading request stream")
965
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.EXCEPTION', description=str(ex))
966
967
        if not id_.isdigit() or int(id_) <= 0:
968
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
969
                                   description='API.INVALID_POINT_ID')
970
971
        new_values = json.loads(raw_json)
972
973
        if 'set_value' not in new_values['data'].keys() or \
974
                not (isinstance(new_values['data']['set_value'], float) or
975
                     isinstance(new_values['data']['set_value'], int)):
976
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
977
                                   description='API.INVALID_SET_VALUE')
978
        set_value = new_values['data']['set_value']
979
980
        cnx = mysql.connector.connect(**config.myems_system_db)
981
        cursor = cnx.cursor()
982
983
        cursor.execute(" SELECT name "
984
                       " FROM tbl_points "
985
                       " WHERE id = %s ", (id_,))
986
        if cursor.fetchone() is None:
987
            cursor.close()
988
            cnx.close()
989
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
990
                                   description='API.POINT_NOT_FOUND')
991
992
        add_value = (" INSERT INTO tbl_points_set_values (point_id, set_value, utc_date_time) "
993
                     " VALUES (%s, %s, %s) ")
994
        cursor.execute(add_value, (id_, set_value, datetime.utcnow()))
995
        cnx.commit()
996
        cursor.close()
997
        cnx.close()
998
999
        resp.status = falcon.HTTP_200
1000
1001
1002
class PointExport:
1003
    def __init__(self):
1004
        pass
1005
1006
    @staticmethod
1007
    def on_options(req, resp, id_):
1008
        _ = req
1009
        resp.status = falcon.HTTP_200
1010
        _ = id_
1011
1012 View Code Duplication
    @staticmethod
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1013
    def on_get(req, resp, id_):
1014
        """Handles GET requests"""
1015
        admin_control(req)
1016
        if not id_.isdigit() or int(id_) <= 0:
1017
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1018
                                   description='API.INVALID_POINT_ID')
1019
1020
        # Redis cache key
1021
        cache_key = f'point:export:{id_}'
1022
        cache_expire = 28800  # 8 hours in seconds (long-term cache)
1023
1024
        # Try to get from Redis cache (only if Redis is enabled)
1025
        redis_client = None
1026
        if config.redis.get('is_enabled', False):
1027
            try:
1028
                redis_client = redis.Redis(
1029
                    host=config.redis['host'],
1030
                    port=config.redis['port'],
1031
                    password=config.redis['password'] if config.redis['password'] else None,
1032
                    db=config.redis['db'],
1033
                    decode_responses=True,
1034
                    socket_connect_timeout=2,
1035
                    socket_timeout=2
1036
                )
1037
                redis_client.ping()
1038
                cached_result = redis_client.get(cache_key)
1039
                if cached_result:
1040
                    resp.text = cached_result
1041
                    return
1042
            except Exception:
1043
                # If Redis connection fails, continue to database query
1044
                pass
1045
1046
        # Cache miss or Redis error - query database
1047
        cnx = mysql.connector.connect(**config.myems_system_db)
1048
        cursor = cnx.cursor()
1049
1050
        query = (" SELECT id, name, uuid "
1051
                 " FROM tbl_data_sources ")
1052
        cursor.execute(query)
1053
        rows_data_sources = cursor.fetchall()
1054
1055
        data_source_dict = dict()
1056
        if rows_data_sources is not None and len(rows_data_sources) > 0:
1057
            for row in rows_data_sources:
1058
                data_source_dict[row[0]] = {"id": row[0],
1059
                                            "name": row[1],
1060
                                            "uuid": row[2]}
1061
1062
        query = (" SELECT id, name, data_source_id, object_type, units, "
1063
                 "        high_limit, low_limit, higher_limit, lower_limit, ratio, offset_constant, "
1064
                 "        is_trend, is_virtual, address, description, faults, definitions "
1065
                 " FROM tbl_points "
1066
                 " WHERE id = %s ")
1067
        cursor.execute(query, (id_,))
1068
        row = cursor.fetchone()
1069
        cursor.close()
1070
        cnx.close()
1071
        if row is None:
1072
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
1073
                                   description='API.POINT_NOT_FOUND')
1074
1075
        result = {"id": row[0],
1076
                  "name": row[1],
1077
                  "data_source": data_source_dict.get(row[2], None),
1078
                  "object_type": row[3],
1079
                  "units": row[4],
1080
                  "high_limit": row[5],
1081
                  "low_limit": row[6],
1082
                  "higher_limit": row[7],
1083
                  "lower_limit": row[8],
1084
                  "ratio": Decimal(row[9]),
1085
                  "offset_constant": Decimal(row[10]),
1086
                  "is_trend": bool(row[11]),
1087
                  "is_virtual": bool(row[12]),
1088
                  "address": row[13],
1089
                  "description": row[14],
1090
                  "faults": row[15],
1091
                  "definitions": row[16]}
1092
1093
        # Store result in Redis cache
1094
        result_json = json.dumps(result)
1095
        if redis_client:
1096
            try:
1097
                redis_client.setex(cache_key, cache_expire, result_json)
1098
            except Exception:
1099
                # If cache set fails, ignore and continue
1100
                pass
1101
1102
        resp.text = result_json
1103
1104
1105
class PointImport:
1106
    def __init__(self):
1107
        pass
1108
1109
    @staticmethod
1110
    def on_options(req, resp):
1111
        _ = req
1112
        resp.status = falcon.HTTP_200
1113
1114
    @staticmethod
1115
    @user_logger
1116
    def on_post(req, resp):
1117
        """Handles POST requests"""
1118
        admin_control(req)
1119
        try:
1120
            raw_json = req.stream.read().decode('utf-8')
1121
        except UnicodeDecodeError as ex:
1122
            print("Failed to decode request")
1123
            raise falcon.HTTPError(status=falcon.HTTP_400,
1124
                                   title='API.BAD_REQUEST',
1125
                                   description='API.INVALID_ENCODING')
1126
        except Exception as ex:
1127
            print("Unexpected error reading request stream")
1128
            raise falcon.HTTPError(status=falcon.HTTP_400,
1129
                                   title='API.BAD_REQUEST',
1130
                                   description='API.FAILED_TO_READ_REQUEST_STREAM')
1131
1132
        new_values = json.loads(raw_json)
1133
1134
        if 'name' not in new_values.keys() or \
1135
                not isinstance(new_values['name'], str) or \
1136
                len(str.strip(new_values['name'])) == 0:
1137
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1138
                                   description='API.INVALID_POINT_NAME')
1139
        name = str.strip(new_values['name'])
1140
1141
        if 'id' not in new_values['data_source'].keys() or \
1142
                not isinstance(new_values['data_source']['id'], int) or \
1143
                new_values['data_source']['id'] <= 0:
1144
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1145
                                   description='API.INVALID_DATA_SOURCE_ID')
1146
        data_source_id = new_values['data_source']['id']
1147
1148
        if 'object_type' not in new_values.keys() \
1149
                or str.strip(new_values['object_type']) not in (
1150
                'ENERGY_VALUE', 'ANALOG_VALUE', 'DIGITAL_VALUE', 'TEXT_VALUE'):
1151
            raise falcon.HTTPError(status=falcon.HTTP_400,
1152
                                   title='API.BAD_REQUEST',
1153
                                   description='API.INVALID_OBJECT_TYPE')
1154
        object_type = str.strip(new_values['object_type'])
1155
1156
        if 'units' not in new_values.keys() or \
1157
                not isinstance(new_values['units'], str) or \
1158
                len(str.strip(new_values['units'])) == 0:
1159
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1160
                                   description='API.INVALID_UNITS')
1161
        units = str.strip(new_values['units'])
1162
1163
        if 'high_limit' not in new_values.keys() or \
1164
                not (isinstance(new_values['high_limit'], float) or
1165
                     isinstance(new_values['high_limit'], int)):
1166
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1167
                                   description='API.INVALID_HIGH_LIMIT_VALUE')
1168
        high_limit = new_values['high_limit']
1169
1170
        if 'low_limit' not in new_values.keys() or \
1171
                not (isinstance(new_values['low_limit'], float) or
1172
                     isinstance(new_values['low_limit'], int)):
1173
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1174
                                   description='API.INVALID_LOW_LIMIT_VALUE')
1175
        low_limit = new_values['low_limit']
1176
1177
        # the higher_limit is optional
1178
        if 'higher_limit' not in new_values.keys() or \
1179
                new_values['higher_limit'] is None:
1180
            higher_limit = None
1181
        elif not (isinstance(new_values['higher_limit'], float) or
1182
                  isinstance(new_values['higher_limit'], int)):
1183
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1184
                                   description='API.INVALID_HIGHER_LIMIT_VALUE')
1185
        else:
1186
            higher_limit = new_values['higher_limit']
1187
1188
        # the lower_limit is optional
1189
        if 'lower_limit' not in new_values.keys() or \
1190
                new_values['lower_limit'] is None:
1191
            lower_limit = None
1192
        elif not (isinstance(new_values['lower_limit'], float) or
1193
                  isinstance(new_values['lower_limit'], int)):
1194
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1195
                                   description='API.INVALID_LOWER_LIMIT_VALUE')
1196
        else:
1197
            lower_limit = new_values['lower_limit']
1198
1199
        if 'ratio' not in new_values.keys() or \
1200
                not (isinstance(new_values['ratio'], float) or
1201
                     isinstance(new_values['ratio'], int)):
1202
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1203
                                   description='API.INVALID_RATIO_VALUE')
1204
        ratio = new_values['ratio']
1205
1206
        if 'offset_constant' not in new_values.keys() or \
1207
                not (isinstance(new_values['offset_constant'], float) or
1208
                     isinstance(new_values['offset_constant'], int)):
1209
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1210
                                   description='API.INVALID_OFFSET_CONSTANT_VALUE')
1211
        offset_constant = new_values['offset_constant']
1212
1213
        if 'is_trend' not in new_values.keys() or \
1214
                not isinstance(new_values['is_trend'], bool):
1215
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1216
                                   description='API.INVALID_IS_TREND_VALUE')
1217
        is_trend = new_values['is_trend']
1218
1219
        if 'is_virtual' not in new_values.keys() or \
1220
                not isinstance(new_values['is_virtual'], bool):
1221
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1222
                                   description='API.INVALID_IS_VIRTUAL_VALUE')
1223
        if new_values['is_virtual'] is True and object_type == 'DIGITAL_VALUE':
1224
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1225
                                   description='API.VIRTUAL_POINT_CAN_NOT_BE_DIGITAL_VALUE')
1226
        if new_values['is_virtual'] is True and object_type == 'TEXT_VALUE':
1227
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1228
                                   description='API.VIRTUAL_POINT_CAN_NOT_BE_TEXT_VALUE')
1229
        is_virtual = new_values['is_virtual']
1230
1231
        if 'address' not in new_values.keys() or \
1232
                not isinstance(new_values['address'], str) or \
1233
                len(str.strip(new_values['address'])) == 0:
1234
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1235
                                   description='API.INVALID_ADDRESS')
1236
        address = str.strip(new_values['address'])
1237
1238
        if 'description' in new_values.keys() and \
1239
                new_values['description'] is not None and \
1240
                len(str(new_values['description'])) > 0:
1241
            description = str.strip(new_values['description'])
1242
        else:
1243
            description = None
1244
1245
        if 'faults' in new_values.keys() and \
1246
                new_values['faults'] is not None and \
1247
                len(str(new_values['faults'])) > 0:
1248
            faults = str.strip(new_values['faults'])
1249
        else:
1250
            faults = None
1251
1252
        if 'definitions' in new_values.keys() and \
1253
                new_values['definitions'] is not None and \
1254
                len(str(new_values['definitions'])) > 0:
1255
            definitions = str.strip(new_values['definitions'])
1256
        else:
1257
            definitions = None
1258
1259
        cnx = mysql.connector.connect(**config.myems_system_db)
1260
        cursor = cnx.cursor()
1261
1262
        cursor.execute(" SELECT name "
1263
                       " FROM tbl_points "
1264
                       " WHERE name = %s AND data_source_id = %s ", (name, data_source_id))
1265
        if cursor.fetchone() is not None:
1266
            cursor.close()
1267
            cnx.close()
1268
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1269
                                   description='API.POINT_NAME_IS_ALREADY_IN_USE')
1270
1271
        cursor.execute(" SELECT name "
1272
                       " FROM tbl_data_sources "
1273
                       " WHERE id = %s ", (data_source_id,))
1274
        if cursor.fetchone() is None:
1275
            cursor.close()
1276
            cnx.close()
1277
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1278
                                   description='API.INVALID_DATA_SOURCE_ID')
1279
1280
        add_value = (" INSERT INTO tbl_points (name, data_source_id, object_type, units, "
1281
                     "                         high_limit, low_limit, higher_limit, lower_limit, ratio, "
1282
                     "                         offset_constant, is_trend, is_virtual, address, description, faults, "
1283
                     "                         definitions) "
1284
                     " VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) ")
1285
        cursor.execute(add_value, (name,
1286
                                   data_source_id,
1287
                                   object_type,
1288
                                   units,
1289
                                   high_limit,
1290
                                   low_limit,
1291
                                   higher_limit,
1292
                                   lower_limit,
1293
                                   ratio,
1294
                                   offset_constant,
1295
                                   is_trend,
1296
                                   is_virtual,
1297
                                   address,
1298
                                   description,
1299
                                   faults,
1300
                                   definitions))
1301
        new_id = cursor.lastrowid
1302
        cnx.commit()
1303
        cursor.close()
1304
        cnx.close()
1305
1306
        # Clear cache after importing new point
1307
        clear_point_cache()
1308
1309
        resp.status = falcon.HTTP_201
1310
        resp.location = '/points/' + str(new_id)
1311
1312
1313
class PointClone:
1314
    def __init__(self):
1315
        pass
1316
1317
    @staticmethod
1318
    def on_options(req, resp, id_):
1319
        _ = req
1320
        resp.status = falcon.HTTP_200
1321
        _ = id_
1322
1323
    @staticmethod
1324
    @user_logger
1325
    def on_post(req, resp, id_):
1326
        admin_control(req)
1327
        if not id_.isdigit() or int(id_) <= 0:
1328
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1329
                                   description='API.INVALID_POINT_ID')
1330
1331
        cnx = mysql.connector.connect(**config.myems_system_db)
1332
        cursor = cnx.cursor()
1333
1334
        query = (" SELECT id, name, data_source_id, object_type, units, "
1335
                 "        high_limit, low_limit, higher_limit, lower_limit, ratio, offset_constant, "
1336
                 "        is_trend, is_virtual, address, description, faults, definitions "
1337
                 " FROM tbl_points "
1338
                 " WHERE id = %s ")
1339
        cursor.execute(query, (id_,))
1340
        row = cursor.fetchone()
1341
        if row is None:
1342
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
1343
                                   description='API.POINT_NOT_FOUND')
1344
1345
        result = {"id": row[0],
1346
                  "name": row[1],
1347
                  "data_source_id": row[2],
1348
                  "object_type": row[3],
1349
                  "units": row[4],
1350
                  "high_limit": row[5],
1351
                  "low_limit": row[6],
1352
                  "higher_limit": row[7],
1353
                  "lower_limit": row[8],
1354
                  "ratio": Decimal(row[9]),
1355
                  "offset_constant": Decimal(row[10]),
1356
                  "is_trend": bool(row[11]),
1357
                  "is_virtual": bool(row[12]),
1358
                  "address": row[13],
1359
                  "description": row[14],
1360
                  "faults": row[15],
1361
                  "definitions": row[16]}
1362
        timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6])
1363
        if config.utc_offset[0] == '-':
1364
            timezone_offset = -timezone_offset
1365
        new_name = (str.strip(result['name']) +
1366
                    (datetime.utcnow() + timedelta(minutes=timezone_offset)).isoformat(sep='-', timespec='seconds'))
1367
        add_value = (" INSERT INTO tbl_points (name, data_source_id, object_type, units, "
1368
                     "                         high_limit, low_limit, higher_limit, lower_limit, ratio, "
1369
                     "                         offset_constant, is_trend, is_virtual, address, description, faults, "
1370
                     "                         definitions) "
1371
                     " VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) ")
1372
        cursor.execute(add_value, (new_name,
1373
                                   result['data_source_id'],
1374
                                   result['object_type'],
1375
                                   result['units'],
1376
                                   result['high_limit'],
1377
                                   result['low_limit'],
1378
                                   result['higher_limit'],
1379
                                   result['lower_limit'],
1380
                                   result['ratio'],
1381
                                   result['offset_constant'],
1382
                                   result['is_trend'],
1383
                                   result['is_virtual'],
1384
                                   result['address'],
1385
                                   result['description'],
1386
                                   result['faults'],
1387
                                   result['definitions']))
1388
        new_id = cursor.lastrowid
1389
        cnx.commit()
1390
        cursor.close()
1391
        cnx.close()
1392
1393
        # Clear cache after cloning point
1394
        clear_point_cache()
1395
1396
        resp.status = falcon.HTTP_201
1397
        resp.location = '/points/' + str(new_id)
1398
1399