custom widget: send command to node
-
hi,
i have read the "widget - sensor custom buttons", "rgb controller", "custom widget" and "custom widget - dynamic images on dashboard" topics but did only get a hint on what to do.
what i have:
a custom widget with a nice, touch enabled color picker, which is working. which means:- can be displayed on the dashboard
- and is usable
- and javascript events are fired (currently logs a rgb hex value to browser console)
- using: iro.js
what i want:
- send the selected rgb hex value to a node
i saw a mention to create a custom angular controller and plug that into Utilities > HTML additional headers > AngularJS custom controllers.
can anyone provide an example of a angularjs controller to put there?i tried to use Utilities > Scripts like in the example "Custom widget" but the variables defined there do not seem to be accessible in the Templates. at least i was unable to use it to send a value to the node.
so... i am currently stuck.
is there any documentation (other than plowing through mycontrollers code) which describes how to add a custom widget which can send a value to a node generated in js? i know its possible to send a value using the Utilities > Scripts but this is rather static, as the script only runs during widget creation. which means it only runs on opening the dashboard and if i change the color-picker by touching/clicking it, i can not push the value to a node. -
@elgarfo Yes, It is possible to add your custom controller in
Utilities > HTML additional headers > AngularJS custom controllers
. can you share your current work, I can extend from there? -
@jkandasa, thanks for helping me out.
currently i have this as template:
<style type="text/css"> .wrap { color: #fff; background: #171F30; margin: 0 auto; display: flex; flex-direction: row; align-items: center; justify-content: center; } </style> <div class="wrap"> <div class="colorPicker"></div> </div> <script> var colorPicker = new iro.ColorPicker(".colorPicker", { width: 320, height: 320, color: {r: 255, g: 0, b: 0}, anticlockwise: true, borderWidth: 1, borderColor: "#fff", }); colorPicker.on("color:change", function(color) { console.log("hex: " + color.hexString); }); </script>
and this for the script:
var colorSensor = mcApi.uidTag().getByUid("hi-wz-rgb-1").getResource(); var uuid = mcApi.utils().getUUID();
i tried to copy the "Sensors" controller from github modify it to make for a "Sensors2" controller an pasted that into the "AngularJS custom controllers":
'use strict'; angular.module('adf.widget.myc-sen-vars2', []) .config(function(dashboardProvider){ dashboardProvider .widget('mycSenVars2', { title: 'Sensors2', description: 'Monitor and change sensors state', templateUrl: 'controllers/adf-widgets/adf-myc-sen-vars/view.html?mcv=${mc.gui.version}', controller: 'mycSenVarsController2', controllerAs: 'mycSenVars2', config: { variableIds:[], showIcon: true, itemsPerRow:"2", refreshTime:30, }, edit: { templateUrl: 'controllers/adf-widgets/adf-myc-sen-vars/edit.html?mcv=${mc.gui.version}', controller: 'mycSenVarsEditController2', controllerAs: 'mycSenVarsEdit2', } }); }) .controller('mycSenVarsController2', function($scope, $interval, config, mchelper, $filter, SensorsFactory, TypesFactory, CommonServices){ var mycSenVars2 = this; mycSenVars2.showLoading = true; mycSenVars2.isSyncing = true; mycSenVars2.variables = {}; $scope.tooltipEnabled = false; $scope.hideVariableName= !config.showIcon; $scope.cs = CommonServices; //HVAC heater options - HVAC flow state $scope.hvacOptionsFlowState = TypesFactory.getHvacOptionsFlowState(); //HVAC heater options - HVAC flow mode $scope.hvacOptionsFlowMode = TypesFactory.getHvacOptionsFlowMode(); //HVAC heater options - HVAC fan speed $scope.hvacOptionsFanSpeed = TypesFactory.getHvacOptionsFanSpeed(); //Defined variable types list $scope.definedVariableTypes = CommonServices.getSensorVariablesKnownList(); //update rgba color $scope.updateRgba = function(variable){ variable.value = CommonServices.rgba2hex(variable.rgba); $scope.updateVariable(variable); }; function loadVariables(){ mycSenVars2.isSyncing = true; SensorsFactory.getVariables({'ids':config.variableIds}, function(response){ mycSenVars2.variables = response; mycSenVars2.isSyncing = false; if(mycSenVars2.showLoading){ mycSenVars2.showLoading = false; } }); }; function updateVariables(){ if(mycSenVars2.isSyncing){ return; }else if(config.variableIds.length > 0){ loadVariables(); } } //load variables initially loadVariables(); //updateVariables(); //Update Variable / Send Payload $scope.updateVariable = function(variable){ SensorsFactory.updateVariable(variable, function(){ //update Success },function(error){ displayRestError.display(error); }); }; // refresh every second var promise = $interval(updateVariables, config.refreshTime*1000); // cancel interval on scope destroy $scope.$on('$destroy', function(){ $interval.cancel(promise); }); }).controller('mycSenVarsEditController2', function($scope, $interval, config, mchelper, $filter, TypesFactory, CommonServices){ var mycSenVarsEdit2 = this; mycSenVarsEdit2.cs = CommonServices; mycSenVarsEdit2.variables = TypesFactory.getSensorVariables(); });
but this does not show up at all in the dashboard.
that's why i am asking for a simple example for a custom angular controller as i am not too familiar with angular yet.
-
-
@jkandasa ok thats a little sad. but this will not stop me from trying
lets say i wanted to write a custom widget (lets also assume something simple like an html5 slider/range input)
template:
<input id="slider1" type="range" min="0" max="100" /> <script type="text/javascript"> $('#slider1').on('change', function(e) { console.log('slider1 value: ' + $(this).val()); }); </script>
script:
var stepperMotor = mcApi.uidTag().getByUid("hi-wz-blinds-1").getResource(); var uuid = mcApi.utils().getUUID();
as far as i can tell, this is a pretty basic example which does not rely on incompatible external libs. what do i need to add to which section to make this a widget with two-way binding?
all i am looking for is an example for a custom 2 way widget.
-
@elgarfo I do not know how to do with HTML 5. however I made a two-way widget with the angular slider.
Steps:
- create HTML template with sensor variable id
- create dummy javascript to supply buildings
- add custom css and js files.
- add custom controller
- add dependency module in angular (workaround, I do not find a way to include new modules into the controller creation)
HTML template:
<div ng-controller="myCustomWidgetController" ng-init="sVariable.id=${sId};loadVariable();"> <rzslider rz-slider-model="sVariable.value" rz-slider-options="slider.options"></rzslider> </div>
Dummy JavaScript:
// dummy script
CSS and JS file:
https://cdnjs.cloudflare.com/ajax/libs/angularjs-slider/6.6.0/rzslider.min.css https://cdnjs.cloudflare.com/ajax/libs/angularjs-slider/6.6.0/rzslider.min.js
Custom controller:
myControllerModule.controller('myCustomWidgetController', function($scope, SensorsFactory){ $scope.sVariable = { id: null, value:null }; $scope.loadVariable = function(){ SensorsFactory.getVariable({id:$scope.sVariable.id}, function(response){ if(response.value != undefined){ $scope.sVariable.value = parseInt(response.value); } }); } $scope.slider = { options: { floor: 0, ceil: 100, step: 1, minLimit: 0, maxLimit: 90, onEnd: function() { $scope.sendPayload($scope.sVariable); }, } }; // send payload $scope.sendPayload = function(sVariable){ SensorsFactory.updateVariable(sVariable, function(){ //update Success },function(error){ displayRestError.display(error); }); }; });
Workaround, add a module on the app level
File: mycontroller/www/app.js
rzModule
Widget in dashboard
-
@jkandasa your support is outstanding
your example works good and i can use it to build a dashboard to my needs. thank you very very much.the reason it took me so long to respond is, that i needed to trace a startup error on my end:
[$injector:nomod] Module 'rzModule' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.
symptoms:
- dashboard not loading correctly ({{ variable }} everywhere, nothing works)
- angular throwing error about undefined module
- testing with dev-console on firefox
angular.module('rzModule');
yielded no error. - rzslider.js was actually loaded
cause:
- firefox does not support
<link rel="import">
- the polyfill i pushed to github fb3d0d96 is working but:
- too late.
so the polyfill for the import-feature is working, as i am able to load the rzModule after the dashboard html loaded completely and angular tried to load.
but it is not working fast enough.i am currently trying to find a way to either delay angular startup until all imports are handled or to find a better way to include the
/_configurations/additional-headers.html
current workaround: put
<script src="https://cdnjs.cloudflare.com/ajax/libs/angularjs-slider/6.6.1/rzslider.js"></script>
directly into index.html for firefoxEDIT:
html imports are also deprecated in chrome since v70 and will be removed with v73 which is only two versions from now (v71 as of this writing). see: chrome platform status -
@elgarfo If we change anything in our current implementation, can fix the issue in both firefox and chrome? Any idea?
-
@elgarfo Thank you for the PR https://github.com/mycontroller-org/mycontroller/pull/486
-
How can I download the latest SNAPSHOT with this PR?
Is this it? Https: //travis-ci.org/mycontroller-org/mycontroller/builds/472109919 -
@wanvo Compiled and uploaded in SNAPSHOT location.
-
@jkandasa Thanx!
I checked this widget in Chrome and Firefox, it works great. -
I'm just testing out various examples to understand how things work, I'm getting
Failed at: ${sId} [in template "unknown" at line 1, column 69]
which I assume is from the first line of the html template, its not finding the angularjs custom controller ? I did modify app.js.
I'm pretty sure I've done all of the above to get the slider to appear on dashboard, but I must of missed something. I pasted the angularjs custom controller into the text field in HTML additional headers.
Any help thanks
-
@RMF69 To myself... the script binding id mistyped {sId:2}... got the camel case wrong. Slider is now visible but I get 400 error... as I don't have the variable set up correctly which will get updated ? I've created a global variable "sVariable" but I don't think this is right. How do I get the value out of the slider when its value changes ?
-
@RMF69 Ok looking through code, I understand that its updating a sensor.variabel and that this id is passed in via the script binding {sId:31}... 31 is the id of a sensor S_LIGHT_LEVEL![alt text](
I still get 400 bad requests... andERROR [Acme.Utils.ThreadPool(0)-PooledThread: Acme.Serve.Serve$ServeConnection@1415568e] [org.mycontroller.standalone.api.jaxrs.exception.mappers.ApplicationExceptionMapper:42] ApplicationException, org.jboss.resteasy.spi.ApplicationException: java.lang.NullPointerException
I'd love to understand what I've done wrong:)
-
@RMF69 Can you report your MyController version? and if you modified the script please report your script.
-
Thanks, I've got the latest, Version 1.5.0. And I've not changed anything, just the script binding {sId:31} where 31 is the id of a light dimmer virtual sensor... see picture. I took the ID from the url of that page in the picture below.
-
@RMF69 Click on the
Edit
icon on theLevel
variable and use theid
from the URL.in version 1.5, you no need to hardcode
rzModule
onmycontroller/www/app.js
. We have an option on UI to update angular modules.
-
@jkandasa Thanks for confirming that in 1.5, the hardcoding of rzModule in mycontroller/www/app.js isn't required. However if I remove rzModule then the slider widget isn't displayed.
Still I can't get the example to work...I've got the id from the place you mention, mine is still 31... I've also noticed that the intial value of the sensor->variable isn't populated. I created simple script to update the value
var myImports = new JavaImporter(java.lang, java.util, org.mycontroller.standalone.utils.McUtils); with(myImports) { dimSensor = mcApi.uidTag().getByUid("dimSensor").getResource(); var v = dimSensor.value; //value2/3 etc dimSensor.setValue(parseInt(v) + 1); mcApi.sensor().sendPayload(dimSensor); }
I insert the following code into "AngularJs custom controllers"
myControllerModule.controller('myCustomWidgetController', function($scope, SensorsFactory){ $scope.sVariable = { id: null, value:null }; $scope.loadVariable = function(){ SensorsFactory.getVariable({id:$scope.sVariable.id}, function(response){ if(response.value != undefined){ $scope.sVariable.value = parseInt(response.value); } }); } $scope.slider = { options: { floor: 0, ceil: 100, step: 1, minLimit: 0, maxLimit: 90, onEnd: function() { $scope.sendPayload($scope.sVariable); }, } }; // send payload $scope.sendPayload = function(sVariable){ SensorsFactory.updateVariable(sVariable, function(){ //update Success },function(error){ displayRestError.display(error); }); }; });
Which is just the original example above, no changes... it seems to me that the pointer/reference to the variable I want to have connected to the slider is correct or working, both when the value is initialized on the loadVariable and when updated. But I just can't see what I've done wrong.
-
@RMF69 Do you see any error on the browser console and/or in MyController log file?