Commit 606aa863 authored by Administrator's avatar Administrator
Browse files

Update go.mod, go.sum, notificationModels.go, README.md files

parents
# models
package models
import (
"fmt"
"math"
"strings"
"time"
"go.mongodb.org/mongo-driver/bson/primitive"
)
/*
* Types and Constants
*/
type OPERATORS int
type HYSTERESISMODE int
type META int
const (
LT OPERATORS = iota
LTE
EQ
NEQ
GT
GTE
OPEN
CLOSED
EITHER
)
const (
META_DELTA META = iota
META_MAGSWON
max_elements
)
const (
NONE HYSTERESISMODE = iota
VALUE
PERCENTAGE
)
/*
* Methods and Fucntions
*/
/*
* Method to convert a Hysteresis mode to a string
*/
func (h HYSTERESISMODE) String() string {
switch h {
case 1:
return "value"
case 2:
return "%"
default: // Either NONE or an invalid input, so either way there is no hysteresis
return ""
}
}
/*
* Function to convert a an int to a Hyestesis mode or an error
*/
func IntToHysteresisMode(modeIn int) (HYSTERESISMODE, error) {
switch modeIn {
case 0, 1, 2:
return HYSTERESISMODE(modeIn), nil
default:
return HYSTERESISMODE(0), fmt.Errorf("invalid hysteresis mode %v", modeIn)
}
}
/*
* Method to convert an Operator to a string
*/
func (op OPERATORS) String() (s string) {
switch op {
case 0:
s = ("LT")
case 1:
s = ("LTE")
case 2:
s = ("EQ")
case 3:
s = ("NEQ")
case 4:
s = ("GT")
case 5:
s = ("GTE")
case 6:
s = ("OPENED")
case 7:
s = ("CLOSED")
case 8:
s = ("EITHER")
}
return s // default case
}
/*
* Function to convert a string to and operator, case insensitive
*/
func OperatorToString(o string) OPERATORS {
switch strings.ToUpper(o) {
case "LT":
return 0
case "LTE":
return 1
case "EQ":
return 2
case "NEQ":
return 3
case "GT":
return 4
case "GTE":
return 5
case "OPENED":
return 6
case "CLOSED":
return 7
case "EITHER":
return 8
}
panic("invalid operator")
}
/*
* Method to check if a string is a valid Operator
*/
func IsOperator(o string) bool {
switch strings.ToUpper(o) {
case "LT", "LTE", "EQ", "NEQ", "GT", "GTE", "OPEN", "CLOSED", "EITHER":
return true
default:
return false
}
}
/*
* Function to convert an int to an Operator
*/
func IntToOperator(op int) (OPERATORS, error) {
switch op {
case 0, 1, 2, 3, 4, 5, 6, 7, 8:
return OPERATORS(op), nil
default:
return OPERATORS(0), fmt.Errorf("undefined op code %v", op)
}
}
/*
* Function to determine if a rule is satisfied, based on the input value, specification and pervious status
* For >, >=, <, <=, hysteresis is plus or minus from the defined threshold
* For =, !=, hysteresis centered around defined threshold
* default output is false (initial value of output variable r)
* notify is used as a falg to indicate that the Notifucation Status should be updated
*/
func (op OPERATORS) Test(v OpData, spec RulesSpec, status RuleStatus) (res bool, notify bool) {
var hysteresisValue float64
var hysteresisEnabled bool // determine the value for hysteresis, if defined
switch {
case spec.Hysteresis.Mode == int(VALUE):
// use the value as is
hysteresisValue = spec.Hysteresis.Value
hysteresisEnabled = true
case spec.Hysteresis.Mode == int(PERCENTAGE):
// convert a percentage into an absolute value
hysteresisValue = (spec.Hysteresis.Value / 100) * spec.Threshold
hysteresisEnabled = true
default: // both true or both false, either way results in no hysteresis
hysteresisValue = 0.0
hysteresisEnabled = false
}
if status.ChangedAt.IsZero() { // initial state (no status), so just compare against the threshold (same as status == false)
switch op {
case 0: // LT
res = v.Value < spec.Threshold
notify = res != status.CurrentState
case 1: //LTE
res = v.Value <= spec.Threshold
notify = res != status.CurrentState
case 2: // EQ
if hysteresisEnabled {
res = (v.Value > (spec.Threshold - hysteresisValue/2)) && (v.Value < (spec.Threshold + hysteresisValue/2))
} else {
res = v.Value == spec.Threshold
}
notify = res != status.CurrentState
case 3: // NEQ
if hysteresisEnabled {
res = (v.Value < (spec.Threshold - hysteresisValue/2)) && (v.Value > (spec.Threshold + hysteresisValue/2))
} else {
res = v.Value != spec.Threshold
}
notify = res != status.CurrentState
case 4: // GT
res = v.Value > spec.Threshold
notify = res != status.CurrentState
case 5: // GTE
res = v.Value >= spec.Threshold
notify = res != status.CurrentState
case 6: // OPEN
delta := v.Meta[META_DELTA].(int64)
switch {
case delta < 0:
res = math.Round(v.Value) == 1 // node reset, to make an intentional state chage to drive a notification
notify = true
case delta == 0:
res = math.Round(v.Value) == 1 // shouldn't get here, so just return the current status i.e. no change
notify = false
case delta == 1: // should only return TRUE if the previous value was CLOSED ???
if (v.Meta[META_MAGSWON].(bool) && v.Value > 0.5) || (!v.Meta[META_MAGSWON].(bool) && v.Value < 0.5) { // true
res = true
notify = true
} else { // false
res = false
notify = false
}
default: // > 1
res = true // if there has been more than 1 transition, the the switch has been opened
notify = true
}
case 7: // CLOSED
delta := v.Meta[META_DELTA].(int64)
switch {
case delta < 0:
res = math.Round(v.Value) == 1 // node reset, to make an intentional state chage to drive a notification
notify = true
case delta == 0:
res = math.Round(v.Value) == 1 // shouldn't get here, so just return the current status i.e. no change
notify = false
case delta == 1:
if (v.Meta[META_MAGSWON].(bool) && v.Value < 0.5) || (!v.Meta[META_MAGSWON].(bool) && v.Value > 0.5) { // true
res = true
notify = true
} else { // false
res = false
notify = false
}
default: // > 1
res = true // if there has been more than 1 transition, the switch has been opened
notify = true
}
case 8: // EITHER
delta := v.Meta[META_DELTA].(int64)
switch {
case delta < 0:
res = math.Round(v.Value) == 1 // node reset, to make an intentional state chage to drive a notification
notify = true
case delta == 0:
res = math.Round(v.Value) == 1 // shouldn't get here, so just return the current status i.e. no change
notify = false
default: // >= 1
res = true //
notify = true
}
}
} else {
if status.CurrentState { // result of the last rule evaluation was true
switch op {
case 0: // LT
res = v.Value < spec.Threshold+hysteresisValue
notify = true
case 1: //LTE
res = v.Value <= spec.Threshold+hysteresisValue
notify = true
case 2: // EQ
if hysteresisEnabled {
res = (v.Value > (spec.Threshold - hysteresisValue/2)) && (v.Value < (spec.Threshold + hysteresisValue/2))
} else {
res = v.Value == spec.Threshold
}
notify = true
case 3: // NEQ
if hysteresisEnabled {
res = (v.Value < (spec.Threshold - hysteresisValue/2)) || (v.Value > (spec.Threshold + hysteresisValue/2))
} else {
res = v.Value != spec.Threshold
}
notify = true
case 4: // GT
res = v.Value > spec.Threshold-hysteresisValue
notify = true
case 5: // GTE
res = v.Value >= spec.Threshold-hysteresisValue
notify = true
case 6: // OPEN
delta := v.Meta[META_DELTA].(int64)
switch {
case delta < 0:
res = math.Round(v.Value) == 1 // node reset, to make an intentional state chage to drive a notification
notify = true
case delta == 0:
res = math.Round(v.Value) == 1 // shouldn't get here, so just return the current status i.e. no change
notify = false
case delta == 1:
if (v.Meta[META_MAGSWON].(bool) && v.Value > 0.5) || (!v.Meta[META_MAGSWON].(bool) && v.Value < 0.5) { // true
res = true
notify = true
} else { // false
res = false
notify = false
}
default: // > 1
res = true // if there has been more than 1 transition, the the switch has been opened
notify = true
}
case 7: // CLOSED
delta := v.Meta[META_DELTA].(int64)
switch {
case delta < 0:
res = math.Round(v.Value) == 1 // node reset, to make an intentional state chage to drive a notification
notify = true
case delta == 0:
res = math.Round(v.Value) == 1 // shouldn't get here, so just return the current status i.e. no change
notify = false
case delta == 1:
if (v.Meta[META_MAGSWON].(bool) && v.Value < 0.5) || (!v.Meta[META_MAGSWON].(bool) && v.Value > 0.5) { // true
res = true
notify = true
} else { // false
res = false
notify = false
}
default: // > 1
res = true // if there has been more than 1 transition, the the switch has been opened
notify = true
}
case 8: // EITHER
delta := v.Meta[META_DELTA].(int64)
switch {
case delta < 0:
res = math.Round(v.Value) == 1 // node reset, to make an intentional state chage to drive a notification
notify = true
case delta == 0:
res = math.Round(v.Value) == 1 // shouldn't get here, so just return the current status i.e. no change
notify = false
default: // >= 1
res = true //
notify = true
}
}
} else { // result of the last rule evaluation was false
switch op {
case 0: // LT
res = v.Value < spec.Threshold
notify = true
case 1: //LTE
res = v.Value <= spec.Threshold
notify = true
case 2: // EQ
if hysteresisEnabled {
res = (v.Value > (spec.Threshold - hysteresisValue/2)) && (v.Value < (spec.Threshold + hysteresisValue/2))
} else {
res = v.Value == spec.Threshold
}
notify = true
case 3: // NEQ
if hysteresisEnabled {
res = (v.Value < (spec.Threshold - hysteresisValue/2)) || (v.Value > (spec.Threshold + hysteresisValue/2))
} else {
res = v.Value != spec.Threshold
}
notify = true
case 4: // GT
res = v.Value > spec.Threshold
notify = true
case 5: // GTE
res = v.Value >= spec.Threshold
notify = true
case 6: // OPEN
delta := v.Meta[META_DELTA].(int64)
switch {
case delta < 0:
res = math.Round(v.Value) == 1 // node reset, to make an intentional state chage to drive a notification
notify = true
case delta == 0:
res = math.Round(v.Value) == 1 // shouldn't get here, so just return the current status i.e. no change
notify = false
case delta == 1:
if (v.Meta[META_MAGSWON].(bool) && v.Value > 0.5) || (!v.Meta[META_MAGSWON].(bool) && v.Value < 0.5) { // true
res = true
notify = true
} else { // false
res = false
notify = false
}
default: // > 1
res = true // if there has been more than 1 transition, the the switch has been opened
notify = true
}
case 7: // CLOSED
delta := v.Meta[META_DELTA].(int64)
switch {
case delta < 0:
res = math.Round(v.Value) == 1 // node reset, to make an intentional state chage to drive a notification
notify = true
case delta == 0:
res = math.Round(v.Value) == 1 // shouldn't get here, so just return the current status i.e. no change
notify = false
case delta == 1:
if (v.Meta[META_MAGSWON].(bool) && v.Value < 0.5) || (!v.Meta[META_MAGSWON].(bool) && v.Value > 0.5) { // true
res = true
notify = true
} else { // false
res = false
notify = false
}
}
case 8: // EITHER
delta := v.Meta[META_DELTA].(int64)
switch {
case delta < 0:
res = math.Round(v.Value) == 1 // node reset, to make an intentional state chage to drive a notification
notify = true
case delta == 0:
res = math.Round(v.Value) == 1 // shouldn't get here, so just return the current status i.e. no change
notify = false
default: // >= 1
res = true
notify = true
}
}
}
}
return res, notify // default case
}
/*
* Type definitions for the Notification structure
*/
/*
* Top level definition of objects
*/
type Notification struct {
ID primitive.ObjectID `json:"id" bson:"_id,omitempty"`
GroupID primitive.ObjectID `json:"group_id" bson:"group_id,omitempty"`
Users []primitive.ObjectID `json:"users,omitempty" bson:"users,omitempty"` // User Object ID's
Rules map[string]Rules `json:"rules,omitempty" bson:"rules,omitempty"` // key is DeviceEUI
Email NotificationEmail `json:"email,omitempty" bson:"email,omitempty"`
Config Configuration `json:"configuration,omitempty" bson:"configuration,omitempty"`
Status NotificationStatus `json:"status,omitempty" bson:"status" binding:"required"`
Name string `json:"name,omitempty" bson:"name" binding:"required"`
}
/*
* Initialization function, to be called on the declaration of a notification object
*/
func (n *Notification) Init() {
//n.Users = make([]NotificationUser, 0, 10)
n.Rules = make(map[string]Rules)
}
/*
* Rules for a Notification, split into the SPecification and Status of the rule
*/
type Rules struct {
Spec RulesSpec `json:"spec" bson:"spec,omitempty"` // The specification for the rule
Status RuleStatus `json:"status" bson:"status,omitempty"` // the result of evaluation of the rule
}
/*
* Rule Specification
* The parameter should be a dot separated string of the sensor type and elemID e.g. "ta.ta0"
*/
type RulesSpec struct {
ID primitive.ObjectID `json:"rule_id" bson:"rule_id,omitempty"`
NotificationID primitive.ObjectID `json:"notification_id" bson:"notification_id,omitempty"`
DeviceEUI string `json:"device_eui" bson:"device_eui" binding:"required"`
Parameter string `json:"parameter" bson:"parameter" binding:"required"`
Operator int `json:"operator" bson:"operator" binding:"required"`
Threshold float64 `json:"threshold" bson:"threshold" binding:"required"`
Hysteresis Hysteresis `json:"hysteresis" bson:"hysteresis,omitempty"`
}
/*
* Various information to use when sending a notification email
*/
type NotificationEmail struct {
Subject string `json:"subject" bson:"subject,omitempty"`
Body string `json:"body" bson:"body,omitempty"`
Trigger string `json:"trigger" bson:"trigger,omitempty"` // reason the email was triggered
}
/*
* Configuration for a Notification
*/
type Configuration struct {
RepeatInterval int `json:"repeat_interval" bson:"repeat_interval,omitempty"` // (minutes) how frequently the action should repeat while in the asserted state
HoldOn int `json:"hold_on" bson:"hold_on,omitempty"` // (minutes) how long to remain in the asserted state after the rule is evaluated
HoldOff int `json:"hold_off" bson:"hold_off,omitempty"` // (minutes) how long to remain in the not asserted state after the rule is evaluated
AssertOn bool `json:"assert_on" bson:"assert_on,omitempty"`
LatchOn bool `json:"latch_on" bson:"latch_on,omitempty"`
}
/*
* Information about the overall state of the Notification, separate from the rule(s)
*/
type NotificationStatus struct {
Active bool `json:"active" bson:"active" binding:"required"`
Current bool `json:"current" bson:"current,omitempty"` // Current State of the evaluation of the rules
Triggered bool `json:"triggered" bson:"triggered,omitempty"` // Current State Notification, also taking into account the LatchedOn setting. Cleared v
TriggeredAt time.Time `json:"triggered_at" bson:"triggered_at,omitempty"` // timestamp of when the state changed from not triggered to triggered
LastTrigger time.Time `json:"last_trigger" bson:"last_trigger,omitempty"` // timestamp of the last time the rule was assessed and evaluated to true
EvaluatedAt time.Time `json:"evaluated_at" bson:"evaluated_at,omitempty"` // last time the rules were evalutated
}
/*
* Status object for a rule
*/
type RuleStatus struct {
ChangedAt time.Time `json:"changed_at" bson:"changed_at,omitempty"` // timestamp of the last state change
CurrentState bool `json:"state" bson:"state" binding:"required"` // Current State (triggered/not triggered)
Value float64 `json:"value" bson:"value" binding:"required"` // Value that triggered the change of state
PreviousState bool `json:"previousState" bson:"previousState" binding:"required"` // previous value
}
/*
* Defines the value and mode for the hysteresis
*/
type Hysteresis struct {
Value float64 `json:"value" bson:"value,omitempty"`
Mode int `json:"mode" bson:"mode,omitempty"` // NONE, VALUE or PERCENTAGE
}
type OpData struct {
Value float64 // the basic data that a operator will use for decisions
Meta []interface{} // additional, operator specific, data e.g. number of MagSwitch transitions etc
}
func (d *OpData) Init() {
d.Meta = make([]interface{}, max_elements)
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment