Skip to content

Commit

Permalink
add transform data and template for control item display value
Browse files Browse the repository at this point in the history
allow to control the data (transform data function)
and graphical representation (template)
of a control item set to display value

add sliceDATAusingTimestamps JS function to get DATA for a variable key
using the daterangepicker and the timeline slinder

add "Template not found" template
  • Loading branch information
clavay authored and trombastic committed Dec 2, 2023
1 parent 9e156ba commit 8972f71
Show file tree
Hide file tree
Showing 12 changed files with 1,166 additions and 90 deletions.
71 changes: 71 additions & 0 deletions docs/control_item.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
Control item
============

Control items are elements of crontol panels which have two functions by default:

- display the last value of a variable,
- control the value of a variable.

To create one in the administration interface, you need at least to:

- enter a label,
- select a variable OR a variable property.
- chose a type : display the value or control the value of the variable/variable property,

You can also:

- select the order in the control panel using the position attribute : lower is at the top of the control panel,
- add options using display value options or control element options.

Display value options
---------------------

It allows adding options to a control item configured to display value.

To create one in the administration interface, you need at least to:

- enter a title,
- choose a template to change the graphic rendering,
- choose if you want to replace a timestamp value by a human readable format,
- transform the data before show it in the user interface: see section below,
- apply color levels: see section below.

### Transform data

#### Usage (configuration)

You can use a data transformation to pass the data through a function before displaying it (for example, display the minimum of the variable in the selected time range).

You may need to specify additional information at the bottom depending on the tranformation needs (as for the Count Value transformation).

#### Creation (developer)

A plugin can add a new transform data to the list.

To do so you can create them automatically in the *ready* function of the *AppConfig* class in *apps.py*.
Have a look at the [*hmi.apps.py*](https://github.com/pyscada/PyScada/blob/main/pyscada/hmi/apps.py).

The fields of a transform data are :
- inline_model_name : the model name to add an inline to the admin page which can add additional fields needed by the transform data function (as TransformDataCountValue for the Count Value function),
- short_name : the name displayed in the admin interface,
- js_function_name : the name of the JavaScript function which will be called to transform the data,
- js_files : a coma separated list of the JavaScript files to add,
- css_files : a coma separated list of the CSS files to add,
- need_historical_data : set to True if the transform data function needs the variable data for the whole period selected by the date time range picker, set to False if it only needs the last value.

### Template

You can choose a specific template to display you control item.

#### Creation (developer)

A plugin can add a new control item template.

To do so you can create them automatically in the *ready* function of the *AppConfig* class in *apps.py*.
Have a look at the [*hmi.apps.py*](https://github.com/pyscada/PyScada/blob/main/pyscada/hmi/apps.py).

The fields of a template are :
- label the template name to display,
- template_name : the file name to use,
- js_files : a coma separated list of the JavaScript files to add,
- css_files : a coma separated list of the CSS files to add.
60 changes: 58 additions & 2 deletions pyscada/hmi/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
DisplayValueOption,
ControlElementOption,
DisplayValueColorOption,
DisplayValueOptionTemplate,
)
from pyscada.hmi.models import CustomHTMLPanel
from pyscada.hmi.models import Widget
Expand All @@ -25,6 +26,7 @@
from pyscada.hmi.models import Pie
from pyscada.hmi.models import Theme
from pyscada.hmi.models import CssClass
from pyscada.hmi.models import TransformData

from django.utils.translation import gettext_lazy as _
from django.contrib import admin
Expand Down Expand Up @@ -318,15 +320,46 @@ class DisplayValueColorOptionInline(admin.TabularInline):
extra = 0


class TransformDataAdmin(admin.ModelAdmin):
# only allow viewing and deleting
def has_module_permission(self, request):
return False

def has_add_permission(self, request):
return False

def has_change_permission(self, request, obj=None):
return False

def has_delete_permission(self, request, obj=None):
return True


class DisplayValueOptionTemplateAdmin(admin.ModelAdmin):
# only allow viewing and deleting
def has_module_permission(self, request):
return False

def has_add_permission(self, request):
return False

def has_change_permission(self, request, obj=None):
return False

def has_delete_permission(self, request, obj=None):
return True


class DisplayValueOptionAdmin(admin.ModelAdmin):
fieldsets = (
(
None,
{
"fields": (
"name",
"type",
"title",
"template",
"timestamp_conversion",
"transform_data",
),
},
),
Expand All @@ -346,10 +379,31 @@ class DisplayValueOptionAdmin(admin.ModelAdmin):
save_as = True
save_as_continue = True
inlines = [DisplayValueColorOptionInline]
# Add inlines for any model with OneToOne relation with Device
related_models = [
field
for field in DisplayValueOption._meta.get_fields()
if issubclass(type(field), OneToOneRel)
]
for m in related_models:
model_dict = dict(model=m.related_model)
if hasattr(m.related_model, "FormSet"):
model_dict["formset"] = m.related_model.FormSet
cl = type(m.name, (admin.StackedInline,), model_dict) # classes=['collapse']
inlines.append(cl)

def has_module_permission(self, request):
return False

class Media:
js = (
# To be sure the jquery files are loaded before our js file
"admin/js/vendor/jquery/jquery.min.js",
"admin/js/jquery.init.js",
# only the inline corresponding to the transform data selected
"pyscada/js/admin/display_inline_transform_data_display_value_option.js",
)


class ControlElementOptionAdmin(admin.ModelAdmin):
save_as = True
Expand Down Expand Up @@ -610,3 +664,5 @@ def has_module_permission(self, request):
admin_site.register(ProcessFlowDiagramItem, ProcessFlowDiagramItemAdmin)
admin_site.register(Theme, ThemeAdmin)
admin_site.register(CssClass, CssClassAdmin)
admin_site.register(TransformData, TransformDataAdmin)
admin_site.register(DisplayValueOptionTemplate, DisplayValueOptionTemplateAdmin)
189 changes: 189 additions & 0 deletions pyscada/hmi/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
from django.db.utils import ProgrammingError, OperationalError
from django.conf import settings


class PyScadaHMIConfig(AppConfig):
Expand All @@ -12,3 +14,190 @@ class PyScadaHMIConfig(AppConfig):

def ready(self):
import pyscada.hmi.signals

try:
from .models import TransformData

# create the control item transform data display value options
# min, max, mean difference, difference percent...
# TODO: do not get the whole historical data for first, difference, difference percent
TransformData.objects.update_or_create(
short_name="Min",
defaults={
"inline_model_name": "TransformDataMin",
"js_function_name": "PyScadaControlItemDisplayValueTransformDataMin",
"js_files": "pyscada/js/pyscada/TransformDataHmiPlugin.js",
"need_historical_data": True,
},
)
TransformData.objects.update_or_create(
short_name="Max",
defaults={
"inline_model_name": "TransformDataMax",
"js_function_name": "PyScadaControlItemDisplayValueTransformDataMax",
"js_files": "pyscada/js/pyscada/TransformDataHmiPlugin.js",
"need_historical_data": True,
},
)
TransformData.objects.update_or_create(
short_name="Total",
defaults={
"inline_model_name": "TransformDataTotal",
"js_function_name": "PyScadaControlItemDisplayValueTransformDataTotal",
"js_files": "pyscada/js/pyscada/TransformDataHmiPlugin.js",
"need_historical_data": True,
},
)
TransformData.objects.update_or_create(
short_name="Difference",
defaults={
"inline_model_name": "TransformDataDifference",
"js_function_name": "PyScadaControlItemDisplayValueTransformDataDifference",
"js_files": "pyscada/js/pyscada/TransformDataHmiPlugin.js",
"need_historical_data": True,
},
)
TransformData.objects.update_or_create(
short_name="DifferencePercent",
defaults={
"inline_model_name": "TransformDataDifferencePercent",
"js_function_name": "PyScadaControlItemDisplayValueTransformDataDifferencePercent",
"js_files": "pyscada/js/pyscada/TransformDataHmiPlugin.js",
"need_historical_data": True,
},
)
TransformData.objects.update_or_create(
short_name="Delta",
defaults={
"inline_model_name": "TransformDataDelta",
"js_function_name": "PyScadaControlItemDisplayValueTransformDataDelta",
"js_files": "pyscada/js/pyscada/TransformDataHmiPlugin.js",
"need_historical_data": True,
},
)
TransformData.objects.update_or_create(
short_name="Mean",
defaults={
"inline_model_name": "TransformDataMean",
"js_function_name": "PyScadaControlItemDisplayValueTransformDataMean",
"js_files": "pyscada/js/pyscada/TransformDataHmiPlugin.js",
"need_historical_data": True,
},
)
TransformData.objects.update_or_create(
short_name="First",
defaults={
"inline_model_name": "TransformDataFirst",
"js_function_name": "PyScadaControlItemDisplayValueTransformDataFirst",
"js_files": "pyscada/js/pyscada/TransformDataHmiPlugin.js",
"need_historical_data": True,
},
)
TransformData.objects.update_or_create(
short_name="Count",
defaults={
"inline_model_name": "TransformDataCount",
"js_function_name": "PyScadaControlItemDisplayValueTransformDataCount",
"js_files": "pyscada/js/pyscada/TransformDataHmiPlugin.js",
"need_historical_data": True,
},
)
TransformData.objects.update_or_create(
short_name="CountValue",
defaults={
"inline_model_name": "TransformDataCountValue",
"js_function_name": "PyScadaControlItemDisplayValueTransformDataCountValue",
"js_files": "pyscada/js/pyscada/TransformDataHmiPlugin.js",
"need_historical_data": True,
},
)
TransformData.objects.update_or_create(
short_name="Range",
defaults={
"inline_model_name": "TransformDataRange",
"js_function_name": "PyScadaControlItemDisplayValueTransformDataRange",
"js_files": "pyscada/js/pyscada/TransformDataHmiPlugin.js",
"need_historical_data": True,
},
)
TransformData.objects.update_or_create(
short_name="Step",
defaults={
"inline_model_name": "TransformDataStep",
"js_function_name": "PyScadaControlItemDisplayValueTransformDataStep",
"js_files": "pyscada/js/pyscada/TransformDataHmiPlugin.js",
"need_historical_data": True,
},
)
TransformData.objects.update_or_create(
short_name="ChangeCount",
defaults={
"inline_model_name": "TransformDataChangeCount",
"js_function_name": "PyScadaControlItemDisplayValueTransformDataChangeCount",
"js_files": "pyscada/js/pyscada/TransformDataHmiPlugin.js",
"need_historical_data": True,
},
)
TransformData.objects.update_or_create(
short_name="DistinctCount",
defaults={
"inline_model_name": "TransformDataDistinctCount",
"js_function_name": "PyScadaControlItemDisplayValueTransformDataDistinctCount",
"js_files": "pyscada/js/pyscada/TransformDataHmiPlugin.js",
"need_historical_data": True,
},
)
except ProgrammingError:
pass
except OperationalError:
pass

try:
from .models import DisplayValueOptionTemplate

STATIC_URL = (
str(settings.STATIC_URL)
if hasattr(settings, "STATIC_URL")
else "/static/"
)

# create the circular gauge for control item display value option
DisplayValueOptionTemplate.objects.update_or_create(
label="Circular gauge",
defaults={
"template_name": "circular_gauge.html",
"js_files": "pyscada/js/jquery/jquery.tablesorter.min.js,"
+ "pyscada/js/jquery/parser-input-select.js,"
+ "pyscada/js/flot/lib/jquery.mousewheel.js,"
+ "pyscada/js/flot/source/jquery.canvaswrapper.js,"
+ "pyscada/js/flot/source/jquery.colorhelpers.js,"
+ "pyscada/js/flot/source/jquery.flot.js,"
+ "pyscada/js/flot/source/jquery.flot.saturated.js,"
+ "pyscada/js/flot/source/jquery.flot.browser.js,"
+ "pyscada/js/flot/source/jquery.flot.drawSeries.js,"
+ "pyscada/js/flot/source/jquery.flot.errorbars.js,"
+ "pyscada/js/flot/source/jquery.flot.uiConstants.js,"
+ "pyscada/js/flot/source/jquery.flot.logaxis.js,"
+ "pyscada/js/flot/source/jquery.flot.symbol.js,"
+ "pyscada/js/flot/source/jquery.flot.flatdata.js,"
+ "pyscada/js/flot/source/jquery.flot.navigate.js,"
+ "pyscada/js/flot/source/jquery.flot.fillbetween.js,"
+ "pyscada/js/flot/source/jquery.flot.stack.js,"
+ "pyscada/js/flot/source/jquery.flot.touchNavigate.js,"
+ "pyscada/js/flot/source/jquery.flot.hover.js,"
+ "pyscada/js/flot/source/jquery.flot.touch.js,"
+ "pyscada/js/flot/source/jquery.flot.time.js,"
+ "pyscada/js/flot/source/jquery.flot.axislabels.js,"
+ "pyscada/js/flot/source/jquery.flot.selection.js,"
+ "pyscada/js/flot/source/jquery.flot.composeImages.js,"
+ "pyscada/js/flot/source/jquery.flot.legend.js,"
+ "pyscada/js/flot/source/jquery.flot.pie.js,"
+ "pyscada/js/flot/source/jquery.flot.crosshair.js,"
+ "pyscada/js/flot/source/jquery.flot.gauge.js,"
+ "pyscada/js/jquery.flot.axisvalues.js",
},
)
except ProgrammingError:
pass
except OperationalError:
pass
Loading

0 comments on commit 8972f71

Please sign in to comment.