-
Notifications
You must be signed in to change notification settings - Fork 190
/
Copy pathcharacteristic.go
239 lines (198 loc) · 6.57 KB
/
characteristic.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
package characteristic
import (
"fmt"
"net"
"github.com/xiam/to"
)
type ConnChangeFunc func(conn net.Conn, c *Characteristic, newValue, oldValue interface{})
type ChangeFunc func(c *Characteristic, newValue, oldValue interface{})
type GetFunc func() interface{}
// Characteristic is a HomeKit characteristic.
type Characteristic struct {
ID uint64 `json:"iid"` // managed by accessory
Type string `json:"type"`
Perms []string `json:"perms"`
Description string `json:"description,omitempty"` // manufacturer description (optional)
Value interface{} `json:"value,omitempty"` // nil for write-only characteristics
Format string `json:"format"`
Unit string `json:"unit,omitempty"`
MaxLen int `json:"maxLen,omitempty"`
MaxValue interface{} `json:"maxValue,omitempty"`
MinValue interface{} `json:"minValue,omitempty"`
StepValue interface{} `json:"minStep,omitempty"`
// unused
Events bool `json:"-"`
updateOnSameValue bool // if true the update notifications
connValueUpdateFuncs []ConnChangeFunc
valueChangeFuncs []ChangeFunc
valueGetFunc GetFunc
}
// NewCharacteristic returns a characteristic
// If no permissions are specified, the value of PermsAll() is used.
//
// If permissions are write-only the setter methods (UpdateValue and UpdateValueFromRemote)
// don't set the Value field. The OnLocalChange and OnRemoteChange have the new
// value set as expect, but characteristics current and old value are nil.
func NewCharacteristic(typ string) *Characteristic {
return &Characteristic{
Type: typ,
connValueUpdateFuncs: make([]ConnChangeFunc, 0),
valueChangeFuncs: make([]ChangeFunc, 0),
}
}
func (c *Characteristic) GetValue() interface{} {
return c.getValue(nil)
}
func (c *Characteristic) GetValueFromConnection(conn net.Conn) interface{} {
return c.getValue(conn)
}
func (c *Characteristic) OnValueGet(fn GetFunc) {
c.valueGetFunc = fn
}
func (c *Characteristic) UpdateValue(value interface{}) {
c.updateValue(value, nil, false)
}
func (c *Characteristic) UpdateValueFromConnection(value interface{}, conn net.Conn) {
c.updateValue(value, conn, true)
}
func (c *Characteristic) OnValueUpdate(fn ChangeFunc) {
c.valueChangeFuncs = append(c.valueChangeFuncs, fn)
}
func (c *Characteristic) OnValueUpdateFromConn(fn ConnChangeFunc) {
c.connValueUpdateFuncs = append(c.connValueUpdateFuncs, fn)
}
// Equal returns true when receiver has the values as the argument.
func (c *Characteristic) Equal(other interface{}) bool {
if characteristic, ok := other.(*Characteristic); ok == true {
// The value type (e.g. float32, bool,...) of property `Value` may be different even though
// they look the same. They are equal when they have the same string representation.
value := fmt.Sprintf("%+v", c.Value)
otherValue := fmt.Sprintf("%+v", characteristic.Value)
return value == otherValue && c.ID == characteristic.ID && c.Type == characteristic.Type && len(c.Perms) == len(characteristic.Perms) && c.Description == characteristic.Description && c.Format == characteristic.Format && c.Unit == characteristic.Unit && c.MaxLen == characteristic.MaxLen && c.MaxValue == characteristic.MaxValue && c.MinValue == characteristic.MinValue && c.StepValue == characteristic.StepValue && c.Events == characteristic.Events
}
return false
}
// Private
func (c *Characteristic) IsReadable() bool {
return readPerm(c.Perms)
}
func (c *Characteristic) IsWritable() bool {
return writePerm(c.Perms)
}
func (c *Characteristic) IsObservable() bool {
return eventPerm(c.Perms)
}
func (c *Characteristic) getValue(conn net.Conn) interface{} {
if c.valueGetFunc != nil {
c.updateValue(c.valueGetFunc(), conn, false)
}
return c.Value
}
// Sets the value of the characteristic
// The implementation makes sure that the type of the value stays the same
// E.g. Type of characteristic value int, calling updateValue("10.5") sets the value to int(10)
//
// When permissions are write only and checkPerms is true, this methods does not set the Value field.
func (c *Characteristic) updateValue(value interface{}, conn net.Conn, checkPerms bool) {
value = c.convert(value)
// Value must be within min and max
switch c.Format {
case FormatFloat:
value = c.clampFloat(value.(float64))
case FormatUInt8, FormatUInt16, FormatUInt32, FormatUInt64, FormatInt32:
value = c.clampInt(value.(int))
}
if c.Value == value && !c.updateOnSameValue {
return
}
// Ignore new values from remote when permissions don't allow write and checkPerms is true
if checkPerms && !c.IsWritable() {
return
}
old := c.Value
if c.IsReadable() {
c.Value = value
}
if conn != nil {
c.onValueUpdateFromConn(c.connValueUpdateFuncs, conn, value, old)
} else {
c.onValueUpdate(c.valueChangeFuncs, value, old)
}
}
func (c *Characteristic) onValueUpdate(funcs []ChangeFunc, newValue, oldValue interface{}) {
for _, fn := range funcs {
fn(c, newValue, oldValue)
}
}
func (c *Characteristic) onValueUpdateFromConn(funcs []ConnChangeFunc, conn net.Conn, newValue, oldValue interface{}) {
for _, fn := range funcs {
fn(conn, c, newValue, oldValue)
}
}
func (c *Characteristic) clampFloat(value float64) interface{} {
min, minOK := c.MinValue.(float64)
max, maxOK := c.MaxValue.(float64)
if maxOK == true && value > max {
value = max
} else if minOK == true && value < min {
value = min
}
return value
}
func (c *Characteristic) clampInt(value int) interface{} {
min, minOK := c.MinValue.(int)
max, maxOK := c.MaxValue.(int)
if maxOK == true && value > max {
value = max
} else if minOK == true && value < min {
value = min
}
return value
}
func (c *Characteristic) convert(v interface{}) interface{} {
switch c.Format {
case FormatFloat:
return to.Float64(v)
case FormatUInt8:
return int(to.Uint64(v))
case FormatUInt16:
return int(to.Uint64(v))
case FormatUInt32:
return int(to.Uint64(v))
case FormatInt32:
return int(to.Uint64(v))
case FormatUInt64:
return int(to.Uint64(v))
case FormatBool:
return to.Bool(v)
default:
return v
}
}
// readPerm returns true when perms include read permission
func readPerm(perms []string) bool {
for _, perm := range perms {
if perm == PermRead {
return true
}
}
return false
}
// writePerm returns true when perms include write permission
func writePerm(permissions []string) bool {
for _, value := range permissions {
if value == PermWrite {
return true
}
}
return false
}
// eventPerm returns true when perms include events permission
func eventPerm(permissions []string) bool {
for _, value := range permissions {
if value == PermEvents {
return true
}
}
return false
}