Skip to content

Commit

Permalink
Added very first version
Browse files Browse the repository at this point in the history
  • Loading branch information
scadaguru committed May 25, 2020
1 parent 8de4141 commit 89c07ae
Show file tree
Hide file tree
Showing 10 changed files with 598 additions and 0 deletions.
114 changes: 114 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# pyenv
.python-version

# celery beat schedule file
celerybeat-schedule

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/

# Ignore for PyCharm
.idea

# Ignore for erxsyslog
logs/

# Ignore config.yaml
config.yaml
settings.json
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# pysmtreader

API for https://www.smartmetertexas.com

# Notes:
1. Make sure to rename config-example.yaml to config.yaml and change "_REPLACE_" text to your values
2. Big thanks to https://github.com/keatontaylor/smartmetertexas-api for providing API documentation
21 changes: 21 additions & 0 deletions config-example.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
logs:
level: debug # debug, info(default), warning, error, critical
log_file_name: pysmt # without extension, log extension will be added automatically

health_check:
log_info_line_at: 30 # in minutes, 0: disable

smartmetertexas: # smartmetertexas.com
base_url: https://smartmetertexas.com/api
username: _REPLACE_ # your user name for smartmetertexas.com
password: _REPLACE_ # your password for smartmetertexas.com
esiid: _REPLACE_ # your ESSID for smartmetertexas.com
meter_number: _REPLACE_ # your Meter Number for smartmetertexas.com
poll_interval_minutes: 60 # 0: disable, do set below 30 as smartmetertexas.com will not allow readming more than twice in an hour
wait_interval_before_ondemand_read_minutes: 5
force_first_read: False # if true it will attempt to read Smart Meter Texas, otherwise at poll_interval

home_assistant: # Home Assistant access details
base_url: _REPLACE_ # your Home Assistant URL/IP, no slash (/) at the end for example: http://192.168.1.149:8123
access_token: _REPLACE_ # your Home Assistant access token
ha_entity: sensor.smt_reading # home assistnat entity name to be created
12 changes: 12 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
version: "3"
services:
pysmtreader:
build:
context: .
dockerfile: ./dockerfile
image: pysmtreader:0.1
container_name: pysmtreader
volumes:
- ./:/config
- /etc/localtime:/etc/localtime:ro
restart: unless-stopped
8 changes: 8 additions & 0 deletions dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
FROM python:3.8

RUN pip3 install requests PyYAML

COPY ./*.py ./*.yaml /bin/
WORKDIR /bin

CMD ["python", "-u", "./main.py"]
92 changes: 92 additions & 0 deletions helper_common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import datetime
import os
import traceback
import yaml


class CommonHelper:
log_level_debug = 1
log_level_info = 2
log_level_warning = 3
log_level_error = 4
log_level_critical = 5

def __init__(self, config_folder):
self.config_folder = config_folder
self.config = yaml.load(open(self.config_folder + 'config.yaml'))

self.log_level = 2
if self.config["logs"]["level"] == "debug":
self.log_level = 1
elif self.config["logs"]["level"] == "info":
self.log_level = 2
elif self.config["logs"]["level"] == "warning":
self.log_level = 3
elif self.config["logs"]["level"] == "error":
self.log_level = 4
elif self.config["logs"]["level"] == "critical":
self.log_level = 5

self.log_folder = self.config_folder + "logs/"
self.log_file_name = self.log_folder + \
self.config["logs"]["log_file_name"]

if not os.path.exists(self.log_folder):
os.makedirs(self.config_folder + "logs")
self.log_info(
"CommonHelper:__init__(): Creating log folder: " + self.log_folder)

def get_seconds_till_next_minute(self):
cur_time = datetime.datetime.now()
next_minute = cur_time + \
datetime.timedelta(seconds=59 - cur_time.second,
microseconds=999999 - cur_time.microsecond)
diff = next_minute - cur_time
diff_ms = (diff.seconds * 1000000 + diff.microseconds) / 1000000.0
return diff_ms

def log_debug(self, str_print):
self._log(self.log_level_debug, str_print)

def log_info(self, str_print):
self._log(self.log_level_info, str_print)

def log_warning(self, str_print):
self._log(self.log_level_warning, str_print)

def log_error(self, str_print):
self._log(self.log_level_error, str_print)

def log_critical(self, str_print):
self._log(self.log_level_critical, str_print)

def log_data(self, str_print):
self._log(self.log_level_critical, str_print)

def _log(self, log_level, str_print):
if log_level >= self.log_level:
try:
log_file_name = self.log_file_name + "-" + \
datetime.datetime.now().strftime('%Y-%m-%d') + ".log"
log_str = datetime.datetime.now().strftime('%H:%M:%S.%f')[
:-3] + self._get_log_level_to_string(log_level) + str_print
print(log_str)
with open(log_file_name, "a") as log_file:
log_file.write(log_str + "\n")
except Exception as e:
print(datetime.datetime.now().strftime('%H:%M:%S.%f')[
:-3] + " : CommonHelper:log():Exception: " + str(e))

def _get_log_level_to_string(self, log_level):
log_level_str = ": "
if log_level == self.log_level_debug:
log_level_str = ":debug: "
elif log_level == self.log_level_info:
log_level_str = ":info: "
elif log_level == self.log_level_warning:
log_level_str = ":warn: "
elif log_level == self.log_level_error:
log_level_str = ":error: "
elif log_level == self.log_level_critical:
log_level_str = ":critical: "
return log_level_str
78 changes: 78 additions & 0 deletions helper_ha.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import json
from requests import post, get
import yaml
import traceback


class CustomHAHelper:
def __init__(self, config_folder):
try:
self.config = yaml.load(open(config_folder + 'config.yaml'))
self.base_url = self.config["home_assistant"]["base_url"]
self.access_token = self.config["home_assistant"]["access_token"]
except Exception as e:
exception_info = "\nException: {}\n Call Stack: {}".format(
str(e), str(traceback.format_exc()))
print("CustomHAHelper:__init__():Exception: " + exception_info)

def ha_get_entity_state(self, entity_name):
state = None
response = self.ha_get_sensor(entity_name)
if response:
state = response.json()['state']
return state

def ha_get_entity_attribute(self, entity_name, attribute_name):
attribute_value = None
response = self.ha_get_sensor(entity_name)
if response:
attribute_value = response.json()['attributes'][attribute_name]
return attribute_value

def ha_set_entity_state(self, entity_name, state_str=None, attributes=None, payload=None):
if payload == None:
payload = {"state": state_str}
if attributes:
payload['attributes'] = attributes
return self.ha_update_sensor(entity_name, payload)

def ha_get_sensor(self, entity_name):
get_url = "{}/api/states/{}".format(self.base_url, entity_name)
headers = {
'Authorization': "Bearer " + self.access_token
}
response = get(get_url, headers=headers)
return response

def ha_update_sensor(self, entity_name, payload):
post_url = "{}/api/states/{}".format(self.base_url, entity_name)
headers = {
'Authorization': "Bearer " + self.access_token
}
response = post(post_url, data=json.dumps(payload), headers=headers)
return response

def ha_service_notify(self, message, whom):
post_url = "{}/api/services/notify/{}".format(self.base_url, whom)
headers = {
'Authorization': "Bearer " + self.access_token
}
payload = {
"message": message
}
response = post(post_url, data=json.dumps(payload), headers=headers)
return response

def ha_service_update_device_tracker(self, mac_address=None, status_str=None, payload=None):
post_url = "{}/api/services/device_tracker/see".format(self.base_url)
headers = {
'Authorization': "Bearer " + self.access_token
}
if payload == None:
payload = {
"mac": mac_address,
"location_name": status_str,
"attributes": {"source_type": "script"}
}
response = post(post_url, data=json.dumps(payload), headers=headers)
return response
Loading

0 comments on commit 89c07ae

Please sign in to comment.