-
Notifications
You must be signed in to change notification settings - Fork 60
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add switchbot status publisher #505
Changes from 6 commits
d7b924a
07a0164
58f77f1
78c3633
9a0e042
c567159
261c360
f754384
d0465ce
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
Header header # timestamp | ||
|
||
float64 battery # the current battery level, 0-100 | ||
|
||
string power # ON/OFF state | ||
string device_mode # pressMode, switchMode, or customizeMode | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you add candidates constants of this field to this message like https://docs.ros.org/en/noetic/api/visualization_msgs/html/msg/Marker.html ?
or something like that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added 3 candedate constants as
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
Header header # timestamp | ||
|
||
float64 temperature # temperature in celsius | ||
float64 humidity # humidity percentage | ||
|
||
int64 light_level # the level of illuminance of the ambience light, 1~20 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
Header header # timestamp | ||
|
||
float64 temperature # temperature in celsius | ||
float64 humidity # humidity percentage | ||
float64 battery # the current battery level, 0-100 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
Header header # timestamp | ||
|
||
float64 voltage # the voltage of the device, measured in Volt | ||
float64 weight # the power consumed in a day, measured in Watts | ||
float64 current # the current of the device at the moment, measured in Amp | ||
|
||
int32 minutes_day # he duration that the device has been used during a day, measured in minutes |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
Header header # timestamp | ||
|
||
string power # ON/OFF state | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I changed the type to bool. |
||
string color # the color value, RGB "255:255:255" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about split this field into There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I split rgb_string = status['color']
r, g, b = map(int, rgb_string.split(':'))
msg.color_r = r
msg.color_g = g
msg.color_b = b |
||
|
||
int64 brightness # the brightness value, range from 1 to 100 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
#!/usr/bin/env python | ||
|
||
import os.path | ||
from requests import ConnectionError | ||
import rospy | ||
from switchbot_ros.switchbot import SwitchBotAPIClient | ||
from switchbot_ros.switchbot import DeviceError, SwitchBotAPIError | ||
from switchbot_ros.msg import Meter, PlugMini, Hub2, Bot, StripLight | ||
|
||
|
||
class SwitchBotStatusPublisher: | ||
""" | ||
Publissh your switchbot status with ROS and SwitchBot API | ||
""" | ||
def __init__(self): | ||
# SwitchBot configs | ||
# '~token' can be file path or raw characters | ||
token = rospy.get_param('~token') | ||
if os.path.exists(token): | ||
with open(token) as f: | ||
self.token = f.read().replace('\n', '') | ||
else: | ||
self.token = token | ||
|
||
# Switchbot API v1.1 needs secret key | ||
secret = rospy.get_param('~secret', None) | ||
if secret is not None and os.path.exists(secret): | ||
with open(secret, 'r', encoding='utf-8') as f: | ||
self.secret = f.read().replace('\n', '') | ||
else: | ||
self.secret = secret | ||
|
||
# Initialize switchbot client | ||
self.bots = self.get_switchbot_client() | ||
self.print_apiversion() | ||
|
||
# Get parameters for publishing | ||
self.rate = rospy.get_param('~rate', 0.1) | ||
rospy.loginfo('Rate: ' + str(self.rate)) | ||
|
||
device_name = rospy.get_param('~device_name') | ||
if device_name: | ||
self.device_name = device_name | ||
else: | ||
rospy.logerr('No Device Name') | ||
return | ||
|
||
self.device_type = None | ||
self.device_list = sorted( | ||
self.bots.device_list, | ||
key=lambda device: str(device.get('deviceName'))) | ||
for device in self.device_list: | ||
device_name = str(device.get('deviceName')) | ||
if self.device_name == device_name: | ||
self.device_type = str(device.get('deviceType')) | ||
|
||
if self.device_type: | ||
rospy.loginfo('deviceName: ' + self.device_name + ' / deviceType: ' + self.device_type) | ||
else: | ||
rospy.logerr('Invalid Device Name: ' + self.device_name) | ||
return | ||
|
||
topic_name = '~' + self.device_name | ||
topic_name = topic_name.replace('-', '_') | ||
|
||
# Publisher Message Class for each device type | ||
if self.device_type == 'Remote': | ||
rospy.logerr('Device Type: "' + self.device_type + '" has no status in specifications.') | ||
return | ||
else: | ||
if self.device_type == 'Meter': | ||
self.msg_class = Meter | ||
elif self.device_type == 'MeterPlus': | ||
self.msg_class = Meter | ||
elif self.device_type == 'WoIOSensor': | ||
self.msg_class = Meter | ||
elif self.device_type == 'Hub 2': | ||
self.msg_class = Hub2 | ||
elif self.device_type == 'Plug Mini (JP)': | ||
self.msg_class = PlugMini | ||
elif self.device_type == 'Plug Mini (US)': | ||
self.msg_class = PlugMini | ||
elif self.device_type == 'Bot': | ||
self.msg_class = Bot | ||
elif self.device_type == 'Strip Light': | ||
self.msg_class = StripLight | ||
else: | ||
rospy.logerr('No publisher process for "' + self.device_type + '" in switchbot_status_publisher.py') | ||
return | ||
|
||
self.status_pub = rospy.Publisher(topic_name, self.msg_class, queue_size=1, latch=True) | ||
|
||
rospy.loginfo('Ready: SwitchBot Status Publisher for ' + self.device_name) | ||
|
||
|
||
def get_switchbot_client(self): | ||
try: | ||
client = SwitchBotAPIClient(token=self.token, secret=self.secret) | ||
rospy.loginfo('Switchbot API Client initialized.') | ||
return client | ||
except ConnectionError: # If the machine is not connected to the internet | ||
rospy.logwarn_once('Failed to connect to the switchbot server. The client would try connecting to it when subscribes the ActionGoal topic.') | ||
return None | ||
|
||
|
||
def spin(self): | ||
rate = rospy.Rate(self.rate) | ||
while not rospy.is_shutdown(): | ||
rate.sleep() | ||
if self.bots is None: | ||
self.bots = self.get_switchbot_client() | ||
|
||
if self.device_type == 'Remote': | ||
return | ||
else: | ||
status = self.get_device_status(device_name=self.device_name) | ||
|
||
if status: | ||
time = rospy.get_rostime() | ||
if self.msg_class == Meter: | ||
msg = Meter() | ||
msg.header.stamp = time | ||
msg.temperature = status['temperature'] | ||
msg.humidity = status['humidity'] | ||
msg.battery = status['battery'] | ||
elif self.msg_class == Hub2: | ||
msg = Hub2() | ||
msg.header.stamp = time | ||
msg.temperature = status['temperature'] | ||
msg.humidity = status['humidity'] | ||
msg.light_level = status['lightLevel'] | ||
elif self.msg_class == PlugMini: | ||
msg = PlugMini() | ||
msg.header.stamp = time | ||
msg.voltage = status['voltage'] | ||
msg.weight = status['weight'] | ||
msg.current = status['electricCurrent'] | ||
msg.minutes_day = status['electricityOfDay'] | ||
elif self.msg_class == Bot: | ||
msg = Bot() | ||
msg.header.stamp = time | ||
msg.battery = status['battery'] | ||
msg.power = status['power'] | ||
msg.device_mode = status['deviceMode'] | ||
elif self.msg_class == StripLight: | ||
msg = StripLight() | ||
msg.header.stamp = time | ||
msg.power = status['power'] | ||
msg.color = status['color'] | ||
msg.brightness = status['brightness'] | ||
else: | ||
return | ||
|
||
if msg: | ||
self.status_pub.publish(msg) | ||
|
||
|
||
def get_device_status(self, device_name=None): | ||
if self.bots is None: | ||
return | ||
elif device_name: | ||
status = self.bots.device_status(device_name=device_name) | ||
return status | ||
else: | ||
return | ||
|
||
|
||
def print_apiversion(self): | ||
if self.bots is None: | ||
return | ||
|
||
apiversion_str = 'Using SwitchBot API '; | ||
apiversion_str += self.bots.api_version; | ||
rospy.loginfo(apiversion_str) | ||
|
||
|
||
if __name__ == '__main__': | ||
try: | ||
rospy.init_node('switchbot_status_publisher') | ||
ssp = SwitchBotStatusPublisher() | ||
ssp.spin() | ||
except rospy.ROSInterruptException: | ||
pass |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,8 @@ def __init__(self, | |
actionname, | ||
SwitchBotCommandAction | ||
) | ||
rospy.loginfo("Waiting for action server to start.") | ||
self.action_client.wait_for_server() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Basically it is nice to check if action server is active. So could you add There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I changed timeout in def __init__(self,
actionname='switchbot_ros/switch',
topicname='switchbot_ros/devices',
timeout=5): rospy.loginfo("Waiting for action server to start. (timeout: " + str(timeout) + "[sec])")
self.action_client.wait_for_server(timeout=rospy.Duration(timeout,0)) |
||
|
||
def get_devices(self, timeout=None): | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about
bool
instead ofstring
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed the type to bool.