Trying to port KasaSmartPowerStrip to CircuitPython using a Adafruit ESP32S3 Feather
I am trying to port the KSPS module to CircuitPython on a ESP32S3.
I have to switch the socket module to socketport, that seems to be a simple substitution but it stops there.
I am just cutting and pasting, very new to Python.
If this is a lot of work I might just stick to running the code on my Windows machine but would rather not.
Here is the Thonny Editor output, plus the changes I've made in the code:
%Run -c $EDITOR_CONTENT from KasaSmartPowerStrip import SmartPowerStrip power_strip = SmartPowerStrip('192.168.0.1') Traceback (most recent call last): File "
", line 1, in File "KasaSmartPowerStrip.py", line 16, in init File "KasaSmartPowerStrip.py", line 42, in get_system_info File "KasaSmartPowerStrip.py", line 202, in _udp_send_command AttributeError: 'module' object has no attribute 'socket'
import socketpool import json import struct from builtins import bytes
class SmartPowerStrip(object):
def __init__(self, ip, device_id=None, timeout=2.0, protocol='tcp'):
self.ip = ip
self.port = 9999
self.protocol = protocol
self.device_id = device_id
self.sys_info = None
self.timeout = timeout
self.sys_info = self.get_system_info()['system']['get_sysinfo']
if not self.device_id:
self.device_id = self.sys_info['deviceId']
def set_wifi_credentials(self, ssid, psk, key_type='3'):
'''
:param ssid: router ssid
:param psk: router passkey
:param key_type: 3 is WPA2, 2 might be WPA and 1 might be WEP?
:return: command response
'''
wifi_command = '{"netif":{"set_stainfo":{"ssid":"' + ssid + '","password":"' + \
psk + '","key_type":' + key_type + '}}}'
return self.send_command(wifi_command, self.protocol)
def set_cloud_server_url(self, server_url=''):
server_command = '{"cnCloud":{"set_server_url":{"server":"' + server_url + '"}}}'
return self.send_command(server_command, self.protocol)
def get_system_info(self):
return self._udp_send_command('{"system":{"get_sysinfo":{}}}')
def get_realtime_energy_info(self, plug_num=None, plug_name=None):
plug_id = self._get_plug_id(plug_num=plug_num, plug_name=plug_name)
energy_command = '{"context":{"child_ids":["' + plug_id + '"]},"emeter":{"get_realtime":{}}}'
response = self.send_command(energy_command, self.protocol)
realtime_energy_data = response['emeter']['get_realtime']
return realtime_energy_data
def get_historical_energy_info(self, month, year, plug_num=None, plug_name=None):
plug_id = self._get_plug_id(plug_num=plug_num, plug_name=plug_name)
energy_command = '{"context":{"child_ids":["' + plug_id + '"]},' + \
'"emeter":{"get_daystat":{"month": ' + month + ',"year":' + year + '}}}'
response = self.send_command(energy_command, self.protocol)
historical_energy_data = response['emeter']['get_daystat']['day_list']
return historical_energy_data
def toggle_relay_leds(self, state):
state_int = self._get_plug_state_int(state, reverse=True)
led_command = '{"system":{"set_led_off":{"off":' + str(state_int) + '}}}'
return self.send_command(led_command, self.protocol)
def set_plug_name(self, plug_num, plug_name):
plug_id = self._get_plug_id(plug_num=plug_num)
set_name_command = '{"context":{"child_ids":["' + plug_id + \
'"]},"system":{"set_dev_alias":{"alias":"' + plug_name + '"}}}'
return self.send_command(set_name_command, self.protocol)
def get_plug_info(self, plug_num):
target_plug = [plug for plug in self.sys_info['children'] if plug['id'] == str(int(plug_num)-1).zfill(2)]
return target_plug
# toggle multiple plugs by id or name
def toggle_plugs(self, state, plug_num_list=None, plug_name_list=None):
state_int = self._get_plug_state_int(state)
plug_id_list_str = self._get_plug_id_list_str(plug_num_list=plug_num_list, plug_name_list=plug_name_list)
all_relay_command = '{"context":{"child_ids":' + plug_id_list_str + '},' + \
'"system":{"set_relay_state":{"state":' + str(state_int) + '}}}'
return self.send_command(all_relay_command, self.protocol)
# toggle a single plug
def toggle_plug(self, state, plug_num=None, plug_name=None):
state_int = self._get_plug_state_int(state)
plug_id = self._get_plug_id(plug_num=plug_num, plug_name=plug_name)
relay_command = '{"context":{"child_ids":["' + plug_id + '"]},' + \
'"system":{"set_relay_state":{"state":' + str(state_int) + '}}}'
return self.send_command(relay_command, self.protocol)
def reboot(self, delay=1):
reboot_command = '{"system":{"reboot":{"delay":' + str(delay) + '}}}'
return self.send_command(reboot_command, self.protocol)
# manually send a command
def send_command(self, command, protocol='tcp'):
if protocol == 'tcp':
return self._tcp_send_command(command)
elif protocol == 'udp':
return self._udp_send_command(command)
else:
raise ValueError("Protocol must be 'tcp' or 'udp'")
def _get_plug_state_int(self, state, reverse=False):
if state.lower() == 'on':
if reverse:
state_int = 0
else:
state_int = 1
elif state.lower() == 'off':
if reverse:
state_int = 1
else:
state_int = 0
else:
raise ValueError("Invalid state, must be 'on' or 'off'")
return state_int
# create a string with a list of plug_ids that can be inserted directly into a command
def _get_plug_id_list_str(self, plug_num_list=None, plug_name_list=None):
plug_id_list = []
if plug_num_list:
for plug_num in plug_num_list:
# add as str to remove the leading u
plug_id_list.append(str(self._get_plug_id(plug_num=plug_num)))
elif plug_name_list:
for plug_name in plug_name_list:
# add as str to remove the leading u
plug_id_list.append(str(self._get_plug_id(plug_name=plug_name)))
# convert to double quotes and turn the whole list into a string
plug_id_list_str = str(plug_id_list).replace("'", '"')
return plug_id_list_str
# get the plug child_id to be used with commands
def _get_plug_id(self, plug_num=None, plug_name=None):
if plug_num and self.device_id:
plug_id = self.device_id + str(plug_num-1).zfill(2)
elif plug_name and self.sys_info:
target_plug = [plug for plug in self.sys_info['children'] if plug['alias'] == plug_name]
if target_plug:
plug_id = self.device_id + target_plug[0]['id']
else:
raise ValueError('Unable to find plug with name ' + plug_name)
else:
raise ValueError('Unable to find plug. Provide a valid plug_num or plug_name')
return plug_id
def _tcp_send_command(self, command):
sock_tcp = socketpool.socket(socketpool.AF_INET, socketpool.SOCK_STREAM)
sock_tcp.settimeout(self.timeout)
sock_tcp.connect((self.ip, self.port))
sock_tcp.send(self._encrypt_command(command))
data = sock_tcp.recv(2048)
sock_tcp.close()
# the first 4 chars are the length of the command so can be excluded
return json.loads(self._decrypt_command(data[4:]))
def _udp_send_command(self, command):
client_socket = socketpool.socket(socket.AF_INET, socket.SOCK_DGRAM)
client_socket.settimeout(self.timeout)
addr = (self.ip, self.port)
client_socket.sendto(self._encrypt_command(command, prepend_length=False), addr)
data, server = client_socket.recvfrom(2048)
return json.loads(self._decrypt_command(data))
@staticmethod
def _encrypt_command(string, prepend_length=True):
key = 171
result = b''
# when sending get_sysinfo using udp the length of the command is not needed but
# with all other commands using tcp it is
if prepend_length:
result = struct.pack(">I", len(string))
for i in bytes(string.encode('latin-1')):
a = key ^ i
key = a
result += bytes([a])
return result
@staticmethod
def _decrypt_command(string):
key = 171
result = b''
for i in bytes(string):
a = key ^ i
key = i
result += bytes([a])
return result.decode('latin-1')
I am not able to test this myself but try starting over and just replace import socket with from socketpool import SocketPool as socket and that might work without any other changes.
Hello:
Thank you for your reply, this is what I got:
I hope I put in the right command as you described:
from socketpool import SocketPool.socket as socket import json import struct from builtins import bytes
class SmartPowerStrip(object):
def __init__(self, ip, device_id=None, timeout=2.0, protocol='tcp'):
self.ip = ip
self.port = 9999
self.protocol = protocol
self.device_id = device_id
self.sys_info = None
self.timeout = timeout
self.sys_info = self.get_system_info()['system']['get_sysinfo']
if not self.device_id:
self.device_id = self.sys_info['deviceId']
etc.
But there's an immediate error:
%Run -c $EDITOR_CONTENT
Traceback (most recent call last):
File "
Thank you again for your help
Regards, Jordan Glogau
On Tue, Jan 31, 2023 at 10:32 AM p-doyle @.***> wrote:
I am not able to test this myself but try starting over and just replace import socket with from socketpool import SocketPool.socket as socket and that might work without any other changes.
— Reply to this email directly, view it on GitHub https://github.com/p-doyle/Python-KasaSmartPowerStrip/issues/10#issuecomment-1410593539, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABCMFG5RK2Y4PKLD43AP4SLWVEWAVANCNFSM6AAAAAAULZY3YU . You are receiving this because you authored the thread.Message ID: @.***>
I just found this:
https://learn.adafruit.com/arduino-to-circuitpython/modules-and-importing
CircuitPython does not have the capability to import large modules available for full Python/CPython as the memory on microcontrollers is very limited.
Adafruit has committed to making a large number of modules available to support a broad range of hardware. This includes sensors, displays, smart LEDs (NeoPixel, etc.) and much more. Just as Adafruit is a leader in providing Open Source Arduino libraries, Adafruit is striving to do the same in the Python world.
There are more features to Python Modules but the above covers the basics. See a Python reference such as this one https://www.w3schools.com/python/default.asp to learn more.
Refer to the Adafruit GitHub repository https://github.com/adafruit for the latest CircuitPython modules available.
On Tue, Jan 31, 2023 at 1:27 PM Jordan Glogau @.***> wrote:
Hello:
Thank you for your reply, this is what I got:
I hope I put in the right command as you described:
from socketpool import SocketPool.socket as socket import json import struct from builtins import bytes
class SmartPowerStrip(object):
def __init__(self, ip, device_id=None, timeout=2.0, protocol='tcp'): self.ip = ip self.port = 9999 self.protocol = protocol self.device_id = device_id self.sys_info = None self.timeout = timeout self.sys_info = self.get_system_info()['system']['get_sysinfo'] if not self.device_id: self.device_id = self.sys_info['deviceId']etc.
But there's an immediate error:
%Run -c $EDITOR_CONTENT
Traceback (most recent call last): File "
", line 1 SyntaxError: invalid syntax Thank you again for your help
Regards, Jordan Glogau
On Tue, Jan 31, 2023 at 10:32 AM p-doyle @.***> wrote:
I am not able to test this myself but try starting over and just replace import socket with from socketpool import SocketPool.socket as socket and that might work without any other changes.
— Reply to this email directly, view it on GitHub https://github.com/p-doyle/Python-KasaSmartPowerStrip/issues/10#issuecomment-1410593539, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABCMFG5RK2Y4PKLD43AP4SLWVEWAVANCNFSM6AAAAAAULZY3YU . You are receiving this because you authored the thread.Message ID: @.***>
I edited the import statement shortly after I posted, can you try using the updated version? from socketpool import SocketPool as socket
p-doyle:
Changed the first line in the code to:
from socketpool import SocketPool as socket
Got the same error
]0;🐍Wi-Fi: off | REPL | 8.0.0-beta.6 \
Adafruit CircuitPython 8.0.0-beta.6 on 2022-12-21; Adafruit Feather ESP32-S3 TFT with ESP32S3
from KasaSmartPowerStrip import SmartPowerStrip power_strip = SmartPowerStrip('192.168.0.1') Traceback (most recent call last): File "
", line 1, in File "KasaSmartPowerStrip.py", line 16, in init File "KasaSmartPowerStrip.py", line 42, in get_system_info File "KasaSmartPowerStrip.py", line 202, in _udp_send_command AttributeError: 'module' object has no attribute 'socket'
BTY, just as an experiment I completely removed the import socket statement and got the same error.
Regards, Jordan Glogau
On Tue, Jan 31, 2023 at 4:45 PM p-doyle @.***> wrote:
I edited the import statement shortly after I posted, can you try using the updated version? from socketpool import SocketPool as socket
— Reply to this email directly, view it on GitHub https://github.com/p-doyle/Python-KasaSmartPowerStrip/issues/10#issuecomment-1411110639, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABCMFG7J2J6GLTRQGKFHTIDWVGBWTANCNFSM6AAAAAAULZY3YU . You are receiving this because you authored the thread.Message ID: @.***>
Would you like me to send you the same Adafruit Feather ESP32-S3 TFT with ESP32S3 to play with,
Free of charge? These little guys are fun.
On Tue, Jan 31, 2023 at 9:10 PM Jordan Glogau @.***> wrote:
p-doyle:
Changed the first line in the code to:
from socketpool import SocketPool as socket
Got the same error
]0;🐍Wi-Fi: off | REPL | 8.0.0-beta.6 \
Adafruit CircuitPython 8.0.0-beta.6 on 2022-12-21; Adafruit Feather ESP32-S3 TFT with ESP32S3
from KasaSmartPowerStrip import SmartPowerStrip power_strip = SmartPowerStrip('192.168.0.1') Traceback (most recent call last): File "
", line 1, in File "KasaSmartPowerStrip.py", line 16, in init File "KasaSmartPowerStrip.py", line 42, in get_system_info File "KasaSmartPowerStrip.py", line 202, in _udp_send_command AttributeError: 'module' object has no attribute 'socket' BTY, just as an experiment I completely removed the import socket statement and got the same error.
Regards, Jordan Glogau
On Tue, Jan 31, 2023 at 4:45 PM p-doyle @.***> wrote:
I edited the import statement shortly after I posted, can you try using the updated version? from socketpool import SocketPool as socket
— Reply to this email directly, view it on GitHub https://github.com/p-doyle/Python-KasaSmartPowerStrip/issues/10#issuecomment-1411110639, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABCMFG7J2J6GLTRQGKFHTIDWVGBWTANCNFSM6AAAAAAULZY3YU . You are receiving this because you authored the thread.Message ID: @.***>
P-Doyle:
Could I pay a small fee to get the port to Circuit Python done on the ESP32S3?
Would Paypal work for you?
Regards, Jordan Gloagu
On Tue, Jan 31, 2023 at 9:13 PM Jordan Glogau @.***> wrote:
Would you like me to send you the same Adafruit Feather ESP32-S3 TFT with ESP32S3 to play with,
Free of charge? These little guys are fun.
On Tue, Jan 31, 2023 at 9:10 PM Jordan Glogau @.***> wrote:
p-doyle:
Changed the first line in the code to:
from socketpool import SocketPool as socket
Got the same error
]0;🐍Wi-Fi: off | REPL | 8.0.0-beta.6 \
Adafruit CircuitPython 8.0.0-beta.6 on 2022-12-21; Adafruit Feather ESP32-S3 TFT with ESP32S3
from KasaSmartPowerStrip import SmartPowerStrip power_strip = SmartPowerStrip('192.168.0.1') Traceback (most recent call last): File "
", line 1, in File "KasaSmartPowerStrip.py", line 16, in init File "KasaSmartPowerStrip.py", line 42, in get_system_info File "KasaSmartPowerStrip.py", line 202, in _udp_send_command AttributeError: 'module' object has no attribute 'socket' BTY, just as an experiment I completely removed the import socket statement and got the same error.
Regards, Jordan Glogau
On Tue, Jan 31, 2023 at 4:45 PM p-doyle @.***> wrote:
I edited the import statement shortly after I posted, can you try using the updated version? from socketpool import SocketPool as socket
— Reply to this email directly, view it on GitHub https://github.com/p-doyle/Python-KasaSmartPowerStrip/issues/10#issuecomment-1411110639, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABCMFG7J2J6GLTRQGKFHTIDWVGBWTANCNFSM6AAAAAAULZY3YU . You are receiving this because you authored the thread.Message ID: @.***>
No need for payment. I have one of the boards and will take a look this weekend.
p-doyle:
Thank you very much, really appreciated.
Regards, Jordan Glogau
I created a new branch with the changes, please see https://github.com/p-doyle/Python-KasaSmartPowerStrip/blob/CircuitPython/KasaSmartPowerStrip.py and let me know if that works.
Hi:
I still think a small fee might help things move forward ;-)
If you don't have the bandwidth I may change to a 4 Channel Relay Board with or without WiFI/MQTT.
Regards, Jordan Glogau
On Thu, Feb 2, 2023 at 3:04 PM p-doyle @.***> wrote:
No need for payment. I have one of the boards and will take a look this weekend.
— Reply to this email directly, view it on GitHub https://github.com/p-doyle/Python-KasaSmartPowerStrip/issues/10#issuecomment-1414301325, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABCMFG3DU5KSR5NHXVGK2JTWVQHMRANCNFSM6AAAAAAULZY3YU . You are receiving this because you authored the thread.Message ID: @.***>
Did you see my last reply? I've uploaded to changes to https://github.com/p-doyle/Python-KasaSmartPowerStrip/blob/CircuitPython/KasaSmartPowerStrip.py
P-Doyle:
I missed that, thank you, I'll try it out after I get a new Feather. The last one seems to have bricked.
Regards, Jordan Glogau
I tried the regular Python code and got this:
from KasaSmartPowerStrip import SmartPowerStrip power_strip = SmartPowerStrip('192.168.0.1') Traceback (most recent call last): File "
", line 1, in File "C:\Users\jglog\Documents\Arduino\python\Python-KasaSmartPowerStrip-master\Python-KasaSmartPowerStrip-master\KasaSmartPowerStrip.py", line 16, in init self.sys_info = self.get_system_info()['system']['get_sysinfo'] File "C:\Users\jglog\Documents\Arduino\python\Python-KasaSmartPowerStrip-master\Python-KasaSmartPowerStrip-master\KasaSmartPowerStrip.py", line 42, in get_system_info return self._udp_send_command('{"system":{"get_sysinfo":{}}}') File "C:\Users\jglog\Documents\Arduino\python\Python-KasaSmartPowerStrip-master\Python-KasaSmartPowerStrip-master\KasaSmartPowerStrip.py", line 209, in _udp_send_command data, server = client_socket.recvfrom(2048) TimeoutError: timed out
I would imagine I am doing something wrong but can't tell. I tried the old version and get the same error, so I am not at all sure what I configured differently
You are running that script on the feather, correct? Are you connecting to the power strip's wifi network first?
This is the error back, doesn't like the syntax
Traceback (most recent call last): File "C:\Users\jglog\Documents\Arduino\python\Python-KasaSmartPowerStrip-master-old\KasaSmartPowerStrip.py", line 1 from socketpool import SocketPool.socket as socket ^ SyntaxError: invalid syntax
On Wed, Feb 15, 2023 at 8:47 AM p-doyle @.***> wrote:
Did you see my last reply? I've uploaded to changes to https://github.com/p-doyle/Python-KasaSmartPowerStrip/blob/CircuitPython/KasaSmartPowerStrip.py
— Reply to this email directly, view it on GitHub https://github.com/p-doyle/Python-KasaSmartPowerStrip/issues/10#issuecomment-1431397115, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABCMFG755SNM57W4IVT3X5TWXTM77ANCNFSM6AAAAAAULZY3YU . You are receiving this because you authored the thread.Message ID: @.***>
I am running the revised regular script on Windows 10.
As I said in my previous email, my Feather seems to have bricked. Waiting for an answer from Adafruit in that regard.
So I was just experimenting with the regular revised version and that is where the problem came from.
Plus the SyntaxError itself seems wrong, but what do I know?
error is from.
On Wed, Feb 15, 2023 at 4:06 PM Jordan Glogau @.***> wrote:
This is the error back, doesn't like the syntax
Traceback (most recent call last): File "C:\Users\jglog\Documents\Arduino\python\Python-KasaSmartPowerStrip-master-old\KasaSmartPowerStrip.py", line 1 from socketpool import SocketPool.socket as socket ^ SyntaxError: invalid syntax
On Wed, Feb 15, 2023 at 8:47 AM p-doyle @.***> wrote:
Did you see my last reply? I've uploaded to changes to https://github.com/p-doyle/Python-KasaSmartPowerStrip/blob/CircuitPython/KasaSmartPowerStrip.py
— Reply to this email directly, view it on GitHub https://github.com/p-doyle/Python-KasaSmartPowerStrip/issues/10#issuecomment-1431397115, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABCMFG755SNM57W4IVT3X5TWXTM77ANCNFSM6AAAAAAULZY3YU . You are receiving this because you authored the thread.Message ID: @.***>
If you are running this on Windows please make sure to use the original script at https://github.com/p-doyle/Python-KasaSmartPowerStrip/blob/master/KasaSmartPowerStrip.py
Sorry for the late reply.
I got the regular script for Python3 on windows working without a problem. Connected to the wrong wifi without realizing it.
Here the latest error from the CircuitPython version:
%Run -c $EDITOR_CONTENT
- from* KasaSmartPowerStrip import SmartPowerStrip power_strip = SmartPowerStrip('192.168.0.1')
Traceback (most recent call last):
File "
File "KasaSmartPowerStrip.py", line 16, in init File "KasaSmartPowerStrip.py", line 42, in get_system_info File "KasaSmartPowerStrip.py", line 202, in _udp_send_command AttributeError: 'module' object has no attribute 'socket'
On Fri, Feb 17, 2023 at 10:44 AM p-doyle @.***> wrote:
If you are running this on Windows please make sure to use the original script at https://github.com/p-doyle/Python-KasaSmartPowerStrip/blob/master/KasaSmartPowerStrip.py
— Reply to this email directly, view it on GitHub https://github.com/p-doyle/Python-KasaSmartPowerStrip/issues/10#issuecomment-1434826279, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABCMFG2R4FAQNHTZYNQLQY3WX6MFHANCNFSM6AAAAAAULZY3YU . You are receiving this because you authored the thread.Message ID: @.***>