Skip to content

Commit fb32bec

Browse files
authored
Merge pull request #505 from y-yosuke/add-switchbot-status-publisher
Add switchbot status publisher
2 parents e6173b8 + d0465ce commit fb32bec

File tree

10 files changed

+255
-3
lines changed

10 files changed

+255
-3
lines changed

switchbot_ros/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ add_message_files(
1414
FILES
1515
Device.msg
1616
DeviceArray.msg
17+
Meter.msg
18+
PlugMini.msg
19+
Hub2.msg
20+
Bot.msg
21+
StripLight.msg
1722
)
1823

1924
add_action_files(

switchbot_ros/launch/switchbot.launch

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
<arg name="token" />
33
<arg name="secret" default="''" />
44
<arg name="respawn" default="true" />
5+
<arg name="pub_status" default="false" />
6+
<arg name="pub_status_rate" default="0.1" />
7+
<arg name="pub_device_name" default="bot74a" />
58

69
<node name="switchbot_ros" pkg="switchbot_ros" type="switchbot_ros_server.py"
710
respawn="$(arg respawn)" output="screen">
@@ -10,4 +13,16 @@
1013
secret: $(arg secret)
1114
</rosparam>
1215
</node>
16+
17+
<node if="$(arg pub_status)"
18+
name="switchbot_status_publisher" pkg="switchbot_ros" type="switchbot_status_publisher.py"
19+
respawn="$(arg respawn)" output="screen">
20+
<rosparam subst_value="true">
21+
token: $(arg token)
22+
secret: $(arg secret)
23+
device_name: $(arg pub_device_name)
24+
rate: $(arg pub_status_rate)
25+
</rosparam>
26+
</node>
27+
1328
</launch>

switchbot_ros/msg/Bot.msg

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
string DEVICEMODE_PRESS = "pressMode"
2+
string DEVICEMODE_SWITCH = "switchMode"
3+
string DEVICEMODE_CUSTOMIZE = "customizeMode"
4+
5+
Header header # timestamp
6+
7+
float64 battery # the current battery level, 0-100
8+
9+
bool power # ON/OFF state True/False
10+
string device_mode # pressMode, switchMode, or customizeMode

switchbot_ros/msg/Hub2.msg

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Header header # timestamp
2+
3+
float64 temperature # temperature in celsius
4+
float64 humidity # humidity percentage
5+
6+
int64 light_level # the level of illuminance of the ambience light, 1~20

switchbot_ros/msg/Meter.msg

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Header header # timestamp
2+
3+
float64 temperature # temperature in celsius
4+
float64 humidity # humidity percentage
5+
float64 battery # the current battery level, 0-100

switchbot_ros/msg/PlugMini.msg

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Header header # timestamp
2+
3+
float64 voltage # the voltage of the device, measured in Volt
4+
float64 weight # the power consumed in a day, measured in Watts
5+
float64 current # the current of the device at the moment, measured in Amp
6+
7+
int32 minutes_day # he duration that the device has been used during a day, measured in minutes

switchbot_ros/msg/StripLight.msg

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Header header # timestamp
2+
3+
bool power # ON/OFF state True/False
4+
5+
int64 brightness # the brightness value, range from 1 to 100
6+
int64 color_r # Red color value 0-255
7+
int64 color_g # Green color value 0-255
8+
int64 color_b # Blue color value 0-255

switchbot_ros/scripts/control_switchbot.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
devices = client.get_devices()
1010
print(devices)
1111

12-
client.control_device('pendant-light', 'turnOff')
12+
client.control_device('pendant-light', 'turnOn', wait=True)
1313

14-
client.control_device('bot74a', 'turnOn')
14+
client.control_device('bot74a', 'turnOn', wait=True)
1515

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
#!/usr/bin/env python
2+
3+
import os.path
4+
from requests import ConnectionError
5+
import rospy
6+
from switchbot_ros.switchbot import SwitchBotAPIClient
7+
from switchbot_ros.switchbot import DeviceError, SwitchBotAPIError
8+
from switchbot_ros.msg import Meter, PlugMini, Hub2, Bot, StripLight
9+
10+
11+
class SwitchBotStatusPublisher:
12+
"""
13+
Publissh your switchbot status with ROS and SwitchBot API
14+
"""
15+
def __init__(self):
16+
# SwitchBot configs
17+
# '~token' can be file path or raw characters
18+
token = rospy.get_param('~token')
19+
if os.path.exists(token):
20+
with open(token) as f:
21+
self.token = f.read().replace('\n', '')
22+
else:
23+
self.token = token
24+
25+
# Switchbot API v1.1 needs secret key
26+
secret = rospy.get_param('~secret', None)
27+
if secret is not None and os.path.exists(secret):
28+
with open(secret, 'r', encoding='utf-8') as f:
29+
self.secret = f.read().replace('\n', '')
30+
else:
31+
self.secret = secret
32+
33+
# Initialize switchbot client
34+
self.bots = self.get_switchbot_client()
35+
self.print_apiversion()
36+
37+
# Get parameters for publishing
38+
self.rate = rospy.get_param('~rate', 0.1)
39+
rospy.loginfo('Rate: ' + str(self.rate))
40+
41+
device_name = rospy.get_param('~device_name')
42+
if device_name:
43+
self.device_name = device_name
44+
else:
45+
rospy.logerr('No Device Name')
46+
return
47+
48+
self.device_type = None
49+
self.device_list = sorted(
50+
self.bots.device_list,
51+
key=lambda device: str(device.get('deviceName')))
52+
for device in self.device_list:
53+
device_name = str(device.get('deviceName'))
54+
if self.device_name == device_name:
55+
self.device_type = str(device.get('deviceType'))
56+
57+
if self.device_type:
58+
rospy.loginfo('deviceName: ' + self.device_name + ' / deviceType: ' + self.device_type)
59+
else:
60+
rospy.logerr('Invalid Device Name: ' + self.device_name)
61+
return
62+
63+
topic_name = '~' + self.device_name
64+
topic_name = topic_name.replace('-', '_')
65+
66+
# Publisher Message Class for each device type
67+
if self.device_type == 'Remote':
68+
rospy.logerr('Device Type: "' + self.device_type + '" has no status in specifications.')
69+
return
70+
else:
71+
if self.device_type == 'Meter':
72+
self.msg_class = Meter
73+
elif self.device_type == 'MeterPlus':
74+
self.msg_class = Meter
75+
elif self.device_type == 'WoIOSensor':
76+
self.msg_class = Meter
77+
elif self.device_type == 'Hub 2':
78+
self.msg_class = Hub2
79+
elif self.device_type == 'Plug Mini (JP)':
80+
self.msg_class = PlugMini
81+
elif self.device_type == 'Plug Mini (US)':
82+
self.msg_class = PlugMini
83+
elif self.device_type == 'Bot':
84+
self.msg_class = Bot
85+
elif self.device_type == 'Strip Light':
86+
self.msg_class = StripLight
87+
else:
88+
rospy.logerr('No publisher process for "' + self.device_type + '" in switchbot_status_publisher.py')
89+
return
90+
91+
self.status_pub = rospy.Publisher(topic_name, self.msg_class, queue_size=1, latch=True)
92+
93+
rospy.loginfo('Ready: SwitchBot Status Publisher for ' + self.device_name)
94+
95+
96+
def get_switchbot_client(self):
97+
try:
98+
client = SwitchBotAPIClient(token=self.token, secret=self.secret)
99+
rospy.loginfo('Switchbot API Client initialized.')
100+
return client
101+
except ConnectionError: # If the machine is not connected to the internet
102+
rospy.logwarn_once('Failed to connect to the switchbot server. The client would try connecting to it when subscribes the ActionGoal topic.')
103+
return None
104+
105+
106+
def spin(self):
107+
rate = rospy.Rate(self.rate)
108+
while not rospy.is_shutdown():
109+
rate.sleep()
110+
if self.bots is None:
111+
self.bots = self.get_switchbot_client()
112+
113+
if self.device_type == 'Remote':
114+
return
115+
else:
116+
status = self.get_device_status(device_name=self.device_name)
117+
118+
if status:
119+
time = rospy.get_rostime()
120+
if self.msg_class == Meter:
121+
msg = Meter()
122+
msg.header.stamp = time
123+
msg.temperature = status['temperature']
124+
msg.humidity = status['humidity']
125+
msg.battery = status['battery']
126+
elif self.msg_class == Hub2:
127+
msg = Hub2()
128+
msg.header.stamp = time
129+
msg.temperature = status['temperature']
130+
msg.humidity = status['humidity']
131+
msg.light_level = status['lightLevel']
132+
elif self.msg_class == PlugMini:
133+
msg = PlugMini()
134+
msg.header.stamp = time
135+
msg.voltage = status['voltage']
136+
msg.weight = status['weight']
137+
msg.current = status['electricCurrent']
138+
msg.minutes_day = status['electricityOfDay']
139+
elif self.msg_class == Bot:
140+
msg = Bot()
141+
msg.header.stamp = time
142+
msg.battery = status['battery']
143+
if status['power'] == 'on':
144+
msg.power = True
145+
else:
146+
msg.power = False
147+
msg.device_mode = status['deviceMode']
148+
elif self.msg_class == StripLight:
149+
msg = StripLight()
150+
msg.header.stamp = time
151+
if status['power'] == 'on':
152+
msg.power = True
153+
else:
154+
msg.power = False
155+
msg.brightness = status['brightness']
156+
rgb_string = status['color']
157+
r, g, b = map(int, rgb_string.split(':'))
158+
msg.color_r = r
159+
msg.color_g = g
160+
msg.color_b = b
161+
else:
162+
return
163+
164+
if msg:
165+
self.status_pub.publish(msg)
166+
167+
168+
def get_device_status(self, device_name=None):
169+
if self.bots is None:
170+
return
171+
elif device_name:
172+
status = self.bots.device_status(device_name=device_name)
173+
return status
174+
else:
175+
return
176+
177+
178+
def print_apiversion(self):
179+
if self.bots is None:
180+
return
181+
182+
apiversion_str = 'Using SwitchBot API ';
183+
apiversion_str += self.bots.api_version;
184+
rospy.loginfo(apiversion_str)
185+
186+
187+
if __name__ == '__main__':
188+
try:
189+
rospy.init_node('switchbot_status_publisher')
190+
ssp = SwitchBotStatusPublisher()
191+
ssp.spin()
192+
except rospy.ROSInterruptException:
193+
pass

switchbot_ros/src/switchbot_ros/switchbot_ros_client.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,17 @@ class SwitchBotROSClient(object):
99

1010
def __init__(self,
1111
actionname='switchbot_ros/switch',
12-
topicname='switchbot_ros/devices'):
12+
topicname='switchbot_ros/devices',
13+
timeout=5):
1314

1415
self.actionname = actionname
1516
self.topicname = topicname
1617
self.action_client = actionlib.SimpleActionClient(
1718
actionname,
1819
SwitchBotCommandAction
1920
)
21+
rospy.loginfo("Waiting for action server to start. (timeout: " + str(timeout) + "[sec])")
22+
self.action_client.wait_for_server(timeout=rospy.Duration(timeout,0))
2023

2124
def get_devices(self, timeout=None):
2225

0 commit comments

Comments
 (0)