summaryrefslogtreecommitdiff
path: root/mappers
diff options
context:
space:
mode:
authorfisherxu <fisherxu1@gmail.com>2019-07-26 11:23:33 +0800
committerfisherxu <fisherxu1@gmail.com>2019-07-26 14:33:54 +0800
commit26902a3027f10d4b66e8b72aabd2bc1e58066865 (patch)
treef6b5e1736bb9ec7e11a6b3dbbd822290d694244e /mappers
parentMerge pull request #935 from sujithsimon22/ble (diff)
downloadkubeedge-26902a3027f10d4b66e8b72aabd2bc1e58066865.tar.gz
rename device dir to mappers
Diffstat (limited to 'mappers')
-rw-r--r--mappers/bluetooth_mapper/Dockerfile9
-rw-r--r--mappers/bluetooth_mapper/Makefile12
-rw-r--r--mappers/bluetooth_mapper/README.md4
-rw-r--r--mappers/bluetooth_mapper/action_manager/action_manager.go133
-rw-r--r--mappers/bluetooth_mapper/configuration/config.go217
-rw-r--r--mappers/bluetooth_mapper/configuration/config.yaml40
-rw-r--r--mappers/bluetooth_mapper/configuration/config_map_types.go134
-rw-r--r--mappers/bluetooth_mapper/controller/controller.go228
-rw-r--r--mappers/bluetooth_mapper/data_converter/data_converter.go117
-rw-r--r--mappers/bluetooth_mapper/deployment.yaml31
-rw-r--r--mappers/bluetooth_mapper/helper/helper.go215
-rw-r--r--mappers/bluetooth_mapper/main.go61
-rw-r--r--mappers/bluetooth_mapper/scheduler/scheduler.go117
-rw-r--r--mappers/bluetooth_mapper/watcher/watcher.go190
-rw-r--r--mappers/modbus_mapper/Dockerfile10
-rw-r--r--mappers/modbus_mapper/Makefile5
-rw-r--r--mappers/modbus_mapper/README.md4
-rw-r--r--mappers/modbus_mapper/conf/conf.json6
-rw-r--r--mappers/modbus_mapper/deployment.yaml39
-rw-r--r--mappers/modbus_mapper/dpl/deviceProfile.json42
-rw-r--r--mappers/modbus_mapper/sample/sample_server.js33
-rw-r--r--mappers/modbus_mapper/scripts/start_modbusmapper.sh14
-rw-r--r--mappers/modbus_mapper/src/common.js93
-rw-r--r--mappers/modbus_mapper/src/constant.js17
-rw-r--r--mappers/modbus_mapper/src/devicetwin.js230
-rw-r--r--mappers/modbus_mapper/src/index.js328
-rw-r--r--mappers/modbus_mapper/src/modbus.js204
-rw-r--r--mappers/modbus_mapper/src/package-lock.json1281
-rw-r--r--mappers/modbus_mapper/src/package.json23
-rw-r--r--mappers/modbus_mapper/src/watchfile.js106
30 files changed, 3943 insertions, 0 deletions
diff --git a/mappers/bluetooth_mapper/Dockerfile b/mappers/bluetooth_mapper/Dockerfile
new file mode 100644
index 000000000..b98cdf959
--- /dev/null
+++ b/mappers/bluetooth_mapper/Dockerfile
@@ -0,0 +1,9 @@
+FROM ubuntu:16.04
+
+CMD mkdir -p kubeedge
+
+COPY . kubeedge/
+
+WORKDIR kubeedge
+
+ENTRYPOINT ["/kubeedge/main","-logtostderr=true"] \ No newline at end of file
diff --git a/mappers/bluetooth_mapper/Makefile b/mappers/bluetooth_mapper/Makefile
new file mode 100644
index 000000000..fa2e96698
--- /dev/null
+++ b/mappers/bluetooth_mapper/Makefile
@@ -0,0 +1,12 @@
+
+# make bluetooth_mapper
+.PHONY: default bluetooth_mapper lint bluetooth_mapper_image
+bluetooth_mapper:
+ go build main.go
+
+lint:
+ golangci-lint run --disable-all -E golint ./...
+ go vet ./...
+
+bluetooth_mapper_image: bluetooth_mapper
+ docker build -t bluetooth_mapper:v1.0 .
diff --git a/mappers/bluetooth_mapper/README.md b/mappers/bluetooth_mapper/README.md
new file mode 100644
index 000000000..80b586a16
--- /dev/null
+++ b/mappers/bluetooth_mapper/README.md
@@ -0,0 +1,4 @@
+
+# Bluetooth Mapper
+
+Click [here](https://github.com/kubeedge/kubeedge/blob/master/docs/mappers/bluetooth_mapper.md#bluetooth-mapper) for detailed documentation of the bluetooth mapper. \ No newline at end of file
diff --git a/mappers/bluetooth_mapper/action_manager/action_manager.go b/mappers/bluetooth_mapper/action_manager/action_manager.go
new file mode 100644
index 000000000..4336e19ca
--- /dev/null
+++ b/mappers/bluetooth_mapper/action_manager/action_manager.go
@@ -0,0 +1,133 @@
+/*
+Copyright 2019 The KubeEdge Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package actionmanager
+
+import (
+ "errors"
+ "fmt"
+ "strings"
+
+ "github.com/golang/glog"
+ "github.com/paypal/gatt"
+
+ "github.com/kubeedge/kubeedge/mappers/bluetooth_mapper/data_converter"
+)
+
+const (
+ ActionWrite = "WRITE"
+ ActionRead = "READ"
+)
+
+// GattPeripheral represents the remote gatt peripheral device
+var GattPeripheral gatt.Peripheral
+
+// CharacteristicsList contains the set of characteristics exposed by the device
+var CharacteristicsList = make([]*gatt.Characteristic, 0)
+
+// Operation is structure to define device operation
+type Operation struct {
+ // Action can be one of read/write corresponding to get/set respectively
+ Action string `yaml:"action" json:"action"`
+ // Characteristic refers to the characteristic on which the operation needs to be performed
+ CharacteristicUUID string `yaml:"characteristic-uuid" json:"characteristic-uuid"`
+ // Value is the value to be written in case of write action and value read from the device in case of read action
+ Value []byte `yaml:"value" json:"value"`
+}
+
+// Action is structure to define a device action
+type Action struct {
+ // PerformImmediately indicates whether the action is to be performed immediately or not
+ PerformImmediately bool `yaml:"perform-immediately" json:"perform-immediately"`
+ // Name is the name of the Action
+ Name string `yaml:"name" json:"name"`
+ // Operation specifies the operation to be performed for this action
+ Operation Operation `yaml:"operation" json:"operation"`
+}
+
+//ActionManager is a structure that contains a list of actions
+type ActionManager struct {
+ Actions []Action `yaml:"actions"`
+}
+
+//PerformOperation executes the operation
+func (action *Action) PerformOperation(readConverter ...dataconverter.DataRead) {
+ glog.Infof("Performing operations associated with action: %s", action.Name)
+ characteristic, err := FindCharacteristic(action.Operation.CharacteristicUUID)
+ if err != nil {
+ glog.Errorf("Error in finding characteristics: %s", err)
+ }
+ if strings.ToUpper(action.Operation.Action) == ActionRead {
+ readValue, err := ReadCharacteristic(GattPeripheral, characteristic)
+ if err != nil {
+ glog.Errorf("Error in reading characteristic: %s", err)
+ return
+ }
+ converted := false
+ for _, conversionAction := range readConverter[0].Actions {
+ if strings.ToUpper(conversionAction.ActionName) == strings.ToUpper(action.Name) {
+ convertedValue := fmt.Sprintf("%f", conversionAction.ConversionOperation.ConvertReadData(readValue))
+ action.Operation.Value = []byte(convertedValue)
+ converted = true
+ }
+ }
+ if !converted {
+ action.Operation.Value = readValue
+ }
+ glog.Infof("Read Successful")
+ } else if strings.ToUpper(action.Operation.Action) == ActionWrite {
+ if action.Operation.Value == nil {
+ glog.Errorf("Please provide a value to be written")
+ return
+ }
+ err := WriteCharacteristic(GattPeripheral, characteristic, action.Operation.Value)
+ if err != nil {
+ glog.Errorf("Error in writing characteristic: %s", err)
+ return
+ }
+ glog.Infof("Write Successful")
+ }
+}
+
+//FindCharacteristic is used to find the bluetooth characteristic
+func FindCharacteristic(characteristicUUID string) (*gatt.Characteristic, error) {
+ for _, c := range CharacteristicsList {
+ if c.UUID().String() == characteristicUUID {
+ return c, nil
+ }
+ }
+ return nil, errors.New("unable to find the specified characteristic: " + characteristicUUID)
+}
+
+//ReadCharacteristic is used to read the value of the characteristic
+func ReadCharacteristic(p gatt.Peripheral, c *gatt.Characteristic) ([]byte, error) {
+ value, err := p.ReadCharacteristic(c)
+ if err != nil {
+ glog.Errorf("Error in reading characteristic, err: %s\n", err)
+ return nil, err
+ }
+ return value, nil
+}
+
+//WriteCharacteristic is used to write some value into the characteristic
+func WriteCharacteristic(p gatt.Peripheral, c *gatt.Characteristic, b []byte) error {
+ err := p.WriteCharacteristic(c, b, false)
+ if err != nil {
+ glog.Errorf("Error in writing characteristic, err: %s\n", err)
+ return err
+ }
+ return nil
+}
diff --git a/mappers/bluetooth_mapper/configuration/config.go b/mappers/bluetooth_mapper/configuration/config.go
new file mode 100644
index 000000000..eb216ccae
--- /dev/null
+++ b/mappers/bluetooth_mapper/configuration/config.go
@@ -0,0 +1,217 @@
+/*
+Copyright 2019 The KubeEdge Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package configuration
+
+import (
+ "encoding/json"
+ "errors"
+ "io/ioutil"
+ "reflect"
+ "strings"
+
+ "github.com/kubeedge/kubeedge/mappers/bluetooth_mapper/action_manager"
+ "github.com/kubeedge/kubeedge/mappers/bluetooth_mapper/data_converter"
+ "github.com/kubeedge/kubeedge/mappers/bluetooth_mapper/scheduler"
+ "github.com/kubeedge/kubeedge/mappers/bluetooth_mapper/watcher"
+
+ "gopkg.in/yaml.v2"
+)
+
+//ConfigFilePath contains the location of the configuration file
+var ConfigFilePath = "configuration/config.yaml"
+
+//ConfigMapPath contains the location of the configuration file
+var ConfigMapPath = "/opt/kubeedge/deviceProfile.json"
+
+//Config is the global configuration used by all the modules of the mapper
+var Config *BLEConfig
+
+//Blutooth protocol name
+const (
+ ProtocolName string = "BLUETOOTH"
+ READWRITE string = "ReadWrite"
+ READ string = "ReadOnly"
+)
+
+//BLEConfig is the main structure that stores the configuration information read from both the config file as well as the config map
+type BLEConfig struct {
+ Mqtt Mqtt `yaml:"mqtt"`
+ Device Device `yaml:"device"`
+ Watcher watcher.Watcher `yaml:"watcher"`
+ Scheduler scheduler.Scheduler `yaml:"scheduler"`
+ ActionManager actionmanager.ActionManager `yaml:"action-manager"`
+ Converter dataconverter.Converter `yaml:"data-converter"`
+}
+
+//ReadConfigFile is the structure that is used to read the config file to get configuration information from the user
+type ReadConfigFile struct {
+ Mqtt Mqtt `yaml:"mqtt"`
+ DeviceModelName string `yaml:"device-model-name"`
+ ActionManager ActionManagerConfig `yaml:"action-manager"`
+ Watcher watcher.Watcher `yaml:"watcher"`
+ Scheduler scheduler.Scheduler `yaml:"scheduler"`
+}
+
+//ActionManagerConfig is a structure that contains a list of actions
+type ActionManagerConfig struct {
+ Actions []Action `yaml:"actions"`
+}
+
+//Action is structure to define a device action
+type Action struct {
+ //PerformImmediately signifies whether the action is to be performed by the action-manager immediately or not
+ PerformImmediately bool `yaml:"perform-immediately" json:"perform-immediately"`
+ //Name is the name of the Action
+ Name string `yaml:"name" json:"name"`
+ //PropertyName is the name of the property defined in the device CRD
+ PropertyName string `yaml:"device-property-name" json:"device-property-name"`
+}
+
+//Mqtt structure contains the MQTT specific configurations
+type Mqtt struct {
+ Mode int `yaml:"mode"`
+ InternalServer string `yaml:"internal-server"`
+ Server string `yaml:"server"`
+}
+
+//Device structure contains the device specific configurations
+type Device struct {
+ ID string `yaml:"id"`
+ Name string `yaml:"name"`
+}
+
+//ReadFromConfigFile is used to load the information from the configuration file
+func (readConfigFile *ReadConfigFile) ReadFromConfigFile() error {
+ yamlFile, err := ioutil.ReadFile(ConfigFilePath)
+ if err != nil {
+ return err
+ }
+ err = yaml.Unmarshal(yamlFile, readConfigFile)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+//Load is used to consolidate the information loaded from the configuration file and the configmaps
+func (b *BLEConfig) Load() error {
+ readConfigFile := ReadConfigFile{}
+ readConfigMap := DeviceProfile{}
+ err := readConfigFile.ReadFromConfigFile()
+ if err != nil {
+ return errors.New("Error while reading from configuration file " + err.Error())
+ }
+ err = readConfigMap.ReadFromConfigMap()
+ if err != nil {
+ return errors.New("Error while reading from config map " + err.Error())
+ }
+ b.Mqtt = readConfigFile.Mqtt
+ b.Scheduler = readConfigFile.Scheduler
+ b.Watcher = readConfigFile.Watcher
+ // Assign device information obtained from config file
+ for _, device := range readConfigMap.DeviceInstances {
+ if strings.ToUpper(device.Model) == strings.ToUpper(readConfigFile.DeviceModelName) {
+ b.Device.ID = device.ID
+ b.Device.Name = device.Model
+ }
+ }
+ // Assign information required by action manager
+ for _, actionConfig := range readConfigFile.ActionManager.Actions {
+ action := actionmanager.Action{}
+ action.Name = actionConfig.Name
+ action.PerformImmediately = actionConfig.PerformImmediately
+
+ for _, propertyVisitor := range readConfigMap.PropertyVisitors {
+ if strings.ToUpper(propertyVisitor.ModelName) == strings.ToUpper(b.Device.Name) && strings.ToUpper(propertyVisitor.PropertyName) == strings.ToUpper(actionConfig.PropertyName) && strings.ToUpper(propertyVisitor.Protocol) == ProtocolName {
+ propertyVisitorBytes, err := json.Marshal(propertyVisitor.VisitorConfig)
+ if err != nil {
+ return errors.New("Error in marshalling data property visitor configuration: " + err.Error())
+ }
+ bluetoothPropertyVisitor := VisitorConfigBluetooth{}
+ err = json.Unmarshal(propertyVisitorBytes, &bluetoothPropertyVisitor)
+ if err != nil {
+ return errors.New("Error in unmarshalling data property visitor configuration: " + err.Error())
+ }
+ action.Operation.CharacteristicUUID = bluetoothPropertyVisitor.CharacteristicUUID
+ newBluetoothVisitorConfig := VisitorConfigBluetooth{}
+ if !reflect.DeepEqual(bluetoothPropertyVisitor.BluetoothDataConverter, newBluetoothVisitorConfig.BluetoothDataConverter) {
+ readAction := dataconverter.ReadAction{}
+ readAction.ActionName = actionConfig.Name
+ readAction.ConversionOperation.StartIndex = bluetoothPropertyVisitor.BluetoothDataConverter.StartIndex
+ readAction.ConversionOperation.EndIndex = bluetoothPropertyVisitor.BluetoothDataConverter.EndIndex
+ readAction.ConversionOperation.ShiftRight = bluetoothPropertyVisitor.BluetoothDataConverter.ShiftRight
+ readAction.ConversionOperation.ShiftLeft = bluetoothPropertyVisitor.BluetoothDataConverter.ShiftLeft
+ for _, readOperations := range bluetoothPropertyVisitor.BluetoothDataConverter.OrderOfOperations {
+ readAction.ConversionOperation.OrderOfExecution = append(readAction.ConversionOperation.OrderOfExecution, readOperations.BluetoothOperationType)
+ switch strings.ToUpper(readOperations.BluetoothOperationType) {
+ case strings.ToUpper(BluetoothAdd):
+ readAction.ConversionOperation.Add = readOperations.BluetoothOperationValue
+ case strings.ToUpper(BluetoothSubtract):
+ readAction.ConversionOperation.Subtract = readOperations.BluetoothOperationValue
+ case strings.ToUpper(BluetoothMultiply):
+ readAction.ConversionOperation.Multiply = readOperations.BluetoothOperationValue
+ case strings.ToUpper(BluetoothDivide):
+ readAction.ConversionOperation.Divide = readOperations.BluetoothOperationValue
+ }
+ }
+ b.Converter.DataRead.Actions = append(b.Converter.DataRead.Actions, readAction)
+ }
+ if bluetoothPropertyVisitor.DataWriteToBluetooth != nil {
+ writeAttribute := dataconverter.WriteAttribute{}
+ writeAttribute.Operations = make(map[string]dataconverter.DataMap, 1)
+ dataMap := dataconverter.DataMap{}
+ dataMap.DataMapping = bluetoothPropertyVisitor.DataWriteToBluetooth
+ writeAttribute.Operations[actionConfig.Name] = dataMap
+ writeAttribute.Name = propertyVisitor.PropertyName
+ b.Converter.DataWrite.Attributes = append(b.Converter.DataWrite.Attributes, writeAttribute)
+ }
+ }
+ }
+ for _, deviceModel := range readConfigMap.DeviceModels {
+ if strings.ToUpper(deviceModel.Name) == strings.ToUpper(b.Device.Name) {
+ for _, property := range deviceModel.Properties {
+ if strings.ToUpper(property.Name) == strings.ToUpper(actionConfig.PropertyName) {
+ if property.AccessMode == READWRITE {
+ action.Operation.Action = "Write"
+ if strings.ToUpper(property.DataType) == "INT" {
+ value := string(int(property.DefaultValue.(float64)))
+ action.Operation.Value = []byte(value)
+ } else if strings.ToUpper(property.DataType) == "STRING" {
+ for _, converterAttribute := range b.Converter.DataWrite.Attributes {
+ if strings.ToUpper(converterAttribute.Name) == strings.ToUpper(actionConfig.PropertyName) {
+ for operationName, dataMap := range converterAttribute.Operations {
+ if action.Name == operationName {
+ if _, ok := dataMap.DataMapping[property.DefaultValue.(string)]; ok {
+ action.Operation.Value = dataMap.DataMapping[property.DefaultValue.(string)]
+ }
+ }
+ }
+ }
+ }
+ }
+ } else if property.AccessMode == READ {
+ action.Operation.Action = "Read"
+ }
+ }
+ }
+ }
+ }
+ b.ActionManager.Actions = append(b.ActionManager.Actions, action)
+ }
+ Config = b
+ return nil
+}
diff --git a/mappers/bluetooth_mapper/configuration/config.yaml b/mappers/bluetooth_mapper/configuration/config.yaml
new file mode 100644
index 000000000..b536bcf44
--- /dev/null
+++ b/mappers/bluetooth_mapper/configuration/config.yaml
@@ -0,0 +1,40 @@
+mqtt:
+ mode: 0 # 0 -internal mqtt broker 1 - external mqtt broker
+ server: tcp://127.0.0.1:1883 # external mqtt broker url.
+ internal-server: tcp://127.0.0.1:1884 # internal mqtt broker url.
+device-model-name: cc2650-sensortag
+action-manager:
+ actions:
+ - name: IRTemperatureConfiguration
+ perform-immediately: true
+ device-property-name: temperature-enable #property-name defined in the device model
+ - name: IRTemperatureData
+ perform-immediately: false
+ device-property-name: temperature #property-name defined in the device model
+ - name: IOConfigurationInitialize
+ perform-immediately: true
+ device-property-name: io-config-initialize #property-name defined in the device model
+ - name: IODataInitialize
+ perform-immediately: true
+ device-property-name: io-data-initialize #property-name defined in the device model
+ - name: IOConfiguration
+ perform-immediately: true
+ device-property-name: io-config #property-name defined in the device model
+ - name: IOData
+ perform-immediately: false
+ device-property-name: io-data #property-name defined in the device model
+scheduler:
+ schedules:
+ - name: temperature
+ interval: 3000
+ occurrence-limit: 10 # if it is 0, then the event will execute infinitely
+ actions:
+ - IRTemperatureData # Action name defined in the action-manager section
+watcher:
+ device-twin-attributes :
+ - device-property-name: io-data # the twin attribute name defined while creating device
+ actions: # list of action names, defined in the action-manager section, to be executed on the device
+ - IOConfigurationInitialize
+ - IODataInitialize
+ - IOConfiguration
+ - IOData
diff --git a/mappers/bluetooth_mapper/configuration/config_map_types.go b/mappers/bluetooth_mapper/configuration/config_map_types.go
new file mode 100644
index 000000000..855b8d930
--- /dev/null
+++ b/mappers/bluetooth_mapper/configuration/config_map_types.go
@@ -0,0 +1,134 @@
+/*
+Copyright 2019 The KubeEdge Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package configuration
+
+import (
+ "encoding/json"
+ "io/ioutil"
+)
+
+// Bluetooth Protocol Operation type
+const (
+ BluetoothAdd string = "Add"
+ BluetoothSubtract string = "Subtract"
+ BluetoothMultiply string = "Multiply"
+ BluetoothDivide string = "Divide"
+)
+
+// DeviceProfile is structure to store in configMap
+type DeviceProfile struct {
+ DeviceInstances []DeviceInstance `json:"deviceInstances,omitempty"`
+ DeviceModels []DeviceModel `json:"deviceModels,omitempty"`
+ Protocols []Protocol `json:"protocols,omitempty"`
+ PropertyVisitors []PropertyVisitor `json:"propertyVisitors,omitempty"`
+}
+
+// DeviceInstance is structure to store device in deviceProfile.json in configmap
+type DeviceInstance struct {
+ ID string `json:"id,omitempty"`
+ Name string `json:"name,omitempty"`
+ Protocol string `json:"protocol,omitempty"`
+ Model string `json:"model,omitempty"`
+}
+
+// DeviceModel is structure to store deviceModel in deviceProfile.json in configmap
+type DeviceModel struct {
+ Name string `json:"name,omitempty"`
+ Description string `json:"description,omitempty"`
+ Properties []Property `json:"properties,omitempty"`
+}
+
+// Property is structure to store deviceModel property
+type Property struct {
+ Name string `json:"name,omitempty"`
+ DataType string `json:"dataType,omitempty"`
+ Description string `json:"description,omitempty"`
+ AccessMode string `json:"accessMode,omitempty"`
+ DefaultValue interface{} `json:"defaultValue,omitempty"`
+ Minimum int64 `json:"minimum,omitempty"`
+ Maximum int64 `json:"maximum,omitempty"`
+ Unit string `json:"unit,omitempty"`
+}
+
+// Protocol is structure to store protocol in deviceProfile.json in configmap
+type Protocol struct {
+ Name string `json:"name,omitempty"`
+ Protocol string `json:"protocol,omitempty"`
+ ProtocolConfig interface{} `json:"protocol_config,omitempty"`
+}
+
+// PropertyVisitor is structure to store propertyVisitor in deviceProfile.json in configmap
+type PropertyVisitor struct {
+ Name string `json:"name,omitempty"`
+ PropertyName string `json:"propertyName,omitempty"`
+ ModelName string `json:"modelName,omitempty"`
+ Protocol string `json:"protocol,omitempty"`
+ VisitorConfig interface{} `json:"visitorConfig,omitempty"`
+}
+
+// Common visitor configurations for bluetooth protocol
+type VisitorConfigBluetooth struct {
+ // Required: Unique ID of the corresponding operation
+ CharacteristicUUID string `json:"characteristicUUID,omitempty"`
+ // Responsible for converting the data coming from the platform into a form that is understood by the bluetooth device
+ // For example: "ON":[1], "OFF":[0]
+ //+optional
+ DataWriteToBluetooth map[string][]byte `json:"dataWrite,omitempty"`
+ // Responsible for converting the data being read from the bluetooth device into a form that is understandable by the platform
+ //+optional
+ BluetoothDataConverter BluetoothReadConverter `json:"dataConverter,omitempty"`
+}
+
+// Specifies the operations that may need to be performed to convert the data
+type BluetoothReadConverter struct {
+ // Required: Specifies the start index of the incoming byte stream to be considered to convert the data.
+ // For example: start-index:2, end-index:3 concatenates the value present at second and third index of the incoming byte stream. If we want to reverse the order we can give it as start-index:3, end-index:2
+ StartIndex int `json:"startIndex,omitempty"`
+ // Required: Specifies the end index of incoming byte stream to be considered to convert the data
+ // the value specified should be inclusive for example if 3 is specified it includes the third index
+ EndIndex int `json:"endIndex,omitempty"`
+ // Refers to the number of bits to shift left, if left-shift operation is necessary for conversion
+ // +optional
+ ShiftLeft uint `json:"shiftLeft,omitempty"`
+ // Refers to the number of bits to shift right, if right-shift operation is necessary for conversion
+ // +optional
+ ShiftRight uint `json:"shiftRight,omitempty"`
+ // Specifies in what order the operations(which are required to be performed to convert incoming data into understandable form) are performed
+ //+optional
+ OrderOfOperations []BluetoothOperations `json:"orderOfOperations,omitempty"`
+}
+
+// Specify the operation that should be performed to convert incoming data into understandable form
+type BluetoothOperations struct {
+ // Required: Specifies the operation to be performed to convert incoming data
+ BluetoothOperationType string `json:"operationType,omitempty"`
+ // Required: Specifies with what value the operation is to be performed
+ BluetoothOperationValue float64 `json:"operationValue,omitempty"`
+}
+
+//ReadFromConfigMap is used to load the information from the configmaps that are provided from the cloud
+func (deviceProfile *DeviceProfile) ReadFromConfigMap() error {
+ jsonFile, err := ioutil.ReadFile(ConfigMapPath)
+ if err != nil {
+ return err
+ }
+ err = json.Unmarshal(jsonFile, deviceProfile)
+ if err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/mappers/bluetooth_mapper/controller/controller.go b/mappers/bluetooth_mapper/controller/controller.go
new file mode 100644
index 000000000..6bbc1238c
--- /dev/null
+++ b/mappers/bluetooth_mapper/controller/controller.go
@@ -0,0 +1,228 @@
+/*
+Copyright 2019 The KubeEdge Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package controller
+
+import (
+ "encoding/json"
+ "strings"
+
+ MQTT "github.com/eclipse/paho.mqtt.golang"
+ "github.com/golang/glog"
+ "github.com/paypal/gatt"
+ "github.com/paypal/gatt/examples/option"
+
+ "github.com/kubeedge/kubeedge/mappers/bluetooth_mapper/action_manager"
+ "github.com/kubeedge/kubeedge/mappers/bluetooth_mapper/configuration"
+ "github.com/kubeedge/kubeedge/mappers/bluetooth_mapper/data_converter"
+ "github.com/kubeedge/kubeedge/mappers/bluetooth_mapper/helper"
+ "github.com/kubeedge/kubeedge/mappers/bluetooth_mapper/scheduler"
+ "github.com/kubeedge/kubeedge/mappers/bluetooth_mapper/watcher"
+)
+
+// constants which can be used to convey topic information
+const (
+ MapperTopicPrefix = "$ke/device/bluetooth-mapper/"
+ WatcherTopicSuffix = "/watcher/create"
+ SchedulerCreateTopicSuffix = "/scheduler/create"
+ SchedulerDeleteTopicSuffix = "/scheduler/delete"
+ ActionManagerCreateTopicSuffix = "/action-manager/create"
+ ActionManagerDeleteTopicSuffix = "/action-manager/delete"
+)
+
+var topicMap = make(map[string]MQTT.MessageHandler)
+
+//Config contains the configuration used by the controller
+type Config struct {
+ Mqtt configuration.Mqtt `yaml:"mqtt"`
+ Device configuration.Device `yaml:"device"`
+ Watcher watcher.Watcher `yaml:"watcher"`
+ Scheduler scheduler.Scheduler `yaml:"scheduler"`
+ ActionManager actionmanager.ActionManager `yaml:"action-manager"`
+ Converter dataconverter.Converter `yaml:"data-converter"`
+}
+
+// initTopicMap initializes topics to their respective handler functions
+func (c *Config) initTopicMap() {
+ topicMap[MapperTopicPrefix+c.Device.ID+WatcherTopicSuffix] = c.handleWatchMessage
+ topicMap[MapperTopicPrefix+c.Device.ID+SchedulerCreateTopicSuffix] = c.handleScheduleCreateMessage
+ topicMap[MapperTopicPrefix+c.Device.ID+SchedulerDeleteTopicSuffix] = c.handleScheduleDeleteMessage
+ topicMap[MapperTopicPrefix+c.Device.ID+ActionManagerCreateTopicSuffix] = c.handleActionCreateMessage
+ topicMap[MapperTopicPrefix+c.Device.ID+ActionManagerDeleteTopicSuffix] = c.handleActionDeleteMessage
+}
+
+//Start starts the controller of the mapper
+func (c *Config) Start() {
+ c.initTopicMap()
+ helper.MqttConnect(c.Mqtt.Mode, c.Mqtt.InternalServer, c.Mqtt.Server)
+ subscribeAllTopics()
+ helper.ControllerWg.Add(1)
+ device, err := gatt.NewDevice(option.DefaultClientOptions...)
+ if err != nil {
+ glog.Fatalf("Failed to open device, err: %s\n", err)
+ return
+ }
+ go c.Watcher.Initiate(device, c.Device.Name, c.Device.ID, c.ActionManager.Actions, c.Converter)
+
+ <-watcher.DeviceConnected
+ for _, action := range c.ActionManager.Actions {
+ if action.PerformImmediately {
+ action.PerformOperation(c.Converter.DataRead)
+ }
+ }
+
+ for _, schedule := range c.Scheduler.Schedules {
+ helper.ControllerWg.Add(1)
+ go schedule.ExecuteSchedule(c.ActionManager.Actions, c.Converter.DataRead, c.Device.ID)
+ }
+ helper.ControllerWg.Wait()
+}
+
+//subscribeAllTopics subscribes to mqtt topics associated with mapper
+func subscribeAllTopics() {
+ for key, value := range topicMap {
+ helper.TokenClient = helper.Client.Subscribe(key, 0, value)
+ if helper.TokenClient.Wait() && helper.TokenClient.Error() != nil {
+ glog.Errorf("subscribe() Error in topic: %s is: %s", key, helper.TokenClient.Error())
+ }
+ }
+}
+
+//handleWatchMessage is the MQTT handler function for changing watcher configuration at runtime
+func (c *Config) handleWatchMessage(client MQTT.Client, message MQTT.Message) {
+ newWatch := watcher.Watcher{}
+ err := json.Unmarshal(message.Payload(), &newWatch)
+ if err != nil {
+ glog.Errorf("Error in unmarshalling: %s", err)
+ }
+ c.Watcher = newWatch
+ configuration.Config.Watcher = c.Watcher
+ glog.Infof("New watcher has been started")
+ glog.Infof("New Watcher: %v", c.Watcher)
+}
+
+//handleScheduleCreateMessage is the MQTT handler function for adding schedules at runtime
+func (c *Config) handleScheduleCreateMessage(client MQTT.Client, message MQTT.Message) {
+ newSchedules := []scheduler.Schedule{}
+ err := json.Unmarshal(message.Payload(), &newSchedules)
+ if err != nil {
+ glog.Errorf("Error in unmarshalling: %s", err)
+ }
+ for _, newSchedule := range newSchedules {
+ scheduleExists := false
+ for scheduleIndex, schedule := range c.Scheduler.Schedules {
+ if schedule.Name == newSchedule.Name {
+ c.Scheduler.Schedules[scheduleIndex] = newSchedule
+ scheduleExists = true
+ break
+ }
+ }
+ if scheduleExists {
+ c.Scheduler.Schedules = append(c.Scheduler.Schedules, newSchedule)
+ glog.Infof("Schedule: %s has been updated", newSchedule.Name)
+ glog.Infof("Updated Schedule: %v", newSchedule)
+ } else {
+ glog.Infof("Schedule: %s has been added", newSchedule.Name)
+ glog.Infof("New Schedule: %v", newSchedule)
+ }
+ configuration.Config.Scheduler = c.Scheduler
+ helper.ControllerWg.Add(1)
+ newSchedule.ExecuteSchedule(c.ActionManager.Actions, c.Converter.DataRead, c.Device.ID)
+ }
+}
+
+//handleScheduleDeleteMessage is the MQTT handler function for deleting schedules at runtime
+func (c *Config) handleScheduleDeleteMessage(client MQTT.Client, message MQTT.Message) {
+ schedulesToBeDeleted := []scheduler.Schedule{}
+ err := json.Unmarshal(message.Payload(), &schedulesToBeDeleted)
+ if err != nil {
+ glog.Errorf("Error in unmarshalling: %s", err)
+ }
+ for _, scheduleToBeDeleted := range schedulesToBeDeleted {
+ scheduleExists := false
+ for index, schedule := range c.Scheduler.Schedules {
+ if strings.ToUpper(schedule.Name) == strings.ToUpper(scheduleToBeDeleted.Name) {
+ scheduleExists = true
+ copy(c.Scheduler.Schedules[index:], c.Scheduler.Schedules[index+1:])
+ c.Scheduler.Schedules = c.Scheduler.Schedules[:len(c.Scheduler.Schedules)-1]
+ break
+ }
+ }
+ configuration.Config.Scheduler = c.Scheduler
+ if !scheduleExists {
+ glog.Errorf("Schedule: %s does not exist", scheduleToBeDeleted.Name)
+ } else {
+ glog.Infof("Schedule: %s has been deleted ", scheduleToBeDeleted.Name)
+ }
+ }
+}
+
+//handleActionCreateMessage MQTT handler function for adding actions at runtime
+func (c *Config) handleActionCreateMessage(client MQTT.Client, message MQTT.Message) {
+ newActions := []actionmanager.Action{}
+ err := json.Unmarshal(message.Payload(), &newActions)
+ if err != nil {
+ glog.Errorf("Error in unmarshalling: %s", err)
+ }
+ for _, newAction := range newActions {
+ actionExists := false
+ for actionIndex, action := range c.ActionManager.Actions {
+ if action.Name == newAction.Name {
+ c.ActionManager.Actions[actionIndex] = newAction
+ actionExists = true
+ break
+ }
+ }
+ if actionExists {
+ c.ActionManager.Actions = append(c.ActionManager.Actions, newAction)
+ glog.Infof("Action: %s has been updated", newAction.Name)
+ glog.Infof("Updated Action: %v", newAction)
+ } else {
+ glog.Infof("Action: %s has been added ", newAction.Name)
+ glog.Infof("New Action: %v", newAction)
+ }
+ configuration.Config.ActionManager = c.ActionManager
+ if newAction.PerformImmediately {
+ newAction.PerformOperation(c.Converter.DataRead)
+ }
+ }
+}
+
+//handleActionDeleteMessage MQTT handler function for deleting actions at runtime
+func (c *Config) handleActionDeleteMessage(client MQTT.Client, message MQTT.Message) {
+ actionsToBeDeleted := []actionmanager.Action{}
+ err := json.Unmarshal(message.Payload(), &actionsToBeDeleted)
+ if err != nil {
+ glog.Errorf("Error in unmarshalling: %s", err)
+ }
+ for _, actionToBeDeleted := range actionsToBeDeleted {
+ actionExists := false
+ for index, action := range c.ActionManager.Actions {
+ if strings.ToUpper(action.Name) == strings.ToUpper(actionToBeDeleted.Name) {
+ actionExists = true
+ copy(c.ActionManager.Actions[index:], c.ActionManager.Actions[index+1:])
+ c.ActionManager.Actions = c.ActionManager.Actions[:len(c.ActionManager.Actions)-1]
+ break
+ }
+ }
+ configuration.Config.ActionManager = c.ActionManager
+ if !actionExists {
+ glog.Errorf("Action: %s did not exist", actionToBeDeleted.Name)
+ } else {
+ glog.Infof("Action: %s has been deleted ", actionToBeDeleted.Name)
+ }
+ }
+}
diff --git a/mappers/bluetooth_mapper/data_converter/data_converter.go b/mappers/bluetooth_mapper/data_converter/data_converter.go
new file mode 100644
index 000000000..833bfc38f
--- /dev/null
+++ b/mappers/bluetooth_mapper/data_converter/data_converter.go
@@ -0,0 +1,117 @@
+/*
+Copyright 2019 The KubeEdge Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package dataconverter
+
+import (
+ "strconv"
+ "strings"
+)
+
+// Bluetooth Protocol Operation type
+const (
+ BluetoothAdd string = "Add"
+ BluetoothSubtract string = "Subtract"
+ BluetoothMultiply string = "Multiply"
+ BluetoothDivide string = "Divide"
+)
+
+//Converter is the structure that contains data conversion specific configuration
+type Converter struct {
+ DataWrite DataWrite `yaml:"write"`
+ DataRead DataRead `yaml:"read"`
+}
+
+//dataWrite structure contains configuration information specific to data-writes
+type DataWrite struct {
+ Attributes []WriteAttribute `yaml:"attributes"`
+}
+
+//WriteAttribute structure contains the name of the attribute as well as a data-map of values to be written
+type WriteAttribute struct {
+ Name string `yaml:"name"`
+ Operations map[string]DataMap `yaml:"operations"`
+}
+
+//DataMap structure contains a mapping between the value that arrives from the platform (expected value) and
+// the byte value to be written into the device
+type DataMap struct {
+ DataMapping map[string][]byte `yaml:"data-map"`
+}
+
+//dataRead structure contains configuration information specific to data-read
+type DataRead struct {
+ Actions []ReadAction `yaml:"actions"`
+}
+
+//ReadAction specifies the name of the action along with the conversion operations to be performed in case of data-read
+type ReadAction struct {
+ ActionName string `yaml:"action-name"`
+ ConversionOperation ReadOperation `yaml:"conversion-operation"`
+}
+
+//ReadOperation specifies how to convert the data received from the device into meaningful data
+type ReadOperation struct {
+ StartIndex int `yaml:"start-index"`
+ EndIndex int `yaml:"end-index"`
+ ShiftLeft uint `yaml:"shift-left"`
+ ShiftRight uint `yaml:"shift-right"`
+ Multiply float64 `yaml:"multiply"`
+ Divide float64 `yaml:"divide"`
+ Add float64 `yaml:"add"`
+ Subtract float64 `yaml:"subtract"`
+ OrderOfExecution []string `yaml:"order-of-execution"`
+}
+
+//ConvertReadData is the function responsible to convert the data read from the device into meaningful data
+func (operation *ReadOperation) ConvertReadData(data []byte) float64 {
+ var intermediateResult uint64
+ var initialValue []byte
+ var initialStringValue = ""
+ if operation.StartIndex <= operation.EndIndex {
+ for index := operation.StartIndex; index <= operation.EndIndex; index++ {
+ initialValue = append(initialValue, data[index])
+ }
+ } else {
+ for index := operation.StartIndex; index >= operation.EndIndex; index-- {
+ initialValue = append(initialValue, data[index])
+ }
+ }
+ for _, value := range initialValue {
+ initialStringValue = initialStringValue + strconv.Itoa(int(value))
+ }
+ initialByteValue, _ := strconv.ParseUint(initialStringValue, 16, 16)
+
+ if operation.ShiftLeft != 0 {
+ intermediateResult = initialByteValue << operation.ShiftLeft
+ } else if operation.ShiftRight != 0 {
+ intermediateResult = initialByteValue >> operation.ShiftRight
+ }
+ finalResult := float64(intermediateResult)
+ for _, executeOperation := range operation.OrderOfExecution {
+ switch strings.ToUpper(executeOperation) {
+ case strings.ToUpper(BluetoothAdd):
+ finalResult = finalResult + operation.Add
+ case strings.ToUpper(BluetoothSubtract):
+ finalResult = finalResult - operation.Subtract
+ case strings.ToUpper(BluetoothMultiply):
+ finalResult = finalResult * operation.Multiply
+ case strings.ToUpper(BluetoothDivide):
+ finalResult = finalResult / operation.Divide
+ }
+ }
+ return finalResult
+}
diff --git a/mappers/bluetooth_mapper/deployment.yaml b/mappers/bluetooth_mapper/deployment.yaml
new file mode 100644
index 000000000..3d236ec84
--- /dev/null
+++ b/mappers/bluetooth_mapper/deployment.yaml
@@ -0,0 +1,31 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: bluetooth-device-mapper-deployment
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: bluetooth-mapper
+ template:
+ metadata:
+ labels:
+ app: bluetooth-mapper
+ spec:
+ hostNetwork: true
+ containers:
+ - name: bluetooth-mapper-container
+ image: <your_dockerhub_username>/bluetooth_mapper:v1.0
+ imagePullPolicy: Always
+ securityContext:
+ privileged: true
+ volumeMounts:
+ - name: config-volume
+ mountPath: /opt/kubeedge/
+ nodeSelector:
+ bluetooth: "true"
+ volumes:
+ - name: config-volume
+ configMap:
+ name: device-profile-config-<edge_node_name>
+ restartPolicy: Always
diff --git a/mappers/bluetooth_mapper/helper/helper.go b/mappers/bluetooth_mapper/helper/helper.go
new file mode 100644
index 000000000..6ef5ed394
--- /dev/null
+++ b/mappers/bluetooth_mapper/helper/helper.go
@@ -0,0 +1,215 @@
+/*
+Copyright 2019 The KubeEdge Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package helper
+
+import (
+ "crypto/tls"
+ "encoding/json"
+ "sync"
+ "time"
+
+ MQTT "github.com/eclipse/paho.mqtt.golang"
+ "github.com/golang/glog"
+)
+
+var (
+ DeviceETPrefix = "$hw/events/device/"
+ DeviceETStateUpdateSuffix = "/state/update"
+ TwinETUpdateSuffix = "/twin/update"
+ TwinETCloudSyncSuffix = "/twin/cloud_updated"
+ TwinETGetSuffix = "/twin/get"
+ TwinETGetResultSuffix = "/twin/get/result"
+)
+
+var TwinResult DeviceTwinResult
+var Wg sync.WaitGroup
+var ControllerWg sync.WaitGroup
+var TwinAttributes []string
+
+var TokenClient Token
+var ClientOpts *MQTT.ClientOptions
+var Client MQTT.Client
+
+//Token interface to validate the MQTT connection.
+type Token interface {
+ Wait() bool
+ WaitTimeout(time.Duration) bool
+ Error() error
+}
+
+//DeviceStateUpdate is the structure used in updating the device state
+type DeviceStateUpdate struct {
+ State string `json:"state,omitempty"`
+}
+
+//BaseMessage the base struct of event message
+type BaseMessage struct {
+ EventID string `json:"event_id"`
+ Timestamp int64 `json:"timestamp"`
+}
+
+//TwinValue the struct of twin value
+type TwinValue struct {
+ Value *string `json:"value,omitempty"`
+ Metadata *ValueMetadata `json:"metadata,omitempty"`
+}
+
+//ValueMetadata the meta of value
+type ValueMetadata struct {
+ Timestamp int64 `json:"timestamp,omitempty"`
+}
+
+//TypeMetadata the meta of value type
+type TypeMetadata struct {
+ Type string `json:"type,omitempty"`
+}
+
+//TwinVersion twin version
+type TwinVersion struct {
+ CloudVersion int64 `json:"cloud"`
+ EdgeVersion int64 `json:"edge"`
+}
+
+//MsgTwin the struct of device twin
+type MsgTwin struct {
+ Expected *TwinValue `json:"expected,omitempty"`
+ Actual *TwinValue `json:"actual,omitempty"`
+ Optional *bool `json:"optional,omitempty"`
+ Metadata *TypeMetadata `json:"metadata,omitempty"`
+ ExpectedVersion *TwinVersion `json:"expected_version,omitempty"`
+ ActualVersion *TwinVersion `json:"actual_version,omitempty"`
+}
+
+//DeviceTwinUpdate the struct of device twin update
+type DeviceTwinUpdate struct {
+ BaseMessage
+ Twin map[string]*MsgTwin `json:"twin"`
+}
+
+//DeviceTwinResult device get result
+type DeviceTwinResult struct {
+ BaseMessage
+ Twin map[string]*MsgTwin `json:"twin"`
+}
+
+// HubclientInit create mqtt client config
+func HubClientInit(server, clientID, username, password string) *MQTT.ClientOptions {
+ opts := MQTT.NewClientOptions().AddBroker(server).SetClientID(clientID).SetCleanSession(true)
+ if username != "" {
+ opts.SetUsername(username)
+ if password != "" {
+ opts.SetPassword(password)
+ }
+ }
+ tlsConfig := &tls.Config{InsecureSkipVerify: true, ClientAuth: tls.NoClientCert}
+ opts.SetTLSConfig(tlsConfig)
+ return opts
+}
+
+//MqttConnect function felicitates the MQTT connection
+func MqttConnect(mqttMode int, mqttInternalServer, mqttServer string) {
+ // Initiate the MQTT connection
+ if mqttMode == 0 {
+ ClientOpts = HubClientInit(mqttInternalServer, "eventbus", "", "")
+ } else if mqttMode == 1 {
+ ClientOpts = HubClientInit(mqttServer, "eventbus", "", "")
+ }
+ Client = MQTT.NewClient(ClientOpts)
+ if TokenClient = Client.Connect(); TokenClient.Wait() && TokenClient.Error() != nil {
+ glog.Errorf("client.Connect() Error is %s", TokenClient.Error())
+ }
+}
+
+//ChangeTwinValue sends the updated twin value to the edge through the MQTT broker
+func ChangeTwinValue(updateMessage DeviceTwinUpdate, deviceID string) {
+ twinUpdateBody, err := json.Marshal(updateMessage)
+ if err != nil {
+ glog.Errorf("Error in marshalling: %s", err)
+ }
+ deviceTwinUpdate := DeviceETPrefix + deviceID + TwinETUpdateSuffix
+ TokenClient = Client.Publish(deviceTwinUpdate, 0, false, twinUpdateBody)
+ if TokenClient.Wait() && TokenClient.Error() != nil {
+ glog.Errorf("client.publish() Error in device twin update is %s", TokenClient.Error())
+ }
+}
+
+//SyncToCloud function syncs the updated device twin information to the cloud
+func SyncToCloud(updateMessage DeviceTwinUpdate, deviceID string) {
+ deviceTwinResultUpdate := DeviceETPrefix + deviceID + TwinETCloudSyncSuffix
+ twinUpdateBody, err := json.Marshal(updateMessage)
+ if err != nil {
+ glog.Errorf("Error in marshalling: %s", err)
+ }
+ TokenClient = Client.Publish(deviceTwinResultUpdate, 0, false, twinUpdateBody)
+ if TokenClient.Wait() && TokenClient.Error() != nil {
+ glog.Errorf("client.publish() Error in device twin update is: %s", TokenClient.Error())
+ }
+}
+
+//GetTwin function is used to get the device twin details from the edge
+func GetTwin(updateMessage DeviceTwinUpdate, deviceID string) {
+ getTwin := DeviceETPrefix + deviceID + TwinETGetSuffix
+ twinUpdateBody, err := json.Marshal(updateMessage)
+ if err != nil {
+ glog.Errorf("Error in marshalling: %s", err)
+ }
+ TokenClient = Client.Publish(getTwin, 0, false, twinUpdateBody)
+ if TokenClient.Wait() && TokenClient.Error() != nil {
+ glog.Errorf("client.publish() Error in device twin get is: %s ", TokenClient.Error())
+ }
+}
+
+//subscribe function subscribes the device twin information through the MQTT broker
+func TwinSubscribe(deviceID string) {
+ getTwinResult := DeviceETPrefix + deviceID + TwinETGetResultSuffix
+ TokenClient = Client.Subscribe(getTwinResult, 0, OnTwinMessageReceived)
+ if TokenClient.Wait() && TokenClient.Error() != nil {
+ glog.Errorf("subscribe() Error in device twin result get is: %s", TokenClient.Error())
+ }
+ for {
+ time.Sleep(1 * time.Second)
+ if TwinResult.Twin != nil {
+ for k := range TwinResult.Twin {
+ TwinAttributes = append(TwinAttributes, k)
+ }
+ Wg.Done()
+ break
+ }
+ }
+}
+
+// OnTwinMessageReceived callback function which is called when message is received
+func OnTwinMessageReceived(client MQTT.Client, message MQTT.Message) {
+ err := json.Unmarshal(message.Payload(), &TwinResult)
+ if err != nil {
+ glog.Errorf("Error in unmarshalling: %s", err)
+ }
+}
+
+//CreateActualUpdateMessage function is used to create the device twin update message
+func CreateActualUpdateMessage(updatedTwinAttributes map[string]string) DeviceTwinUpdate {
+ var deviceTwinUpdateMessage DeviceTwinUpdate
+ deviceTwinUpdateMessage.Twin = map[string]*MsgTwin{}
+ for _, twinAttribute := range TwinAttributes {
+ if actualValue, ok := updatedTwinAttributes[twinAttribute]; ok {
+ deviceTwinUpdateMessage.Twin[twinAttribute] = &MsgTwin{}
+ deviceTwinUpdateMessage.Twin[twinAttribute].Actual = &TwinValue{Value: &actualValue}
+ deviceTwinUpdateMessage.Twin[twinAttribute].Metadata = &TypeMetadata{Type: "Updated"}
+ }
+ }
+ return deviceTwinUpdateMessage
+}
diff --git a/mappers/bluetooth_mapper/main.go b/mappers/bluetooth_mapper/main.go
new file mode 100644
index 000000000..1c433c61f
--- /dev/null
+++ b/mappers/bluetooth_mapper/main.go
@@ -0,0 +1,61 @@
+/*
+Copyright 2019 The KubeEdge Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package main
+
+import (
+ "flag"
+ "os"
+
+ "github.com/golang/glog"
+
+ "github.com/kubeedge/kubeedge/mappers/bluetooth_mapper/configuration"
+ "github.com/kubeedge/kubeedge/mappers/bluetooth_mapper/controller"
+)
+
+//usage is responsible for setting up the default settings of all defined command-line flags for glog.
+func usage() {
+ flag.PrintDefaults()
+ os.Exit(2)
+}
+
+//init for getting command line arguments for glog
+func init() {
+ flag.Usage = usage
+ // NOTE: This next line is key you have to call flag.Parse() for the command line
+ // options or "flags" that are defined in the glog module to be picked up.
+ flag.Parse()
+}
+
+// main function
+func main() {
+ BleConfig := configuration.BLEConfig{}
+ // load config
+ err := BleConfig.Load()
+ if err != nil {
+ glog.Errorf("Error in loading configuration: %s", err)
+ os.Exit(1)
+ }
+ bleController := controller.Config{
+ Watcher: BleConfig.Watcher,
+ ActionManager: BleConfig.ActionManager,
+ Scheduler: BleConfig.Scheduler,
+ Converter: BleConfig.Converter,
+ Device: BleConfig.Device,
+ Mqtt: BleConfig.Mqtt,
+ }
+ bleController.Start()
+}
diff --git a/mappers/bluetooth_mapper/scheduler/scheduler.go b/mappers/bluetooth_mapper/scheduler/scheduler.go
new file mode 100644
index 000000000..047aff4b8
--- /dev/null
+++ b/mappers/bluetooth_mapper/scheduler/scheduler.go
@@ -0,0 +1,117 @@
+/*
+Copyright 2019 The KubeEdge Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package scheduler
+
+import (
+ "encoding/json"
+ "fmt"
+ "strings"
+ "time"
+
+ "github.com/golang/glog"
+
+ "github.com/kubeedge/kubeedge/mappers/bluetooth_mapper/action_manager"
+ "github.com/kubeedge/kubeedge/mappers/bluetooth_mapper/data_converter"
+ "github.com/kubeedge/kubeedge/mappers/bluetooth_mapper/helper"
+)
+
+const (
+ MapperTopicPrefix = "$ke/device/bluetooth-mapper/"
+ SchedulerResultSuffix = "/scheduler/result"
+ defaultEventFrequency = 5000
+)
+
+// Schedule is structure to define a schedule
+type Schedule struct {
+ // Name is name of the schedule. It should be unique so that a stop-chan
+ // can be made corresponding to name to stop the schedule.
+ Name string `yaml:"name" json:"name"`
+ // Interval is the time in milliseconds after which this action are to be performed
+ Interval int `yaml:"interval" json:"interval"`
+ //OccurrenceLimit refers to the number of time the action can occur, if it is 0, then the event will execute infinitely
+ OccurrenceLimit int `yaml:"occurrence-limit" json:"occurrence-limit"`
+ // Actions is list of Actions to be performed in this schedule
+ Actions []string `yaml:"actions"`
+}
+
+//Scheduler structure contains the list of schedules to be scheduled
+type Scheduler struct {
+ Schedules []Schedule `yaml:"schedules" json:"schedules"`
+}
+
+//ScheduleResult structure contains the format in which telemetry data will be published on the MQTT topic
+type ScheduleResult struct {
+ EventName string
+ TimeStamp int64
+ EventResult string
+}
+
+// ExecuteSchedule is responsible for scheduling the operations
+func (schedule *Schedule) ExecuteSchedule(actionManager []actionmanager.Action, dataConverter dataconverter.DataRead, deviceID string) {
+ glog.Infof("Executing schedule: %s", schedule.Name)
+ if schedule.OccurrenceLimit != 0 {
+ for iteration := 0; iteration < schedule.OccurrenceLimit; iteration++ {
+ schedule.performScheduleOperation(actionManager, dataConverter, deviceID)
+ }
+ } else {
+ for {
+ schedule.performScheduleOperation(actionManager, dataConverter, deviceID)
+ }
+ }
+ helper.ControllerWg.Done()
+}
+
+// performScheduleOperation is responsible for performing the operations associated with the schedule
+func (schedule *Schedule) performScheduleOperation(actionManager []actionmanager.Action, dataConverter dataconverter.DataRead, deviceID string) {
+ var scheduleResult ScheduleResult
+ actionExists := false
+ for _, actionName := range schedule.Actions {
+ for _, action := range actionManager {
+ if strings.ToUpper(action.Name) == strings.ToUpper(actionName) {
+ actionExists = true
+ glog.Infof("Performing scheduled operation: %s", action.Name)
+ action.PerformOperation(dataConverter)
+ scheduleResult.EventName = actionName
+ scheduleResult.TimeStamp = time.Now().UnixNano() / 1e6
+ scheduleResult.EventResult = fmt.Sprintf("%s", action.Operation.Value)
+ publishScheduleResult(scheduleResult, deviceID)
+ }
+ }
+ if schedule.Interval == 0 {
+ schedule.Interval = defaultEventFrequency
+ }
+ if !actionExists {
+ glog.Errorf("Action %s does not exist. Exiting from schedule !!!", actionName)
+ break
+ }
+ time.Sleep(time.Duration(time.Duration(schedule.Interval) * time.Millisecond))
+ }
+}
+
+//publishScheduleResult publishes the telemetry data on the given MQTT topic
+func publishScheduleResult(scheduleResult ScheduleResult, deviceID string) {
+ scheduleResultTopic := MapperTopicPrefix + deviceID + SchedulerResultSuffix
+ glog.Infof("Publishing schedule: %s result on topic: %s", scheduleResult.EventName, scheduleResultTopic)
+ scheduleResultBody, err := json.Marshal(scheduleResult)
+ if err != nil {
+ glog.Errorf("Error: %s", err)
+ }
+ helper.TokenClient = helper.Client.Publish(scheduleResultTopic, 0, false, scheduleResultBody)
+ if helper.TokenClient.Wait() && helper.TokenClient.Error() != nil {
+ glog.Errorf("client.publish() Error in device twin get is %s", helper.TokenClient.Error())
+ }
+}
diff --git a/mappers/bluetooth_mapper/watcher/watcher.go b/mappers/bluetooth_mapper/watcher/watcher.go
new file mode 100644
index 000000000..9218b8ac5
--- /dev/null
+++ b/mappers/bluetooth_mapper/watcher/watcher.go
@@ -0,0 +1,190 @@
+/*
+Copyright 2019 The KubeEdge Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package watcher
+
+import (
+ "errors"
+ "os"
+ "reflect"
+ "strings"
+ "time"
+
+ "github.com/golang/glog"
+ "github.com/paypal/gatt"
+
+ "github.com/kubeedge/kubeedge/mappers/bluetooth_mapper/action_manager"
+ "github.com/kubeedge/kubeedge/mappers/bluetooth_mapper/data_converter"
+ "github.com/kubeedge/kubeedge/mappers/bluetooth_mapper/helper"
+)
+
+var DeviceConnected = make(chan bool)
+var done = make(chan struct{})
+var deviceName string
+var deviceID string
+var actionManager []actionmanager.Action
+var dataConverter dataconverter.Converter
+
+//Watch structure contains the watcher specific configurations
+type Watcher struct {
+ DeviceTwinAttributes []Attribute `yaml:"device-twin-attributes" json:"device-twin-attributes"`
+}
+
+//Attribute structure contains the name of the attribute along with the actions to be performed for this attribute
+type Attribute struct {
+ Name string `yaml:"device-property-name" json:"device-property-name"`
+ Actions []string `yaml:"actions" json:"actions"`
+}
+
+//Initiate initiates the watcher module
+func (w *Watcher) Initiate(device gatt.Device, nameOfDevice, idOfDevice string, actions []actionmanager.Action, converter dataconverter.Converter) {
+ deviceID = idOfDevice
+ deviceName = nameOfDevice
+ actionManager = actions
+ dataConverter = converter
+ // Register optional handlers.
+ device.Handle(
+ gatt.PeripheralConnected(w.onPeripheralConnected),
+ gatt.PeripheralDisconnected(onPeripheralDisconnected),
+ gatt.PeripheralDiscovered(onPeripheralDiscovered),
+ )
+ device.Init(onStateChanged)
+ <-done
+ glog.Infof("Watcher Done")
+}
+
+//onStateChanged contains the operations to be performed when the state of the peripheral device changes
+func onStateChanged(device gatt.Device, s gatt.State) {
+ switch s {
+ case gatt.StatePoweredOn:
+ glog.Infof("Scanning for BLE device Broadcasts...")
+ device.Scan([]gatt.UUID{}, true)
+ return
+ default:
+ device.StopScanning()
+ }
+}
+
+//onPeripheralDiscovered contains the operations to be performed as soon as the peripheral device is discovered
+func onPeripheralDiscovered(p gatt.Peripheral, a *gatt.Advertisement, rssi int) {
+ if strings.ToUpper(a.LocalName) == strings.ToUpper(strings.Replace(deviceName, "-", " ", -1)) {
+ glog.Infof("Device: %s found !!!! Stop Scanning for devices", deviceName)
+ // Stop scanning once we've got the peripheral we're looking for.
+ p.Device().StopScanning()
+ glog.Infof("Connecting to %s", deviceName)
+ p.Device().Connect(p)
+ }
+}
+
+//onPeripheralDisconnected contains the operations to be performed as soon as the peripheral device is disconnected
+func onPeripheralDisconnected(p gatt.Peripheral, err error) {
+ glog.Infof("Disconnecting from bluetooth device....")
+ DeviceConnected <- false
+ close(done)
+ p.Device().CancelConnection(p)
+}
+
+//onPeripheralConnected contains the operations to be performed as soon as the peripheral device is connected
+func (w *Watcher) onPeripheralConnected(p gatt.Peripheral, err error) {
+ actionmanager.GattPeripheral = p
+ ss, err := p.DiscoverServices(nil)
+ if err != nil {
+ glog.Errorf("Failed to discover services, err: %s\n", err)
+ os.Exit(1)
+ }
+ for _, s := range ss {
+ // Discovery characteristics
+ cs, err := p.DiscoverCharacteristics(nil, s)
+ if err != nil {
+ glog.Errorf("Failed to discover characteristics for service %s, err: %v\n", s.Name(), err)
+ continue
+ }
+ actionmanager.CharacteristicsList = append(actionmanager.CharacteristicsList, cs...)
+ }
+ DeviceConnected <- true
+ for {
+ newWatcher := &Watcher{}
+ if !reflect.DeepEqual(w, newWatcher) {
+ err := w.EquateTwinValue(deviceID)
+ if err != nil {
+ glog.Errorf("Error in watcher functionality: %s", err)
+ }
+ }
+ }
+}
+
+//EquateTwinValue is responsible for equating the actual state of the device to the expected state that has been set and syncing back the result to the cloud
+func (w *Watcher) EquateTwinValue(deviceID string) error {
+ var updateMessage helper.DeviceTwinUpdate
+ updatedActualValues := make(map[string]string)
+ helper.Wg.Add(1)
+ glog.Infof("Watching on the device twin values for device with deviceID: %s", deviceID)
+ go helper.TwinSubscribe(deviceID)
+ helper.GetTwin(updateMessage, deviceID)
+ helper.Wg.Wait()
+ twinUpdated := false
+ for _, twinAttribute := range w.DeviceTwinAttributes {
+ if helper.TwinResult.Twin[twinAttribute.Name] != nil {
+ if helper.TwinResult.Twin[twinAttribute.Name].Expected != nil && ((helper.TwinResult.Twin[twinAttribute.Name].Actual == nil) && helper.TwinResult.Twin[twinAttribute.Name].Expected != nil || (*helper.TwinResult.Twin[twinAttribute.Name].Expected.Value != *helper.TwinResult.Twin[twinAttribute.Name].Actual.Value)) {
+ glog.Infof("%s Expected Value : %s", twinAttribute.Name, *helper.TwinResult.Twin[twinAttribute.Name].Expected.Value)
+ if helper.TwinResult.Twin[twinAttribute.Name].Actual == nil {
+ glog.Infof("%s Actual Value: %v", twinAttribute.Name, helper.TwinResult.Twin[twinAttribute.Name].Actual)
+ } else {
+ glog.Infof("%s Actual Value: %s", twinAttribute.Name, *helper.TwinResult.Twin[twinAttribute.Name].Actual.Value)
+ }
+ glog.Infof("Equating the actual value to expected value for: %s", twinAttribute.Name)
+ for _, watcherAction := range twinAttribute.Actions {
+ actionExists := false
+ for _, action := range actionManager {
+ if strings.ToUpper(action.Name) == strings.ToUpper(watcherAction) {
+ actionExists = true
+ for _, converterAttribute := range dataConverter.DataWrite.Attributes {
+ if strings.ToUpper(converterAttribute.Name) == strings.ToUpper(twinAttribute.Name) {
+ for operationName, dataMap := range converterAttribute.Operations {
+ if action.Name == operationName {
+ expectedValue := helper.TwinResult.Twin[twinAttribute.Name].Expected.Value
+ if _, ok := dataMap.DataMapping[*expectedValue]; ok {
+ action.Operation.Value = dataMap.DataMapping[*expectedValue]
+ }
+ }
+ action.PerformOperation()
+ }
+ }
+ }
+ }
+ }
+ if !actionExists {
+ return errors.New("The action: " + watcherAction + " does not exist for this device")
+ }
+ }
+ updatedActualValues[twinAttribute.Name] = *helper.TwinResult.Twin[twinAttribute.Name].Expected.Value
+ twinUpdated = true
+ }
+ } else {
+ return errors.New("The attribute: " + twinAttribute.Name + " does not exist for this device")
+ }
+ }
+ if twinUpdated {
+ updateMessage = helper.CreateActualUpdateMessage(updatedActualValues)
+ helper.ChangeTwinValue(updateMessage, deviceID)
+ time.Sleep(2 * time.Second)
+ glog.Infof("Syncing to cloud.....")
+ helper.SyncToCloud(updateMessage, deviceID)
+ } else {
+ glog.Infof("Actual values are in sync with Expected value")
+ }
+ return nil
+}
diff --git a/mappers/modbus_mapper/Dockerfile b/mappers/modbus_mapper/Dockerfile
new file mode 100644
index 000000000..d62599bc0
--- /dev/null
+++ b/mappers/modbus_mapper/Dockerfile
@@ -0,0 +1,10 @@
+FROM alpine:latest
+
+COPY src/ /opt/src
+COPY conf/ /opt/src/conf
+COPY scripts/ /opt/scripts
+
+RUN chmod +x /opt/scripts/start_modbusmapper.sh
+RUN apk add --update nodejs
+
+CMD sh /opt/scripts/start_modbusmapper.sh
diff --git a/mappers/modbus_mapper/Makefile b/mappers/modbus_mapper/Makefile
new file mode 100644
index 000000000..9be3cb8d8
--- /dev/null
+++ b/mappers/modbus_mapper/Makefile
@@ -0,0 +1,5 @@
+#make modbus_mapper
+.PHONY: default modbus_mapper
+modbus_mapper:
+ cd src && npm install --unsafe-perm=true
+ docker build -t modbus_mapper:v1.0 .
diff --git a/mappers/modbus_mapper/README.md b/mappers/modbus_mapper/README.md
new file mode 100644
index 000000000..e9af90c96
--- /dev/null
+++ b/mappers/modbus_mapper/README.md
@@ -0,0 +1,4 @@
+
+# Modbus Mapper
+
+Click [here](https://github.com/kubeedge/kubeedge/blob/master/docs/mappers/modbus_mapper.md#modbus-mapper) for detailed documentation of the modbus mapper. \ No newline at end of file
diff --git a/mappers/modbus_mapper/conf/conf.json b/mappers/modbus_mapper/conf/conf.json
new file mode 100644
index 000000000..5d80341b8
--- /dev/null
+++ b/mappers/modbus_mapper/conf/conf.json
@@ -0,0 +1,6 @@
+{
+ "mqtt_port": {CONNECTOR_MQTT_PORT},
+ "mqtt_ip": "{CONNECTOR_MQTT_IP}",
+ "dpl_name": "{CONNECTOR_DPL_NAME}",
+ "log_level": "info"
+}
diff --git a/mappers/modbus_mapper/deployment.yaml b/mappers/modbus_mapper/deployment.yaml
new file mode 100644
index 000000000..30a536796
--- /dev/null
+++ b/mappers/modbus_mapper/deployment.yaml
@@ -0,0 +1,39 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: modbus-device-mapper-deployment
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: modbus-mapper
+ template:
+ metadata:
+ labels:
+ app: modbus-mapper
+ spec:
+ hostNetwork: true
+ containers:
+ - name: modbus-mapper-container
+ image: <your_dockerhub_username>/modbus_mapper:v1.0
+ env:
+ - name: CONNECTOR_MQTT_PORT
+ value: "1883"
+ - name: CONNECTOR_MQTT_IP
+ value: 127.0.0.1
+ - name: CONNECTOR_DPL_NAME
+ value: dpl/deviceProfile.json
+ imagePullPolicy: IfNotPresent
+ securityContext:
+ privileged: true
+ volumeMounts:
+ - name: dpl-config-volume
+ mountPath: /opt/src/dpl
+ nodeSelector:
+ modbus: "true"
+ volumes:
+ - name: dpl-config-volume
+ configMap:
+ name: device-profile-config-<edge_node_name>
+ restartPolicy: Always
+ \ No newline at end of file
diff --git a/mappers/modbus_mapper/dpl/deviceProfile.json b/mappers/modbus_mapper/dpl/deviceProfile.json
new file mode 100644
index 000000000..00082d319
--- /dev/null
+++ b/mappers/modbus_mapper/dpl/deviceProfile.json
@@ -0,0 +1,42 @@
+{
+ "deviceInstances": [{
+ "id": "f81e1dce-8152-4f76-b673-d6728b46e395",
+ "name": "modbus-mock-instance-01",
+ "model": "modbus-mock-model",
+ "protocol": "modbus-tcp-01"
+ }],
+ "deviceModels": [{
+ "properties": [{
+ "name": "temperature",
+ "minimum": 0,
+ "maximum": 100,
+ "datatype": "int",
+ "accessMode": "rw"
+ }],
+ "name": "modbus-mock-model",
+ "description": null
+ }],
+ "propertyVisitors": [{
+ "visitorConfig": {
+ "index": 0,
+ "scale": 1.0,
+ "isRegisterSwap": true,
+ "register": "HoldingRegister",
+ "offset": 1,
+ "isSwap": false
+ },
+ "propertyName": "temperature",
+ "modelName": "modbus-mock-model",
+ "protocol": "modbus-tcp",
+ "name": "temperature"
+ }],
+ "protocols": [{
+ "protocolConfig": {
+ "ip": "127.0.0.1",
+ "port": 5028,
+ "slaveID": 1
+ },
+ "protocol": "modbus-tcp",
+ "name": "modbus-tcp-01"
+ }]
+}
diff --git a/mappers/modbus_mapper/sample/sample_server.js b/mappers/modbus_mapper/sample/sample_server.js
new file mode 100644
index 000000000..79341ab76
--- /dev/null
+++ b/mappers/modbus_mapper/sample/sample_server.js
@@ -0,0 +1,33 @@
+// create an empty modbus client
+const ModbusRTU = require("modbus-serial");
+var holdingValue = 5
+var coilValue = false
+var vector = {
+ getHoldingRegister: function() {
+ return holdingValue;
+ },
+ getCoil: function() {
+ return coilValue;
+ },
+ setRegister: function(addr, value, unitID) {
+ // Asynchronous handling supported also here
+ console.log("set register", addr, value, unitID);
+ holdingValue = value;
+ return;
+ },
+ setCoil: function(addr, value, unitID) {
+ // Asynchronous handling supported also here
+ console.log("set coil", addr, value, unitID);
+ coilValue = Boolean(value);
+ return;
+ },
+};
+
+// set the server to answer for modbus requests
+console.log("ModbusTCP listening on modbus://127.0.0.1:5028");
+var serverTCP = new ModbusRTU.ServerTCP(vector, { host: "0.0.0.0", port: 5028, debug: true, unitID: 1 });
+
+serverTCP.on("socketError", function(err){
+ // Handle socket error if needed, can be ignored
+ console.error(err);
+}); \ No newline at end of file
diff --git a/mappers/modbus_mapper/scripts/start_modbusmapper.sh b/mappers/modbus_mapper/scripts/start_modbusmapper.sh
new file mode 100644
index 000000000..80c0dd1b1
--- /dev/null
+++ b/mappers/modbus_mapper/scripts/start_modbusmapper.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+CONFIG_DIR=/opt/src/conf
+
+for VAR in $(env)
+do
+ if [[ ! -z "$(echo $VAR | grep -E '^CONNECTOR_')" ]]; then
+ VAR_NAME=$(echo "$VAR" | sed -r "s/([^=]*)=.*/\1/g")
+ echo "$VAR_NAME=$(eval echo \$$VAR_NAME)"
+ sed -i "s#{$VAR_NAME}#$(eval echo \$$VAR_NAME)#g" $CONFIG_DIR/conf.json
+ fi
+done
+
+cd /opt/src
+node index.js
diff --git a/mappers/modbus_mapper/src/common.js b/mappers/modbus_mapper/src/common.js
new file mode 100644
index 000000000..400848d62
--- /dev/null
+++ b/mappers/modbus_mapper/src/common.js
@@ -0,0 +1,93 @@
+const fs = require('fs');
+const path = require('path');
+const util = require('util');
+const mkdirp = require('mkdirp');
+
+// bitArrayToInt change bit array to Int
+function bitArrayToInt(bitArr, callback) {
+ let bitStr = '';
+ if (bitArr.length > 0 && bitArr.length < 64) {
+ for (let i = 0; i < bitArr.length; i++){
+ bitStr = bitStr + bitArr[i].toString();
+ }
+ num = parseInt(bitStr,2);
+ callback(num);
+ }
+}
+
+// byteArrayToInt change one byte array to Int
+function byteArrayToInt(byteArr, callback) {
+ let bitArr = '';
+ if (byteArr.length > 0 && byteArr.length < 5){
+ for (let i = 0; i < byteArr.length; i++){
+ bitArr = bitArr + (byteArr[i]).toString(2).padStart(8, '0');
+ }
+ callback(parseInt(bitArr, 2));
+ }
+}
+
+// twoByteArrayToInt change two byte array to Int
+function twoByteArrayToInt(byteArr, callback) {
+ let bitArr = '';
+ if (byteArr.length > 0 && byteArr.length < 5){
+ for (let i = 0; i < byteArr.length; i++){
+ bitArr = bitArr + (byteArr[i]).toString(2).padStart(16, '0');
+ }
+ callback(parseInt(bitArr, 2));
+ }
+}
+
+// IntToByteArray change Int to byte array
+function IntToByteArray(value, callback) {
+ if ((value).toString(2).length > 32){
+ let cs1 = (value).toString(2).slice(0,(value).toString(2).length-32);
+ let cs2 = (value).toString(2).slice((value).toString(2).length-32);
+ Int32ToByte(parseInt(cs1, 2), (arr1)=>{
+ Int32ToByte(parseInt(cs2, 2), (arr2)=>{
+ arr1 = arr1.concat(arr2);
+ callback(arr1);
+ });
+ });
+ } else {
+ Int32ToByte(value, (arr)=>{
+ callback(arr);
+ });
+ }
+}
+
+// Int32ToByte change Int32 num to byte array
+function Int32ToByte(value, callback) {
+ let byteArr = [];
+ for (let i = 16; i >= 0; i = i - 16) {
+ if((value >> i & 0xffff) != 0) {
+ byteArr.push(value >> i & 0xffff);
+ }
+ }
+ callback(byteArr);
+}
+
+// switchRegister reverse the order of array
+function switchRegister(data, callback) {
+ let switchData = [];
+ for (let i = 0; i < data.length/2; i++) {
+ switchData[i] = data[data.length-i-1];
+ switchData[data.length-i-1] = data[i];
+ }
+ callback(switchData)
+}
+
+// switchByte exchange lower and higher byte value of two byte data
+function switchByte(data, callback){
+ let switchData = [];
+ let InternalData = [];
+ for (let i = 0; i < data.length; i++){
+ InternalData[0] = data[i] & 0xff;
+ InternalData[1] = data[i] >> 8 & 0xff;
+ byteArrayToInt(InternalData, (bitarr)=>{
+ switchData[i] = bitarr;
+ });
+ }
+ callback(switchData)
+}
+
+module.exports = {bitArrayToInt, byteArrayToInt, IntToByteArray, switchRegister, switchByte, twoByteArrayToInt};
diff --git a/mappers/modbus_mapper/src/constant.js b/mappers/modbus_mapper/src/constant.js
new file mode 100644
index 000000000..39193fe85
--- /dev/null
+++ b/mappers/modbus_mapper/src/constant.js
@@ -0,0 +1,17 @@
+const defaultTopicPrefix = '$hw/events/device/';
+const defaultDirectTopicPrefix = '$hw/devices/';
+const twinDeltaTopic = defaultTopicPrefix + '+/twin/update/delta';
+const twinUpdateTopic = '/twin/update';
+const twinGetResTopic = defaultTopicPrefix + '+/twin/get/result';
+const twinGetTopic = '/twin/get';
+const directGetTopic = '/events/properties/get';
+
+module.exports = {
+ twinDeltaTopic,
+ twinUpdateTopic,
+ defaultTopicPrefix,
+ defaultDirectTopicPrefix,
+ directGetTopic,
+ twinGetTopic,
+ twinGetResTopic
+};
diff --git a/mappers/modbus_mapper/src/devicetwin.js b/mappers/modbus_mapper/src/devicetwin.js
new file mode 100644
index 000000000..6f15d5425
--- /dev/null
+++ b/mappers/modbus_mapper/src/devicetwin.js
@@ -0,0 +1,230 @@
+const constant = require('./constant');
+const common = require('./common');
+const Buffer = require('buffer').Buffer;
+const uuidv4 = require('uuid/v4');
+const util = require('util');
+const async = require('async');
+
+class DeviceTwin {
+ constructor(mqttClient) {
+ this.mqttClient = mqttClient;
+ }
+
+ // transferType transfer data according to the dpl configuration
+ transferType(visitor, property, data, callback) {
+ let transData;
+ async.waterfall([
+ function(callback) {
+ if (visitor.visitorConfig.isRegisterSwap) {
+ common.switchRegister(data, (switchedData)=>{
+ callback(null, switchedData);
+ });
+ } else {
+ callback(null, data);
+ }
+ },
+ function(internalData, callback) {
+ if (visitor.visitorConfig.isSwap && (visitor.visitorConfig.register === 'HoldingRegister' || visitor.visitorConfig.register === 'InputRegister')) {
+ common.switchByte(internalData, (switchedData)=>{
+ callback(null, switchedData);
+ });
+ } else {
+ callback(null, internalData);
+ }
+ }
+ ], function(err, transedData) {
+ transData = transedData;
+ });
+ this.transferDataType(visitor, property, transData, callback);
+ }
+
+ // transferDataType transfer data according to the dpl configuration
+ transferDataType(visitor, property, data, callback) {
+ let transData;
+ switch(property.dataType) {
+ case 'int':
+ case 'float':
+ if (visitor.visitorConfig.register === 'DiscreteInputRegister' || visitor.visitorConfig.register === 'CoilRegister') {
+ common.bitArrayToInt(data, (num)=>{
+ transData = num;
+ });
+ } else if (visitor.visitorConfig.register === 'HoldingRegister' || visitor.visitorConfig.register === 'InputRegister') {
+ common.twoByteArrayToInt(data, (num)=>{
+ transData = num;
+ })
+ }
+
+ if (visitor.visitorConfig.scale !=0 && transData != null) {
+ transData = transData * visitor.visitorConfig.scale;
+ }
+
+ if (property.dataType === 'int') {
+ transData = parseInt(transData);
+ }
+
+ if (property.maximum !== null && transData > parseFloat(property.maximum)) {
+ logger.info("read data is larger than max value, use max value")
+ transData = parseInt(property.maximum);
+ } else if (property.minimum !== null && transData < parseFloat(property.minimum)) {
+ logger.info("read data is smaller than min value, use min value")
+ transData = parseInt(property.minimum);
+ }
+
+ callback(transData);
+ break;
+ case 'string':
+ let buf = new Buffer.from(data);
+ transData = buf.toString('utf8')
+ callback(transData);
+ break;
+ case 'boolean':
+ if (data[0] == 0 || data[0] == 1){
+ transData = Boolean(data[0]);
+ } else {
+ transData = null;
+ }
+ callback(transData);
+ break;
+ default:
+ logger.error('unknown dataType: ', property.dataType);
+ callback(null);
+ break;
+ }
+ }
+
+ // updateActual update actual value to edge mqtt
+ updateActual(deviceID, property, value) {
+ let reply_msg = {
+ event_id: "",
+ timestamp: new Date().getTime()
+ };
+ let twin = {};
+ twin[property.name] = {
+ actual: {
+ value: String(value),
+ metadata: {
+ timestamp: new Date().getTime()
+ }
+ },
+ metadata: {
+ tyep: property.dataType
+ }
+ };
+ reply_msg.twin = twin;
+ this.mqttClient.publish(constant.defaultTopicPrefix + deviceID + constant.twinUpdateTopic, JSON.stringify(reply_msg));
+ }
+
+ // dealUpdate set latest actual value of devicetwin into actualVal map
+ dealUpdate(transData, property, deviceID, actualVals) {
+ if (!actualVals.has(util.format("%s-%s", deviceID, property.name))) {
+ this.updateActual(deviceID, property, transData);
+ actualVals.set(util.format("%s-%s", deviceID, property.name), String(transData));
+ logger.info("update devicetwin[%s] of device[%s] successfully", property.name, deviceID);
+ } else {
+ this.compareActuals(transData, actualVals.get(util.format("%s-%s", deviceID, property.name)), (changed)=>{
+ if (changed) {
+ this.updateActual(deviceID, property, transData);
+ actualVals.set(util.format("%s-%s", deviceID, property.name), String(transData));
+ logger.info("update devicetwin[%s] of device[%s] successfully", property.name, deviceID);
+ }
+ });
+ }
+ }
+
+ // getActuals publish get devicetwin msg to edge mqtt
+ getActuals(deviceID) {
+ let payload_msg = {
+ event_id: "",
+ timestamp: new Date().getTime()
+ };
+ this.mqttClient.publish(constant.defaultTopicPrefix + deviceID + constant.twinGetTopic, JSON.stringify(payload_msg));
+ }
+
+ // setActuals set device property and actual value map
+ setActuals(getMsg, callback) {
+ let deviceTwin = getMsg.twin;
+ let PropActuals = new Map();
+ Object.keys(deviceTwin).forEach(function(key){
+ if (deviceTwin[key].hasOwnProperty('actual')) {
+ PropActuals.set(key, deviceTwin[key].actual.value);
+ }
+ })
+ callback(PropActuals);
+ }
+
+ // setExpecteds set device property and expected value map
+ setExpecteds(getMsg, callback) {
+ let deviceTwin = getMsg.twin;
+ let ProExpect = new Map();
+ Object.keys(deviceTwin).forEach(function(key){
+ if (deviceTwin[key].hasOwnProperty('expected') && !deviceTwin[key].hasOwnProperty('actual') || JSON.stringify(deviceTwin[key].actual) == '{}') {
+ ProExpect.set(key, deviceTwin[key].expected.value);
+ }
+ })
+ callback(ProExpect);
+ }
+
+ // compareActuals compare if data is changed
+ compareActuals(data, cachedActuals, callback) {
+ let changed = false;
+ if (data != cachedActuals) {
+ changed = true;
+ }
+ callback(changed);
+ }
+
+ // UpdateDirectActuals update all devicetwin property to edge mqtt
+ UpdateDirectActuals(devIns, deviceID, actualVals) {
+ if (devIns.has(deviceID)) {
+ let deviceName = devIns.get(deviceID).name;
+ this.generateDirectGetMsg(deviceName, deviceID, actualVals, (directGetMsg)=>{
+ this.mqttClient.publish(constant.defaultDirectTopicPrefix + deviceID + constant.directGetTopic, JSON.stringify(directGetMsg));
+ });
+ }
+ }
+
+ // generateDirectGetMsg generate Direct Get Msg in message format
+ generateDirectGetMsg(deviceName, deviceID, actualVals, callback) {
+ let header = {
+ msg_id: uuidv4(),
+ parent_msg_id: "",
+ timestamp: new Date().getTime(),
+ sync: false
+ };
+ let route = {
+ source: "eventbus",
+ group: "",
+ operation: "upload",
+ resource: util.format("%s%s%s", constant.defaultDirectTopicPrefix, deviceID, constant.directGetTopic)
+ };
+ let content = {
+ data: actualVals,
+ device_name: deviceName,
+ device_id: deviceID,
+ timestamp: new Date().getTime()
+ };
+ let directGetMsg = {
+ header: header,
+ route: route,
+ content: content
+ };
+ callback(directGetMsg);
+ }
+
+ // syncExpected check whether expected value should be update to device
+ static syncExpected(delta, key, callback) {
+ let deviceTwin = delta.twin[key];
+ if (!delta.twin.hasOwnProperty(key)) {
+ logger.error("Invalid device twin ", key);
+ return;
+ }
+ if (!deviceTwin.hasOwnProperty('actual') ||
+ (deviceTwin.hasOwnProperty('expected') && deviceTwin.expected.hasOwnProperty('metadata') && deviceTwin.actual.hasOwnProperty('metadata') &&
+ deviceTwin.expected.metadata.timestamp > deviceTwin.actual.metadata.timestamp &&
+ deviceTwin.expected.value !== deviceTwin.actual.value)) {
+ callback(deviceTwin.expected.value);
+ }
+ }
+}
+
+module.exports = DeviceTwin;
diff --git a/mappers/modbus_mapper/src/index.js b/mappers/modbus_mapper/src/index.js
new file mode 100644
index 000000000..93945032f
--- /dev/null
+++ b/mappers/modbus_mapper/src/index.js
@@ -0,0 +1,328 @@
+const async = require('async');
+const log4js = require('log4js');
+const mqtt = require('mqtt');
+const path = require('path');
+const util = require('util');
+
+const common = require('./common');
+const constant = require('./constant');
+const DeviceTwin = require('./devicetwin');
+const Modbus = require('./modbus');
+const WatchFiles = require('./watchfile');
+
+//default logger options
+log4js.configure({
+ appenders: {
+ out: { type: 'stdout' },
+ },
+ categories: {
+ default: { appenders: ['out'], level: 'info' }
+ }
+});
+logger = log4js.getLogger('appenders');
+
+let options = {
+ port: 1883,
+ host: '127.0.0.1',
+ dpl_name: 'dpl/deviceProfile.json'
+};
+
+let mqtt_client, mqtt_client2, msg, mqtt_options, dt, devIns, devMod, devPro, modVistr;
+let ActualVal = new Map();
+
+async.series([
+ //load conf.json
+ function(callback) {
+ WatchFiles.loadConfig('conf/conf.json', (err, configs)=>{
+ if (err) {
+ logger.error('failed to load config, err: ', err);
+ } else {
+ options = {
+ port: configs.mqtt_port,
+ host: configs.mqtt_ip,
+ dpl_name: configs.dpl_name
+ };
+ callback(err);
+ }
+ });
+ },
+
+ //load dpl first time
+ function(callback) {
+ WatchFiles.loadDpl(options.dpl_name, (devInsMap, devModMap, devProMap, modVistrMap)=>{
+ devIns = devInsMap;
+ devMod = devModMap;
+ devPro = devProMap;
+ modVistr = modVistrMap;
+ callback();
+ });
+ },
+
+ //first get twinget build map
+ function(callback) {
+ mqtt_options = {
+ port: options.port,
+ host: options.host,
+ };
+ mqtt_client = mqtt.connect(mqtt_options);
+ dt = new DeviceTwin(mqtt_client);
+ mqtt_client.on('connect', ()=>{
+ logger.info('connetced to edge mqtt with topic twinGet');
+ mqtt_client.subscribe(constant.twinGetResTopic);
+ for (let instance of devIns) {
+ dt.getActuals(instance[0]);
+ }
+ });
+ callback();
+ },
+
+ //deal with twin get msg and set expected value into device
+ function(callback) {
+ mqtt_client.on('message', (topic, message)=>{
+ try {
+ var msgGet = JSON.parse(message.toString());
+ } catch (err) {
+ logger.error('unmarshal error');
+ return;
+ }
+ let resources = topic.toString().split('/');
+ let deviceID = resources[3];
+ let dt = new DeviceTwin(mqtt_client);
+ let devProtocol, devInstance;
+ if (devPro.has(deviceID) && devIns.has(deviceID)) {
+ devProtocol = devPro.get(deviceID);
+ devInstance = devIns.get(deviceID);
+ } else {
+ logger.error('match visitor failed');
+ }
+ logger.info('recieve twinGet msg, set properties actual value map');
+ if (resources.length === 7 && resources[5] === 'get' && msgGet != null && msgGet.code != 404 && typeof(devProtocol) != 'undefined' && typeof(devInstance) != 'undefined') {
+ dt.setActuals(msgGet, (PropActuals)=>{
+ for (let actual of PropActuals) {
+ ActualVal.set(util.format('%s-%s', deviceID, actual[0]), actual[1]);
+ }
+ });
+ dt.setExpecteds(msgGet, (PropExpecteds)=>{
+ for (let expected of PropExpecteds) {
+ modbusProtocolTransfer(devProtocol.protocol, (transferedProtocol)=>{
+ if (modVistr.has(util.format('%s-%s-%s', devInstance.model, expected[0], transferedProtocol))) {
+ let visitor = modVistr.get(util.format('%s-%s-%s', devInstance.model, expected[0], transferedProtocol));
+ dealDeltaMsg(msgGet, expected[0], visitor, devProtocol, expected[1]);
+ }
+ });
+ }
+ });
+ }
+ });
+ callback();
+ },
+
+ // start mqtt sub delta topic
+ function(callback) {
+ mqtt_options = {
+ port: options.port,
+ host: options.host,
+ };
+ mqtt_client2 = mqtt.connect(mqtt_options);
+ mqtt_client2.on('connect', ()=>{
+ logger.info('connetced to edge mqtt with topic twinDelta');
+ mqtt_client2.subscribe(constant.twinDeltaTopic);
+ });
+ callback();
+ },
+
+ // on receive msg of delta topic
+ function(callback) {
+ logger.info('start to wait for devicetwin update');
+ mqtt_client2.on('message', (topic, message)=>{
+ try {
+ msg = JSON.parse(message.toString());
+ } catch (err) {
+ logger.error('unmarshal error');
+ callback(err);
+ return;
+ }
+
+ //match visitors
+ let resources = topic.toString().split('/');
+ let deviceID = resources[3];
+ let devProtocol, devInstance;
+ if (devPro.has(deviceID) && devIns.has(deviceID)) {
+ devProtocol = devPro.get(deviceID);
+ devInstance = devIns.get(deviceID);
+ } else {
+ logger.error('match visitor failed');
+ }
+
+ try {
+ if (resources.length === 7 && resources[6] === 'delta' && typeof(devProtocol) != 'undefined' && typeof(devInstance) != 'undefined') {
+ logger.info('recieved twinDelta msg');
+ Object.keys(msg.delta).forEach(function(key){
+ modbusProtocolTransfer(devProtocol.protocol, (transferedProtocol)=>{
+ if (modVistr.has(util.format('%s-%s-%s', devInstance.model, key, transferedProtocol))) {
+ let visitor = modVistr.get(util.format('%s-%s-%s', devInstance.model, key, transferedProtocol));
+ DeviceTwin.syncExpected(msg, key, (value)=>{
+ dealDeltaMsg(msg, key, visitor, devProtocol, value);
+ });
+ }
+ });
+ });
+ }
+ } catch (err) {
+ logger.error('failed to change devicetwin of device[%s], err: ', deviceID, err);
+ }
+ });
+ }
+],function(err) {
+ if (err) {
+ logger.error(err);
+ } else {
+ logger.info('changed devicetwin successfully');
+ }
+});
+
+let mqtt_client3;
+logger.info('start to watch dpl config');
+WatchFiles.watchChange(path.join(__dirname, 'dpl'), ()=>{
+ async.series([
+ function(callback) {
+ WatchFiles.loadDpl(options.dpl_name, (devInsMap, devModMap, devProMap, modVistrMap)=>{
+ devIns = devInsMap;
+ devMod = devModMap;
+ devPro = devProMap;
+ modVistr = modVistrMap;
+ callback();
+ });
+ },
+
+ function(callback) {
+ mqtt_options = {
+ port: options.port,
+ host: options.host,
+ };
+ mqtt_client3 = mqtt.connect(mqtt_options);
+ let dt = new DeviceTwin(mqtt_client3);
+ mqtt_client3.on('connect', ()=>{
+ logger.info('connetced to edge mqtt with topic twinGet');
+ mqtt_client.subscribe(constant.twinGetResTopic);
+ for (let instance of devIns) { // let change from var
+ dt.getActuals(instance[0]);
+ }
+ });
+ callback();
+ },
+
+ function(callback) {
+ mqtt_client3.on('message', (topic, message)=>{
+ try {
+ var msgGet = JSON.parse(message.toString());
+ } catch (err) {
+ logger.error('unmarshal error');
+ return;
+ }
+ let resources = topic.toString().split('/');
+ let deviceID = resources[3];
+ let devProtocol, devInstance;
+ if (devPro.has(deviceID) && devIns.has(deviceID)) {
+ devProtocol = devPro.get(deviceID);
+ devInstance = devIns.get(deviceID);
+ } else {
+ logger.error('match visitor failed');
+ }
+
+ let dt = new DeviceTwin(mqtt_client3);
+ if (resources.length === 7 && resources[5] === 'get' && msgGet != null && msgGet.code != 404 && typeof(devProtocol) != 'undefined' && typeof(devInstance) != 'undefined') {
+ logger.info('received twinGet message');
+ dt.setExpecteds(msgGet, (PropExpecteds)=>{
+ for (let expected of PropExpecteds) {
+ dt.compareActuals(expected[1], ActualVal.get(util.format('%s-%s', deviceID, expected[0])),(changed)=>{
+ modbusProtocolTransfer(devProtocol.protocol, (transferedProtocol)=>{
+ if (changed && modVistr.has(util.format('%s-%s-%s', devInstance.model, expected[0], transferedProtocol))) {
+ let visitor = modVistr.get(util.format('%s-%s-%s', devInstance.model, expected[0], transferedProtocol));
+ dealDeltaMsg(msgGet, expected[0], visitor, devProtocol, expected[1]);
+ }
+ });
+ });
+ }
+ });
+ }
+ });
+ callback();
+ }
+ ],function(err) {
+ if (err) {
+ logger.error('failed to load changed dpl config, err: ', err);
+ } else {
+ logger.info('load changed dpl config successfully');
+ }
+ });
+});
+
+logger.info('start to check devicetwin state');
+setInterval(()=>{
+ let dt = new DeviceTwin(mqtt_client);
+ logger.info('chechking devicetwin state');
+ for (let instance of devIns) {
+ if (devPro.has(instance[0])) {
+ let protocol = devPro.get(instance[0]);
+ let actuals = new Map();
+ syncDeviceTwin(dt, instance[0], protocol, actuals);
+ }
+ }
+}, 2000);
+
+// syncDeviceTwin check each property of each device accroding to the dpl configuration
+function syncDeviceTwin(dt, key, protocol, actuals) {
+ async.eachSeries(devMod.get(key).properties, (property, callback)=>{
+ let visitor;
+ if (typeof(protocol) != 'undefined') {
+ modbusProtocolTransfer(protocol.protocol, (transferedProtocol)=>{
+ if (devIns.has(key) && modVistr.has(util.format('%s-%s-%s', devIns.get(key).model, property.name, transferedProtocol))) {
+ visitor = modVistr.get(util.format('%s-%s-%s', devIns.get(key).model, property.name, transferedProtocol));
+ } else {
+ logger.error('failed to match visitor');
+ }
+ });
+ }
+ if (typeof(protocol) != 'undefined' && typeof(visitor) != 'undefined') {
+ let modbus = new Modbus(protocol, visitor);
+ modbus.ModbusUpdate((err, data)=>{
+ if (err) {
+ logger.error('failed to update devicetwin[%s] of device[%s], err: ', property.name, key, err);
+ } else {
+ dt.transferType(visitor, property, data, (transData)=>{
+ if (transData != null) {
+ actuals.set(property.name, String(transData));
+ dt.dealUpdate(transData, property, key, ActualVal);
+ }
+ });
+ callback();
+ }
+ });
+ }
+ },()=>{
+ dt.UpdateDirectActuals(devIns, key, actuals);
+ });
+}
+
+// dealDeltaMsg deal with the devicetwin delta msg
+function dealDeltaMsg(msg, key, visitor, protocol, value) {
+ let modbus = new Modbus(protocol, visitor);
+ modbus.ModbusDelta(msg.twin[key].metadata.type, value, (err, data)=>{
+ if (err) {
+ logger.error('failed to modify register, err: ', err)
+ } else {
+ logger.info('modify register %s successfully', JSON.stringify(data));
+ }
+ })
+}
+
+function modbusProtocolTransfer(protocol, callback) {
+ let transferedProtocol;
+ if (protocol === 'modbus-rtu' || protocol === 'modbus-tcp') {
+ transferedProtocol = 'modbus';
+ } else {
+ transferedProtocol = protocol;
+ }
+ callback(transferedProtocol)
+}
diff --git a/mappers/modbus_mapper/src/modbus.js b/mappers/modbus_mapper/src/modbus.js
new file mode 100644
index 000000000..3167a0cb8
--- /dev/null
+++ b/mappers/modbus_mapper/src/modbus.js
@@ -0,0 +1,204 @@
+const ModbusRTU = require('modbus-serial');
+const Buffer = require('buffer').Buffer;
+const async = require('async');
+const common = require('./common');
+
+class Modbus {
+ constructor(protocol, visitor){
+ this.protocol = protocol;
+ this.visitor = visitor;
+ this.client = new ModbusRTU();
+ }
+
+ // connect to device with modbus protocol
+ connect(callback) {
+ let protocol = this.protocol;
+ let client = this.client;
+ switch(protocol.protocol) {
+ case 'modbus-tcp':
+ client.connectTCP(protocol.protocol_config.ip, { port: parseInt(protocol.protocol_config.port) }, ()=>{
+ client.setTimeout(500);
+ client.setID(parseInt(protocol.protocol_config.slaveID));
+ callback(client);
+ });
+ break;
+ case 'modbus-rtu':
+ async.series([
+ function(callback) {
+ setTimeout(function () {
+ callback(null, null);
+ }, 100);
+ },
+ function (callback) {
+ client.connectRTUBuffered(protocol.protocol_config.serialPort, { baudRate: parseInt(protocol.protocol_config.baudRate) }, ()=>{
+ client.setTimeout(500);
+ client.setID(parseInt(protocol.protocol_config.slaveID));
+ callback(null, client);
+ });
+ }], (err, res) => {callback(res[1]);
+ });
+ break;
+ default:
+ logger.info('unknwon modbus_type ', protocol.protocol);
+ break;
+ }
+ }
+
+ // WriteAction write the value into device registers
+ WriteAction(value, callback) {
+ let visitor = this.visitor;
+ let client = this.client;
+ switch(visitor.visitorConfig.register){
+ case 'CoilRegister':
+ client.writeCoils(parseInt(visitor.visitorConfig.index), value, (err, data)=>{
+ client.close();
+ callback(err, data);
+ });
+ break;
+ case 'HoldingRegister':
+ client.writeRegisters(parseInt(visitor.visitorConfig.index), value, (err, data)=>{
+ client.close();
+ callback(err, data);
+ });
+ break;
+ default:
+ client.close();
+ logger.info('write action is not allowed on register type ', visitor.visitorConfig.register)
+ callback('unkown action', null);
+ break;
+ }
+ }
+
+ // PreWriteAction transfer data before writing data to register
+ PreWriteAction(type, value, callback) {
+ let visitor = this.visitor;
+ let transData;
+ async.waterfall([
+ function(callback) {
+ switch(type) {
+ case 'int':
+ case 'float':
+ value = parseInt(value);
+ if (visitor.visitorConfig.register === 'CoilRegister') {
+ transData = (value).toString(2).split('').map(function(s) { return parseInt(s); });
+ } else if (visitor.visitorConfig.register === 'HoldingRegister') {
+ common.IntToByteArray(value, (byteArr)=>{
+ if (byteArr.length < visitor.visitorConfig.offset) {
+ let zeroArr = new Array(visitor.visitorConfig.offset -byteArr.length).fill(0);
+ byteArr = zeroArr.concat(byteArr);
+ transData = byteArr;
+ } else {
+ transData = byteArr;
+ }
+ });
+ } else {
+ transData = null;
+ }
+ callback(null, transData);
+ break;
+ case 'string': {
+ let buf = new Buffer.from(value);
+ transData = buf.toJSON().data;
+ callback(null, transData);
+ break;
+ }
+ case 'boolean':
+ if (value === 'true') {
+ transData = [1];
+ } else if (value === 'false') {
+ transData = [0];
+ } else {
+ transData = null;
+ }
+ callback(null, transData);
+ break;
+ default:
+ transData = null;
+ callback(null, transData);
+ break;
+ }
+ },
+ function(transData, callback) {
+ if (visitor.visitorConfig.isRegisterSwap && transData != null) {
+ common.switchRegister(transData, (switchedData)=>{
+ callback(null, switchedData);
+ });
+ } else {
+ callback(null, transData);
+ }
+ },
+ function(internalData, callback) {
+ if (visitor.visitorConfig.isSwap && internalData != null && (visitor.visitorConfig.register === 'HoldingRegisters' || visitor.visitorConfig.register === 'CoilsRegisters')) {
+ common.switchByte(internalData, (switchedData)=>{
+ callback(null, switchedData);
+ });
+ } else {
+ callback(null, internalData);
+ }
+ }], function(err, transData) {
+ callback(transData);
+ }
+ );
+ }
+
+ // ReadAction read register data from device
+ ReadAction(callback) {
+ let visitor = this.visitor;
+ let client = this.client;
+ switch (visitor.visitorConfig.register) {
+ case 'CoilRegister':
+ client.readCoils(parseInt(visitor.visitorConfig.index), parseInt(visitor.visitorConfig.offset), (err, data)=>{
+ client.close();
+ callback(err, err?data:[data.data[0]]);
+ });
+ break;
+ case 'DiscreteInputRegister':
+ client.readDiscreteInputs(parseInt(visitor.visitorConfig.index), parseInt(visitor.visitorConfig.offset), (err, data)=>{
+ client.close();
+ callback(err, err?data:[data.data[0]]);
+ });
+ break;
+ case 'HoldingRegister':
+ client.readHoldingRegisters(parseInt(visitor.visitorConfig.index), parseInt(visitor.visitorConfig.offset), (err, data)=>{
+ client.close();
+ callback(err, err?data:data.data);
+ });
+ break;
+ case 'InputRegister':
+ client.readInputRegisters(parseInt(visitor.visitorConfig.index), parseInt(visitor.visitorConfig.offset), (err, data)=>{
+ client.close();
+ callback(err, err?data:data.data);
+ });
+ break;
+ default:
+ client.close();
+ logger.info('read action is not allowed on register type ', visitor.visitorConfig.register)
+ callback('unknown Registers type', null);
+ break;
+ }
+ }
+
+ // ModbusDelta deal with the delta message to modify the register
+ ModbusDelta(type, value, callback) {
+ this.connect(()=>{
+ this.PreWriteAction(type, value, (transData)=>{
+ if (transData != null) {
+ this.WriteAction(transData, (err, data)=>{
+ callback(err, data);
+ });
+ }
+ });
+ });
+ }
+
+ // ModbusUpdate deal with the update message to read the register
+ ModbusUpdate(callback) {
+ this.connect(()=>{
+ this.ReadAction((err, data)=>{
+ callback(err, data);
+ });
+ });
+ }
+}
+
+module.exports = Modbus;
diff --git a/mappers/modbus_mapper/src/package-lock.json b/mappers/modbus_mapper/src/package-lock.json
new file mode 100644
index 000000000..1c2f1de22
--- /dev/null
+++ b/mappers/modbus_mapper/src/package-lock.json
@@ -0,0 +1,1281 @@
+{
+ "name": "modbus_mapper",
+ "version": "1.0.0",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "@serialport/binding-abstract": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@serialport/binding-abstract/-/binding-abstract-2.0.5.tgz",
+ "integrity": "sha512-oRg0QRsXJFKHQbQjmo0regKLZ9JhjLmTqc47ocJgYM5UtU9Q1VFrVPh0B2lr2pfm/tr3aNvTLX1eiVAvXyZ/bg==",
+ "requires": {
+ "debug": "^4.1.1"
+ }
+ },
+ "@serialport/binding-mock": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@serialport/binding-mock/-/binding-mock-2.0.5.tgz",
+ "integrity": "sha512-1kD1qI686pIIolGZ6TPjAtvy8c3XIUlE4OXRZf7ZHaZgGaOUHAUMLKZt4tNTxsfedRTFyiYyHoe5QAbx82R9pQ==",
+ "requires": {
+ "@serialport/binding-abstract": "^2.0.5",
+ "debug": "^4.1.1"
+ }
+ },
+ "@serialport/bindings": {
+ "version": "2.0.8",
+ "resolved": "https://registry.npmjs.org/@serialport/bindings/-/bindings-2.0.8.tgz",
+ "integrity": "sha512-paKLa9JkoH5FAy2sATTdXLCiKpuKn0pN15/etcCqzX8vi25fnQgJ8Yx9Z6zdbcKe1No7s/9PuH9yfjDR61fbOQ==",
+ "requires": {
+ "@serialport/binding-abstract": "^2.0.5",
+ "@serialport/parser-readline": "^2.0.2",
+ "bindings": "^1.3.0",
+ "debug": "^4.1.1",
+ "nan": "^2.13.2",
+ "prebuild-install": "^5.2.1"
+ }
+ },
+ "@serialport/parser-byte-length": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@serialport/parser-byte-length/-/parser-byte-length-2.0.2.tgz",
+ "integrity": "sha512-cUOprk1uRLucCJy6m+wAM4pwdBaB5D4ySi6juwRScP9DTjKUvGWYj5jzuqvftFBvYFmFza89aLj5K23xiiqj7Q=="
+ },
+ "@serialport/parser-cctalk": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@serialport/parser-cctalk/-/parser-cctalk-2.0.2.tgz",
+ "integrity": "sha512-5LMysRv7De+TeeoKzi4+sgouD4tqZEAn1agAVevw+7ILM0m30i1zgZLtchgxtCH7OoQRAkENEVEPc0OwhghKgw=="
+ },
+ "@serialport/parser-delimiter": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-2.0.2.tgz",
+ "integrity": "sha512-zB02LahFfyZmJqak9l37vP/F1K+KCUxd1KQj35OhD1+0q/unMjVTZmsfkxFSM4gkaxP9j7+8USk+LQJ3V8U26Q=="
+ },
+ "@serialport/parser-readline": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-2.0.2.tgz",
+ "integrity": "sha512-thL26dGEHB+eINNydJmzcLLhiqcBQkF+wNTbRaYblTP/6dm7JsfjYSud7bTkN63AgE0xpe9tKXBFqc8zgJ1VKg==",
+ "requires": {
+ "@serialport/parser-delimiter": "^2.0.2"
+ }
+ },
+ "@serialport/parser-ready": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@serialport/parser-ready/-/parser-ready-2.0.2.tgz",
+ "integrity": "sha512-6ynQ+HIIkFQcEO2Hrq4Qmdz+hlJ7kjTHGQ1E7SRN7f70nnys1v3HSke8mjK3RzVw+SwL0rBYjftUdCTrU+7c+Q=="
+ },
+ "@serialport/parser-regex": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@serialport/parser-regex/-/parser-regex-2.0.2.tgz",
+ "integrity": "sha512-7qjYd7AdHUK8fJOmHpXlMRipqRCVMMyDFyf/5TQQiOt6q+BiFjLOtSpVXhakHwgnXanzDYKeRSB8zM0pZZg+LA=="
+ },
+ "@serialport/stream": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@serialport/stream/-/stream-2.0.5.tgz",
+ "integrity": "sha512-9gc3zPoAqs/04mvq8TdZ7GxtnacCDuw3/u0u18UXXHgC/5tNDYkY+hXFIJB1fQFnP5yyNB1L2XLfX974ySJg9Q==",
+ "requires": {
+ "@serialport/binding-mock": "^2.0.5",
+ "debug": "^4.1.1"
+ }
+ },
+ "ansi-regex": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
+ },
+ "anymatch": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.0.3.tgz",
+ "integrity": "sha512-c6IvoeBECQlMVuYUjSwimnhmztImpErfxJzWZhIQinIvQWoGOnB0dLIgifbPHQt5heS6mNlaZG16f06H3C8t1g==",
+ "requires": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ }
+ },
+ "aproba": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
+ "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
+ },
+ "are-we-there-yet": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
+ "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
+ "requires": {
+ "delegates": "^1.0.0",
+ "readable-stream": "^2.0.6"
+ }
+ },
+ "async": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/async/-/async-3.1.0.tgz",
+ "integrity": "sha512-4vx/aaY6j/j3Lw3fbCHNWP0pPaTCew3F6F3hYyl/tHs/ndmV1q7NW9T5yuJ2XAGwdQrP+6Wu20x06U4APo/iQQ=="
+ },
+ "async-limiter": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
+ "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
+ },
+ "balanced-match": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
+ },
+ "base64-js": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz",
+ "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw=="
+ },
+ "binary-extensions": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz",
+ "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow=="
+ },
+ "bindings": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
+ "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
+ "requires": {
+ "file-uri-to-path": "1.0.0"
+ }
+ },
+ "bl": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz",
+ "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==",
+ "requires": {
+ "readable-stream": "^2.3.5",
+ "safe-buffer": "^5.1.1"
+ }
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "requires": {
+ "fill-range": "^7.0.1"
+ }
+ },
+ "buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.1.tgz",
+ "integrity": "sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==",
+ "requires": {
+ "base64-js": "^1.0.2",
+ "ieee754": "^1.1.4"
+ }
+ },
+ "buffer-alloc": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz",
+ "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==",
+ "requires": {
+ "buffer-alloc-unsafe": "^1.1.0",
+ "buffer-fill": "^1.0.0"
+ }
+ },
+ "buffer-alloc-unsafe": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz",
+ "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg=="
+ },
+ "buffer-fill": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz",
+ "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw="
+ },
+ "buffer-from": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
+ "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
+ },
+ "callback-stream": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/callback-stream/-/callback-stream-1.1.0.tgz",
+ "integrity": "sha1-RwGlEmbwbgbqpx/BcjOCLYdfSQg=",
+ "requires": {
+ "inherits": "^2.0.1",
+ "readable-stream": "> 1.0.0 < 3.0.0"
+ }
+ },
+ "chokidar": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.0.2.tgz",
+ "integrity": "sha512-c4PR2egjNjI1um6bamCQ6bUNPDiyofNQruHvKgHQ4gDUP/ITSVSzNsiI5OWtHOsX323i5ha/kk4YmOZ1Ktg7KA==",
+ "requires": {
+ "anymatch": "^3.0.1",
+ "braces": "^3.0.2",
+ "fsevents": "^2.0.6",
+ "glob-parent": "^5.0.0",
+ "is-binary-path": "^2.1.0",
+ "is-glob": "^4.0.1",
+ "normalize-path": "^3.0.0",
+ "readdirp": "^3.1.1"
+ }
+ },
+ "chownr": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.2.tgz",
+ "integrity": "sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A=="
+ },
+ "code-point-at": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
+ "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
+ },
+ "commist": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/commist/-/commist-1.1.0.tgz",
+ "integrity": "sha512-rraC8NXWOEjhADbZe9QBNzLAN5Q3fsTPQtBV+fEVj6xKIgDgNiEVE6ZNfHpZOqfQ21YUzfVNUXLOEZquYvQPPg==",
+ "requires": {
+ "leven": "^2.1.0",
+ "minimist": "^1.1.0"
+ }
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+ },
+ "concat-stream": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
+ "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
+ "requires": {
+ "buffer-from": "^1.0.0",
+ "inherits": "^2.0.3",
+ "readable-stream": "^2.2.2",
+ "typedarray": "^0.0.6"
+ }
+ },
+ "console-control-strings": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
+ "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
+ },
+ "core-util-is": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+ },
+ "d": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
+ "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==",
+ "requires": {
+ "es5-ext": "^0.10.50",
+ "type": "^1.0.1"
+ }
+ },
+ "date-format": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.0.0.tgz",
+ "integrity": "sha512-M6UqVvZVgFYqZL1SfHsRGIQSz3ZL+qgbsV5Lp1Vj61LZVYuEwcMXYay7DRDtYs2HQQBK5hQtQ0fD9aEJ89V0LA=="
+ },
+ "debug": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+ "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "decompress-response": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz",
+ "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=",
+ "requires": {
+ "mimic-response": "^1.0.0"
+ }
+ },
+ "deep-extend": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
+ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="
+ },
+ "delegates": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
+ "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
+ },
+ "detect-libc": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
+ "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups="
+ },
+ "duplexify": {
+ "version": "3.7.1",
+ "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
+ "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==",
+ "requires": {
+ "end-of-stream": "^1.0.0",
+ "inherits": "^2.0.1",
+ "readable-stream": "^2.0.0",
+ "stream-shift": "^1.0.0"
+ }
+ },
+ "end-of-stream": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
+ "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==",
+ "requires": {
+ "once": "^1.4.0"
+ }
+ },
+ "es5-ext": {
+ "version": "0.10.50",
+ "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.50.tgz",
+ "integrity": "sha512-KMzZTPBkeQV/JcSQhI5/z6d9VWJ3EnQ194USTUwIYZ2ZbpN8+SGXQKt1h68EX44+qt+Fzr8DO17vnxrw7c3agw==",
+ "requires": {
+ "es6-iterator": "~2.0.3",
+ "es6-symbol": "~3.1.1",
+ "next-tick": "^1.0.0"
+ }
+ },
+ "es6-iterator": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
+ "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=",
+ "requires": {
+ "d": "1",
+ "es5-ext": "^0.10.35",
+ "es6-symbol": "^3.1.1"
+ }
+ },
+ "es6-map": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz",
+ "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=",
+ "requires": {
+ "d": "1",
+ "es5-ext": "~0.10.14",
+ "es6-iterator": "~2.0.1",
+ "es6-set": "~0.1.5",
+ "es6-symbol": "~3.1.1",
+ "event-emitter": "~0.3.5"
+ }
+ },
+ "es6-set": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz",
+ "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=",
+ "requires": {
+ "d": "1",
+ "es5-ext": "~0.10.14",
+ "es6-iterator": "~2.0.1",
+ "es6-symbol": "3.1.1",
+ "event-emitter": "~0.3.5"
+ }
+ },
+ "es6-symbol": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz",
+ "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=",
+ "requires": {
+ "d": "1",
+ "es5-ext": "~0.10.14"
+ }
+ },
+ "event-emitter": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
+ "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=",
+ "requires": {
+ "d": "1",
+ "es5-ext": "~0.10.14"
+ }
+ },
+ "expand-template": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
+ "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="
+ },
+ "extend": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
+ },
+ "file-uri-to-path": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
+ "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="
+ },
+ "fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "requires": {
+ "to-regex-range": "^5.0.1"
+ }
+ },
+ "flatted": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz",
+ "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg=="
+ },
+ "fs": {
+ "version": "0.0.1-security",
+ "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz",
+ "integrity": "sha1-invTcYa23d84E/I4WLV+yq9eQdQ="
+ },
+ "fs-constants": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
+ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
+ },
+ "fs-extra": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
+ "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ }
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
+ },
+ "fsevents": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.0.7.tgz",
+ "integrity": "sha512-a7YT0SV3RB+DjYcppwVDLtn13UQnmg0SWZS7ezZD0UjnLwXmy8Zm21GMVGLaFGimIqcvyMQaOJBrop8MyOp1kQ==",
+ "optional": true
+ },
+ "gauge": {
+ "version": "2.7.4",
+ "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
+ "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
+ "requires": {
+ "aproba": "^1.0.3",
+ "console-control-strings": "^1.0.0",
+ "has-unicode": "^2.0.0",
+ "object-assign": "^4.1.0",
+ "signal-exit": "^3.0.0",
+ "string-width": "^1.0.1",
+ "strip-ansi": "^3.0.1",
+ "wide-align": "^1.1.0"
+ }
+ },
+ "github-from-package": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
+ "integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4="
+ },
+ "glob": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
+ "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "glob-parent": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.0.0.tgz",
+ "integrity": "sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==",
+ "requires": {
+ "is-glob": "^4.0.1"
+ }
+ },
+ "glob-stream": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz",
+ "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=",
+ "requires": {
+ "extend": "^3.0.0",
+ "glob": "^7.1.1",
+ "glob-parent": "^3.1.0",
+ "is-negated-glob": "^1.0.0",
+ "ordered-read-streams": "^1.0.0",
+ "pumpify": "^1.3.5",
+ "readable-stream": "^2.1.5",
+ "remove-trailing-separator": "^1.0.1",
+ "to-absolute-glob": "^2.0.0",
+ "unique-stream": "^2.0.2"
+ },
+ "dependencies": {
+ "glob-parent": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
+ "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
+ "requires": {
+ "is-glob": "^3.1.0",
+ "path-dirname": "^1.0.0"
+ }
+ },
+ "is-glob": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
+ "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
+ "requires": {
+ "is-extglob": "^2.1.0"
+ }
+ }
+ }
+ },
+ "graceful-fs": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.0.tgz",
+ "integrity": "sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg=="
+ },
+ "has-unicode": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
+ "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk="
+ },
+ "help-me": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/help-me/-/help-me-1.1.0.tgz",
+ "integrity": "sha1-jy1QjQYAtKRW2i8IZVbn5cBWo8Y=",
+ "requires": {
+ "callback-stream": "^1.0.2",
+ "glob-stream": "^6.1.0",
+ "through2": "^2.0.1",
+ "xtend": "^4.0.0"
+ }
+ },
+ "ieee754": {
+ "version": "1.1.13",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
+ "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "requires": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+ },
+ "ini": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
+ "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
+ },
+ "is-absolute": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz",
+ "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==",
+ "requires": {
+ "is-relative": "^1.0.0",
+ "is-windows": "^1.0.1"
+ }
+ },
+ "is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "requires": {
+ "binary-extensions": "^2.0.0"
+ }
+ },
+ "is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI="
+ },
+ "is-fullwidth-code-point": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+ "requires": {
+ "number-is-nan": "^1.0.0"
+ }
+ },
+ "is-glob": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
+ "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
+ "requires": {
+ "is-extglob": "^2.1.1"
+ }
+ },
+ "is-negated-glob": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz",
+ "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI="
+ },
+ "is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
+ },
+ "is-relative": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz",
+ "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==",
+ "requires": {
+ "is-unc-path": "^1.0.0"
+ }
+ },
+ "is-unc-path": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz",
+ "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==",
+ "requires": {
+ "unc-path-regex": "^0.1.2"
+ }
+ },
+ "is-windows": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
+ "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA=="
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+ },
+ "json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE="
+ },
+ "jsonfile": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+ "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
+ "requires": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "leven": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz",
+ "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA="
+ },
+ "lodash": {
+ "version": "4.17.14",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.14.tgz",
+ "integrity": "sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw=="
+ },
+ "log4js": {
+ "version": "4.5.1",
+ "resolved": "https://registry.npmjs.org/log4js/-/log4js-4.5.1.tgz",
+ "integrity": "sha512-EEEgFcE9bLgaYUKuozyFfytQM2wDHtXn4tAN41pkaxpNjAykv11GVdeI4tHtmPWW4Xrgh9R/2d7XYghDVjbKKw==",
+ "requires": {
+ "date-format": "^2.0.0",
+ "debug": "^4.1.1",
+ "flatted": "^2.0.0",
+ "rfdc": "^1.1.4",
+ "streamroller": "^1.0.6"
+ }
+ },
+ "mimic-response": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
+ "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="
+ },
+ "minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "minimist": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
+ },
+ "mkdirp": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+ "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+ "requires": {
+ "minimist": "0.0.8"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
+ }
+ }
+ },
+ "modbus-serial": {
+ "version": "7.6.0",
+ "resolved": "https://registry.npmjs.org/modbus-serial/-/modbus-serial-7.6.0.tgz",
+ "integrity": "sha512-6gEEdl2p2oVpFvfbjdxmCexPgo/0G8wAlobqwQo+8Zz/T5nqmcP1RhX0xqQuNx2r8SjTFipYNVkIiiFXRmi0Ew==",
+ "requires": {
+ "debug": "^4.1.1",
+ "serialport": "^7.1.3"
+ }
+ },
+ "mqtt": {
+ "version": "2.18.8",
+ "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-2.18.8.tgz",
+ "integrity": "sha512-3h6oHlPY/yWwtC2J3geraYRtVVoRM6wdI+uchF4nvSSafXPZnaKqF8xnX+S22SU/FcgEAgockVIlOaAX3fkMpA==",
+ "requires": {
+ "commist": "^1.0.0",
+ "concat-stream": "^1.6.2",
+ "end-of-stream": "^1.4.1",
+ "es6-map": "^0.1.5",
+ "help-me": "^1.0.1",
+ "inherits": "^2.0.3",
+ "minimist": "^1.2.0",
+ "mqtt-packet": "^5.6.0",
+ "pump": "^3.0.0",
+ "readable-stream": "^2.3.6",
+ "reinterval": "^1.1.0",
+ "split2": "^2.1.1",
+ "websocket-stream": "^5.1.2",
+ "xtend": "^4.0.1"
+ },
+ "dependencies": {
+ "pump": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
+ "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+ "requires": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ }
+ }
+ },
+ "mqtt-packet": {
+ "version": "5.6.1",
+ "resolved": "https://registry.npmjs.org/mqtt-packet/-/mqtt-packet-5.6.1.tgz",
+ "integrity": "sha512-eaF9rO2uFrIYEHomJxziuKTDkbWW5psLBaIGCazQSKqYsTaB3n4SpvJ1PexKaDBiPnMLPIFWBIiTYT3IfEJfww==",
+ "requires": {
+ "bl": "^1.2.1",
+ "inherits": "^2.0.3",
+ "process-nextick-args": "^2.0.0",
+ "safe-buffer": "^5.1.0"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
+ "nan": {
+ "version": "2.14.0",
+ "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz",
+ "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg=="
+ },
+ "napi-build-utils": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.1.tgz",
+ "integrity": "sha512-boQj1WFgQH3v4clhu3mTNfP+vOBxorDlE8EKiMjUlLG3C4qAESnn9AxIOkFgTR2c9LtzNjPrjS60cT27ZKBhaA=="
+ },
+ "next-tick": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
+ "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
+ },
+ "node-abi": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.9.0.tgz",
+ "integrity": "sha512-jmEOvv0eanWjhX8dX1pmjb7oJl1U1oR4FOh0b2GnvALwSYoOdU7sj+kLDSAyjo4pfC9aj/IxkloxdLJQhSSQBA==",
+ "requires": {
+ "semver": "^5.4.1"
+ }
+ },
+ "nodejs-base64": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/nodejs-base64/-/nodejs-base64-1.0.3.tgz",
+ "integrity": "sha512-6wLDv7N/MfV4wDsrBC2s8xVfx82iywopGLdAVYYTLmwOHTm6N463Dwr9tP4+GHhfTMPN4h9i2Z0LzMdFB2G5cw=="
+ },
+ "noop-logger": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz",
+ "integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI="
+ },
+ "normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="
+ },
+ "npmlog": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
+ "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
+ "requires": {
+ "are-we-there-yet": "~1.1.2",
+ "console-control-strings": "~1.1.0",
+ "gauge": "~2.7.3",
+ "set-blocking": "~2.0.0"
+ }
+ },
+ "number-is-nan": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
+ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
+ },
+ "object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
+ },
+ "once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "requires": {
+ "wrappy": "1"
+ }
+ },
+ "ordered-read-streams": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz",
+ "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=",
+ "requires": {
+ "readable-stream": "^2.0.1"
+ }
+ },
+ "os-homedir": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
+ "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
+ },
+ "path-dirname": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
+ "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA="
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
+ },
+ "picomatch": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.0.7.tgz",
+ "integrity": "sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA=="
+ },
+ "prebuild-install": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.0.tgz",
+ "integrity": "sha512-aaLVANlj4HgZweKttFNUVNRxDukytuIuxeK2boIMHjagNJCiVKWFsKF4tCE3ql3GbrD2tExPQ7/pwtEJcHNZeg==",
+ "requires": {
+ "detect-libc": "^1.0.3",
+ "expand-template": "^2.0.3",
+ "github-from-package": "0.0.0",
+ "minimist": "^1.2.0",
+ "mkdirp": "^0.5.1",
+ "napi-build-utils": "^1.0.1",
+ "node-abi": "^2.7.0",
+ "noop-logger": "^0.1.1",
+ "npmlog": "^4.0.1",
+ "os-homedir": "^1.0.1",
+ "pump": "^2.0.1",
+ "rc": "^1.2.7",
+ "simple-get": "^2.7.0",
+ "tar-fs": "^1.13.0",
+ "tunnel-agent": "^0.6.0",
+ "which-pm-runs": "^1.0.0"
+ }
+ },
+ "process-nextick-args": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
+ },
+ "pump": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
+ "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
+ "requires": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "pumpify": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz",
+ "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==",
+ "requires": {
+ "duplexify": "^3.6.0",
+ "inherits": "^2.0.3",
+ "pump": "^2.0.0"
+ }
+ },
+ "rc": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
+ "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
+ "requires": {
+ "deep-extend": "^0.6.0",
+ "ini": "~1.3.0",
+ "minimist": "^1.2.0",
+ "strip-json-comments": "~2.0.1"
+ }
+ },
+ "readable-stream": {
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "readdirp": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.1.1.tgz",
+ "integrity": "sha512-XXdSXZrQuvqoETj50+JAitxz1UPdt5dupjT6T5nVB+WvjMv2XKYj+s7hPeAVCXvmJrL36O4YYyWlIC3an2ePiQ==",
+ "requires": {
+ "picomatch": "^2.0.4"
+ }
+ },
+ "reinterval": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reinterval/-/reinterval-1.1.0.tgz",
+ "integrity": "sha1-M2Hs+jymwYKDOA3Qu5VG85D17Oc="
+ },
+ "remove-trailing-separator": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
+ "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8="
+ },
+ "rfdc": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.4.tgz",
+ "integrity": "sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug=="
+ },
+ "safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+ },
+ "semver": {
+ "version": "5.7.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
+ "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA=="
+ },
+ "serialport": {
+ "version": "7.1.5",
+ "resolved": "https://registry.npmjs.org/serialport/-/serialport-7.1.5.tgz",
+ "integrity": "sha512-NplGdqaY+ZL8t3t5egbT+3oqLW4d7WvDT/x1ACxAyWa1fSnx+KTAmlDHeCls39lXwu8voaOr3bPOW4bwM7PdAA==",
+ "requires": {
+ "@serialport/binding-mock": "^2.0.5",
+ "@serialport/bindings": "^2.0.8",
+ "@serialport/parser-byte-length": "^2.0.2",
+ "@serialport/parser-cctalk": "^2.0.2",
+ "@serialport/parser-delimiter": "^2.0.2",
+ "@serialport/parser-readline": "^2.0.2",
+ "@serialport/parser-ready": "^2.0.2",
+ "@serialport/parser-regex": "^2.0.2",
+ "@serialport/stream": "^2.0.5",
+ "debug": "^4.1.1"
+ }
+ },
+ "set-blocking": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
+ },
+ "signal-exit": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
+ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
+ },
+ "simple-concat": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz",
+ "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY="
+ },
+ "simple-get": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.8.1.tgz",
+ "integrity": "sha512-lSSHRSw3mQNUGPAYRqo7xy9dhKmxFXIjLjp4KHpf99GEH2VH7C3AM+Qfx6du6jhfUi6Vm7XnbEVEf7Wb6N8jRw==",
+ "requires": {
+ "decompress-response": "^3.3.0",
+ "once": "^1.3.1",
+ "simple-concat": "^1.0.0"
+ }
+ },
+ "split2": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz",
+ "integrity": "sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==",
+ "requires": {
+ "through2": "^2.0.2"
+ }
+ },
+ "stream-shift": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz",
+ "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI="
+ },
+ "streamroller": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-1.0.6.tgz",
+ "integrity": "sha512-3QC47Mhv3/aZNFpDDVO44qQb9gwB9QggMEE0sQmkTAwBVYdBRWISdsywlkfm5II1Q5y/pmrHflti/IgmIzdDBg==",
+ "requires": {
+ "async": "^2.6.2",
+ "date-format": "^2.0.0",
+ "debug": "^3.2.6",
+ "fs-extra": "^7.0.1",
+ "lodash": "^4.17.14"
+ },
+ "dependencies": {
+ "async": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz",
+ "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==",
+ "requires": {
+ "lodash": "^4.17.11"
+ }
+ },
+ "debug": {
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+ "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ }
+ }
+ },
+ "string-width": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+ "requires": {
+ "code-point-at": "^1.0.0",
+ "is-fullwidth-code-point": "^1.0.0",
+ "strip-ansi": "^3.0.0"
+ }
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+ "requires": {
+ "ansi-regex": "^2.0.0"
+ }
+ },
+ "strip-json-comments": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+ "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo="
+ },
+ "tar-fs": {
+ "version": "1.16.3",
+ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.3.tgz",
+ "integrity": "sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw==",
+ "requires": {
+ "chownr": "^1.0.1",
+ "mkdirp": "^0.5.1",
+ "pump": "^1.0.0",
+ "tar-stream": "^1.1.2"
+ },
+ "dependencies": {
+ "pump": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz",
+ "integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==",
+ "requires": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ }
+ }
+ },
+ "tar-stream": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz",
+ "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==",
+ "requires": {
+ "bl": "^1.0.0",
+ "buffer-alloc": "^1.2.0",
+ "end-of-stream": "^1.0.0",
+ "fs-constants": "^1.0.0",
+ "readable-stream": "^2.3.0",
+ "to-buffer": "^1.1.1",
+ "xtend": "^4.0.0"
+ }
+ },
+ "through2": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
+ "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
+ "requires": {
+ "readable-stream": "~2.3.6",
+ "xtend": "~4.0.1"
+ }
+ },
+ "through2-filter": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz",
+ "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==",
+ "requires": {
+ "through2": "~2.0.0",
+ "xtend": "~4.0.0"
+ }
+ },
+ "to-absolute-glob": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz",
+ "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=",
+ "requires": {
+ "is-absolute": "^1.0.0",
+ "is-negated-glob": "^1.0.0"
+ }
+ },
+ "to-buffer": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz",
+ "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg=="
+ },
+ "to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "requires": {
+ "is-number": "^7.0.0"
+ }
+ },
+ "tunnel-agent": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+ "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+ "requires": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "type": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/type/-/type-1.0.1.tgz",
+ "integrity": "sha512-MAM5dBMJCJNKs9E7JXo4CXRAansRfG0nlJxW7Wf6GZzSOvH31zClSaHdIMWLehe/EGMBkqeC55rrkaOr5Oo7Nw=="
+ },
+ "typedarray": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
+ },
+ "ultron": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz",
+ "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og=="
+ },
+ "unc-path-regex": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz",
+ "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo="
+ },
+ "unique-stream": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz",
+ "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==",
+ "requires": {
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "through2-filter": "^3.0.0"
+ }
+ },
+ "universalify": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
+ },
+ "util": {
+ "version": "0.11.1",
+ "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz",
+ "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==",
+ "requires": {
+ "inherits": "2.0.3"
+ },
+ "dependencies": {
+ "inherits": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+ }
+ }
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
+ },
+ "uuid": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
+ "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
+ },
+ "websocket-stream": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/websocket-stream/-/websocket-stream-5.5.0.tgz",
+ "integrity": "sha512-EXy/zXb9kNHI07TIMz1oIUIrPZxQRA8aeJ5XYg5ihV8K4kD1DuA+FY6R96HfdIHzlSzS8HiISAfrm+vVQkZBug==",
+ "requires": {
+ "duplexify": "^3.5.1",
+ "inherits": "^2.0.1",
+ "readable-stream": "^2.3.3",
+ "safe-buffer": "^5.1.2",
+ "ws": "^3.2.0",
+ "xtend": "^4.0.0"
+ }
+ },
+ "which-pm-runs": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz",
+ "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs="
+ },
+ "wide-align": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
+ "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
+ "requires": {
+ "string-width": "^1.0.2 || 2"
+ }
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+ },
+ "ws": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz",
+ "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==",
+ "requires": {
+ "async-limiter": "~1.0.0",
+ "safe-buffer": "~5.1.0",
+ "ultron": "~1.1.0"
+ }
+ },
+ "xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
+ }
+ }
+}
diff --git a/mappers/modbus_mapper/src/package.json b/mappers/modbus_mapper/src/package.json
new file mode 100644
index 000000000..3737905cd
--- /dev/null
+++ b/mappers/modbus_mapper/src/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "modbus_mapper",
+ "version": "1.0.0",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "",
+ "license": "ISC",
+ "description": "",
+ "dependencies": {
+ "async": "3.1.0",
+ "buffer": "^5.2.1",
+ "chokidar": "3.0.2",
+ "fs": "^0.0.1-security",
+ "log4js": "4.5.1",
+ "modbus-serial": "^7.4.2",
+ "mqtt": "^2.18.8",
+ "nodejs-base64": "^1.0.3",
+ "util": "^0.11.1",
+ "uuid": "^3.3.2"
+ }
+}
diff --git a/mappers/modbus_mapper/src/watchfile.js b/mappers/modbus_mapper/src/watchfile.js
new file mode 100644
index 000000000..1cf13c675
--- /dev/null
+++ b/mappers/modbus_mapper/src/watchfile.js
@@ -0,0 +1,106 @@
+const path = require('path');
+const fs = require('fs');
+const chokidar = require('chokidar');
+const filename = 'dpl/deviceProfile.json';
+const util = require('util');
+
+var devIns = new Map();
+var devMod = new Map();
+var devPro = new Map();
+var modVisitr = new Map();
+
+// watchChange monitor dpl configuration file, reload if changed
+function watchChange(paths, onChange) {
+ if (typeof onChange !== 'function') throw Error(`onChange (${onChange}) is not a function`);
+
+ if (!Array.isArray(paths)) paths = [paths];
+
+ paths.forEach(path => {
+ if (!(path && fs.existsSync(path))) throw Error(`can't find path ${path}`);
+ });
+
+ let watcher = chokidar.watch(paths);
+ watcher.on('ready', ()=>{
+ watcher.on('add', ()=>{
+ logger.info('watched file added, load dpl config');
+ onChange(filename);
+ });
+ });
+}
+
+// loadDpl load dpl configuration file
+function loadDpl(filename, callback) {
+ fs.readFile(filename, 'utf-8', (err, data)=>{
+ if (err) {
+ logger.error('load dpl config error: ', err);
+ } else {
+ let dplConfigs = JSON.parse(data);
+ processData(dplConfigs, callback);
+ }
+ });
+}
+
+// loadConfig load mqtt configuration file
+function loadConfig(filename, callback) {
+ fs.readFile(filename, 'utf-8', (err, data)=>{
+ if (err) {
+ logger.error('load config error: ', err);
+ } else {
+ let configs = JSON.parse(data);
+ callback(null, configs);
+ }
+ });
+}
+
+// processData parse dpl config for each deviceInstance
+function processData(dplConfigs, callback) {
+ for (let i = 0; i < dplConfigs.deviceInstances.length; i++) {
+ buildMaps(dplConfigs, i, (err)=>{
+ if (err) {
+ logger.error('build devIns maps error: ', err)
+ }
+ });
+ }
+ for (let i = 0; i < dplConfigs.deviceModels.length; i++) {
+ for (let j = 0; j < dplConfigs.deviceModels[i].properties.length; j++){
+ buildVisitorMaps(dplConfigs, i, j);
+ }
+ }
+ callback(devIns, devMod, devPro, modVisitr);
+}
+
+// buildMaps build three maps 1.map[deviceID]deviceInstance, 2.map[deviceID]deviceModel, 3.map[deviceID]protocol
+function buildMaps(dplConfigs, i) {
+ devIns.set(dplConfigs.deviceInstances[i].id, dplConfigs.deviceInstances[i]);
+ let foundMod = dplConfigs.deviceModels.findIndex((element)=>{
+ return element.name === dplConfigs.deviceInstances[i].model;
+ });
+ if (foundMod != -1) {
+ devMod.set(dplConfigs.deviceInstances[i].id, dplConfigs.deviceModels[foundMod]);
+ } else {
+ logger.error('failed to find model[%s] for deviceid', dplConfigs.deviceModels[i].model);
+ }
+
+ let foundPro = dplConfigs.protocols.findIndex((element)=>{
+ return element.name === dplConfigs.deviceInstances[i].protocol;
+ });
+ if (foundPro != -1) {
+ devPro.set(dplConfigs.deviceInstances[i].id, dplConfigs.protocols[foundMod]);
+ } else {
+ logger.error('failed to find protocol[%s] for deviceid', dplConfigs.deviceModels[i].protocol);
+ }
+}
+
+// buildVisitorMaps build map[model-property-protocol]propertyVisitor
+function buildVisitorMaps(dplConfigs, i, j) {
+ let foundVisitor = dplConfigs.propertyVisitors.findIndex((element)=>{
+ return element.modelName === dplConfigs.deviceModels[i].name && element.propertyName === dplConfigs.deviceModels[i].properties[j].name;
+ });
+ if (foundVisitor != -1) {
+ modVisitr.set(util.format('%s-%s-%s', dplConfigs.propertyVisitors[foundVisitor].modelName, dplConfigs.propertyVisitors[foundVisitor].propertyName, dplConfigs.propertyVisitors[foundVisitor].protocol), dplConfigs.propertyVisitors[foundVisitor]);
+ } else {
+ logger.error('failed to find visitor for model[%s], property[%s]', dplConfigs.deviceModels[i].name, dplConfigs.deviceModels[i].properties[j].name);
+ }
+}
+
+module.exports = {watchChange, loadDpl, loadConfig};