Fixed live charts updarte in radar mode

This commit is contained in:
RZ_MINIX\rober 2025-06-24 19:16:53 -07:00
parent 5649360929
commit 7317e5e67e
2 changed files with 305 additions and 133 deletions

View File

@ -758,6 +758,30 @@ def GetDeviceDetailsSingle(device_id):
return device_record
def GetDeviceDetailsSingleFromMac(device_mac):
conn = get_db_connection()
sql = "SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'devices';"
with conn.cursor() as cur:
cur.execute(sql)
columns_names = cur.fetchall()
sql = "SELECT * FROM public.devices WHERE device_mac = '" + device_mac + "'"
device_record = {}
with conn.cursor() as cur:
cur.execute(sql)
result = cur.fetchone() #cur.fetchall()
if result != None:
cnt = 0
for field in columns_names:
device_record[field[0]] = result[cnt]
cnt += 1
else:
#device is not in DB so first lets find it in
pass
return device_record
def DeploymentDetails(deployment_id):
@ -1546,20 +1570,20 @@ def StoreDevice2DB(parameters, editing_device_id):
description = '{CleanObject(parameters.get('description'))}',
location = '{CleanObject(parameters.get('location'))}',
close_to = '{CleanObject(parameters.get('close_to'))}',
group_id = {CleanObject(parameters.get('group_id'))},
radar_threshold = '{CleanObject(parameters.get('radar_threshold'))}',
temperature_calib = '{CleanObject(parameters.get('temperature_calib'))}',
humidity_calib = '{CleanObject(parameters.get('humidity_calib'))}'
WHERE device_id = {editing_device_id};
"""
else:
sql = f"""
INSERT INTO public.devices
(device_mac, well_id, description, location, close_to, radar_threshold, temperature_calib, humidity_calib)
(device_mac, well_id, description, location, close_to, radar_threshold, temperature_calib, humidity_calib, group_id)
VALUES
('{CleanObject(parameters.get('device_mac'))}', '{CleanObject(parameters.get('well_id'))}', '{CleanObject(parameters.get('description'))}',
'{CleanObject(parameters.get('location'))}', '{CleanObject(parameters.get('close_to'))}', '{CleanObject(parameters.get('radar_threshold'))}',
'{CleanObject(parameters.get('temperature_calib'))}', '{CleanObject(parameters.get('humidity_calib'))}');
'{CleanObject(parameters.get('temperature_calib'))}', '{CleanObject(parameters.get('humidity_calib'))}', {CleanObject(parameters.get('group_id'))});
"""
logger.debug(f"sql= {sql}")
# Execute update query
@ -7718,7 +7742,7 @@ def CreateMapFast(map_file, devices_list, selected_date, bw, time_zone_s, radar_
stripes = devices_c * sensors_c #2 for upper maxes, lower mins
arr_source_template = np.full((stripes, minutes+4), -0.001, dtype=float)
arr_stretched_template = np.zeros((int(stripes*stretch_by), minutes, 3), dtype=np.uint8) # 3 for RGB channels
arr_source = fast_fill_array_from_timescale_single(day_data, time_from_str, devices_list[1], arr_source_template, s_table_temp[0], time_zone_s)
arr_source = fast_fill_array_from_timescale(day_data, time_from_str, devices_list[1], arr_source_template, time_zone_s)
arr_source = AddLimits_optimized(arr_source, devices_c, sensors_c, percentile=100)
scaled_day = CalcExtremes(arr_source, minutes, stripes)
arr_stretched, vocs_scaled = FillImage_optimized(scaled_day, devices_c, sensors_c, arr_stretched_template, group_by, bw)
@ -13575,6 +13599,17 @@ def decode_state(b64_state):
logger.error(f"Failed to decode client_state '{b64_state}': {e}")
return []
def create_client_state(base_event, call_control_id, prefix):
"""Create a base64 encoded client state string as required by Telnyx API"""
# Create the plain text client state string
plain_state = f"{prefix}_{base_event}_{call_control_id[:8]}" if call_control_id else f"{prefix}_{base_event}_unknownccid"
# Encode to base64 as required by Telnyx API
base64_state = base64.b64encode(plain_state.encode('utf-8')).decode('ascii')
logger.debug(f"Client state created: '{plain_state}' -> base64: '{base64_state}'")
return base64_state
def send_telnyx_command(action_path, params, api_key):
"""
Sends a command to the Telnyx Call Control API actions endpoint.
@ -14614,6 +14649,35 @@ def AddLimits_optimized_filtered(arr_source, devices_c, sensors_c, filtered_s_ta
arr_source[:, 1441] = max_vals
return arr_source
def GetNextWellId(min_well_id):
conn = get_db_connection()
sql = """
SELECT COALESCE(MAX(well_id), 0) AS max_well_id
FROM public.devices
"""
try:
with conn.cursor() as cur:
cur.execute(sql)
result = cur.fetchone()
if result == None:
return min_well_id
else:
max_wel_id = result[0]
if max_wel_id != None and min_well_id != None:
if max_wel_id + 1 > min_well_id:
return max_wel_id + 1
else:
return min_well_id
else:
if max_wel_id != None:
return max_wel_id + 1
except Exception as e:
return min_well_id
#==================================== ADD FUNCTIONS BEFORE ============================================
# Main API class
@ -15925,10 +15989,9 @@ class WellApi:
time_zone_s = GetTimeZoneOfDeployment(deployment_id)
selected_date = form_data.get('date')
date_to = form_data.get('to_date')
if date_to == None:
if date_to == None:
date_to = selected_date
start_date = datetime.datetime.strptime(selected_date, '%Y-%m-%d')
end_date = datetime.datetime.strptime(date_to, '%Y-%m-%d')
@ -16595,28 +16658,40 @@ class WellApi:
elif function == "get_device":
device_id = form_data.get('device_id')
devices = GetVisibleDevices(privileges)
dataa = {}
dataa['Function'] = "device_details"
dataa['device_details'] = []
if privileges == "-1":
#device_det = GetDeviceDetails(device_id)
device_det = GetDeviceDetailsSingle(device_id)
if device_det['radar_threshold'] == None or device_det['radar_threshold'] == "":
device_det['radar_threshold'] = '["s3_max",12]'
dataa['device_details'] = device_det
else:
devices_list = []
for device_id_temp in devices:
devices_list.append(str(device_id_temp[0]))
device_mac = form_data.get('mac')
min_well_id = form_data.get('min_well_id')
if device_id in devices_list:
if device_mac != None:
device_det = GetDeviceDetailsSingleFromMac(device_mac)
print(device_det)
dataa = {}
dataa['Function'] = "device_details"
dataa['device_details'] = device_det
if device_det == {}:
dataa['next_well_id'] = GetNextWellId(min_well_id)
else:
devices = GetVisibleDevices(privileges)
dataa = {}
dataa['Function'] = "device_details"
dataa['device_details'] = {}
if privileges == "-1":
#device_det = GetDeviceDetails(device_id)
device_det = GetDeviceDetailsSingle(device_id)
if device_det['radar_threshold'] == None or device_det['radar_threshold'] == "":
device_det['radar_threshold'] = '["s3_max",12]'
dataa['device_details'] = device_det
else:
devices_list = []
for device_id_temp in devices:
devices_list.append(str(device_id_temp[0]))
if device_id in devices_list:
device_det = GetDeviceDetailsSingle(device_id)
if device_det['radar_threshold'] == None or device_det['radar_threshold'] == "":
device_det['radar_threshold'] = '["s3_max",12]'
dataa['device_details'] = device_det
resp.media = package_response(dataa)

View File

@ -5,7 +5,7 @@
<!--
Written By: Robert Zmrzli robert@zmrinc.com
deployment.html
V3.0
V3.01
-->
@ -1619,103 +1619,200 @@ function ProcessQueue() {
vall = -1000000;
switch(message["payload"]["mtype"]) {
case "radar":
case 16:
//lets charts dictate which sensors are to be displayed
MAC = message["topic"].substring(1);
if(document.getElementById("map_type").value == 1){
i = 4; //4=radar
if (MAC+sensor_tablesn[i] in series_indexes){
i = 4; //4=radar
if (MAC+sensor_tablesn[i] in series_indexes){
series_index = series_indexes[MAC+sensor_tablesn[i]];
series = UniversalChart.series.items[series_index];
if (series.visible == true) {
radar_part = document.getElementById("RadarPart").value;
timee = message["payload"]["time"];
switch(radar_part) {
case "absent":
vall = message["payload"]["radar"][0][1]/message["payload"]["radar"][0][0];
break;
case "moving":
vall = message["payload"]["radar"][0][2]/message["payload"]["radar"][0][0];
break;
case "stationary":
vall = message["payload"]["radar"][0][3]/message["payload"]["radar"][0][0];
break;
case "both":
vall = message["payload"]["radar"][0][4]/message["payload"]["radar"][0][0];
break;
case "m0":
vall = message["payload"]["radar"][0][5]/message["payload"]["radar"][0][0];
break;
case "m1":
vall = message["payload"]["radar"][0][6]/message["payload"]["radar"][0][0];
break;
case "m2":
vall = message["payload"]["radar"][0][7]/message["payload"]["radar"][0][0];
break;
case "m3":
vall = message["payload"]["radar"][0][8]/message["payload"]["radar"][0][0];
break;
case "m4":
vall = message["payload"]["radar"][0][9]/message["payload"]["radar"][0][0];
break;
case "m5":
vall = message["payload"]["radar"][0][10]/message["payload"]["radar"][0][0];
break;
case "m6":
vall = message["payload"]["radar"][0][11]/message["payload"]["radar"][0][0];
break;
case "m7":
vall = message["payload"]["radar"][0][12]/message["payload"]["radar"][0][0];
break;
case "m8":
vall = message["payload"]["radar"][0][13]/message["payload"]["radar"][0][0];
break;
case "s2":
vall = message["payload"]["radar"][1][3]/message["payload"]["radar"][1][0];
break;
case "s3":
vall = message["payload"]["radar"][1][4]/message["payload"]["radar"][1][0];
break;
case "s4":
vall = message["payload"]["radar"][1][5]/message["payload"]["radar"][1][0];
break;
case "s5":
vall = message["payload"]["radar"][1][6]/message["payload"]["radar"][1][0];
break;
case "s6":
vall = message["payload"]["radar"][1][7]/message["payload"]["radar"][1][0];
break;
case "s7":
vall = message["payload"]["radar"][1][8]/message["payload"]["radar"][1][0];
break;
case "s8":
vall = message["payload"]["radar"][1][9]/message["payload"]["radar"][1][0];
break;
case "s28":
vall = 0;
for (i_=3; i_<=9; i_++){
vall += message["payload"]["radar"][1][i_]
}
vall = vall/(7.0*message["payload"]["radar"][1][0]);
break;
}
}
if (vall != -1000000) {
epoch_ms = (timee+hours_delta * 3600) * 1000;
series.data.x.push(new Date(epoch_ms));
series.data.values.push(vall);
//series.data.values.push(vall);
//time_obj = new Date(epoch_ms);
//series.data.x.push(time_obj);
series_index = series_indexes[MAC+sensor_tablesn[i]];
series = UniversalChart.series.items[series_index];
if (series.visible == true) {
radar_part = document.getElementById("RadarPart").value;
timee = message["payload"]["time"];
switch(radar_part) {
case "absent":
vall = message["payload"]["radar"][0][1]/message["payload"]["radar"][0][0];
break;
case "moving":
vall = message["payload"]["radar"][0][2]/message["payload"]["radar"][0][0];
break;
case "stationary":
vall = message["payload"]["radar"][0][3]/message["payload"]["radar"][0][0];
break;
case "both":
vall = message["payload"]["radar"][0][4]/message["payload"]["radar"][0][0];
break;
case "m0":
vall = message["payload"]["radar"][0][5]/message["payload"]["radar"][0][0];
break;
case "m1":
vall = message["payload"]["radar"][0][6]/message["payload"]["radar"][0][0];
break;
case "m2":
vall = message["payload"]["radar"][0][7]/message["payload"]["radar"][0][0];
break;
case "m3":
vall = message["payload"]["radar"][0][8]/message["payload"]["radar"][0][0];
break;
case "m4":
vall = message["payload"]["radar"][0][9]/message["payload"]["radar"][0][0];
break;
case "m5":
vall = message["payload"]["radar"][0][10]/message["payload"]["radar"][0][0];
break;
case "m6":
vall = message["payload"]["radar"][0][11]/message["payload"]["radar"][0][0];
break;
case "m7":
vall = message["payload"]["radar"][0][12]/message["payload"]["radar"][0][0];
break;
case "m8":
vall = message["payload"]["radar"][0][13]/message["payload"]["radar"][0][0];
break;
case "s2":
vall = message["payload"]["radar"][1][3]/message["payload"]["radar"][1][0];
break;
case "s3":
vall = message["payload"]["radar"][1][4]/message["payload"]["radar"][1][0];
break;
case "s4":
vall = message["payload"]["radar"][1][5]/message["payload"]["radar"][1][0];
break;
case "s5":
vall = message["payload"]["radar"][1][6]/message["payload"]["radar"][1][0];
break;
case "s6":
vall = message["payload"]["radar"][1][7]/message["payload"]["radar"][1][0];
break;
case "s7":
vall = message["payload"]["radar"][1][8]/message["payload"]["radar"][1][0];
break;
case "s8":
vall = message["payload"]["radar"][1][9]/message["payload"]["radar"][1][0];
break;
case "s28":
vall = 0;
for (i_=3; i_<=9; i_++){
vall += message["payload"]["radar"][1][i_]
}
vall = vall/(7.0*message["payload"]["radar"][1][0]);
break;
UniversalChart.axes.bottom.labels.roundFirst = true;
UniversalChart.axes.bottom.labels.dateFormat = "shortDateTime";
UniversalChart.draw();
}
}
if (vall != -1000000) {
epoch_ms = (timee+hours_delta * 3600) * 1000;
series.data.x.push(new Date(epoch_ms));
series.data.values.push(vall);
//series.data.values.push(vall);
//time_obj = new Date(epoch_ms);
//series.data.x.push(time_obj);
}
else{
for(i = 0; i<=18; i++){
if (MAC+radar_tablesn[i] in series_indexes){
UniversalChart.axes.bottom.labels.roundFirst = true;
UniversalChart.axes.bottom.labels.dateFormat = "shortDateTime";
UniversalChart.draw();
series_index = series_indexes[MAC+radar_tablesn[i]];
series = UniversalChart.series.items[series_index];
if (series.visible == true) {
radar_part = document.getElementById("RadarPart").value;
timee = message["payload"]["time"];
switch(radar_part) {
case "absent":
vall = message["payload"]["radar"][0][1]/message["payload"]["radar"][0][0];
break;
case "moving":
vall = message["payload"]["radar"][0][2]/message["payload"]["radar"][0][0];
break;
case "stationary":
vall = message["payload"]["radar"][0][3]/message["payload"]["radar"][0][0];
break;
case "both":
vall = message["payload"]["radar"][0][4]/message["payload"]["radar"][0][0];
break;
case "m0":
vall = message["payload"]["radar"][0][5]/message["payload"]["radar"][0][0];
break;
case "m1":
vall = message["payload"]["radar"][0][6]/message["payload"]["radar"][0][0];
break;
case "m2":
vall = message["payload"]["radar"][0][7]/message["payload"]["radar"][0][0];
break;
case "m3":
vall = message["payload"]["radar"][0][8]/message["payload"]["radar"][0][0];
break;
case "m4":
vall = message["payload"]["radar"][0][9]/message["payload"]["radar"][0][0];
break;
case "m5":
vall = message["payload"]["radar"][0][10]/message["payload"]["radar"][0][0];
break;
case "m6":
vall = message["payload"]["radar"][0][11]/message["payload"]["radar"][0][0];
break;
case "m7":
vall = message["payload"]["radar"][0][12]/message["payload"]["radar"][0][0];
break;
case "m8":
vall = message["payload"]["radar"][0][13]/message["payload"]["radar"][0][0];
break;
case "s2":
vall = message["payload"]["radar"][1][3]/message["payload"]["radar"][1][0];
break;
case "s3":
vall = message["payload"]["radar"][1][4]/message["payload"]["radar"][1][0];
break;
case "s4":
vall = message["payload"]["radar"][1][5]/message["payload"]["radar"][1][0];
break;
case "s5":
vall = message["payload"]["radar"][1][6]/message["payload"]["radar"][1][0];
break;
case "s6":
vall = message["payload"]["radar"][1][7]/message["payload"]["radar"][1][0];
break;
case "s7":
vall = message["payload"]["radar"][1][8]/message["payload"]["radar"][1][0];
break;
case "s8":
vall = message["payload"]["radar"][1][9]/message["payload"]["radar"][1][0];
break;
case "s28":
vall = 0;
for (i_=3; i_<=9; i_++){
vall += message["payload"]["radar"][1][i_]
}
vall = vall/(7.0*message["payload"]["radar"][1][0]);
break;
}
}
if (vall != -1000000) {
epoch_ms = (timee+hours_delta * 3600) * 1000;
series.data.x.push(new Date(epoch_ms));
series.data.values.push(vall);
//series.data.values.push(vall);
//time_obj = new Date(epoch_ms);
//series.data.x.push(time_obj);
UniversalChart.axes.bottom.labels.roundFirst = true;
UniversalChart.axes.bottom.labels.dateFormat = "shortDateTime";
UniversalChart.draw();
}
}
}
}
break;
case "sensors":
case 17:
//case "light":
//message["payload"]["topic"]
map_type = document.getElementById("map_type").value;
@ -2218,7 +2315,7 @@ function createCustomLegend() {
const legendWidth = 150; // Width for the legend
const chartTopMargin = 35; // Approximate top margin of chart (for title, etc.)
const chartBottomMargin = 40; // Approximate bottom margin (for axis labels, etc.)
// Create or update custom legend container
let legendDiv = document.getElementById('custom-chart-legend');
if (!legendDiv) {
@ -2235,20 +2332,20 @@ function createCustomLegend() {
legendDiv.style.overflowY = 'auto';
chartContainer.appendChild(legendDiv);
}
// Clear existing legend
legendDiv.innerHTML = '';
// Calculate series area height to match legend height if needed
const seriesAreaHeight = canvasRect.height - chartTopMargin - chartBottomMargin;
legendDiv.style.height = seriesAreaHeight + 'px';
// Create Select All/None controls
const controlsDiv = document.createElement('div');
controlsDiv.style.marginBottom = '1px';
controlsDiv.style.paddingBottom = '1px';
controlsDiv.style.borderBottom = '1px solid #ddd';
// Select All button
const selectAllBtn = document.createElement('button');
selectAllBtn.textContent = 'All';
@ -2258,7 +2355,7 @@ function createCustomLegend() {
selectAllBtn.style.cursor = 'pointer';
selectAllBtn.style.border = '1px solid #ccc';
selectAllBtn.style.backgroundColor = '#f8f8f8';
// Select None button
const selectNoneBtn = document.createElement('button');
selectNoneBtn.textContent = 'None';
@ -2267,12 +2364,12 @@ function createCustomLegend() {
selectNoneBtn.style.cursor = 'pointer';
selectNoneBtn.style.border = '1px solid #ccc';
selectNoneBtn.style.backgroundColor = '#f8f8f8';
// Add buttons to controls
controlsDiv.appendChild(selectAllBtn);
controlsDiv.appendChild(selectNoneBtn);
legendDiv.appendChild(controlsDiv);
// Function to update all checkboxes
function updateAllCheckboxes(checked) {
const checkboxes = legendDiv.querySelectorAll('input[type="checkbox"][data-series-index]');
@ -2283,40 +2380,40 @@ function createCustomLegend() {
});
UniversalChart.draw();
}
// Add event listeners for Select All/None buttons
selectAllBtn.addEventListener('click', function() {
updateAllCheckboxes(true);
});
selectNoneBtn.addEventListener('click', function() {
updateAllCheckboxes(false);
});
// Add legend items
for (let i = 0; i < UniversalChart.series.count(); i++) {
const series = UniversalChart.series.items[i];
// Create legend item container
const itemDiv = document.createElement('div');
itemDiv.style.marginBottom = '5px';
itemDiv.style.display = 'flex';
itemDiv.style.alignItems = 'center';
// Create checkbox
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.checked = series.visible;
checkbox.style.marginRight = '5px';
checkbox.setAttribute('data-series-index', i);
// Add event listener for checkbox
checkbox.addEventListener('change', function() {
const seriesIndex = parseInt(this.getAttribute('data-series-index'));
UniversalChart.series.items[seriesIndex].visible = this.checked;
UniversalChart.draw();
});
// Create color swatch
const colorSwatch = document.createElement('span');
colorSwatch.style.display = 'inline-block';
@ -2324,31 +2421,31 @@ function createCustomLegend() {
colorSwatch.style.height = '12px';
colorSwatch.style.backgroundColor = series.format.stroke.fill;
colorSwatch.style.marginRight = '5px';
// Create label
const label = document.createElement('span');
label.textContent = series.title;
label.style.fontSize = '12px';
// Add elements to item
itemDiv.appendChild(checkbox);
itemDiv.appendChild(colorSwatch);
itemDiv.appendChild(label);
// Add item to legend
legendDiv.appendChild(itemDiv);
}
// Hide the built-in legend
UniversalChart.legend.visible = false;
// Shift the chart panel to the right to make space for the legend
// This might require adjusting the chart margins or bounds
const chartWidth = UniversalChart.canvas.width;
const marginPercentage = ((30+legendWidth) / chartWidth) * 100;
UniversalChart.panel.margins.left = marginPercentage; // 20px extra spacing
UniversalChart.panel.margins.right = 0;
// Redraw the chart with new dimensions
UniversalChart.draw();
}
@ -3619,12 +3716,12 @@ async function GetZGraph() {
formData.append('time', timee);
formData.append('data_type', data_type);
if (document.getElementById("rcr-check").checked){
formData.append('refresh', 1);
formData.append('refresh', 1);
}
else{
formData.append('refresh', 0);
formData.append('refresh', 0);
}
data = await SubmitForm(formData);
if (data.status !== "200 OK") {