Private GIT
Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
P
prometheus-dyson
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Package registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
GitLab community forum
Contribute to GitLab
Provide feedback
Terms and privacy
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
vlbox
prometheus-dyson
Commits
3ccd67dc
Commit
3ccd67dc
authored
Sep 17, 2020
by
Sean Rees
Browse files
Options
Downloads
Patches
Plain Diff
Update metric names to conform more closely to the Prometheus naming scheme[1]
[1]
https://prometheus.io/docs/practices/naming/
parent
3272dd35
Branches
Branches containing commit
No related tags found
No related merge requests found
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
README.md
+29
-15
29 additions, 15 deletions
README.md
grafana.json
+27
-23
27 additions, 23 deletions
grafana.json
main.py
+21
-15
21 additions, 15 deletions
main.py
with
77 additions
and
53 deletions
README.md
+
29
−
15
View file @
3ccd67dc
...
@@ -4,37 +4,51 @@ Prometheus client for DysonLink fans (e.g; Pure Hot+Cool).
...
@@ -4,37 +4,51 @@ Prometheus client for DysonLink fans (e.g; Pure Hot+Cool).
This code only supports Pure Hot+Cool fans at the moment. It should be trivial
This code only supports Pure Hot+Cool fans at the moment. It should be trivial
to extend to other fan types (I just don't have one to test).
to extend to other fan types (I just don't have one to test).
## Dependencies
## Build
```
% bazel build :main
```
If you'd like a Debian package:
```
% baze build :main-deb
```
### Without Bazel
You'll need these dependencies:
```
```
pip install libpurecool
pip install libpurecool
pip install prometheus_client
pip install prometheus_client
```
```
## Metrics
## Metrics
### Environmental
### Environmental
Name | Type | Description
Name | Type | Description
---- | ---- | -----------
---- | ---- | -----------
humidity | gauge | relative humidity percentage
dyson_
humidity
_percent
| gauge | relative humidity percentage
temperature | gauge | ambient temperature in celsius
dyson_
temperature
_celsius
| gauge | ambient temperature in celsius
voc
| gauge | volatile organic compounds (range 0-10?)
dyson_volatile_organic_compounds_units
| gauge | volatile organic compounds (range 0-10?)
d
ust
| gauge | dust level (range 0-10?)
d
yson_dust_units
| gauge | dust level (range 0-10?)
### Operational
### Operational
Name | Type | Description
Name | Type | Description
---- | ---- | -----------
---- | ---- | -----------
fan_mode | enum | AUTO, FAN (what the fan is set to)
dyson_
fan_mode | enum | AUTO, FAN (what the fan is set to)
fan_state | enum | FAN, OFF (what the fan is actually doing)
dyson_
fan_state | enum | FAN, OFF (what the fan is actually doing)
fan_speed | gauge | 0-10 (or -1 if on AUTO)
dyson_
fan_speed
_units
| gauge | 0-10 (or -1 if on AUTO)
oscillation | enum | ON, OFF
dyson_
oscillation
_mode
| enum | ON, OFF
focus_mode | enum | ON, OFF
dyson_
focus_mode | enum | ON, OFF
heat_mode | enum | HEAT, OFF (OFF means "in cooling mode")
dyson_
heat_mode | enum | HEAT, OFF (OFF means "in cooling mode")
heat_state | enum | HEAT, OFF (what the fan is actually doing)
dyson_
heat_state | enum | HEAT, OFF (what the fan is actually doing)
heat_target | gauge | target temperature (celsius)
dyson_
heat_target
_celsius
| gauge | target temperature (celsius)
quality_target | gauge | air quality target (1, 3, 5?)
dyson_
quality_target
_units
| gauge | air quality target (1, 3, 5?)
filter_life | gauge |
hour
s of filter life remaining
dyson_
filter_life
_seconds
| gauge |
second
s of filter life remaining
## Usage
## Usage
...
...
This diff is collapsed.
Click to expand it.
grafana.json
+
27
−
23
View file @
3ccd67dc
...
@@ -52,7 +52,7 @@
...
@@ -52,7 +52,7 @@
"gnetId"
:
null
,
"gnetId"
:
null
,
"graphTooltip"
:
0
,
"graphTooltip"
:
0
,
"id"
:
null
,
"id"
:
null
,
"iteration"
:
1
54374124089
9
,
"iteration"
:
1
60032567880
9
,
"links"
:
[],
"links"
:
[],
"panels"
:
[
"panels"
:
[
{
{
...
@@ -129,11 +129,12 @@
...
@@ -129,11 +129,12 @@
"tableColumn"
:
""
,
"tableColumn"
:
""
,
"targets"
:
[
"targets"
:
[
{
{
"expr"
:
"fan_mode{instance=~
\"
$instance
\"
,name=~
\"
$name
\"
}==1"
,
"expr"
:
"
dyson_
fan_mode{instance=~
\"
$instance
\"
,name=~
\"
$name
\"
}==1"
,
"format"
:
"time_series"
,
"format"
:
"time_series"
,
"instant"
:
true
,
"instant"
:
true
,
"interval"
:
""
,
"intervalFactor"
:
1
,
"intervalFactor"
:
1
,
"legendFormat"
:
"{{fan_mode}}"
,
"legendFormat"
:
"{{
dyson_
fan_mode}}"
,
"refId"
:
"A"
"refId"
:
"A"
}
}
],
],
...
@@ -211,11 +212,12 @@
...
@@ -211,11 +212,12 @@
"tableColumn"
:
""
,
"tableColumn"
:
""
,
"targets"
:
[
"targets"
:
[
{
{
"expr"
:
"fan_state{instance=~
\"
$instance
\"
,name=~
\"
$name
\"
}==1"
,
"expr"
:
"
dyson_
fan_state{instance=~
\"
$instance
\"
,name=~
\"
$name
\"
}==1"
,
"format"
:
"time_series"
,
"format"
:
"time_series"
,
"instant"
:
true
,
"instant"
:
true
,
"interval"
:
""
,
"intervalFactor"
:
1
,
"intervalFactor"
:
1
,
"legendFormat"
:
"{{fan_state}}"
,
"legendFormat"
:
"{{
dyson_
fan_state}}"
,
"refId"
:
"A"
"refId"
:
"A"
}
}
],
],
...
@@ -293,11 +295,12 @@
...
@@ -293,11 +295,12 @@
"tableColumn"
:
""
,
"tableColumn"
:
""
,
"targets"
:
[
"targets"
:
[
{
{
"expr"
:
"oscillation{instance=~
\"
$instance
\"
,name=~
\"
$name
\"
}==1"
,
"expr"
:
"
dyson_
oscillation
_mode
{instance=~
\"
$instance
\"
,name=~
\"
$name
\"
}==1"
,
"format"
:
"time_series"
,
"format"
:
"time_series"
,
"instant"
:
true
,
"instant"
:
true
,
"interval"
:
""
,
"intervalFactor"
:
1
,
"intervalFactor"
:
1
,
"legendFormat"
:
"{{oscillation}}"
,
"legendFormat"
:
"{{
dyson_
oscillation
_mode
}}"
,
"refId"
:
"A"
"refId"
:
"A"
}
}
],
],
...
@@ -375,12 +378,12 @@
...
@@ -375,12 +378,12 @@
"tableColumn"
:
""
,
"tableColumn"
:
""
,
"targets"
:
[
"targets"
:
[
{
{
"expr"
:
"focus_mode{instance=~
\"
$instance
\"
,name=~
\"
$name
\"
}==1"
,
"expr"
:
"
dyson_
focus_mode{instance=~
\"
$instance
\"
,name=~
\"
$name
\"
}==1"
,
"format"
:
"time_series"
,
"format"
:
"time_series"
,
"instant"
:
true
,
"instant"
:
true
,
"interval"
:
""
,
"interval"
:
""
,
"intervalFactor"
:
1
,
"intervalFactor"
:
1
,
"legendFormat"
:
"{{focus_mode}}"
,
"legendFormat"
:
"{{
dyson_
focus_mode}}"
,
"refId"
:
"A"
"refId"
:
"A"
}
}
],
],
...
@@ -458,11 +461,11 @@
...
@@ -458,11 +461,11 @@
"tableColumn"
:
""
,
"tableColumn"
:
""
,
"targets"
:
[
"targets"
:
[
{
{
"expr"
:
"heat_mode{instance=~
\"
$instance
\"
,name=~
\"
$name
\"
}==1"
,
"expr"
:
"
dyson_
heat_mode{instance=~
\"
$instance
\"
,name=~
\"
$name
\"
}==1"
,
"format"
:
"time_series"
,
"format"
:
"time_series"
,
"instant"
:
true
,
"instant"
:
true
,
"intervalFactor"
:
1
,
"intervalFactor"
:
1
,
"legendFormat"
:
"{{heat_mode}}"
,
"legendFormat"
:
"{{
dyson_
heat_mode}}"
,
"refId"
:
"A"
"refId"
:
"A"
}
}
],
],
...
@@ -571,7 +574,7 @@
...
@@ -571,7 +574,7 @@
"#d44a3a"
"#d44a3a"
],
],
"datasource"
:
"${DS_LOCAL}"
,
"datasource"
:
"${DS_LOCAL}"
,
"format"
:
"
h
"
,
"format"
:
"
s
"
,
"gauge"
:
{
"gauge"
:
{
"maxValue"
:
100
,
"maxValue"
:
100
,
"minValue"
:
0
,
"minValue"
:
0
,
...
@@ -622,7 +625,7 @@
...
@@ -622,7 +625,7 @@
"tableColumn"
:
""
,
"tableColumn"
:
""
,
"targets"
:
[
"targets"
:
[
{
{
"expr"
:
"filter_life{instance=~
\"
$instance
\"
,name=~
\"
$name
\"
}"
,
"expr"
:
"
dyson_
filter_life
_seconds
{instance=~
\"
$instance
\"
,name=~
\"
$name
\"
}"
,
"format"
:
"time_series"
,
"format"
:
"time_series"
,
"intervalFactor"
:
1
,
"intervalFactor"
:
1
,
"refId"
:
"A"
"refId"
:
"A"
...
@@ -694,7 +697,7 @@
...
@@ -694,7 +697,7 @@
"steppedLine"
:
false
,
"steppedLine"
:
false
,
"targets"
:
[
"targets"
:
[
{
{
"expr"
:
"d
ust
{instance=~
\"
$instance
\"
,name=~
\"
$name
\"
}"
,
"expr"
:
"d
yson_dust_units
{instance=~
\"
$instance
\"
,name=~
\"
$name
\"
}"
,
"format"
:
"time_series"
,
"format"
:
"time_series"
,
"interval"
:
""
,
"interval"
:
""
,
"intervalFactor"
:
1
,
"intervalFactor"
:
1
,
...
@@ -702,14 +705,14 @@
...
@@ -702,14 +705,14 @@
"refId"
:
"A"
"refId"
:
"A"
},
},
{
{
"expr"
:
"
voc
{instance=~
\"
$instance
\"
,name=~
\"
$name
\"
}"
,
"expr"
:
"
dyson_volatile_organic_compounds_units
{instance=~
\"
$instance
\"
,name=~
\"
$name
\"
}"
,
"format"
:
"time_series"
,
"format"
:
"time_series"
,
"intervalFactor"
:
1
,
"intervalFactor"
:
1
,
"legendFormat"
:
"VOC"
,
"legendFormat"
:
"VOC"
,
"refId"
:
"B"
"refId"
:
"B"
},
},
{
{
"expr"
:
"quality_target{instance=~
\"
$instance
\"
,name=~
\"
$name
\"
}"
,
"expr"
:
"
dyson_
quality_target
_units
{instance=~
\"
$instance
\"
,name=~
\"
$name
\"
}"
,
"format"
:
"time_series"
,
"format"
:
"time_series"
,
"interval"
:
""
,
"interval"
:
""
,
"intervalFactor"
:
1
,
"intervalFactor"
:
1
,
...
@@ -797,7 +800,7 @@
...
@@ -797,7 +800,7 @@
"steppedLine"
:
false
,
"steppedLine"
:
false
,
"targets"
:
[
"targets"
:
[
{
{
"expr"
:
"humidity{instance=~
\"
$instance
\"
,name=~
\"
$name
\"
}"
,
"expr"
:
"
dyson_
humidity
_percent
{instance=~
\"
$instance
\"
,name=~
\"
$name
\"
}"
,
"format"
:
"time_series"
,
"format"
:
"time_series"
,
"intervalFactor"
:
10
,
"intervalFactor"
:
10
,
"legendFormat"
:
"Humidity (%age)"
,
"legendFormat"
:
"Humidity (%age)"
,
...
@@ -883,14 +886,14 @@
...
@@ -883,14 +886,14 @@
"steppedLine"
:
false
,
"steppedLine"
:
false
,
"targets"
:
[
"targets"
:
[
{
{
"expr"
:
"temperature{instance=~
\"
$instance
\"
,name=~
\"
$name
\"
}"
,
"expr"
:
"
dyson_
temperature
_celsius
{instance=~
\"
$instance
\"
,name=~
\"
$name
\"
}"
,
"format"
:
"time_series"
,
"format"
:
"time_series"
,
"intervalFactor"
:
10
,
"intervalFactor"
:
10
,
"legendFormat"
:
"Temperature"
,
"legendFormat"
:
"Temperature"
,
"refId"
:
"A"
"refId"
:
"A"
},
},
{
{
"expr"
:
"heat_target{instance=~
\"
$instance
\"
,name=~
\"
$name
\"
}"
,
"expr"
:
"
dyson_
heat_target
_celsius
{instance=~
\"
$instance
\"
,name=~
\"
$name
\"
}"
,
"format"
:
"time_series"
,
"format"
:
"time_series"
,
"intervalFactor"
:
1
,
"intervalFactor"
:
1
,
"legendFormat"
:
"Heat Target"
,
"legendFormat"
:
"Heat Target"
,
...
@@ -920,7 +923,7 @@
...
@@ -920,7 +923,7 @@
"label"
:
null
,
"label"
:
null
,
"logBase"
:
1
,
"logBase"
:
1
,
"max"
:
null
,
"max"
:
null
,
"min"
:
null
,
"min"
:
"15"
,
"show"
:
true
"show"
:
true
},
},
{
{
...
@@ -987,9 +990,10 @@
...
@@ -987,9 +990,10 @@
"steppedLine"
:
false
,
"steppedLine"
:
false
,
"targets"
:
[
"targets"
:
[
{
{
"expr"
:
"filter_life{instance=~
\"
$instance
\"
,name=~
\"
$name
\"
}"
,
"expr"
:
"
dyson_
filter_life
_seconds
{instance=~
\"
$instance
\"
,name=~
\"
$name
\"
}"
,
"format"
:
"time_series"
,
"format"
:
"time_series"
,
"intervalFactor"
:
1
,
"intervalFactor"
:
1
,
"legendFormat"
:
"Remaining Life - {{name}}"
,
"refId"
:
"A"
"refId"
:
"A"
}
}
],
],
...
@@ -1012,7 +1016,7 @@
...
@@ -1012,7 +1016,7 @@
},
},
"yaxes"
:
[
"yaxes"
:
[
{
{
"format"
:
"
h
"
,
"format"
:
"
s
"
,
"label"
:
null
,
"label"
:
null
,
"logBase"
:
1
,
"logBase"
:
1
,
"max"
:
null
,
"max"
:
null
,
...
@@ -1116,5 +1120,5 @@
...
@@ -1116,5 +1120,5 @@
"timezone"
:
""
,
"timezone"
:
""
,
"title"
:
"Dyson Fan"
,
"title"
:
"Dyson Fan"
,
"uid"
:
"wSsTjyYik"
,
"uid"
:
"wSsTjyYik"
,
"version"
:
7
"version"
:
10
}
}
\ No newline at end of file
This diff is collapsed.
Click to expand it.
main.py
+
21
−
15
View file @
3ccd67dc
...
@@ -35,34 +35,37 @@ class Metrics():
...
@@ -35,34 +35,37 @@ class Metrics():
labels
=
[
'
name
'
,
'
serial
'
]
labels
=
[
'
name
'
,
'
serial
'
]
# Environmental Sensors
# Environmental Sensors
self
.
humidity
=
prometheus_client
.
Gauge
(
'
humidity
'
,
'
Relative humidity (percentage)
'
,
labels
)
self
.
humidity
=
prometheus_client
.
Gauge
(
'
dyson_humidity_percent
'
,
'
Relative humidity (percentage)
'
,
labels
)
self
.
temperature
=
prometheus_client
.
Gauge
(
self
.
temperature
=
prometheus_client
.
Gauge
(
'
temperature
'
,
'
Ambient temperature (celsius)
'
,
labels
)
'
dyson_temperature_celsius
'
,
'
Ambient temperature (celsius)
'
,
labels
)
self
.
voc
=
prometheus_client
.
Gauge
(
'
voc
'
,
'
Level of Volatile organic compounds
'
,
labels
)
self
.
voc
=
prometheus_client
.
Gauge
(
self
.
dust
=
prometheus_client
.
Gauge
(
'
dust
'
,
'
Level of Dust
'
,
labels
)
'
dyson_volatile_organic_compounds_units
'
,
'
Level of Volatile organic compounds
'
,
labels
)
self
.
dust
=
prometheus_client
.
Gauge
(
'
dyson_dust_units
'
,
'
Level of Dust
'
,
labels
)
# Operational State
# Operational State
# Ignoring: tilt (known values OK), standby_monitoring.
# Ignoring: tilt (known values OK), standby_monitoring.
self
.
fan_mode
=
prometheus_client
.
Enum
(
self
.
fan_mode
=
prometheus_client
.
Enum
(
'
fan_mode
'
,
'
Current mode of the fan
'
,
labels
,
states
=
[
'
AUTO
'
,
'
FAN
'
])
'
dyson_
fan_mode
'
,
'
Current mode of the fan
'
,
labels
,
states
=
[
'
AUTO
'
,
'
FAN
'
])
self
.
fan_state
=
prometheus_client
.
Enum
(
self
.
fan_state
=
prometheus_client
.
Enum
(
'
fan_state
'
,
'
Current running state of the fan
'
,
labels
,
states
=
[
'
FAN
'
,
'
OFF
'
])
'
dyson_
fan_state
'
,
'
Current running state of the fan
'
,
labels
,
states
=
[
'
FAN
'
,
'
OFF
'
])
self
.
fan_speed
=
prometheus_client
.
Gauge
(
self
.
fan_speed
=
prometheus_client
.
Gauge
(
'
fan_speed
'
,
'
Current speed of fan (-1 = AUTO)
'
,
labels
)
'
dyson_
fan_speed
_units
'
,
'
Current speed of fan (-1 = AUTO)
'
,
labels
)
self
.
oscillation
=
prometheus_client
.
Enum
(
self
.
oscillation
=
prometheus_client
.
Enum
(
'
oscillation
'
,
'
Current oscillation mode
'
,
labels
,
states
=
[
'
ON
'
,
'
OFF
'
])
'
dyson_
oscillation
_mode
'
,
'
Current oscillation mode
'
,
labels
,
states
=
[
'
ON
'
,
'
OFF
'
])
self
.
focus_mode
=
prometheus_client
.
Enum
(
self
.
focus_mode
=
prometheus_client
.
Enum
(
'
focus_mode
'
,
'
Current focus mode
'
,
labels
,
states
=
[
'
ON
'
,
'
OFF
'
])
'
dyson_
focus_mode
'
,
'
Current focus mode
'
,
labels
,
states
=
[
'
ON
'
,
'
OFF
'
])
self
.
heat_mode
=
prometheus_client
.
Enum
(
self
.
heat_mode
=
prometheus_client
.
Enum
(
'
heat_mode
'
,
'
Current heat mode
'
,
labels
,
states
=
[
'
HEAT
'
,
'
OFF
'
])
'
dyson_
heat_mode
'
,
'
Current heat mode
'
,
labels
,
states
=
[
'
HEAT
'
,
'
OFF
'
])
self
.
heat_state
=
prometheus_client
.
Enum
(
self
.
heat_state
=
prometheus_client
.
Enum
(
'
heat_state
'
,
'
Current heat state
'
,
labels
,
states
=
[
'
HEAT
'
,
'
OFF
'
])
'
dyson_
heat_state
'
,
'
Current heat state
'
,
labels
,
states
=
[
'
HEAT
'
,
'
OFF
'
])
self
.
heat_target
=
prometheus_client
.
Gauge
(
self
.
heat_target
=
prometheus_client
.
Gauge
(
'
heat_target
'
,
'
Heat target temperature (celsius)
'
,
labels
)
'
dyson_
heat_target
_celsius
'
,
'
Heat target temperature (celsius)
'
,
labels
)
self
.
quality_target
=
prometheus_client
.
Gauge
(
self
.
quality_target
=
prometheus_client
.
Gauge
(
'
quality_target
'
,
'
Quality target for fan
'
,
labels
)
'
dyson_
quality_target
_units
'
,
'
Quality target for fan
'
,
labels
)
self
.
filter_life
=
prometheus_client
.
Gauge
(
self
.
filter_life
=
prometheus_client
.
Gauge
(
'
filter_life
'
,
'
Remaining filter life (
hour
s)
'
,
labels
)
'
dyson_
filter_life
_seconds
'
,
'
Remaining filter life (
second
s)
'
,
labels
)
def
update
(
self
,
name
:
str
,
serial
:
str
,
message
:
object
)
->
None
:
def
update
(
self
,
name
:
str
,
serial
:
str
,
message
:
object
)
->
None
:
"""
Receives a sensor or device state update and updates Prometheus metrics.
"""
Receives a sensor or device state update and updates Prometheus metrics.
...
@@ -94,13 +97,16 @@ class Metrics():
...
@@ -94,13 +97,16 @@ class Metrics():
# Convert from Decicelsius to Kelvin.
# Convert from Decicelsius to Kelvin.
heat_target
=
int
(
message
.
heat_target
)
/
10
-
273.2
heat_target
=
int
(
message
.
heat_target
)
/
10
-
273.2
# Convert filter_life from hours to seconds
filter_life
=
int
(
message
.
filter_life
)
*
60
*
60
self
.
oscillation
.
labels
(
name
=
name
,
serial
=
serial
).
state
(
message
.
oscillation
)
self
.
oscillation
.
labels
(
name
=
name
,
serial
=
serial
).
state
(
message
.
oscillation
)
self
.
focus_mode
.
labels
(
name
=
name
,
serial
=
serial
).
state
(
message
.
focus_mode
)
self
.
focus_mode
.
labels
(
name
=
name
,
serial
=
serial
).
state
(
message
.
focus_mode
)
self
.
heat_mode
.
labels
(
name
=
name
,
serial
=
serial
).
state
(
message
.
heat_mode
)
self
.
heat_mode
.
labels
(
name
=
name
,
serial
=
serial
).
state
(
message
.
heat_mode
)
self
.
heat_state
.
labels
(
name
=
name
,
serial
=
serial
).
state
(
message
.
heat_state
)
self
.
heat_state
.
labels
(
name
=
name
,
serial
=
serial
).
state
(
message
.
heat_state
)
self
.
heat_target
.
labels
(
name
=
name
,
serial
=
serial
).
set
(
heat_target
)
self
.
heat_target
.
labels
(
name
=
name
,
serial
=
serial
).
set
(
heat_target
)
self
.
quality_target
.
labels
(
name
=
name
,
serial
=
serial
).
set
(
message
.
quality_target
)
self
.
quality_target
.
labels
(
name
=
name
,
serial
=
serial
).
set
(
message
.
quality_target
)
self
.
filter_life
.
labels
(
name
=
name
,
serial
=
serial
).
set
(
message
.
filter_life
)
self
.
filter_life
.
labels
(
name
=
name
,
serial
=
serial
).
set
(
filter_life
)
else
:
else
:
logging
.
warning
(
'
Received unknown update from
"
%s
"
(serial=%s): %s; ignoring
'
,
logging
.
warning
(
'
Received unknown update from
"
%s
"
(serial=%s): %s; ignoring
'
,
name
,
serial
,
type
(
message
))
name
,
serial
,
type
(
message
))
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment