Skip to content
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

implement some numbers to prepare to control charging current #55

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions const.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@
DEFAULT_MQTT_PORT = "1883"
DEFAULT_MQTT_DISCOVERY_PREFIX = "homeassistant"
DEFAULT_DEVICE_NAME = "JuiceBox"
DEFAULT_MAX_CURRENT = 48
DEFAULT_ADD_STATUS_ATTRIBUTES = False
DEFAULT_IGNORE_REMOTE = False
150 changes: 145 additions & 5 deletions juicepassproxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,14 @@
DEFAULT_MQTT_HOST,
DEFAULT_MQTT_PORT,
DEFAULT_SRC,
DEFAULT_MAX_CURRENT,
DEFAULT_ADD_STATUS_ATTRIBUTES,
DEFAULT_IGNORE_REMOTE
)
from dns import resolver
from ha_mqtt_discoverable import DeviceInfo, Settings
from ha_mqtt_discoverable.sensors import Sensor, SensorInfo
from ha_mqtt_discoverable.sensors import Sensor, SensorInfo, Number, NumberInfo
from paho.mqtt.client import Client, MQTTMessage
from juicebox_telnet import JuiceboxTelnet
from pyproxy import pyproxy

Expand All @@ -50,14 +54,31 @@
"""




def current_max_callback(client: Client, juicebox_handler, message: MQTTMessage):
juicebox_handler.update_current_number("current_max", int(message.payload.decode()) )


def current_max_charging_callback(client: Client, juicebox_handler, message: MQTTMessage):
juicebox_handler.update_current_number("current_max_charging", int(message.payload.decode()) )



class JuiceboxMessageHandler(object):
def __init__(self, device_name, mqtt_settings, juicebox_id=None):

def __init__(self, device_name, mqtt_settings, juicebox_id=None, max_current=DEFAULT_MAX_CURRENT, add_status_attributes=DEFAULT_ADD_STATUS_ATTRIBUTES):
self.mqtt_settings = mqtt_settings
self.device_name = device_name
self.juicebox_id = juicebox_id
self.max_current = max_current
self.add_status_attributes = add_status_attributes
self.entities = {
"status": None,
"current": None,
"current_rating": None,
"current_max": None,
"current_max_charging": None,
"frequency": None,
"energy_lifetime": None,
"energy_session": None,
Expand Down Expand Up @@ -85,6 +106,9 @@ def _init_devices(self):
)
self._init_device_status(device_info)
self._init_device_current(device_info)
self._init_device_current_rating(device_info)
self._init_device_current_max(device_info)
self._init_device_current_max_charging(device_info)
self._init_device_frequency(device_info)
self._init_device_energy_lifetime(device_info)
self._init_device_energy_session(device_info)
Expand Down Expand Up @@ -134,6 +158,50 @@ def _init_device_current(self, device_info):
sensor = Sensor(settings)
self.entities["current"] = sensor

def _init_device_current_rating(self, device_info):
name = "Current Rating"
sensor_info = SensorInfo(
name=name,
unique_id=f"{self.juicebox_id} {name}",
state_class="measurement",
device_class="current",
unit_of_measurement="A",
device=device_info,
)
settings = Settings(mqtt=self.mqtt_settings, entity=sensor_info)
sensor = Sensor(settings)
self.entities["current_rating"] = sensor

def _init_device_current_max(self, device_info):
name = "Max Current"
sensor_info = NumberInfo(
name=name,
unique_id=f"{self.juicebox_id} {name}",
state_class="measurement",
device_class="current",
unit_of_measurement="A",
device=device_info,
max=self.max_current,
)
settings = Settings(mqtt=self.mqtt_settings, entity=sensor_info)
sensor = Number(settings, current_max_callback, self)
self.entities["current_max"] = sensor

def _init_device_current_max_charging(self, device_info):
name = "Max Charging Current"
sensor_info = NumberInfo(
name=name,
unique_id=f"{self.juicebox_id} {name}",
state_class="measurement",
device_class="current",
unit_of_measurement="A",
device=device_info,
max=self.max_current,
)
settings = Settings(mqtt=self.mqtt_settings, entity=sensor_info)
sensor = Number(settings, current_max_charging_callback, self)
self.entities["current_max_charging"] = sensor

def _init_device_frequency(self, device_info):
name = "Frequency"
sensor_info = SensorInfo(
Expand Down Expand Up @@ -218,18 +286,39 @@ def _init_device_power(self, device_info):
sensor = Sensor(settings)
self.entities["power"] = sensor

def update_current_number(self, entity, value):
#TODO check the Current Rating
self.update_number(entity, value)

def update_number(self, entity, value):
logging.info(f"Received {entity} {value} from HA")
# todo: send command to juicebox
# dont use set_value here, in next report the value will be updated and HA will receive the update...

def basic_message_try_parse(self, data):
message = {"type": "basic"}
message["current"] = 0
message["current_max"] = 0
message["current_max_charging"] = 0
message["current_rating"] = 0
message["energy_session"] = 0
active = True
parts = re.split(r",|!|:", str(data).replace("b'", "").replace("'", ""))
parts.pop(0) # JuiceBox ID
parts.pop(-1) # Ending blank
parts.pop(-1) # Checksum

# Undefined parts: v, F, u, M, C, m, t, i, e, r, b, B, P, p
# https://github.com/snicker/juicepassproxy/issues/52
# Undefined parts: F, e, r, b, B, P, p
# s = Counter
# v = version of protocol
# i = Interval number. It contains a 96-slot interval memory (15-minute x 24-hour cycle) and
# this tells you how much energy was consumed in the rolling window as it reports one past
# (or current, if it's reporting the "right-now" interval) interval per message.
# The letter after "i" = the energy in that interval (usually 0 if you're not charging basically 24/7)
# t - probably the report time in seconds - "every 9 seconds" (or may end up being 10).
# It can change its reporting interval if the bit mask in the reply command indicates that it should send reports faster (yet to be determined).
# u - loop counter
for part in parts:
if part[0] == "S":
message["status"] = {
Expand All @@ -247,16 +336,34 @@ def basic_message_try_parse(self, data):
active = message["status"].lower() == "charging"
elif part[0] == "A" and active:
message["current"] = round(float(part.split("A")[1]) * 0.1, 2)
elif part[0] == "m":
message["current_rating"] = float(part.split("m")[1])
elif part[0] == "M":
message["current_max"] = float(part.split("M")[1])
elif part[0] == "C":
message["current_max_charging"] = float(part.split("C")[1])
elif part[0] == "f":
message["frequency"] = round(float(part.split("f")[1]) * 0.01, 2)
elif part[0] == "L":
message["energy_lifetime"] = float(part.split("L")[1])
elif part[0] == "v":
message["protocol_version"] = part.split("v")[1]
elif part[0] == "E" and active:
message["energy_session"] = float(part.split("E")[1])
elif part[0] == "t":
message["report_time"] = part.split("t")[1]
elif part[0] == "v":
message["protocol_version"] = part.split("v")[1]
elif part[0] == "i":
message["interval"] = part.split("i")[1]
elif part[0] == "u":
message["loop_counter"] = part.split("u")[1]
elif part[0] == "T":
message["temperature"] = round(float(part.split("T")[1]) * 1.8 + 32, 2)
elif part[0] == "V":
message["voltage"] = round(float(part.split("V")[1]) * 0.1, 2)
else:
message["unknown_" + part[0]] = part
message["power"] = round(
message.get("voltage", 0) * message.get("current", 0), 2
)
Expand Down Expand Up @@ -288,17 +395,29 @@ def debug_message_try_parse(self, data):
def basic_message_publish(self, message):
logging.debug(f"{message.get('type')} message: {message}")
try:
attributes = {}
for k in message:
entity = self.entities.get(k)
if entity:
entity.set_state(message.get(k))
if isinstance(entity,Number):
entity.set_value(message.get(k))
else:
entity.set_state(message.get(k))
else:
attributes[k] = message.get(k)
if self.add_status_attributes:
self.entities.get("status").set_attributes(attributes)
except Exception as e:
logging.exception(f"Failed to publish sensor data to MQTT: {e}")

def remote_data_handler(self, data):
logging.debug("remote: {}".format(data))
return data

def remote_ignore_data_handler(self, data):
logging.debug("remote: {}".format(data))
return "".encode()

def local_data_handler(self, data):
logging.debug("local: {}".format(data))
if ":DBG," in str(data):
Expand Down Expand Up @@ -483,6 +602,7 @@ def main():
help="Destination IP (and optional port) of EnelX Server. If not defined, --juicebox_host required and then will obtain it automatically. (Ex. 127.0.0.1:8047)",
)
parser.add_argument("--debug", action="store_true")
parser.add_argument("--ignore-remote", action="store_true")
parser.add_argument("-u", "--mqtt_user", type=str, help="MQTT Username")
parser.add_argument("-P", "--mqtt_password", type=str, help="MQTT Password")
parser.add_argument(
Expand Down Expand Up @@ -575,6 +695,7 @@ def main():
logging.info(f"enelx_server: {enelx_server}")
logging.info(f"enelx_port: {enelx_port}")


if args.src:
if ":" in args.src:
src = args.src
Expand Down Expand Up @@ -607,6 +728,7 @@ def main():
config.update({"DST": dst.split(":")[0]})
logging.info(f"dst: {dst}")


if juicebox_id := args.juicebox_id:
pass
elif juicebox_id := get_juicebox_id(args.juicebox_host):
Expand All @@ -620,6 +742,16 @@ def main():
logging.error(
"Cannot get JuiceBox ID from Telnet and not in Config. If a JuiceBox ID is later set or is obtained via Telnet, it will likely create a new JuiceBox Device with new Entities in Home Assistant."
)

max_current = config.get("MAX_CURRENT", DEFAULT_MAX_CURRENT)
logging.info(f"max_current: {max_current}")

add_status_attributes = str(config.get("ADD_STATUS_ATTRIBUTES", DEFAULT_ADD_STATUS_ATTRIBUTES)).lower() == "true"
logging.info(f"add_status_attributes: {add_status_attributes}")

ignore_remote = args.ignore_remote or str(config.get("IGNORE_REMOTE", DEFAULT_IGNORE_REMOTE)).lower() == "true"
logging.info(f"ignore_remote: {ignore_remote}")

write_config(config, config_loc)

mqttsettings = Settings.MQTT(
Expand All @@ -633,12 +765,20 @@ def main():
mqtt_settings=mqttsettings,
device_name=args.device_name,
juicebox_id=juicebox_id,
max_current=max_current,
add_status_attributes=add_status_attributes,
)
handler.basic_message_publish(
{"type": "debug", "debug_message": "INFO: Starting JuicePass Proxy"}
)


pyproxy.LOCAL_DATA_HANDLER = handler.local_data_handler
pyproxy.REMOTE_DATA_HANDLER = handler.remote_data_handler
if ignore_remote:
logging.info("Ignoring remote messages")
pyproxy.REMOTE_DATA_HANDLER = handler.remote_ignore_data_handler
else:
pyproxy.REMOTE_DATA_HANDLER = handler.remote_data_handler

udpc_updater_thread = None
udpc_updater = None
Expand Down