Skip to content

Commit 60bbcad

Browse files
committed
separated data collectors, generalized template, extended README files
1 parent f8835e6 commit 60bbcad

12 files changed

+174
-101
lines changed

README.md

+28-28
Original file line numberDiff line numberDiff line change
@@ -12,27 +12,19 @@ This repo can help you set up an e-ink board and display your Trello and Google
1212

1313
![InkCheck](https://user-images.githubusercontent.com/3463702/250285813-c93ab4b4-c946-4134-a144-b92ad8b61ca0.jpg)
1414

15-
### Google Keep mapping
16-
![Keep](https://user-images.githubusercontent.com/3463702/250350631-abae8a92-2ef2-48c3-8082-0c4bc58d943a.jpg)
17-
It also works with lists as the following Trello example shows. It is rendered the same way.
18-
19-
### Trello mapping
20-
![Trello](https://user-images.githubusercontent.com/3463702/250350632-9970813a-3a66-47c2-b825-de2e0113df19.jpg)
21-
22-
## General description
23-
This is a forked repo from [MagInkDash](https://github.com/markfodor/MagInkDash). That project was used as a base for everything here.
24-
So if you have the opportunity, buy that guy a coffee (link on the MagInkDash README). :v:
25-
26-
InkCheck however differs in many aspects.
27-
It is able to display your Google Keep and/or Trello data on an [Inkplate E-Ink Display](https://soldered.com/product/soldered-inkplate-10-9-7-e-paper-board-with-enclosure-copy/).
28-
2915
You can use it to display:
3016
- your daily schedule
3117
- your long-term goal
3218
- a TODO list
3319
- any text/quote you want to keep in mind
3420

35-
## Hardware Required
21+
## Features
22+
- Periodical image refresh (1 hour with the default setup)
23+
- Landscape and portrait mode
24+
- Renders 2 data columns
25+
- Data providers: Trello, Google Keep
26+
27+
## Hardware
3628
- [Inkplate 10 E-Ink Display](https://soldered.com/product/soldered-inkplate-10-9-7-e-paper-board-with-enclosure-copy/) - Used as a client to display the generated image. If you go with this it will be less hardware tinkering.
3729
- A server, which is powerful enough to run the image generation. It could be a [Raspberry Pi](https://www.raspberrypi.org/)
3830

@@ -74,28 +66,28 @@ cd InkCheck
7466
pip install -r requirements.txt
7567
```
7668

77-
6. Fill the variables in the config.json.
69+
6. Fill the variables in the global.json. Most of the variables are pre-filled, _destinationFolder_ should be the path where your Apache server is running. Sidenote: You can switch between portait and landscape mode by swtiching the _imageWidth_ and _imageHeight_ values.
70+
71+
7. Fill the config.json files for the collectors. More info at the [Data Collectors](#data-collectors)
7872

79-
7. Do a test run and check the logs. If everything is ok you should not see any error logs.
73+
8. Do a test run and check the logs. If everything is ok you should not see any error logs.
8074
```bash
8175
python3 main.py
8276
```
83-
This might takes a bit longer (depends on your hardware - 2-3 mins on a Raspberry Pi Zero). When it is done, you should be able to find the rendered html file (renderer/inkcheck.html) and the screenshot (output/inkcheck.png). The image should be available on your network if you check in a browser: YOUR_SERVER_IP/inkcheck.png
77+
This might takes a bit longer (depends on your hardware, 2-3 mins on a Raspberry Pi Zero). When it is done, you should be able to find the rendered html file (renderer/inkcheck.html) and the screenshot (output/inkcheck.png). The image should be available on your network if you check in a browser: YOUR_SERVER_IP/inkcheck.png
8478

85-
7. Copy all the files (other than the "inkplate" folder) over to your RPi using your preferred means.
86-
87-
8. Run the following command in the RPi Terminal to open crontab.
79+
9. Run the following command in the RPi Terminal to open crontab.
8880
```bash
8981
crontab -e
9082
```
9183

92-
9. Specifically, add the following command to crontab so that the InkCheck Python script runs on the hour, every hour.
84+
10. Specifically, add the following command to crontab so that the InkCheck Python script runs on the hour, every hour.
9385
```bash
9486
0 * * * * cd /location/to/your/InkCheck && python3 main.py
9587
```
9688
If you want to set an other interval check out the [crontab.guru](https://crontab.guru/) site.
9789

98-
10. As for the Inkplate, I'm not going to devote too much space here since there are [official resources that describe how to set it up](https://inkplate.readthedocs.io/en/latest/get-started.html). It may take some trial and error for those new to microcontroller programming but it's all worth it! Only the Arduino portion of the guide is relevant, and you'll need to be able to run *.ino scripts via Arduino IDE before proceeding. From there, compile and upload the "inkplate.ino" file from the "inkplate" folder in the Arduino IDE when connected to the Inkplate. And do not forget to fill the ssid, password and imgurl fields at the top of the ino file. Oh, and the BOTtoken if you want to get notifications about the battery.
90+
11. As for the Inkplate, I'm not going to devote too much space here since there are [official resources that describe how to set it up](https://inkplate.readthedocs.io/en/latest/get-started.html). It may take some trial and error for those new to microcontroller programming but it's all worth it! Only the Arduino portion of the guide is relevant, and you'll need to be able to run *.ino scripts via Arduino IDE before proceeding. From there, compile and upload the "inkplate.ino" file from the "inkplate" folder in the Arduino IDE when connected to the Inkplate. And do not forget to fill the ssid, password and imgurl fields at the top of the ino file. Oh, and the BOTtoken if you want to get notifications about the battery.
9991
Common problems:
10092
- Inkplate is not connected
10193
- Inkplate is not ON - a light blue led should shine through the 3D-printed case next to the ON button.
@@ -105,17 +97,25 @@ Common problems:
10597

10698
12. That's all! Your InkCheck should now be refreshed every hour!
10799

100+
## Data Collectors
101+
Different data sources (e.g: Trello or Google Keep) are handled differently in separated data collectors. You can read more about the setup here:
102+
- [Google Keep](/collectors/googlekeep/README.md)
103+
- [Trello](/collectors/trello/README.md)
104+
108105
## Acknowledgements
106+
- [MagInkDash](https://github.com/markfodor/MagInkDash) - Source of the fork. If you have the opportunity, buy that guy a coffee (link on the MagInkDash README). :v:
109107
- [Lexend Font](https://fonts.google.com/specimen/Lexend)
110108
- [Jinja](https://jinja.palletsprojects.com/)
111109
- [Bootstrap](https://getbootstrap.com/)
112110

113111
## Contribution
114112
Feel free to fork/modify the code to your needs. If you want something to be in this repo, then just open an issue or a pull request.
115113

116-
Things I want to add:
117-
- Generalize the code -> it would be easier to add a new source
118-
- Generalize the template to handle single and double coulmns
119-
- Portrait and landscape mode
114+
Things I want to add/modify:
115+
- Validate input values in the global.json (e.g: timezone)
116+
- Eliminate Apache server and make it pure Python.
117+
- Improve GoogleKeepCollector to use token if possible
118+
- Adjust template for checked list items (sort list and display in a different way)
119+
- Generalize the template to handle single and double columns
120120
- Docker support -> easier to setup the server
121-
- Optional: server endpoint to kickstart the image generation so you do not need to wait for the next refresh
121+
- Optional: server endpoint to kickstart the image generation so you do not need to wait for the next refresh, and easier to test

collectors/abstractBaseCollector.py

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from abc import ABC, abstractmethod
2+
from model.columnData import ColumnData
3+
4+
5+
class AbstractBaseCollector(ABC):
6+
@abstractmethod
7+
def get_data(self) -> ColumnData:
8+
pass

collectors/googlekeep/README.md

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Google Keep Collector
2+
3+
## Mapping
4+
![Keep Text note](https://user-images.githubusercontent.com/3463702/250350631-abae8a92-2ef2-48c3-8082-0c4bc58d943a.jpg)
5+
![Keep List note](https://user-images.githubusercontent.com/3463702/253660376-61843d5a-88c1-4d07-8404-19f8a102fc95.jpg)
6+
7+
8+
## Setup
9+
To generate an app password:
10+
11+
1. Go to your [Google Account](https://myaccount.google.com/)
12+
2. Select Security.
13+
3. Under "Signing in to Google," select 2-Step Verification.
14+
4. At the bottom of the page, select App passwords.
15+
5. Enter a name that helps you remember where you’ll use the app password.
16+
6. Select Generate.
17+
7. Now you can copy the generated password to the config.json
18+
```
19+
{
20+
"username": "YOUR_MAIL_ADDRESS",
21+
"password": "YOUR_PASSWORD",
22+
"nodeName": "Daily schedule",
23+
"onlyUncheckedItems": true
24+
}
25+
```
26+
The "onlyUncheckedItems" field is only interesting when you work with lists. If the value is _true_, it will only display the unchecked items.

collectors/googlekeep/config.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"username": "YOUR_MAIL_ADDRESS",
33
"password": "YOUR_PASSWORD",
4-
"nodeName": "YOUR_NODE_NAME"
4+
"nodeName": "YOUR_NODE_NAME",
5+
"onlyUncheckedItems": true
56
}

collectors/googlekeep/googleKeepCollector.py

+13-10
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,19 @@
33
from logger.logger import logger
44
from model.columnData import ColumnData
55
from util.configHelper import read_config
6+
from collectors.abstractBaseCollector import AbstractBaseCollector
67

78

8-
class GoogleKeepHelper:
9+
class GoogleKeepCollector(AbstractBaseCollector):
910

1011
def __init__(self):
1112
config = read_config(str(pathlib.Path(__file__).parent.absolute()))
1213

1314
if config:
1415
self.username = config['username']
1516
self.password = config['password']
16-
self.nodeName = config['nodeName']
17+
self.node_name = config['nodeName']
18+
self.only_unchecked_items = config['onlyUncheckedItems']
1719

1820
logger.info('Google Keep config is set.')
1921
else:
@@ -27,20 +29,20 @@ def __init__(self):
2729
else:
2830
logger.error('Google Keep login failed.')
2931

30-
def get_items_on_node_by_node_id(self, node_id, only_unchecked):
32+
def _get_items_on_node_by_node_id(self, node_id):
3133
node = self.client.get(node_id)
3234

3335
if isinstance(node, gkeepapi.node.List):
34-
data = ColumnData(title=self.nodeName, is_list=True)
36+
data: ColumnData = ColumnData(title=self.node_name, is_list=True)
3537
for item in node.items:
36-
if not only_unchecked or not item.checked:
38+
if not self.only_unchecked_items or not item.checked:
3739
data.items.append(item.text)
3840

3941
logger.info('Data collection from Google Keep is done.')
4042
return data
4143
# assuming it is a simple text node
4244
else:
43-
data = ColumnData(title=self.nodeName, is_list=False)
45+
data: ColumnData = ColumnData(title=self.node_name, is_list=False)
4446
data.text = node.text
4547
return data
4648

@@ -53,9 +55,10 @@ def _search_node_id(self, name):
5355
return node.id
5456
return None
5557

56-
def search_node_id_by_name(self, only_unchecked):
57-
nodeId = self._search_node_id(self.nodeName)
58+
def get_data(self) -> ColumnData:
59+
nodeId = self._search_node_id(self.node_name)
5860
if not nodeId:
59-
logger.error(f"node not found with name: {self.nodeName}")
61+
logger.error(f"node not found with name: {self.node_name}")
62+
return None
6063
else:
61-
return self.get_items_on_node_by_node_id(nodeId, only_unchecked)
64+
return self._get_items_on_node_by_node_id(nodeId)

collectors/trello/README.md

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Trello Collector
2+
3+
## Mapping
4+
![Trello](https://user-images.githubusercontent.com/3463702/250350632-9970813a-3a66-47c2-b825-de2e0113df19.jpg)
5+
6+
```
7+
{
8+
"key": "YOUR_API_KEY",
9+
"token": "YOUR_TOKEN",
10+
"board": "Test board",
11+
"list": "Long-term Goals"
12+
}
13+
```
14+
15+
## Setup
16+
Everything you need to do is written on the [Atlassian guide](https://developer.atlassian.com/cloud/trello/guides/rest-api/api-introduction/).
17+
18+
When you have your newly created app you will be able to see the corresponding **API key** -> click on the app.
19+
![Trello app](https://user-images.githubusercontent.com/3463702/253630254-3f9e753a-4f27-49ce-baef-cd55cba5d0e2.png)
20+
21+
After that you just need to authorize it and get the **token**.
22+
![Trello token](https://user-images.githubusercontent.com/3463702/253630914-5f3f9e95-e28f-4d7f-9625-3643b2f75ec5.png)
23+
24+
Once you have a board created on your workspace and have a list on it, you will be able to fill the config.json.

collectors/trello/trelloCollector.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
from logger.logger import logger
44
from model.columnData import ColumnData
55
from util.configHelper import read_config
6+
from collectors.abstractBaseCollector import AbstractBaseCollector
67

78

8-
class TrelloCollector:
9+
class TrelloCollector(AbstractBaseCollector):
910

1011
def __init__(self):
1112
config = read_config(str(pathlib.Path(__file__).parent.absolute()))
@@ -23,7 +24,7 @@ def __init__(self):
2324

2425
self.client = TrelloClient(self.key, None, self.token, None)
2526

26-
def get_data(self):
27+
def get_data(self) -> ColumnData:
2728
boards = self.client.search(self.board, True, ['boards'])
2829

2930
if not boards:
@@ -35,7 +36,7 @@ def get_data(self):
3536

3637
# "There can be only one" in the result set
3738
selected_board = boards[0]
38-
data = ColumnData(title=self.board, is_list=True)
39+
data: ColumnData = ColumnData(title=self.board, is_list=True)
3940

4041
# these are a columns on the board
4142
lists = selected_board.all_lists()

global.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
"timestampFormat": "%Y.%m.%d %H:%M:%S",
44
"imageWidth": 1200,
55
"imageHeight": 825,
6-
"rotateAngle": 0,
7-
"destinationFolder": ""
6+
"collectors": ["GoogleKeepCollector", "TrelloCollector"],
7+
"destinationFolder": "YOUR_DESTINATION_FOLDER"
88
}

main.py

+28-14
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,47 @@
44
CSS stylesheet.
55
"""
66

7+
import sys
78
import json
89
from datetime import datetime as dt
910
from pytz import timezone
1011
from logger.logger import logger
1112
from renderer.renderer import Renderer
12-
from collectors.googlekeep.googleKeepCollector import GoogleKeepHelper
13+
from model.columnData import ColumnData
14+
from collectors.abstractBaseCollector import AbstractBaseCollector
15+
from util.configHelper import validate_collectors
16+
17+
# Import the collector here so it can be in globals
18+
from collectors.googlekeep.googleKeepCollector import GoogleKeepCollector
1319
from collectors.trello.trelloCollector import TrelloCollector
1420

1521

1622
if __name__ == '__main__':
17-
configFile = open('global.json')
18-
config = json.load(configFile)
23+
config_file = open('global.json')
24+
config = json.load(config_file)
25+
26+
time_zone: str = timezone(config['timezone'])
27+
timestamp_format: str = config['timestampFormat']
28+
image_width: int = config['imageWidth']
29+
image_height: int = config['imageHeight']
30+
collectors: list[str] = config['collectors']
31+
destination_folder: str = config['destinationFolder']
1932

20-
timeZone = timezone(config['timezone'])
21-
timestampFormat = config['timestampFormat']
22-
imageWidth = config['imageWidth']
23-
imageHeight = config['imageHeight']
24-
rotateAngle = config['rotateAngle']
25-
destinationFolder = config['destinationFolder']
33+
valid = validate_collectors(collectors)
34+
if not valid:
35+
logger.error('Invalid config. Exiting...')
36+
sys.exit(1)
2637

2738
logger.info('Global config is set.')
2839

29-
timestamp = dt.now(timeZone).strftime(timestampFormat)
30-
google_keep_data = GoogleKeepHelper().search_node_id_by_name(True)
31-
trello_data = TrelloCollector().get_data()
40+
timestamp = dt.now(time_zone).strftime(timestamp_format)
41+
data_list: list[ColumnData] = []
42+
for collector_name in collectors:
43+
clazz = globals()[collector_name]
44+
collector: AbstractBaseCollector = clazz()
45+
data_list.append(collector.get_data())
3246

33-
renderer = Renderer(imageWidth, imageHeight, rotateAngle)
34-
renderer.render(timestamp, google_keep_data, trello_data, destinationFolder)
47+
renderer = Renderer(image_width, image_height)
48+
renderer.render(timestamp, data_list, destination_folder)
3549

3650
logger.info("Inkcheck image is updated.")

0 commit comments

Comments
 (0)