adding more files
This commit is contained in:
93
README.md
Normal file
93
README.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# ai-agents
|
||||
|
||||
|
||||
|
||||
## Getting started
|
||||
|
||||
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
|
||||
|
||||
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
|
||||
|
||||
## Add your files
|
||||
|
||||
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
|
||||
- [ ] [Add files using the command line](https://docs.gitlab.com/topics/git/add_files/#add-files-to-a-git-repository) or push an existing Git repository with the following command:
|
||||
|
||||
```
|
||||
cd existing_repo
|
||||
git remote add origin http://192.168.50.14:8929/johan/ai-agents.git
|
||||
git branch -M main
|
||||
git push -uf origin main
|
||||
```
|
||||
|
||||
## Integrate with your tools
|
||||
|
||||
- [ ] [Set up project integrations](http://192.168.50.14:8929/johan/ai-agents/-/settings/integrations)
|
||||
|
||||
## Collaborate with your team
|
||||
|
||||
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
|
||||
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
|
||||
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
|
||||
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
|
||||
- [ ] [Set auto-merge](https://docs.gitlab.com/user/project/merge_requests/auto_merge/)
|
||||
|
||||
## Test and Deploy
|
||||
|
||||
Use the built-in continuous integration in GitLab.
|
||||
|
||||
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/)
|
||||
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
|
||||
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
|
||||
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
|
||||
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
|
||||
|
||||
***
|
||||
|
||||
# Editing this README
|
||||
|
||||
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template.
|
||||
|
||||
## Suggestions for a good README
|
||||
|
||||
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
|
||||
|
||||
## Name
|
||||
Choose a self-explaining name for your project.
|
||||
|
||||
## Description
|
||||
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
|
||||
|
||||
## Badges
|
||||
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
|
||||
|
||||
## Visuals
|
||||
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
|
||||
|
||||
## Installation
|
||||
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
|
||||
|
||||
## Usage
|
||||
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
|
||||
|
||||
## Support
|
||||
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
|
||||
|
||||
## Roadmap
|
||||
If you have ideas for releases in the future, it is a good idea to list them in the README.
|
||||
|
||||
## Contributing
|
||||
State if you are open to contributions and what your requirements are for accepting them.
|
||||
|
||||
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
|
||||
|
||||
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
|
||||
|
||||
## Authors and acknowledgment
|
||||
Show your appreciation to those who have contributed to the project.
|
||||
|
||||
## License
|
||||
For open source projects, say how it is licensed.
|
||||
|
||||
## Project status
|
||||
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
|
||||
116
ask_llm.py
Normal file
116
ask_llm.py
Normal file
@@ -0,0 +1,116 @@
|
||||
import requests, json
|
||||
import getopt
|
||||
import sys
|
||||
from enum import IntEnum
|
||||
|
||||
#docker exec -it ollama ollama run llama3.2
|
||||
|
||||
class State(IntEnum):
|
||||
IDLE = 0
|
||||
THINKING = 1
|
||||
RESPONDING = 2
|
||||
DONE = 3
|
||||
|
||||
state = State.IDLE
|
||||
|
||||
#spinner
|
||||
#https://www.youtube.com/watch?v=vk_s-gDTqkM
|
||||
|
||||
def _usage():
|
||||
print("Usage: ask_llm.py [options] \"your question here\"")
|
||||
print("Options:")
|
||||
print(" -h, --help Show this help message and exit")
|
||||
print(" -p, --print Print the command without executing (dry run)")
|
||||
print(" -i, --ip=IP Specify the IP address of the Ollama server (default: 192.168.50.215)")
|
||||
print(" -m, --model=MODEL Specify the model to use (default: llama3.2)")
|
||||
print("Example:")
|
||||
print('e.g python3 ask_llm.py --ip="192.168.50.215" -m "dolphin3:8b" "summarize this file: $(cat docker_log_pihole.txt)"')
|
||||
|
||||
def update_state(new_state):
|
||||
global state
|
||||
|
||||
if new_state != state:
|
||||
print(f"\n\n---\n\n## State changed from {state.name} to {new_state.name}\n\n")
|
||||
state = new_state
|
||||
|
||||
def ask_llm(url, payload):
|
||||
response = requests.post(url, json=payload, stream=True) # stream=True tells requests to read the response as a live data stream
|
||||
|
||||
# Ollama sends one JSON object per line as it generates text
|
||||
for line in response.iter_lines():
|
||||
if line:
|
||||
data = json.loads(line.decode("utf-8"))
|
||||
#print(data)
|
||||
try:
|
||||
if 'error' in data:
|
||||
print("Error from Ollama API: " + data['error'])
|
||||
update_state(State.DONE)
|
||||
continue
|
||||
except Exception as e:
|
||||
print("Exception while checking for error: " + str(e))
|
||||
|
||||
if 'thinking' in data and data['thinking'] != '':
|
||||
update_state(State.THINKING)
|
||||
print(data['thinking'], end="", flush=True)
|
||||
|
||||
if 'response' in data and data['response'] != '':
|
||||
update_state(State.RESPONDING)
|
||||
print(data['response'], end="", flush=True)
|
||||
|
||||
if 'done' in data and data['done'] == True:
|
||||
update_state(State.DONE)
|
||||
print('done_reason: ' + data['done_reason'])
|
||||
print('total_duration: ' + str(data['total_duration']))
|
||||
print('load_duration: ' + str(data['load_duration']))
|
||||
print('prompt_eval_count: ' + str(data['prompt_eval_count']))
|
||||
print('prompt_eval_duration: ' + str(data['prompt_eval_duration']))
|
||||
print('eval_count: ' + str(data['eval_count']))
|
||||
print('eval_duration: ' + str(data['eval_duration']))
|
||||
print('\n')
|
||||
|
||||
def main():
|
||||
# Define the Ollama API endpoint
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], "phi:m:", ["ip=", "print", "help", "model="])
|
||||
except getopt.GetoptError as err:
|
||||
# print help information and exit:
|
||||
print(err) # will print something like "option -a not recognized"
|
||||
sys.exit(2)
|
||||
|
||||
_ip = "192.168.50.14"
|
||||
_model = "llama3.2" # or "gpt-oss:20b" or "gemma3:4b" or "qwen3:8b"
|
||||
### parse input command line
|
||||
for o, a in opts:
|
||||
if o in ("-h", "--help"):
|
||||
_usage()
|
||||
sys.exit()
|
||||
elif o in ("-p", "--print"):
|
||||
# Dry run option, not implemented in this snippet
|
||||
pass
|
||||
elif o in ("-i", "--ip"):
|
||||
_ip = a
|
||||
elif o in ("-m", "--model"):
|
||||
_model = a
|
||||
else:
|
||||
print(o, a)
|
||||
assert False, "unhandled option"
|
||||
|
||||
if args =="" or len(args) == 0:
|
||||
print("No command provided. Use -h for help.")
|
||||
sys.exit(2)
|
||||
|
||||
_url = "http://" + _ip + ":11434/api/generate"
|
||||
|
||||
#print ("ask: " + args[0])
|
||||
|
||||
# Send a prompt to the model
|
||||
payload = {
|
||||
"model": _model,
|
||||
"prompt": args[0]
|
||||
}
|
||||
|
||||
ask_llm(_url, payload)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
185
docker_log_pihole.txt
Normal file
185
docker_log_pihole.txt
Normal file
@@ -0,0 +1,185 @@
|
||||
[i] Setting up user & group for the pihole user
|
||||
[i] PIHOLE_UID not set in environment, using default (1000)
|
||||
[i] PIHOLE_GID not set in environment, using default (1000)
|
||||
|
||||
[i] Starting FTL configuration
|
||||
[i] Password already set in config file
|
||||
[i] Starting crond for scheduled scripts. Randomizing times for gravity and update checker
|
||||
|
||||
[i] Ensuring logrotate script exists in /etc/pihole
|
||||
|
||||
[i] Gravity migration checks
|
||||
[i] Existing gravity database found - schema will be upgraded if necessary
|
||||
|
||||
|
||||
[i] pihole-FTL pre-start checks
|
||||
[i] Setting capabilities on pihole-FTL where possible
|
||||
[i] Applying the following caps to pihole-FTL:
|
||||
* CAP_CHOWN
|
||||
* CAP_NET_BIND_SERVICE
|
||||
* CAP_NET_RAW
|
||||
* CAP_NET_ADMIN
|
||||
|
||||
[i] Starting pihole-FTL (no-daemon) as pihole
|
||||
|
||||
[i] Version info:
|
||||
Core version is v6.3 (Latest: v6.3)
|
||||
Web version is v6.4 (Latest: v6.4)
|
||||
FTL version is v6.4.1 (Latest: v6.4.1)
|
||||
|
||||
2025-11-30 17:09:32.751 UTC [70M] INFO: ########## FTL started on 1be86b5a6692! ##########
|
||||
2025-11-30 17:09:32.752 UTC [70M] INFO: FTL branch: master
|
||||
2025-11-30 17:09:32.752 UTC [70M] INFO: FTL version: v6.4.1
|
||||
2025-11-30 17:09:32.752 UTC [70M] INFO: FTL commit: 8d1add8d
|
||||
2025-11-30 17:09:32.752 UTC [70M] INFO: FTL date: 2025-11-27 18:02:19 +0000
|
||||
2025-11-30 17:09:32.752 UTC [70M] INFO: FTL user: pihole
|
||||
2025-11-30 17:09:32.752 UTC [70M] INFO: Compiled for linux/amd64 (compiled on CI) using cc (Alpine 14.2.0) 14.2.0
|
||||
2025-11-30 17:09:32.754 UTC [70M] INFO: Wrote config file:
|
||||
2025-11-30 17:09:32.754 UTC [70M] INFO: - 162 total entries
|
||||
2025-11-30 17:09:32.754 UTC [70M] INFO: - 159 entries are default
|
||||
2025-11-30 17:09:32.754 UTC [70M] INFO: - 3 entries are modified
|
||||
2025-11-30 17:09:32.754 UTC [70M] INFO: - 0 entries are forced through environment
|
||||
2025-11-30 17:09:32.756 UTC [70M] INFO: Parsed config file /etc/pihole/pihole.toml successfully
|
||||
2025-11-30 17:09:32.756 UTC [70M] INFO: PID file does not exist or not readable
|
||||
2025-11-30 17:09:32.756 UTC [70M] INFO: No other running FTL process found.
|
||||
2025-11-30 17:09:32.756 UTC [70M] WARNING: Insufficient permissions to set process priority to -10 (CAP_SYS_NICE required), process priority remains at 0
|
||||
2025-11-30 17:09:32.758 UTC [70M] INFO: PID of FTL process: 70
|
||||
2025-11-30 17:09:32.758 UTC [70M] INFO: listening on 0.0.0.0 port 53
|
||||
2025-11-30 17:09:32.758 UTC [70M] INFO: listening on :: port 53
|
||||
2025-11-30 17:09:32.759 UTC [70M] INFO: PID of FTL process: 70
|
||||
2025-11-30 17:09:32.760 UTC [70M] INFO: Database version is 21
|
||||
2025-11-30 17:09:32.760 UTC [70M] INFO: Database successfully initialized
|
||||
2025-11-30 17:09:33.187 UTC [70M] INFO: Imported 43832 queries from the on-disk database (it has 1248190 rows)
|
||||
2025-11-30 17:09:33.188 UTC [70M] INFO: Parsing queries in database
|
||||
2025-11-30 17:09:33.225 UTC [70M] INFO: 10000 queries parsed...
|
||||
2025-11-30 17:09:33.253 UTC [70M] INFO: 20000 queries parsed...
|
||||
2025-11-30 17:09:33.284 UTC [70M] INFO: 30000 queries parsed...
|
||||
2025-11-30 17:09:33.316 UTC [70M] INFO: 40000 queries parsed...
|
||||
2025-11-30 17:09:33.329 UTC [70M] INFO: Imported 43832 queries from the long-term database
|
||||
2025-11-30 17:09:33.329 UTC [70M] INFO: -> Total DNS queries: 43832
|
||||
2025-11-30 17:09:33.329 UTC [70M] INFO: -> Cached DNS queries: 29711
|
||||
2025-11-30 17:09:33.329 UTC [70M] INFO: -> Forwarded DNS queries: 8206
|
||||
2025-11-30 17:09:33.329 UTC [70M] INFO: -> Blocked DNS queries: 5721
|
||||
2025-11-30 17:09:33.329 UTC [70M] INFO: -> Unknown DNS queries: 0
|
||||
2025-11-30 17:09:33.329 UTC [70M] INFO: -> Unique domains: 2831
|
||||
2025-11-30 17:09:33.329 UTC [70M] INFO: -> Unique clients: 2
|
||||
2025-11-30 17:09:33.329 UTC [70M] INFO: -> DNS cache records: 372
|
||||
2025-11-30 17:09:33.329 UTC [70M] INFO: -> Known forward destinations: 2
|
||||
2025-11-30 17:09:33.437 UTC [70M] WARNING: Insufficient permissions to set system time (CAP_SYS_TIME required), NTP client not available
|
||||
2025-11-30 17:09:33.437 UTC [70/T72] INFO: NTP server listening on 0.0.0.0:123 (IPv4)
|
||||
2025-11-30 17:09:33.437 UTC [70/T73] INFO: NTP server listening on :::123 (IPv6)
|
||||
2025-11-30 17:09:33.437 UTC [70M] INFO: FTL is running as user pihole (UID 1000)
|
||||
2025-11-30 17:09:33.438 UTC [70M] INFO: Reading certificate from /etc/pihole/tls.pem ...
|
||||
2025-11-30 17:09:33.440 UTC [70M] INFO: Using SSL/TLS certificate file /etc/pihole/tls.pem
|
||||
2025-11-30 17:09:33.441 UTC [70M] INFO: Web server ports:
|
||||
2025-11-30 17:09:33.441 UTC [70M] INFO: - 0.0.0.0:80 (HTTP, IPv4, optional, OK)
|
||||
2025-11-30 17:09:33.441 UTC [70M] INFO: - 0.0.0.0:443 (HTTPS, IPv4, optional, OK)
|
||||
2025-11-30 17:09:33.441 UTC [70M] INFO: - [::]:80 (HTTP, IPv6, optional, OK)
|
||||
2025-11-30 17:09:33.441 UTC [70M] INFO: - [::]:443 (HTTPS, IPv6, optional, OK)
|
||||
2025-11-30 17:09:33.441 UTC [70M] INFO: Restored 0 API sessions from the database
|
||||
2025-11-30 17:09:33.442 UTC [70M] INFO: Blocking status is enabled
|
||||
2025-11-30 17:09:33.541 UTC [70/T74] INFO: Compiled 0 allow and 0 deny regex for 2 clients in 0.1 msec
|
||||
|
||||
[i] Container stop requested...
|
||||
[i] pihole-FTL is running - Attempting to shut it down cleanly
|
||||
|
||||
2025-12-05 22:02:06.985 UTC [70M] INFO: Asked to terminate by "N/A" (PID 135431, user root UID 0)
|
||||
2025-12-05 22:02:07.051 UTC [70/T77] INFO: Terminating timer thread
|
||||
2025-12-05 22:02:07.063 UTC [70/T74] INFO: Terminating database thread
|
||||
2025-12-05 22:02:07.255 UTC [70M] INFO: Finished final database update
|
||||
2025-12-05 22:02:07.255 UTC [70M] INFO: Waiting for threads to join
|
||||
2025-12-05 22:02:07.255 UTC [70M] INFO: Thread housekeeper (1) is idle, terminating it.
|
||||
2025-12-05 22:02:07.255 UTC [70M] INFO: Thread dns-client (2) is idle, terminating it.
|
||||
2025-12-05 22:02:07.255 UTC [70M] INFO: Thread webserver (7) is idle, terminating it.
|
||||
2025-12-05 22:02:07.255 UTC [70M] INFO: All threads joined
|
||||
2025-12-05 22:02:07.257 UTC [70M] INFO: PID file emptied
|
||||
2025-12-05 22:02:07.258 UTC [70M] INFO: Stored 0 API sessions in the database
|
||||
2025-12-05 22:02:07.601 UTC [70M] INFO: ########## FTL terminated after 5d 4h 52m 34s (code 0)! ##########
|
||||
|
||||
[i] pihole-FTL exited with status 0
|
||||
|
||||
[i] Container will now stop or restart depending on your restart policy
|
||||
https://docs.docker.com/engine/containers/start-containers-automatically/#use-a-restart-policy
|
||||
|
||||
[i] Setting up user & group for the pihole user
|
||||
[i] PIHOLE_UID not set in environment, using default (1000)
|
||||
[i] PIHOLE_GID not set in environment, using default (1000)
|
||||
|
||||
[i] Starting FTL configuration
|
||||
[i] Password already set in config file
|
||||
[i] Starting crond for scheduled scripts. Randomizing times for gravity and update checker
|
||||
|
||||
[i] Ensuring logrotate script exists in /etc/pihole
|
||||
|
||||
[i] Gravity migration checks
|
||||
[i] Existing gravity database found - schema will be upgraded if necessary
|
||||
|
||||
|
||||
[i] pihole-FTL pre-start checks
|
||||
[i] Setting capabilities on pihole-FTL where possible
|
||||
[i] Applying the following caps to pihole-FTL:
|
||||
* CAP_CHOWN
|
||||
* CAP_NET_BIND_SERVICE
|
||||
* CAP_NET_RAW
|
||||
* CAP_NET_ADMIN
|
||||
|
||||
[i] Starting pihole-FTL (no-daemon) as pihole
|
||||
|
||||
[i] Version info:
|
||||
Core version is v6.3 (Latest: v6.3)
|
||||
Web version is v6.4 (Latest: v6.4)
|
||||
FTL version is v6.4.1 (Latest: v6.4.1)
|
||||
|
||||
2025-12-05 22:02:19.999 UTC [68M] INFO: ########## FTL started on 1be86b5a6692! ##########
|
||||
2025-12-05 22:02:19.999 UTC [68M] INFO: FTL branch: master
|
||||
2025-12-05 22:02:19.999 UTC [68M] INFO: FTL version: v6.4.1
|
||||
2025-12-05 22:02:19.999 UTC [68M] INFO: FTL commit: 8d1add8d
|
||||
2025-12-05 22:02:19.999 UTC [68M] INFO: FTL date: 2025-11-27 18:02:19 +0000
|
||||
2025-12-05 22:02:19.999 UTC [68M] INFO: FTL user: pihole
|
||||
2025-12-05 22:02:19.999 UTC [68M] INFO: Compiled for linux/amd64 (compiled on CI) using cc (Alpine 14.2.0) 14.2.0
|
||||
2025-12-05 22:02:20.002 UTC [68M] INFO: Wrote config file:
|
||||
2025-12-05 22:02:20.002 UTC [68M] INFO: - 162 total entries
|
||||
2025-12-05 22:02:20.002 UTC [68M] INFO: - 159 entries are default
|
||||
2025-12-05 22:02:20.002 UTC [68M] INFO: - 3 entries are modified
|
||||
2025-12-05 22:02:20.002 UTC [68M] INFO: - 0 entries are forced through environment
|
||||
2025-12-05 22:02:20.004 UTC [68M] INFO: Parsed config file /etc/pihole/pihole.toml successfully
|
||||
2025-12-05 22:02:20.004 UTC [68M] INFO: PID file does not exist or not readable
|
||||
2025-12-05 22:02:20.004 UTC [68M] INFO: No other running FTL process found.
|
||||
2025-12-05 22:02:20.004 UTC [68M] WARNING: Insufficient permissions to set process priority to -10 (CAP_SYS_NICE required), process priority remains at 0
|
||||
2025-12-05 22:02:20.005 UTC [68M] INFO: PID of FTL process: 68
|
||||
2025-12-05 22:02:20.006 UTC [68M] INFO: listening on 0.0.0.0 port 53
|
||||
2025-12-05 22:02:20.006 UTC [68M] INFO: listening on :: port 53
|
||||
2025-12-05 22:02:20.007 UTC [68M] INFO: PID of FTL process: 68
|
||||
2025-12-05 22:02:20.008 UTC [68M] INFO: Database version is 21
|
||||
2025-12-05 22:02:20.008 UTC [68M] INFO: Database successfully initialized
|
||||
2025-12-05 22:02:20.421 UTC [68M] INFO: Imported 31484 queries from the on-disk database (it has 1416729 rows)
|
||||
2025-12-05 22:02:20.421 UTC [68M] INFO: Parsing queries in database
|
||||
2025-12-05 22:02:20.452 UTC [68M] INFO: 10000 queries parsed...
|
||||
2025-12-05 22:02:20.488 UTC [68M] INFO: 20000 queries parsed...
|
||||
2025-12-05 22:02:20.523 UTC [68M] INFO: 30000 queries parsed...
|
||||
2025-12-05 22:02:20.529 UTC [68M] INFO: Imported 31484 queries from the long-term database
|
||||
2025-12-05 22:02:20.529 UTC [68M] INFO: -> Total DNS queries: 31484
|
||||
2025-12-05 22:02:20.529 UTC [68M] INFO: -> Cached DNS queries: 22035
|
||||
2025-12-05 22:02:20.529 UTC [68M] INFO: -> Forwarded DNS queries: 6135
|
||||
2025-12-05 22:02:20.529 UTC [68M] INFO: -> Blocked DNS queries: 3246
|
||||
2025-12-05 22:02:20.529 UTC [68M] INFO: -> Unknown DNS queries: 0
|
||||
2025-12-05 22:02:20.529 UTC [68M] INFO: -> Unique domains: 2286
|
||||
2025-12-05 22:02:20.529 UTC [68M] INFO: -> Unique clients: 2
|
||||
2025-12-05 22:02:20.529 UTC [68M] INFO: -> DNS cache records: 238
|
||||
2025-12-05 22:02:20.529 UTC [68M] INFO: -> Known forward destinations: 2
|
||||
2025-12-05 22:02:20.677 UTC [68M] WARNING: Insufficient permissions to set system time (CAP_SYS_TIME required), NTP client not available
|
||||
2025-12-05 22:02:20.677 UTC [68/T70] INFO: NTP server listening on 0.0.0.0:123 (IPv4)
|
||||
2025-12-05 22:02:20.677 UTC [68M] INFO: FTL is running as user pihole (UID 1000)
|
||||
2025-12-05 22:02:20.677 UTC [68M] INFO: Reading certificate from /etc/pihole/tls.pem ...
|
||||
2025-12-05 22:02:20.677 UTC [68M] INFO: Using SSL/TLS certificate file /etc/pihole/tls.pem
|
||||
2025-12-05 22:02:20.678 UTC [68M] INFO: Web server ports:
|
||||
2025-12-05 22:02:20.678 UTC [68M] INFO: - 0.0.0.0:80 (HTTP, IPv4, optional, OK)
|
||||
2025-12-05 22:02:20.678 UTC [68M] INFO: - 0.0.0.0:443 (HTTPS, IPv4, optional, OK)
|
||||
2025-12-05 22:02:20.678 UTC [68M] INFO: - [::]:80 (HTTP, IPv6, optional, OK)
|
||||
2025-12-05 22:02:20.678 UTC [68M] INFO: - [::]:443 (HTTPS, IPv6, optional, OK)
|
||||
2025-12-05 22:02:20.678 UTC [68M] INFO: Restored 0 API sessions from the database
|
||||
2025-12-05 22:02:20.678 UTC [68/T71] INFO: NTP server listening on :::123 (IPv6)
|
||||
2025-12-05 22:02:20.680 UTC [68M] INFO: Blocking status is enabled
|
||||
2025-12-05 22:02:20.784 UTC [68/T72] INFO: Compiled 0 allow and 0 deny regex for 2 clients in 0.0 msec
|
||||
2025-12-07 04:45:01.079 UTC [68/T72] INFO: Gravity database has been updated, reloading now
|
||||
2025-12-07 04:45:01.086 UTC [68/T72] INFO: Compiled 0 allow and 0 deny regex for 2 clients in 0.1 msec
|
||||
108
fake_ChatOllama.py
Normal file
108
fake_ChatOllama.py
Normal file
@@ -0,0 +1,108 @@
|
||||
"""Minimal fallback ChatOllama implementation for testing.
|
||||
|
||||
This provides a tiny `ChatOllama` class that works with expressions
|
||||
like `prompt | llm` by implementing `__ror__` and returns a simple
|
||||
fake response object with a `content` attribute.
|
||||
"""
|
||||
from typing import Any
|
||||
|
||||
|
||||
class FakeResponse:
|
||||
def __init__(self, content: str) -> None:
|
||||
self.content = content
|
||||
|
||||
|
||||
class _Chain:
|
||||
def __init__(self, llm: "ChatOllama", prompt: Any) -> None:
|
||||
self.llm = llm
|
||||
self.prompt = prompt
|
||||
|
||||
def invoke(self, inputs: dict) -> FakeResponse:
|
||||
service = inputs.get("service", "<unknown>")
|
||||
data = inputs.get("data", "")
|
||||
|
||||
# Summarize the provided data so the fake response is slightly useful
|
||||
try:
|
||||
if isinstance(data, list):
|
||||
summary = f"{len(data)} items"
|
||||
elif isinstance(data, str):
|
||||
summary = f"{len(data)} characters"
|
||||
else:
|
||||
summary = type(data).__name__
|
||||
except Exception:
|
||||
summary = "uninspectable"
|
||||
|
||||
content = (
|
||||
f"[FAKE LLM] Analysis for service '{service}': input contains {summary}."
|
||||
)
|
||||
return FakeResponse(content)
|
||||
|
||||
|
||||
class ChatOllama:
|
||||
"""A tiny fake ChatOllama-compatible object.
|
||||
|
||||
Usage (matches fallback import in `log_reader_agent.py`):
|
||||
from fake_ChatOllama import ChatOllama
|
||||
llm = ChatOllama(model="llama3:8b")
|
||||
chain = prompt | llm
|
||||
resp = chain.invoke({"service": "X", "data": "..."})
|
||||
print(resp.content)
|
||||
"""
|
||||
|
||||
def __init__(self, model: str = "llama", base_url: str | None = None, **kwargs) -> None:
|
||||
self.model = model
|
||||
self.base_url = base_url
|
||||
self.kwargs = kwargs
|
||||
|
||||
def __repr__(self) -> str: # pragma: no cover - trivial
|
||||
return f"<ChatOllama model={self.model!r} base_url={self.base_url!r}>"
|
||||
|
||||
def __ror__(self, other: Any) -> _Chain:
|
||||
"""Support `prompt | llm` by returning a chain-like object."""
|
||||
return _Chain(self, other)
|
||||
|
||||
# Convenience: allow direct invoke if someone calls llm.invoke(...)
|
||||
def invoke(self, inputs: dict) -> FakeResponse:
|
||||
return _Chain(self, None).invoke(inputs)
|
||||
|
||||
# Make the object callable so langchain_core can coerce it into a Runnable
|
||||
def __call__(self, inputs: dict, *args, **kwargs) -> FakeResponse:
|
||||
# Support being called with either a plain dict (from manual invoke)
|
||||
# or with a Prompt/ChatPromptValue object (from `prompt | llm` execution).
|
||||
if isinstance(inputs, dict):
|
||||
return self.invoke(inputs)
|
||||
|
||||
# Fallback: coerce non-dict prompt values to text and return a fake response
|
||||
try:
|
||||
# Try common method names for prompt-like objects
|
||||
if hasattr(inputs, "to_string") and callable(inputs.to_string):
|
||||
text = inputs.to_string()
|
||||
elif hasattr(inputs, "to_text") and callable(inputs.to_text):
|
||||
text = inputs.to_text()
|
||||
else:
|
||||
text = str(inputs)
|
||||
except Exception:
|
||||
text = str(inputs)
|
||||
|
||||
content = """A Pi-Hole docker log!
|
||||
|
||||
Let's analyze the service health:
|
||||
|
||||
**System Time**: There is a warning indicating that the system time cannot be set (CAP_SYS_TIME required). This might impact the accuracy of logs and timestamps.
|
||||
|
||||
**NTP Client**: The NTP client is not available, which could lead to issues with synchronizing system time and potentially affecting DNS resolution.
|
||||
|
||||
**DNS Queries**: A significant number of DNS queries are being processed (31484 in this case), indicating that the Pi-Hole is handling a substantial volume of traffic.
|
||||
|
||||
**Cache Records**: The cache records count is relatively low (238), suggesting that the cache might not be as effective at reducing the load on the Pi-Hole.
|
||||
|
||||
**Blocked Queries**: There are 3246 blocked queries, which could indicate a decent amount of malicious traffic being filtered out.
|
||||
|
||||
**Unique Domains and Clients**: There are 2286 unique domains and 2 clients being tracked, suggesting that the Pi-Hole is handling traffic from at least two distinct sources.
|
||||
|
||||
**Web Server Ports**: The web server is listening on HTTP (port 80) and HTTPS (port 443) for both IPv4 and IPv6 traffic.
|
||||
|
||||
**API Sessions**: There are no API sessions restored from the database, which might indicate that the Pi-Hole is not currently interacting with any external services or APIs.
|
||||
|
||||
Overall, it appears that the Pi-Hole is functioning correctly, handling DNS queries, blocking malicious traffic, and maintaining a cache of records. However, the system time issue and lack of NTP client availability might warrant further investigation to ensure accurate logging and timestamping."""
|
||||
return FakeResponse(content)
|
||||
26
llm.py
Normal file
26
llm.py
Normal file
@@ -0,0 +1,26 @@
|
||||
import requests, json
|
||||
|
||||
#docker exec -it ollama ollama run llama3.2
|
||||
|
||||
# Define the local Ollama API endpoint
|
||||
#url = "http://192.168.50.14:11434/api/generate"
|
||||
#url = "http://localhost:11434/api/generate"
|
||||
url = "http://192.168.50.215:11434/api/generate"
|
||||
|
||||
# Send a prompt to the Gemma 3 model
|
||||
payload = {
|
||||
"model": "llama3.2",
|
||||
#"model": "gpt-oss:20b",
|
||||
"prompt": "list all running docker containers"
|
||||
}
|
||||
|
||||
# stream=True tells requests to read the response as a live data stream
|
||||
response = requests.post(url, json=payload, stream=True)
|
||||
#print(response)
|
||||
# Ollama sends one JSON object per line as it generates text
|
||||
for line in response.iter_lines():
|
||||
if line:
|
||||
data = json.loads(line.decode("utf-8"))
|
||||
# Each chunk has a "response" key containing part of the text
|
||||
if "response" in data:
|
||||
print(data["response"], end="", flush=True)
|
||||
10
mail-test.sh
Normal file
10
mail-test.sh
Normal file
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
function curl_mail {
|
||||
curl --url 'smtps://mailout.privat.bahnhof.se:465' --ssl-reqd\
|
||||
--mail-from 'johan@rydson.st'\
|
||||
--mail-rcpt 'johan.p1sson@gmail.com'\
|
||||
--user 'mc536304:Traceur22Traceur'\
|
||||
-T <(echo -e 'From: johan@rydson.st\nTo: johan.p1sson@gmail.com\nSubject: yo-bot Test\n\n'$1)
|
||||
}
|
||||
|
||||
curl_mail "$(python3 ask_llm.py --ip="192.168.50.215" -m "dolphin3:8b" 'you are Yo-bot. Write a short, friendly mail to Johan, ask how he is doing. do not add any mail subject')"
|
||||
61
pr_review.sh
Normal file
61
pr_review.sh
Normal file
@@ -0,0 +1,61 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
printf "Usage: %s [-s|--source <branch>] [-t|--target <branch>] [source target] (target defaults to 'main')\n" "$0"
|
||||
exit 2
|
||||
}
|
||||
|
||||
source_branch=""
|
||||
# default target branch to main
|
||||
target_branch="main"
|
||||
|
||||
# Parse options: -s|--source and -t|--target. Falls back to positional args.
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-s|--source)
|
||||
source_branch="$2"; shift 2;;
|
||||
-t|--target)
|
||||
target_branch="$2"; shift 2;;
|
||||
-h|--help)
|
||||
usage;;
|
||||
--)
|
||||
shift; break;;
|
||||
-*)
|
||||
printf "Unknown option: %s\n" "$1"; usage;;
|
||||
*)
|
||||
if [[ -z "$source_branch" ]]; then
|
||||
source_branch="$1"
|
||||
elif [[ -z "$target_branch" ]]; then
|
||||
target_branch="$1"
|
||||
fi
|
||||
shift;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$source_branch" ]]; then
|
||||
printf "no source branch specified, using current HEAD\n"
|
||||
source_branch="HEAD"
|
||||
else
|
||||
printf "checking out source branch: %s\n" "$source_branch"
|
||||
git checkout "$source_branch"
|
||||
fi
|
||||
|
||||
# Run unit tests
|
||||
printf "\nUnit test results:\n´´´bash\n"
|
||||
python3 -m unittest discover
|
||||
printf "´´´\n"
|
||||
|
||||
# Get list of changed files between source and target branches
|
||||
export CHANGED_FILES=$(git diff --name-only "$source_branch" "$target_branch")
|
||||
for file in $CHANGED_FILES; do
|
||||
if [ -f "${file}" ]; then
|
||||
printf "\nHere is the diff between source (%s) version of %s and target (%s):\n\n" "$source_branch" "$file" "$target_branch"
|
||||
printf "´´´diff\n"
|
||||
git --no-pager diff -U1000 -W "$target_branch" -- "$file"
|
||||
printf "´´´\n"
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
# adjust model context length if needed
|
||||
759
pr_review.txt
Normal file
759
pr_review.txt
Normal file
@@ -0,0 +1,759 @@
|
||||
review my pull request.
|
||||
this files has changed:
|
||||
RPi/fake_smbus.py
|
||||
dro.py
|
||||
test_model.py
|
||||
todo.txt
|
||||
|
||||
test_model.py passes all tests, here is the result:
|
||||
´´´bash
|
||||
smbus2 not available; using fake smbus2.py
|
||||
........
|
||||
----------------------------------------------------------------------
|
||||
Ran 8 tests in 0.000s
|
||||
|
||||
OK
|
||||
´´´
|
||||
|
||||
here is the diff between my version of RPi/fake_smbus.py and main:
|
||||
´´´diff
|
||||
+import tkinter as tk
|
||||
+
|
||||
+encoders = []
|
||||
+class SMBus:
|
||||
+ def __init__(self, bus):
|
||||
+ print(f"fake SMBus initialized on bus {bus}")
|
||||
+ self.bus = bus
|
||||
+
|
||||
+
|
||||
+ def read_i2c_block_data(self, addr, cmd, length):
|
||||
+ data = []
|
||||
+ for encoder in encoders:
|
||||
+ pos_as_byte_array = encoder.get_position().to_bytes(2, 'little', signed=True)
|
||||
+ data.append(pos_as_byte_array[0])
|
||||
+ data.append(pos_as_byte_array[1])
|
||||
+
|
||||
+ #print(f"SMBus data set to: {data}")
|
||||
+ return data
|
||||
+
|
||||
+def createTestEncoder():
|
||||
+ encoder = TestEncoder()
|
||||
+ encoders.append(encoder)
|
||||
+ return encoder
|
||||
+
|
||||
+class TestEncoder:
|
||||
+ """ represent a test encoder controlled by keyboard input """
|
||||
+ def __init__(self):
|
||||
+ self._position = 0
|
||||
+
|
||||
+ def _rotate_cw(self, event=None):
|
||||
+ self._position += 1
|
||||
+
|
||||
+ def _rotate_ccw(self, event=None):
|
||||
+ self._position -= 1
|
||||
+
|
||||
+ def get_position(self) -> int:
|
||||
+ return self._position
|
||||
´´´
|
||||
|
||||
---
|
||||
|
||||
here is the diff between my version of dro.py and main:
|
||||
´´´diff
|
||||
diff --git a/dro.py b/dro.py
|
||||
index 5071466..6e3b919 100755
|
||||
--- a/dro.py
|
||||
+++ b/dro.py
|
||||
@@ -1,396 +1,370 @@
|
||||
#/usr/bin/python3
|
||||
-import math
|
||||
-import encoder
|
||||
-import threading
|
||||
-import time
|
||||
import tkinter as tk
|
||||
from tkinter import simpledialog
|
||||
from enum import IntEnum
|
||||
-import RPi.GPIO as GPIO
|
||||
import atexit
|
||||
import subprocess
|
||||
import getopt
|
||||
import sys
|
||||
import shutil
|
||||
+import logging
|
||||
+
|
||||
+logger = logging.getLogger(__name__)
|
||||
+
|
||||
+# Prefer system-installed `smbus2`, otherwise fall back to local `smbus2.py`.
|
||||
+try:
|
||||
+ import smbus2
|
||||
+except ImportError:
|
||||
+ import RPi.fake_smbus as smbus2 # type: ignore
|
||||
+ logger.warning('smbus2 not available; using fake smbus2.py')
|
||||
|
||||
class XMode(IntEnum):
|
||||
RADIUS = 0
|
||||
DIAMETER = 1
|
||||
|
||||
class Updated(IntEnum):
|
||||
POS_X = 0
|
||||
POS_Z = 1
|
||||
X_MODE = 2
|
||||
FULLSCREEN = 3
|
||||
|
||||
-X_DIRECTION_PIN = 13 #gpio 23
|
||||
-X_CLOCK_PIN = 11 #gpio 24
|
||||
-Z_DIRECTION_PIN = 18 #gpio 17
|
||||
-Z_CLOCK_PIN = 16 #gpio 27
|
||||
-
|
||||
class Axis:
|
||||
""" represent one axis in the model """
|
||||
- def __init__(self, name):
|
||||
- self._position = 0
|
||||
- self._scale = 1
|
||||
+ def __init__(self, name: str):
|
||||
+ self._position = 0 # raw position from encoder
|
||||
+ self._scale = 1.0
|
||||
+ self._offset = 0.0
|
||||
self._name = name
|
||||
|
||||
- def set_position(self, pos):
|
||||
+ def get_raw_position(self):
|
||||
+ """Return the uncompensated raw position (no offset applied)."""
|
||||
+ return self._position
|
||||
+
|
||||
+ def set_position(self, pos: int):
|
||||
self._position = pos
|
||||
|
||||
- def get_position(self):
|
||||
- return self._position
|
||||
+ def set_offset(self, offset: float):
|
||||
+ self._offset = offset
|
||||
|
||||
- def get_position_as_string(self):
|
||||
- return str("{:.2f}".format(self._position))
|
||||
+ def get_position(self):
|
||||
+ return self._position + self._offset
|
||||
|
||||
- def set_scale(self, scale):
|
||||
- self._scale = scale
|
||||
+ def set_scale(self, scale: float):
|
||||
+ self._scale = scale
|
||||
|
||||
- def update_position(self, direction):
|
||||
- self._position += direction * self._scale
|
||||
+ def get_scale(self) -> float:
|
||||
+ """Return the scale factor (mm per step)."""
|
||||
+ return self._scale
|
||||
|
||||
|
||||
class Model:
|
||||
""" represent the DRO model """
|
||||
def __init__(self, fullscreen = False):
|
||||
self._observers = []
|
||||
self._x_mode = XMode.RADIUS # denote x axis mode (radius/diameter)
|
||||
|
||||
self._axis = {}
|
||||
self._axis['x'] = Axis('x')
|
||||
self._axis['z'] = Axis('z')
|
||||
|
||||
self._fullscreen = fullscreen
|
||||
|
||||
def attatch(self, o):
|
||||
self._observers.append(o)
|
||||
|
||||
def notify(self, updated = None):
|
||||
for o in self._observers:
|
||||
o.update(updated)
|
||||
|
||||
- def set_position(self, pos, axis):
|
||||
+ def steps_to_position(self, axis, steps):
|
||||
if axis in self._axis:
|
||||
- factor = 0.5 if axis == 'x' and self.get_x_mode() == XMode.DIAMETER else 1
|
||||
- self._axis[axis].set_position(factor*pos)
|
||||
+ return steps * self._axis[axis].get_scale()
|
||||
+ return 0
|
||||
|
||||
- self.notify(self._string_to_updated_axis(axis))
|
||||
+ def set_position(self, axis, pos):
|
||||
+ """ set position in mm. Always radius for x axis """
|
||||
+ if axis in self._axis:
|
||||
+ if abs(self._axis[axis].get_position() - pos) >= 0.005:
|
||||
+ self._axis[axis].set_position(pos)
|
||||
+ self.notify(self._string_to_updated_axis(axis))
|
||||
|
||||
- def get_position_as_string(self, axis):
|
||||
+ def set_offset(self, axis, offset):
|
||||
+ """ set offset in mm. Always radius for x axis """
|
||||
if axis in self._axis:
|
||||
- return str("{:.2f}".format(self.get_position(axis)))
|
||||
- return None
|
||||
+ self._axis[axis].set_offset(offset)
|
||||
+ self.notify(self._string_to_updated_axis(axis))
|
||||
|
||||
def get_position(self, axis):
|
||||
if axis in self._axis:
|
||||
- factor = 2 if axis == 'x' and self.get_x_mode() == XMode.DIAMETER else 1
|
||||
- return factor*self._axis[axis].get_position()
|
||||
+ return self._axis[axis].get_position()
|
||||
return None
|
||||
|
||||
- def update_position(self, direction, axis):
|
||||
- """ update position of given axis and notify observers """
|
||||
+ def get_position_uncompensated(self, axis):
|
||||
+ """ get position without offset applied """
|
||||
if axis in self._axis:
|
||||
- self._axis[axis].update_position(direction)
|
||||
-
|
||||
- self.notify(self._string_to_updated_axis(axis))
|
||||
+ return self._axis[axis].get_raw_position()
|
||||
+ return None
|
||||
|
||||
def get_x_mode(self):
|
||||
return self._x_mode
|
||||
|
||||
def set_toggle_x_mode(self):
|
||||
if self._x_mode == XMode.RADIUS:
|
||||
self._x_mode = XMode.DIAMETER
|
||||
-
|
||||
elif self._x_mode == XMode.DIAMETER:
|
||||
self._x_mode = XMode.RADIUS
|
||||
|
||||
self.notify(Updated.X_MODE)
|
||||
|
||||
def set_scale(self, axis, scale):
|
||||
if axis in self._axis:
|
||||
self._axis[axis].set_scale(scale)
|
||||
|
||||
def set_fullscreen(self, v):
|
||||
self._fullscreen = v
|
||||
self.notify(Updated.FULLSCREEN)
|
||||
|
||||
def is_fullscreen(self):
|
||||
return self._fullscreen
|
||||
|
||||
def _string_to_updated_axis(self, axis_str):
|
||||
if axis_str == 'x':
|
||||
return Updated.POS_X
|
||||
if axis_str == 'z':
|
||||
return Updated.POS_Z
|
||||
|
||||
return None
|
||||
|
||||
class Controller:
|
||||
""" represent the controller in the MVC pattern """
|
||||
def __init__(self, model, test_mode = False):
|
||||
self._model = model
|
||||
self._test_mode = test_mode
|
||||
|
||||
- def handle_x_position_update(self, dir):
|
||||
- self._model.update_position(dir, 'x')
|
||||
+ self._bus = smbus2.SMBus(1)
|
||||
+ self._address = 0x08 # Arduino I2C Address
|
||||
+
|
||||
+ def handle_x_position_update(self, steps):
|
||||
+ self._model.set_position('x', self._model.steps_to_position('x', steps))
|
||||
|
||||
- def handle_z_position_update(self, dir):
|
||||
- self._model.update_position(dir, 'z')
|
||||
+ def handle_z_position_update(self, steps):
|
||||
+ self._model.set_position('z', self._model.steps_to_position('z', steps))
|
||||
|
||||
def handle_btn_x0_press(self):
|
||||
- self._model.set_position(0, 'x')
|
||||
+ self._model.set_offset('x', -self._model.get_position_uncompensated('x'))
|
||||
|
||||
def handle_btn_z0_press(self):
|
||||
- self._model.set_position(0, 'z')
|
||||
+ self._model.set_offset('z', -self._model.get_position_uncompensated('z'))
|
||||
|
||||
def handle_btn_toggle_x_mode(self):
|
||||
self._model.set_toggle_x_mode()
|
||||
|
||||
def hanlde_btn_x(self):
|
||||
title = 'current radius' if self._model.get_x_mode() == XMode.RADIUS else 'current diameter'
|
||||
pos = simpledialog.askfloat('x', title, initialvalue=0.0, minvalue=-4000.0, maxvalue=4000.0)
|
||||
+
|
||||
+ if self._model.get_x_mode() == XMode.DIAMETER and pos is not None:
|
||||
+ pos = pos / 2.0
|
||||
+
|
||||
if pos is not None:
|
||||
- self._model.set_position(pos, 'x')
|
||||
+ self._model.set_offset('x', pos-self._model.get_position_uncompensated('x'))
|
||||
|
||||
def hanlde_btn_z(self):
|
||||
pos = simpledialog.askfloat('z', 'current z position', initialvalue=0.0, minvalue=-4000.0, maxvalue=4000.0)
|
||||
if pos is not None:
|
||||
- self._model.set_position(pos, 'z')
|
||||
+ self._model.set_offset('z', pos-self._model.get_position_uncompensated('z'))
|
||||
|
||||
def toggle_fullscreen(self, event=None):
|
||||
if self._model.is_fullscreen() == True:
|
||||
self._model.set_fullscreen(False)
|
||||
else:
|
||||
self._model.set_fullscreen(True)
|
||||
|
||||
def end_fullscreen(self, event=None):
|
||||
self._model.set_fullscreen(False)
|
||||
|
||||
def handle_btn_calc(self):
|
||||
calculator_cmd = shutil.which('galculator')
|
||||
|
||||
if calculator_cmd != None:
|
||||
try:
|
||||
subprocess.Popen(calculator_cmd)
|
||||
except:
|
||||
print("can't start calculator")
|
||||
else:
|
||||
print("can't find calculator")
|
||||
|
||||
def shutdown(self):
|
||||
if not self._test_mode:
|
||||
subprocess.call('sudo shutdown -h now', shell=True)
|
||||
else:
|
||||
print('will not shutdown in test mode')
|
||||
|
||||
+ def poll_i2c_data(self):
|
||||
+ """ poll I2C data from Arduino """
|
||||
+ try:
|
||||
+ data = self._bus.read_i2c_block_data(self._address, 0, 4)
|
||||
+ #self.handle_x_position_update(int(data[0] + (data[1] << 8)))
|
||||
+ self.handle_x_position_update(int.from_bytes(bytes(data[0:2]), 'little', signed=True))
|
||||
+ self.handle_z_position_update(int.from_bytes(bytes(data[2:4]), 'little', signed=True))
|
||||
+ except Exception as e:
|
||||
+ logger.error(f"I2C read error: {e}")
|
||||
+
|
||||
class View:
|
||||
""" represent the view in the MVC pattern """
|
||||
def __init__(self, model, test_mode = False):
|
||||
self._model = model
|
||||
self._model.attatch(self)
|
||||
self._controller = Controller(model, test_mode)
|
||||
- self.x_dirty = False
|
||||
- self.z_dirty = False
|
||||
-
|
||||
- # use tkinter's after() loop on the main thread instead of a background thread
|
||||
- # This avoids cross-thread UI calls and reduces context switching overhead.
|
||||
|
||||
self.window = tk.Tk()
|
||||
self.window.resizable(False, False)
|
||||
#self.window.attributes('-zoomed', zoomed) # This just maximizes it so we can see the window. It's nothing to do with fullscreen.
|
||||
self.window.attributes("-fullscreen", self._model.is_fullscreen())
|
||||
|
||||
w_frame = tk.Frame(self.window)
|
||||
w_frame.grid(row=0, column=0, sticky='n')
|
||||
e_frame = tk.Frame(self.window)
|
||||
e_frame.grid(row=0, column=1, sticky='n')
|
||||
|
||||
# x frame
|
||||
x_frame = tk.Frame(w_frame, relief=tk.GROOVE, borderwidth=3)
|
||||
x_frame.grid(row=0, column=0, padx=5, pady=5)
|
||||
# use a StringVar for quicker updates from the main thread
|
||||
- self.x_var = tk.StringVar(value=self._model.get_position_as_string('x'))
|
||||
+ self.x_var = tk.StringVar(value=self._position_to_string(self._model.get_position('x')))
|
||||
self.x_label = tk.Label(x_frame, textvariable=self.x_var, anchor="e", font=("Helvetica", 80), fg="green", bg="black", width=6)
|
||||
self.x_label.grid(row=0, column=0, padx=5, pady=5)
|
||||
btn_set_x = tk.Button(master=x_frame, text="X", padx=5, pady=5, font=("Helvetica", 80), command=self._controller.hanlde_btn_x)
|
||||
btn_set_x.grid(row=0, column=1, padx=5, pady=5, sticky="nw")
|
||||
btn_set_x0 = tk.Button(master=x_frame, text="X_0", padx=5, pady=5, font=("Helvetica", 80), command=self._controller.handle_btn_x0_press)
|
||||
btn_set_x0.grid(row=0, column=2, padx=5, pady=5, sticky="nw")
|
||||
|
||||
# Z frame
|
||||
z_frame = tk.Frame(w_frame, relief=tk.GROOVE, borderwidth=3)
|
||||
z_frame.grid(row=1, column=0, padx=5, pady=5)#, sticky="nw")
|
||||
- self.z_var = tk.StringVar(value=self._model.get_position_as_string('z'))
|
||||
+ self.z_var = tk.StringVar(value=self._position_to_string(self._model.get_position('z')))
|
||||
self.z_label = tk.Label(z_frame, textvariable=self.z_var, anchor="e", font=("Helvetica", 80), fg="green", bg="black", width=6)
|
||||
self.z_label.grid(row=0, column=0, padx=5, pady=5)
|
||||
btn_set_z = tk.Button(master=z_frame, text="Z", padx=5, pady=5, font=("Helvetica", 80), command=self._controller.hanlde_btn_z)
|
||||
btn_set_z.grid(row=0, column=1, padx=5, pady=5, sticky="nw")
|
||||
btn_set_z0 = tk.Button(master=z_frame, text="Z_0", padx=5, pady=5, font=("Helvetica", 80), command=self._controller.handle_btn_z0_press)
|
||||
btn_set_z0.grid(row=0, column=2, padx=5, pady=5, sticky="nw")
|
||||
|
||||
# status frame
|
||||
status_frame = tk.Frame(w_frame, relief=tk.GROOVE, borderwidth=3)
|
||||
status_frame.grid(row=3, column=0, padx=5, pady=5, sticky='e')
|
||||
self.x_mode_var = tk.StringVar(value=str(self._mode_to_text()))
|
||||
self.x_mode_label = tk.Label(status_frame, textvariable=self.x_mode_var, font=("Helvetica", 80), fg="red")
|
||||
self.x_mode_label.grid(row=0, column=0, padx=5, pady=5, sticky="e")
|
||||
|
||||
# button frame
|
||||
btn_frame = tk.Frame(e_frame, relief=tk.GROOVE, borderwidth=3)
|
||||
btn_frame.grid(row=0, column=0, padx=5, pady=5)
|
||||
|
||||
btn_rd = tk.Button(master=btn_frame, text="r/D", padx=5, pady=5, font=("Helvetica", 80), width=4, command=self._controller.handle_btn_toggle_x_mode)
|
||||
btn_rd.grid(row=1, column=0, padx=5, pady=5, sticky="nw")
|
||||
|
||||
btn_calc = tk.Button(master=btn_frame, text="Calc", padx=5, pady=5, font=("Helvetica", 80), width=4, command=self._controller.handle_btn_calc)
|
||||
btn_calc.grid(row=2, column=0, padx=5, pady=15, sticky="nw")
|
||||
|
||||
#btn_enter = tk.Button(master=btn_frame, text="TBD", padx=5, pady=5, font=("Helvetica", 80), width=4)#, command=self._controller.horizontal_end)
|
||||
self.photo = tk.PhotoImage(file="power.png")
|
||||
btn_off = tk.Button(master=btn_frame, image=self.photo, command=self._controller.shutdown)
|
||||
btn_off.grid(row=3, column=0, padx=5, pady=15, sticky="nw")
|
||||
|
||||
# bind keyboar inputs
|
||||
self.window.bind("<F11>", self._controller.toggle_fullscreen)
|
||||
self.window.bind("<Escape>", self._controller.end_fullscreen)
|
||||
|
||||
# schedule the GUI update loop on the main thread
|
||||
- self._last_x = self._model.get_position('x')
|
||||
- self._last_z = self._model.get_position('z')
|
||||
self._update_interval_ms = 100 # update at 10Hz by default
|
||||
- self.window.after(self._update_interval_ms, self._update_view)
|
||||
+ self.window.after(self._update_interval_ms, self._poll_input)
|
||||
self.window.update()
|
||||
- #self.update()
|
||||
|
||||
def _mode_to_text(self):
|
||||
if self._model.get_x_mode() == XMode.DIAMETER:
|
||||
return 'D'
|
||||
if self._model.get_x_mode() == XMode.RADIUS:
|
||||
return 'r'
|
||||
|
||||
def _position_to_string(self, pos):
|
||||
return str("{:.2f}".format(pos))
|
||||
|
||||
def start(self):
|
||||
self.window.mainloop()
|
||||
|
||||
def update_x(self):
|
||||
""" update x position in view (main thread) """
|
||||
- self.x_dirty = False
|
||||
pos = self._model.get_position('x')
|
||||
- # avoid frequent updates when change is negligible
|
||||
- if abs(pos - self._last_x) >= 0.005:
|
||||
- self._last_x = pos
|
||||
- self.x_var.set(self._position_to_string(pos))
|
||||
+ if self._model.get_x_mode() == XMode.DIAMETER:
|
||||
+ pos = pos * 2.0
|
||||
+ self.x_var.set(self._position_to_string(pos))
|
||||
|
||||
def update_z(self):
|
||||
""" update z position in view (main thread) """
|
||||
- self.z_dirty = False
|
||||
pos = self._model.get_position('z')
|
||||
- if abs(pos - self._last_z) >= 0.005:
|
||||
- self._last_z = pos
|
||||
- self.z_var.set(self._position_to_string(pos))
|
||||
+ self.z_var.set(self._position_to_string(pos))
|
||||
|
||||
def update(self, updated = None):
|
||||
""" called by model when something has changed """
|
||||
if updated == Updated.POS_X:
|
||||
- self.x_dirty = True
|
||||
+ self.update_x()
|
||||
if updated == Updated.POS_Z:
|
||||
- self.z_dirty = True
|
||||
+ self.update_z()
|
||||
if updated == Updated.X_MODE:
|
||||
# main thread will pick this up through the scheduled update
|
||||
self.x_mode_var.set(str(self._mode_to_text()))
|
||||
self.update_x()
|
||||
if updated == Updated.FULLSCREEN:
|
||||
self.window.attributes("-fullscreen", self._model.is_fullscreen())
|
||||
|
||||
- def _update_view(self):
|
||||
+ def _poll_input(self):
|
||||
""" scheduled function (runs on tkinter main loop) to update the view."""
|
||||
try:
|
||||
- if self.x_dirty:
|
||||
- self.update_x()
|
||||
- if self.z_dirty:
|
||||
- self.update_z()
|
||||
+ # request data
|
||||
+ self._controller.poll_i2c_data()
|
||||
+
|
||||
finally:
|
||||
# re-schedule
|
||||
- self.window.after(self._update_interval_ms, self._update_view)
|
||||
-
|
||||
-@atexit.register
|
||||
-def cleanup():
|
||||
- GPIO.cleanup(X_CLOCK_PIN)
|
||||
- GPIO.cleanup(X_DIRECTION_PIN)
|
||||
- GPIO.cleanup(Z_CLOCK_PIN)
|
||||
- GPIO.cleanup(Z_DIRECTION_PIN)
|
||||
- print("done")
|
||||
-
|
||||
-def test(arg):
|
||||
- print(arg)
|
||||
-
|
||||
-
|
||||
-class TestEncoder:
|
||||
- """ represent a test encoder controlled by keyboard input """
|
||||
- def __init__(self, view, event_cbk, inc_key = 'a', dec_key = 'b'):
|
||||
-
|
||||
- view.window.bind(inc_key, self._rotate_cw)
|
||||
- view.window.bind(dec_key, self._rotate_ccw)
|
||||
-
|
||||
- self._event_cbk = event_cbk
|
||||
-
|
||||
- def _rotate_cw(self, event=None):
|
||||
- self._event_cbk(encoder.Direction.CW)
|
||||
-
|
||||
- def _rotate_ccw(self, event=None):
|
||||
- self._event_cbk(encoder.Direction.CCW)
|
||||
+ self.window.after(self._update_interval_ms, self._poll_input)
|
||||
|
||||
def main():
|
||||
_zoomed = False
|
||||
_test = False
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], "", ["zoomed", "test"])
|
||||
except getopt.GetoptError as err:
|
||||
# print help information and exit:
|
||||
print(err) # will print something like "option -a not recognized"
|
||||
sys.exit(2)
|
||||
|
||||
for o, a in opts:
|
||||
- print(o)
|
||||
- print(a)
|
||||
if o == "--zoomed":
|
||||
_zoomed = True
|
||||
elif o == "--test":
|
||||
_test = True
|
||||
else:
|
||||
assert False, "unhandled option"
|
||||
|
||||
model = Model(_zoomed)
|
||||
view = View(model, _test)
|
||||
|
||||
model.set_scale('x', -2.5/200) #negative to flip direction
|
||||
model.set_scale('z', 90/1000)
|
||||
|
||||
- GPIO.setwarnings(False) ## Turn off warnings
|
||||
- GPIO.setmode(GPIO.BOARD) ## Use BOARD pin numbering
|
||||
- GPIO.setup(X_DIRECTION_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
|
||||
- GPIO.setup(Z_DIRECTION_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
|
||||
- GPIO.setup(X_CLOCK_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
|
||||
- GPIO.setup(Z_CLOCK_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
|
||||
-
|
||||
if _test:
|
||||
- x_test_encoder = TestEncoder(view, view._controller.handle_x_position_update, 'a', 'z')
|
||||
- z_test_encoder = TestEncoder(view, view._controller.handle_z_position_update, 's', 'x')
|
||||
-
|
||||
- x_encoder = encoder.Encoder(X_CLOCK_PIN, X_DIRECTION_PIN, view._controller.handle_x_position_update)
|
||||
- z_encoder = encoder.Encoder(Z_CLOCK_PIN, Z_DIRECTION_PIN, view._controller.handle_z_position_update)
|
||||
-
|
||||
- GPIO.add_event_detect(X_CLOCK_PIN, GPIO.RISING)
|
||||
- GPIO.add_event_detect(Z_CLOCK_PIN, GPIO.RISING)
|
||||
-
|
||||
- GPIO.add_event_callback(X_CLOCK_PIN, x_encoder.update)
|
||||
- GPIO.add_event_callback(Z_CLOCK_PIN, z_encoder.update)
|
||||
+ try:
|
||||
+ x_test_encoder = smbus2.createTestEncoder()
|
||||
+ view.window.bind('a', x_test_encoder._rotate_cw)
|
||||
+ view.window.bind('z', x_test_encoder._rotate_ccw)
|
||||
+ z_test_encoder = smbus2.createTestEncoder()
|
||||
+ view.window.bind('s', z_test_encoder._rotate_cw)
|
||||
+ view.window.bind('x', z_test_encoder._rotate_ccw)
|
||||
+ except:
|
||||
+ logger.error("couldn't create test encoders...")
|
||||
|
||||
view.start()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
´´´
|
||||
|
||||
---
|
||||
|
||||
here is the diff between my version of test_model.py and main:
|
||||
´´´diff
|
||||
diff --git a/test_model.py b/test_model.py
|
||||
index dae5311..91e21b5 100644
|
||||
--- a/test_model.py
|
||||
+++ b/test_model.py
|
||||
@@ -1,95 +1,93 @@
|
||||
import unittest
|
||||
import dro
|
||||
-import encoder
|
||||
|
||||
class Observer:
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
self.nbr_of_calls = 0
|
||||
self.has_been_called = False
|
||||
|
||||
def update(self, updated = None):
|
||||
self.nbr_of_calls += 1
|
||||
self.has_been_called = True
|
||||
self.updated = updated
|
||||
|
||||
def reset(self):
|
||||
self.nbr_of_calls = 0
|
||||
self.has_been_called = False
|
||||
|
||||
class TestModel(unittest.TestCase):
|
||||
#def check_pos(self, actual, expected):
|
||||
# self.assertTrue(int(self.model.get_position_as_string('x')) == 1)
|
||||
|
||||
def setUp(self):
|
||||
self.model = dro.Model()
|
||||
|
||||
def test_init(self):
|
||||
self.assertEqual(self.model.get_position('x'), 0)
|
||||
self.assertEqual(self.model.get_position('z'), 0)
|
||||
|
||||
- def test_notify_observers(self):
|
||||
- o = Observer()
|
||||
- self.model.attatch(o)
|
||||
-
|
||||
- self.assertFalse(o.has_been_called)
|
||||
- self.model.update_position(encoder.Direction.CW, 'x')
|
||||
- self.assertTrue(o.has_been_called)
|
||||
- self.assertTrue(int(self.model.get_position('x')) == 1)
|
||||
- self.assertTrue(o.updated == dro.Updated.POS_X)
|
||||
-
|
||||
- o.reset()
|
||||
-
|
||||
- self.model.update_position(encoder.Direction.CW, 'z')
|
||||
- self.assertTrue(int(self.model.get_position('z')) == 1)
|
||||
- self.assertTrue(o.has_been_called)
|
||||
- self.assertTrue(int(self.model.get_position('z')) == 1)
|
||||
- self.assertTrue(o.updated == dro.Updated.POS_Z)
|
||||
-
|
||||
def test_wrong_axis(self):
|
||||
o = Observer()
|
||||
self.model.attatch(o)
|
||||
- self.model.set_position(1, 'apa')
|
||||
+ self.model.set_position('apa', 1)
|
||||
|
||||
self.assertFalse(o.has_been_called)
|
||||
|
||||
def test_set_pos(self):
|
||||
o = Observer()
|
||||
self.model.attatch(o)
|
||||
|
||||
- self.model.set_position(1, 'x')
|
||||
+ self.model.set_position('x', 1)
|
||||
self.assertTrue(o.updated == dro.Updated.POS_X)
|
||||
- self.model.set_position(-11, 'z')
|
||||
+ self.model.set_position('z', -11)
|
||||
self.assertTrue(o.updated == dro.Updated.POS_Z)
|
||||
|
||||
self.assertEqual(o.nbr_of_calls, 2)
|
||||
self.assertEqual(int(self.model.get_position('x')), 1)
|
||||
self.assertEqual(int(self.model.get_position('z')), -11)
|
||||
|
||||
def test_get_pos_as_string(self):
|
||||
- self.model.set_position(-11.49, 'x')
|
||||
+ self.model.set_position('x', -11.49)
|
||||
|
||||
self.assertEqual(self.model.get_position('x'), -11.49)
|
||||
|
||||
def test_toogle_x_mode(self):
|
||||
o = Observer()
|
||||
self.model.attatch(o)
|
||||
|
||||
- self.model.set_position(-11.49, 'x')
|
||||
+ self.model.set_position('x', -11.49)
|
||||
self.model.set_toggle_x_mode()
|
||||
- self.assertEqual(self.model.get_position('x'), -22.98)
|
||||
+ self.assertEqual(self.model.get_position('x'), -11.49)
|
||||
self.assertTrue(o.updated == dro.Updated.X_MODE)
|
||||
|
||||
- def test_set_diam(self):
|
||||
- if self.model.get_x_mode() == dro.XMode.RADIUS:
|
||||
- self.model.set_toggle_x_mode()
|
||||
- self.assertEqual(self.model.get_x_mode(), dro.XMode.DIAMETER)
|
||||
-
|
||||
- self.model.set_position(10, 'x')
|
||||
- self.assertEqual(self.model.get_position('x'), 10)
|
||||
- self.model.set_toggle_x_mode()
|
||||
- self.assertEqual(self.model.get_position('x'), 5)
|
||||
+ def test_set_position_no_notify_if_unchanged(self):
|
||||
+ o = Observer()
|
||||
+ self.model.attatch(o)
|
||||
+
|
||||
+ self.model.set_position('x', 10)
|
||||
+ self.assertEqual(o.nbr_of_calls, 1)
|
||||
+
|
||||
+ self.model.set_position('x', 10)
|
||||
+ self.assertEqual(o.nbr_of_calls, 1) # No change, no notify
|
||||
+
|
||||
+ self.model.set_position('x', 20)
|
||||
+ self.assertEqual(o.nbr_of_calls, 2) # Changed, should notify
|
||||
+
|
||||
+ def test_set_position_no_notify_if_small_change(self):
|
||||
+ o = Observer()
|
||||
+ self.model.attatch(o)
|
||||
+
|
||||
+ self.model.set_position('x', 10)
|
||||
+ self.assertEqual(o.nbr_of_calls, 1)
|
||||
+
|
||||
+ self.model.set_position('x', 10+0.003)
|
||||
+ self.assertEqual(o.nbr_of_calls, 1) # No change, no notify
|
||||
|
||||
+ def test_steps_to_position(self):
|
||||
+ self.model.set_scale('x', 2.5/200) # 200 steps per unit
|
||||
+ pos = self.model.steps_to_position('x', 500)
|
||||
+ self.assertEqual(pos, 6.25) # 200 * 1.5 = 300 steps
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
´´´
|
||||
|
||||
---
|
||||
|
||||
here is the diff between my version of todo.txt and main:
|
||||
´´´diff
|
||||
diff --git a/todo.txt b/todo.txt
|
||||
index cf193f0..fd78b5e 100644
|
||||
--- a/todo.txt
|
||||
+++ b/todo.txt
|
||||
@@ -1,46 +1,50 @@
|
||||
-askfloat funkar inte med alltid??????
|
||||
|
||||
-tester för r/D och ser/read position
|
||||
+askfloat funkar inte med alltid??????
|
||||
|
||||
+KLAR i2c:
|
||||
+ handle_btn_x0_press måste sätta en offset i axis som gör att offset+position blir 0
|
||||
+ self._model.set_offset('x', -get_position)
|
||||
+ motsvarande för hanlde_btn_x
|
||||
+
|
||||
KLART
|
||||
desktop-filen pekar på ett shell-script som startar dro.py
|
||||
dro.desktop autostartar inte???
|
||||
men går att starta via file-explorer?????
|
||||
|
||||
KLART
|
||||
stänga av med shutdown utan lösen:
|
||||
sudo visudo
|
||||
user_name ALL=(ALL) NOPASSWD: /sbin/poweroff, /sbin/reboot, /sbin/shutdown
|
||||
|
||||
KLART
|
||||
stänga av med fysisk knapp:
|
||||
"adding this to /boot/config.txt:
|
||||
dtoverlay=gpio-shutdown
|
||||
|
||||
KLART avstängningsknapp
|
||||
knapp som kortsluter pin 5 (GPIO3, grön) och jord
|
||||
och
|
||||
"adding this to /boot/config.txt:
|
||||
dtoverlay=gpio-shutdown
|
||||
"
|
||||
och
|
||||
https://askubuntu.com/questions/168879/shutdown-from-terminal-without-entering-password
|
||||
|
||||
SW
|
||||
knapp till funktion/command:
|
||||
subprocess.call('sudo shutdown -h now', shell=True)
|
||||
|
||||
utöka test.py
|
||||
*funkar det med olika ISR? eller måste det vara en central?
|
||||
VERKAR FUNKA
|
||||
|
||||
lägg till z också...
|
||||
GJODE ETT TEST MED z_clock_pin på gpio13 FUNKAR...
|
||||
|
||||
HW funkar inte
|
||||
testa enligt c:\Users\johan\Documents\verkstad\emco compact 5\bob\my-bob.drawio
|
||||
// 1. 4.7kOhm motstånd pull up, inte pull up i HW...
|
||||
// verkar bli för liten spänning
|
||||
eller
|
||||
2. inget motstånd, pull up i HW... FUNKAR...
|
||||
det blir 3.22V HIGH på GPIO
|
||||
´´´
|
||||
Reference in New Issue
Block a user