diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md
new file mode 100644
index 0000000..43c8d4c
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug.md
@@ -0,0 +1,43 @@
+---
+name: Bug Report
+about: Submit a bug report
+labels: "bug"
+title: '[BUG]'
+---
+
+
+**Bug Report**
+
+(A clear and concise description of what the bug is.)
+
+**To Reproduce**
+
+```python
+# Ideally, a small sample program that demonstrates the problem.
+```
+
+**Expected Behavior**
+
+
+
+**Actual Behavior**
+
+
+
+**Your Environment**
+
+
+
+- SASPy version used:
+- Python version used:
+- O.S. running the code:
+- VLT Vendor and Model:
+
+**Connection Method (check one please)**
+- [ ] Prolific USB Cable
+- [ ] Raspberry miniUART
+- [ ] RS-232 to TTL (Like MAX3323)
+- [ ] Other:
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/feature.md b/.github/ISSUE_TEMPLATE/feature.md
new file mode 100644
index 0000000..e9c1758
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature.md
@@ -0,0 +1,15 @@
+---
+name: Feature Proposal
+about: This is a template for proposing a new feature to SASPy
+title: '[FEATURE]'
+---
+
+**Author**:
+
+### Summary of the feature being proposed
+(write here)
+
+### What value does this feature bring to SASPy ?
+(write here)
+### Are you willing to implement this feature yourself?
+(write here)
diff --git a/.github/workflows/versioning.yml b/.github/workflows/versioning.yml
new file mode 100644
index 0000000..6e0218a
--- /dev/null
+++ b/.github/workflows/versioning.yml
@@ -0,0 +1,24 @@
+name: Semantic Release
+
+on:
+ push:
+ branches:
+ - master
+
+jobs:
+ release:
+ runs-on: ubuntu-latest
+ concurrency: release
+ permissions:
+ id-token: write
+ contents: write
+
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+
+ - name: Python Semantic Release
+ uses: python-semantic-release/python-semantic-release@master
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 894a44c..cdf5167 100644
--- a/.gitignore
+++ b/.gitignore
@@ -35,6 +35,9 @@ MANIFEST
pip-log.txt
pip-delete-this-directory.txt
+# Local testing
+example.py
+
# Unit test / coverage reports
htmlcov/
.tox/
@@ -102,3 +105,8 @@ venv.bak/
# mypy
.mypy_cache/
+
+# IDEs
+.idea
+
+.benchmark
\ No newline at end of file
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
new file mode 100644
index 0000000..672a0b2
--- /dev/null
+++ b/.readthedocs.yaml
@@ -0,0 +1,32 @@
+# .readthedocs.yaml
+# Read the Docs configuration file
+# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
+
+# Required
+version: 2
+
+# Set the OS, Python version and other tools you might need
+build:
+ os: ubuntu-22.04
+ tools:
+ python: "3.10"
+ # You can also specify other tool versions:
+ # nodejs: "19"
+ # rust: "1.64"
+ # golang: "1.19"
+
+# Build documentation in the "docs/" directory with Sphinx
+sphinx:
+ configuration: docs/conf.py
+
+# Optionally build your docs in additional formats such as PDF and ePub
+formats:
+ - pdf
+
+
+# Optional but recommended, declare the Python requirements required
+# to build your documentation
+# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
+python:
+ install:
+ - requirements: docs/requirements.txt
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..1697b3a
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,365 @@
+# CHANGELOG
+
+
+
+## v0.0.1 (2024-03-02)
+
+### Fix
+
+* fix: adding var and function renaming
+
+check_last_transaction it must be modifiable. There are machines that… ([`eac5177`](https://github.com/zacharytomlinson/saspy/commit/eac51775421e621f93945e589d870ea7f700d6e5))
+
+### Unknown
+
+* check_last_transaction it must be modifiable. There are machines that change the transaction number after receiving the signal, but there are also machines that will change the number after responding with Full transfer successful to aft_clean_transaction_poll.
+With this option, you are deprived of one check, but you avoid an unexpected error occurring at an inconvenient moment.
+Use check_last_transaction=False only for machines that do not change a transaction number ([`cf68fd7`](https://github.com/zacharytomlinson/saspy/commit/cf68fd71314686cc7ddcc2e9b708e246f3e7e61a))
+
+
+## v0.0.0 (2024-03-02)
+
+### Chore
+
+* chore: adding ci for semantic-release ([`cfd0202`](https://github.com/zacharytomlinson/saspy/commit/cfd0202ec5c8003c2430bab5ea0d4da13eefd8e2))
+
+* chore: readme update to integrate readthedocs
+
+Signed-off-by: Antonio D'Angelo <antonio.dangelo@wtbss.com> ([`a0958ac`](https://github.com/zacharytomlinson/saspy/commit/a0958acdc8fe9be2b81baa7bd81aa347552dd2d4))
+
+* chore: readthedocs should work now
+
+Signed-off-by: Antonio D'Angelo <antonio.dangelo@wtbss.com> ([`d7c4afb`](https://github.com/zacharytomlinson/saspy/commit/d7c4afb108b2368f41fd355352cdb1fb63ec02de))
+
+* chore: way to much to summarize.
+
+- Added more comments (still to end...)
+- Fixed few typo (again)
+- Fixed wrong mapping (again)
+- cleaned code (again)
+
+Signed-off-by: Antonio D'Angelo <antonio.dangelo@wtbss.com> ([`84b9bde`](https://github.com/zacharytomlinson/saspy/commit/84b9bdeb21d204977144d86f5da78bfc66bb8d2a))
+
+* chore: way to much to summarize.
+
+- Added comments (still to end...)
+- Fixed wrong func call/methods
+- Fixed wrong mapping
+- Removed eft (is legacy in some model...but eft has been removed)
+- cleaned code
+- i dont remember what else i did....sorry
+
+Signed-off-by: Antonio D'Angelo <antonio.dangelo@wtbss.com> ([`86728d1`](https://github.com/zacharytomlinson/saspy/commit/86728d1899c946be23947363bb8a33653532901c))
+
+* chore(feat): Added "perpetual" or "infinite" mode where if true the lib will try forever to connect to the vlt. Default value is False
+
+Signed-off-by: Antonio D'Angelo <antonio.dangelo@wtbss.com> ([`4eb40b6`](https://github.com/zacharytomlinson/saspy/commit/4eb40b674c325a68d0eb8bf131972f9eec3896eb))
+
+* chore: updated the example in accordance with new features
+
+Signed-off-by: Antonio D'Angelo <antonio.dangelo@wtbss.com> ([`da94329`](https://github.com/zacharytomlinson/saspy/commit/da94329310b9c334ef547246aa8f3d242254daea))
+
+* chore: updated, again, config.yml to avoid type coercion during config var retrieval
+
+Signed-off-by: Antonio D'Angelo <antonio.dangelo@wtbss.com> ([`df773db`](https://github.com/zacharytomlinson/saspy/commit/df773db80d8b3830b2f1ea2d02d6151335760c04))
+
+* chore: fix function is_open, was calling a wrong method. How the heck did it work till now ?
+
+Signed-off-by: Antonio D'Angelo <antonio.dangelo@wtbss.com> ([`566bf17`](https://github.com/zacharytomlinson/saspy/commit/566bf179709c2776ba72d7a4c00e4a34398fd866))
+
+* chore: updated config.yml in order to be more in line with common settings and updated default timeout as said by @dedalgr ([`c886351`](https://github.com/zacharytomlinson/saspy/commit/c8863518f232b8b5743a082782869ee947252391))
+
+* chore: update bug template and relative typo fix ([`2b7867b`](https://github.com/zacharytomlinson/saspy/commit/2b7867b9b205ef1b43d8f8d3a6206a34284d5af9))
+
+* chore: fix bug #18 ([`0ef0718`](https://github.com/zacharytomlinson/saspy/commit/0ef071813283c451f1fa1aaa3da584e7fb082766))
+
+* chore: typo error fix ([`738f33a`](https://github.com/zacharytomlinson/saspy/commit/738f33a30e9e12c8375a04c4a9ff3f4adb0f3fe8))
+
+* chore: bug.md update ([`1fbd0b9`](https://github.com/zacharytomlinson/saspy/commit/1fbd0b95c2df2243464bb6820d16729dc1dbb31e))
+
+* chore: feature template ([`a4547a6`](https://github.com/zacharytomlinson/saspy/commit/a4547a673c93455aa5deb3caad81733ae1d114d8))
+
+* chore: bug template ([`d110043`](https://github.com/zacharytomlinson/saspy/commit/d110043f39cc1c7701996603ccde9418a449d43f))
+
+* chore: readme update
+
+Made more in line with the project and gave some guidelines ([`bc99e3a`](https://github.com/zacharytomlinson/saspy/commit/bc99e3aa7dccc67058c91e5085bab1e104a62dbf))
+
+* chore: fixed some missing translation and removed unused constant ([`86a7902`](https://github.com/zacharytomlinson/saspy/commit/86a790212f04400d820adca055d5c6022b609f9e))
+
+* chore: fix var name from constant not initializated to self.poll_timeout ([`59ef4e5`](https://github.com/zacharytomlinson/saspy/commit/59ef4e5dae244cf72c30c0c262c2376adf3d93a7))
+
+* chore: readme update
+
+Added logo for library ([`ce40a2d`](https://github.com/zacharytomlinson/saspy/commit/ce40a2d8e1a67da895b67ebb0f9f4f83718457cf))
+
+* chore: fix conf.py path ([`c1e0792`](https://github.com/zacharytomlinson/saspy/commit/c1e0792d98217a370a08a9a4859abab4e4c83b80))
+
+* chore: docs, first draft ([`1c2f245`](https://github.com/zacharytomlinson/saspy/commit/1c2f2454eb813fc7e01c2aa0858f3e0d7dcf568c))
+
+* chore: gitignore update to not push around the benchmark folder ([`210aaa6`](https://github.com/zacharytomlinson/saspy/commit/210aaa66c1d1ec0c30c2b92c6847bf905327da6a))
+
+* chore: test case to be done ([`7813d65`](https://github.com/zacharytomlinson/saspy/commit/7813d65ffb2c89d830652bcbdab3f712ce57f0fd))
+
+* chore: code reformatting, use of models, remnoved dictionaries, added read the docs config file, docs shpinx files ([`beb7427`](https://github.com/zacharytomlinson/saspy/commit/beb7427b47d6f92c8c4bac9b6be8e956fce7cf3f))
+
+* chore: requirements update ([`ac1e2e5`](https://github.com/zacharytomlinson/saspy/commit/ac1e2e5afc957a96b1a620b8363ae2c1118baaa2))
+
+* chore: requirements update ([`504168f`](https://github.com/zacharytomlinson/saspy/commit/504168f69221047673b64cdad059414f4582a709))
+
+* chore: reformatted with black ([`7462a17`](https://github.com/zacharytomlinson/saspy/commit/7462a17defda1886f8585ed0146d6bfdcac8118c))
+
+* chore: updated test case skeleton ([`35a9f90`](https://github.com/zacharytomlinson/saspy/commit/35a9f90ac225db8be21a1f2fc2decf041fe80f3d))
+
+* chore: detached config file from library. It's a lib and not a program ([`0289aaa`](https://github.com/zacharytomlinson/saspy/commit/0289aaababece926cd9e7e1e77a53fdabc48b7b2))
+
+* chore: test case skeleton ([`754ee7d`](https://github.com/zacharytomlinson/saspy/commit/754ee7dd9ab345308d26a8c4ef487245c9fd9f6b))
+
+* chore: test case skeleton ([`b9d9e30`](https://github.com/zacharytomlinson/saspy/commit/b9d9e30565b68d9388a50d549bc215adb6307e17))
+
+* chore: black reformatting ([`8452a79`](https://github.com/zacharytomlinson/saspy/commit/8452a7930c15127ffa2fa56b22b7c023246b2fe4))
+
+* chore: forgot to check if self.trasaction is populated before using it ([`cc8bfba`](https://github.com/zacharytomlinson/saspy/commit/cc8bfba2af23631d529cfb070feaeed9120114f6))
+
+* chore: fix typo, var/func naming confusion, restructure of the project, config management, readme update, added requirements, small fixes ([`50e14c5`](https://github.com/zacharytomlinson/saspy/commit/50e14c58450740a229fb5f833a982a7df82253c0))
+
+### Documentation
+
+* docs: adding changelog ([`8c540c8`](https://github.com/zacharytomlinson/saspy/commit/8c540c8f11ff3cd5795aa08bcd977625203d7548))
+
+### Unknown
+
+* Merge pull request #27 from zacharytomlinson/zmt/crc-validate-utilfunc ([`e551937`](https://github.com/zacharytomlinson/saspy/commit/e5519376d1a5f3e70db9ed25b0cedd0b7abc9197))
+
+* Moved crc check to util class and added a deprecation annotation so we don't have to implement breaking changes instantly ([`1668cff`](https://github.com/zacharytomlinson/saspy/commit/1668cffc8ffaa355cb0c26e934ffd94f2b641316))
+
+* Merge pull request #26 from zacharytomlinson/gh-workflows-changelog
+
+GitHub Workflows and Changelog ([`94f8b99`](https://github.com/zacharytomlinson/saspy/commit/94f8b99e7fa2c070628602aa95ab1943e0aac3ae))
+
+* Merge pull request #25 from zacharytomlinson/zacharytomlinson-crc-adhoc
+
+CRC adhoc ([`e0eef0c`](https://github.com/zacharytomlinson/saspy/commit/e0eef0c6574a496ab1f1a9924e9a2f7ba6a8d82d))
+
+* Finalizing CRC changes ([`6f2374d`](https://github.com/zacharytomlinson/saspy/commit/6f2374d8dcecf2677a5e20b36a58cc5adf12a4c2))
+
+* Finalizing CRC changes ([`001171a`](https://github.com/zacharytomlinson/saspy/commit/001171a461bc1b0922fb4c068e2e60eb0988f368))
+
+* Comparing machine crc with my calculated crc for error checking ([`a6fd702`](https://github.com/zacharytomlinson/saspy/commit/a6fd7029bda60bc31724a4f68cd59f1846cc0f3b))
+
+* Comparing machine crc with my calculated crc for error checking ([`82d282f`](https://github.com/zacharytomlinson/saspy/commit/82d282faa6b352cddf8973a180867e399bf08ef5))
+
+* Comparing machine crc with my calculated crc for error checking ([`43e7ccd`](https://github.com/zacharytomlinson/saspy/commit/43e7ccd8a398c2f2afb143340ee4b86e78e3c7d7))
+
+* Comparing machine crc with my calculated crc for error checking ([`7135703`](https://github.com/zacharytomlinson/saspy/commit/7135703c46af00a56f033b83f89e446f24f63c4a))
+
+* Comparing machine crc with my calculated crc for error checking ([`4f2bb96`](https://github.com/zacharytomlinson/saspy/commit/4f2bb96564b0b1f0f0322501feb91f5c69ff442f))
+
+* Comparing machine crc with my calculated crc for error checking ([`196de6e`](https://github.com/zacharytomlinson/saspy/commit/196de6e341e670a1c6f64bd658a491db404b9640))
+
+* Comparing machine crc with my calculated crc for error checking ([`1364184`](https://github.com/zacharytomlinson/saspy/commit/13641840b6646c7e2f0f69b596dd9b1872ed28c2))
+
+* Comparing machine crc with my calculated crc for error checking ([`eee2f99`](https://github.com/zacharytomlinson/saspy/commit/eee2f993ad1b04bcfba3a8a2361ffe01a7b20fe6))
+
+* Comparing machine crc with my calculated crc for error checking ([`62a03ed`](https://github.com/zacharytomlinson/saspy/commit/62a03ed9c4ae3c6137eaad1e60c7435709f881ac))
+
+* Comparing machine crc with my calculated crc for error checking ([`d08de12`](https://github.com/zacharytomlinson/saspy/commit/d08de12d6ff78cfa8d74f3216e54c02b1bd3cdf8))
+
+* Adjust CRC calculation ([`feecdf9`](https://github.com/zacharytomlinson/saspy/commit/feecdf9bb85e9eb513834f9dbe1aa96284f76710))
+
+* Adjust CRC calculation ([`89f6324`](https://github.com/zacharytomlinson/saspy/commit/89f63245db7d9aaa72ccedf31aaafffba86db8ca))
+
+* Adjust CRC calculation ([`13d7f89`](https://github.com/zacharytomlinson/saspy/commit/13d7f89ba9f2c150a1403d5a5e3d00bca28225c1))
+
+* Adjust CRC calculation ([`fed7391`](https://github.com/zacharytomlinson/saspy/commit/fed739164b219ff6339179bb97b1e2e9f323fbd7))
+
+* Adjust CRC calculation ([`633ff39`](https://github.com/zacharytomlinson/saspy/commit/633ff39a81ce442c6f559876169a8056532f1a61))
+
+* Adjust CRC calculation ([`3ed7bed`](https://github.com/zacharytomlinson/saspy/commit/3ed7bed8b8778cd44067bee7d7748f50d912836d))
+
+* Adjust CRC calculation ([`7b7bdc7`](https://github.com/zacharytomlinson/saspy/commit/7b7bdc7bb8c08e320518f800cbdd0489b29150c5))
+
+* Adjust CRC calculation ([`e237f23`](https://github.com/zacharytomlinson/saspy/commit/e237f23cb6ab08eaa0d7471648950e0f28915a47))
+
+* Adjust CRC calculation ([`a1275d6`](https://github.com/zacharytomlinson/saspy/commit/a1275d6bab2393237c959144936b118484319be0))
+
+* Adjust CRC calculation ([`a720ff7`](https://github.com/zacharytomlinson/saspy/commit/a720ff77e4960cd59044f8dba359c335167889fb))
+
+* Adjust CRC calculation ([`89ad50b`](https://github.com/zacharytomlinson/saspy/commit/89ad50b2390a23627d73eb0bc4088c032da46eee))
+
+* Adjust CRC calculation ([`72af8ba`](https://github.com/zacharytomlinson/saspy/commit/72af8ba673eb0ed44f18b45b59992e43532e3ed3))
+
+* Adjust CRC calculation ([`29f304b`](https://github.com/zacharytomlinson/saspy/commit/29f304b6775035882b82ccb2897b871676d7f212))
+
+* Adjust CRC calculation ([`2a7ac6b`](https://github.com/zacharytomlinson/saspy/commit/2a7ac6b680d3f822d74c4f835abebf2a614f98a6))
+
+* Adjust CRC calculation ([`080b3d0`](https://github.com/zacharytomlinson/saspy/commit/080b3d02b2d160a22ce2b745b3d7a426d5aeae69))
+
+* Adjust CRC calculation ([`4bf56e1`](https://github.com/zacharytomlinson/saspy/commit/4bf56e12eef6f08f4dfaf533be179f55dbdb5b45))
+
+* Adjust CRC calculation ([`93b63f5`](https://github.com/zacharytomlinson/saspy/commit/93b63f5ddec93d99779fb48a1015b74cbdbefbc1))
+
+* Adjust CRC calculation ([`210ca76`](https://github.com/zacharytomlinson/saspy/commit/210ca76c1943d502de20a8627dff8f36ced642ac))
+
+* Adjust CRC calculation ([`79ade1a`](https://github.com/zacharytomlinson/saspy/commit/79ade1a817de5d68957c3202f270c15a6e24270d))
+
+* Adjust CRC calculation ([`f01614c`](https://github.com/zacharytomlinson/saspy/commit/f01614cacd0b1a354c13f4d1eb8f8639b21c36dc))
+
+* Adjust CRC calculation ([`07c6444`](https://github.com/zacharytomlinson/saspy/commit/07c644486f52e2015b2bd9fcd220b2157602557c))
+
+* Adjust CRC calculation ([`b30feff`](https://github.com/zacharytomlinson/saspy/commit/b30feff4c6dcbf24de9465b23670faa91a707bd8))
+
+* Adjust CRC calculation ([`5523a27`](https://github.com/zacharytomlinson/saspy/commit/5523a271fb96f8dde990f793c04a7b822331bbfd))
+
+* Adjust CRC calculation ([`cf15d6d`](https://github.com/zacharytomlinson/saspy/commit/cf15d6d90a4843c0feb5e53b8c9f3f385241ad58))
+
+* Adjust CRC calculation ([`4593c36`](https://github.com/zacharytomlinson/saspy/commit/4593c36015867291bd45efce35a134372e4374aa))
+
+* Condence CRC calculation ([`7ad3066`](https://github.com/zacharytomlinson/saspy/commit/7ad306642bc932fff84099d84d74520feebb2f1d))
+
+* CHange old CRC calculation to not parse utf-8 and changed byte array in calculation to bytes object ([`049072f`](https://github.com/zacharytomlinson/saspy/commit/049072fdb48affe1bd2aa6a451a961fa86b5f2d3))
+
+* Change printing of crc to bit shifted output ([`98dc70f`](https://github.com/zacharytomlinson/saspy/commit/98dc70fbe3e4c81d7007def6e1d3d3be2f1b8cc7))
+
+* Added endianness to crc return ([`e43209a`](https://github.com/zacharytomlinson/saspy/commit/e43209aa6d63d290f8a4cf31202c9e40b4208bde))
+
+* sas version call is not working ([`47e676e`](https://github.com/zacharytomlinson/saspy/commit/47e676e7789f98e655eb9cb225787d04cd139dae))
+
+* returning crc as tuple of ints ([`c86b1fc`](https://github.com/zacharytomlinson/saspy/commit/c86b1fc7308563a0d75aa0228e47e89b956928e9))
+
+* log ([`0b88553`](https://github.com/zacharytomlinson/saspy/commit/0b88553d6bdd194e442434764fe47e7433045535))
+
+* oops ([`79d048f`](https://github.com/zacharytomlinson/saspy/commit/79d048fd96530c858b3fb878f71ced51fb8f69cc))
+
+* Fix calculation bit shift direction ([`95beabc`](https://github.com/zacharytomlinson/saspy/commit/95beabcc3463eb2ae3c6fabb4771d3b9dcd350ff))
+
+* Change dynamic pyload type to static ([`65e9d1c`](https://github.com/zacharytomlinson/saspy/commit/65e9d1ccfb2787d936252c555acf6ec15db5be4f))
+
+* Change Crc from class to utility file ([`549fc85`](https://github.com/zacharytomlinson/saspy/commit/549fc85f930eb6a677d4773d7c825b743083e7b7))
+
+* Added disable real time default back in ([`d103c01`](https://github.com/zacharytomlinson/saspy/commit/d103c012b225e58552f60b270b1f0408844518e8))
+
+* Config adjust ([`04937b4`](https://github.com/zacharytomlinson/saspy/commit/04937b460f9ae91c83326717b9bc184cd95938ad))
+
+* Static CRC method in utilities ([`ff004ad`](https://github.com/zacharytomlinson/saspy/commit/ff004adc02a0f5aa9d3751f3a1559316c33e79cf))
+
+* Merge pull request #24 from zacharytomlinson/chore-chore-chore-i-hate-my-life
+
+A lot of nice stuff.... ([`9a7b504`](https://github.com/zacharytomlinson/saspy/commit/9a7b504ad3a482120ad73e95ddc42ce36f006f45))
+
+* Merge pull request #21 from zacharytomlinson/flake8-static-analysis ([`27f7c54`](https://github.com/zacharytomlinson/saspy/commit/27f7c54ee6f80796aa67cf7afc0bd2f285c5d09a))
+
+* undid automated **kwargs insertions ([`ec63ece`](https://github.com/zacharytomlinson/saspy/commit/ec63ece16d105dc53a330e62d9c00e7f6b28258d))
+
+* add example.py to gitignore and ran static code analysis (flake8) on sas.py ([`814e143`](https://github.com/zacharytomlinson/saspy/commit/814e143e2e4d055fe9cc7d1608238a202be09dbe))
+
+* Merge pull request #19 from zacharytomlinson/18-bug-error-in-choosing-right-datamodel
+
+chore: fix bug #18 ([`0effe2c`](https://github.com/zacharytomlinson/saspy/commit/0effe2cabaf8e06eafac3e08b3b60233c877f25a))
+
+* Merge pull request #20 from zacharytomlinson/crc-checkresponse-fix
+
+Fixed crc comparators in _check_response ([`603ed04`](https://github.com/zacharytomlinson/saspy/commit/603ed0468ba302ff79ee95929300a4ed3afc0f59))
+
+* remove fix me pertaining to bug ([`fce8c2d`](https://github.com/zacharytomlinson/saspy/commit/fce8c2dd97b058bde7776e832baf899d61251ad1))
+
+* Fixed crc comparators in _check_response ([`7e38e0b`](https://github.com/zacharytomlinson/saspy/commit/7e38e0b4c0cd2abe92a38aac9158589bbffb6b89))
+
+* Merge pull request #17 from zacharytomlinson/well-it-wasnt-me-patch-1 ([`7d336e1`](https://github.com/zacharytomlinson/saspy/commit/7d336e1ffc2d31dd00d18647ecad7df2c8b56427))
+
+* Merge pull request #15 from zacharytomlinson/readme-update ([`c55a8b6`](https://github.com/zacharytomlinson/saspy/commit/c55a8b691cffba9beab3f990538ef28e13c0b7c5))
+
+* Merge pull request #16 from zacharytomlinson/issue-templating ([`1126d4b`](https://github.com/zacharytomlinson/saspy/commit/1126d4b8ad73150f753f853adf307c52896be048))
+
+* Merge pull request #14 from zacharytomlinson/various-fix ([`29a937d`](https://github.com/zacharytomlinson/saspy/commit/29a937d0e34eaaa1d8779f5e7686f153157f4cb0))
+
+* Merge pull request #13 from zacharytomlinson/well-it-wasnt-me-patch-1 ([`dee45af`](https://github.com/zacharytomlinson/saspy/commit/dee45af32a934a00ffdaacefe96551ed64453444))
+
+* Merge pull request #12 from dedalgr/patch-4
+
+chore: fix open/close port logic ([`d896ec7`](https://github.com/zacharytomlinson/saspy/commit/d896ec7a9dc8304cfcbf6460e4495969e2ad8032))
+
+* Update sas.py
+
+FIX port open and close. ([`75beb9b`](https://github.com/zacharytomlinson/saspy/commit/75beb9b95db6a11db175be907f8ed00a30230814))
+
+* Merge pull request #11 from zacharytomlinson/well-it-wasnt-me-patch-2
+
+chore: readme update ([`7f06aa2`](https://github.com/zacharytomlinson/saspy/commit/7f06aa2a2b9183cab6a711f4ad3784be80d5992b))
+
+* Merge pull request #10 from zacharytomlinson/well-it-wasnt-me-patch-1
+
+chore: fix conf.py path ([`08b1c3c`](https://github.com/zacharytomlinson/saspy/commit/08b1c3ce964d5eda684ee2ef4baefbd3d46979ae))
+
+* Merge pull request #8 from zacharytomlinson/models-comments-tests
+
+A lot of stuff ([`f9a2360`](https://github.com/zacharytomlinson/saspy/commit/f9a2360634886d74c986d5dda0fd745806a8e755))
+
+* Merge pull request #6 from dedalgr/patch-3
+
+Update example.py ([`39e1a05`](https://github.com/zacharytomlinson/saspy/commit/39e1a05924aeafcaed6df9341ee5a5eb195f33df))
+
+* Merge pull request #7 from zacharytomlinson/rpi-3-instructions
+
+chore: readme update ([`d6c8af0`](https://github.com/zacharytomlinson/saspy/commit/d6c8af0ab37a13404f05ce9335d1c2557ac2e5b4))
+
+* Added clarification of logic level converter so people don't fry their Pi's ([`76c0d98`](https://github.com/zacharytomlinson/saspy/commit/76c0d989bc00afa9f35259527fdf9ef7f429bb12))
+
+* Added instructions for getting RPI 3 running and stubbed out for RPI 4. Will figure out port closed issue after reading address tomorrow. ([`5448647`](https://github.com/zacharytomlinson/saspy/commit/5448647df14c7089f1fc5c1136c6b835bc229366))
+
+* Transfered dictionaries to dataclasses ([`f2a2187`](https://github.com/zacharytomlinson/saspy/commit/f2a218741a739e4c4ca38f19add57d85e99bda6c))
+
+* Update example.py
+
+Transaction is successful if finish with aft_clean_transaction_poll ([`844b7a4`](https://github.com/zacharytomlinson/saspy/commit/844b7a45a5873cf14471dbd8ad7d48c6c6483bb6))
+
+* Merge pull request #5 from zacharytomlinson/cleaning-and-restructure
+
+Cleaning, Code Restructure, Bug Fixes ([`4bb9397`](https://github.com/zacharytomlinson/saspy/commit/4bb9397c355f1f6a6f1094c02434f80720422234))
+
+* Merge remote-tracking branch 'origin/cleaning-and-restructure' into cleaning-and-restructure
+
+# Conflicts:
+# tests/__init__.py
+# tests/test_sas.py ([`1e699a5`](https://github.com/zacharytomlinson/saspy/commit/1e699a51fcbc07ef0b4c8438620a0a01cd35c19e))
+
+* Merge pull request #3 from zacharytomlinson/black-format
+
+Formatted with black ([`71798dd`](https://github.com/zacharytomlinson/saspy/commit/71798dd5894734b02c92f306245347abd00cfe1b))
+
+* Merge branch 'master' into black-format ([`f2b78d5`](https://github.com/zacharytomlinson/saspy/commit/f2b78d5d53c51218fa4737c313687ed9c90016ba))
+
+* Merge pull request #4 from zacharytomlinson/name-fix
+
+Fixed some name cases ([`86dc186`](https://github.com/zacharytomlinson/saspy/commit/86dc186c7dd552b09e1d29d5d615d36314554f84))
+
+* Fixed some name cases ([`a9bf015`](https://github.com/zacharytomlinson/saspy/commit/a9bf015d177367b8cbf27809361db1e3e46baf08))
+
+* Formatted with black ([`367540f`](https://github.com/zacharytomlinson/saspy/commit/367540f0123735210fd86dd68ae7ceaf7fd9b042))
+
+* Initial passover and cleanup of code ([`8255684`](https://github.com/zacharytomlinson/saspy/commit/82556841424a840345a95318e55299ce0f77e05c))
+
+* Fixed CRC issues with python3 ([`f9edf92`](https://github.com/zacharytomlinson/saspy/commit/f9edf9234fb6e05616d057ce4f53b64fbd9610ae))
+
+* Removed artifacts ([`2844126`](https://github.com/zacharytomlinson/saspy/commit/284412654e26220374c30ff69045e6038783fbcc))
+
+* Game machine ID function does not return financial data ([`ad461cf`](https://github.com/zacharytomlinson/saspy/commit/ad461cf77c59392e866be6246fba1e9d53f5c804))
+
+* ported to python3 and formatted ([`9bc98af`](https://github.com/zacharytomlinson/saspy/commit/9bc98af8611767c7426e472ed48851bddcb22a23))
+
+* Update sas.py ([`6f03c4f`](https://github.com/zacharytomlinson/saspy/commit/6f03c4f77312f74cd6569239b349d77358a7fe28))
+
+* Update sas.py
+
+Its working on IGT, EGT, Amatic, Novomatic, Casino Technology (Tested on all)
+Don't return False. At RAM CLEAR 0 is False and code is broken.
+Do not cache. If you lose SAS it will return the cache.
+Check crc method ([`89065e3`](https://github.com/zacharytomlinson/saspy/commit/89065e3b7343f2940ce2b1e3a81d140b78accf06))
+
+* Update README.md ([`fddf7f3`](https://github.com/zacharytomlinson/saspy/commit/fddf7f35897dc39b9e8124841d1b812bda499ef4))
+
+* Create README.md ([`c29c91a`](https://github.com/zacharytomlinson/saspy/commit/c29c91aff7269dc2b922dab909f7734c3f0da131))
+
+* Fixed CRC bug ([`d5e2bae`](https://github.com/zacharytomlinson/saspy/commit/d5e2bae849c6a60dcb22c48720ab519ed1a1a25e))
+
+* Initial commit ([`35a3b74`](https://github.com/zacharytomlinson/saspy/commit/35a3b74917223682fac75b5ef642283681009df2))
diff --git a/LICENSE b/LICENSE
index a00ad41..09b8b55 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2019 thomas-pythonas
+Copyright (c) 2024 thomas-pythonas | zacharytomlinson | well-it-wasnt-me
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index fdbbb7a..09572a1 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,34 @@
-# saspy
-Slots accounting system protocol based on Python for arm architecture
+# SASPY
+[](https://saspy.readthedocs.io/en/latest/?badge=latest)
-The SAS library was made using python and can be used for linux-based single-board computers (Raspberry, Orange PI, Banana Pi).
-The Library is used for connecting to EGM by RS-232 protocol and for receiving and processing some data of SAS-protocol.
+
+
+
+Slots Accounting System (SAS) Protocol is a standard when comes to Slot Machine and VLTs. And because we were bored and with not enough things to do we decided to build this library
-SAS (Slot Accounting System) protocol is the de facto casino communications standards designed to automate slot machine meter reporting and event logging, player tracking, bonusing, ticketing and cashless gaming.
+## Scope
+This project, once reached a stable release, will be published on [PyPi](https://pypi.org/) as a Python package.
-The EGM and SMIB communicate each other in SAS protocol standards, whereas communication between the SMIB and host is not standardized, which interoperability and standards issue is resolved by new standards, G2S (Game to System) protocol. SMIB also provides touch screen and card readers for AFT and player tracking service.
+# Documentation
+Since this library is still under heavy development (but at least is working !) comments are still not complete and there is a draft of documentation into `docs/` folder.
-The System architecture of SAS Protocol: SAS protocol is used for communication between EGM and SMIB. SAS protocol consists of 3 layers of physical layer, link layer, and application layer. The physical layer leverages RS-232 at 9.2kbps of 1 start bit, 8 data bits, and 1wakeup bit and 1 stop bit. The wakeup bit is used for signaling the frame start byte and needs special care in implementation (described later). Master-slave polling mechanism is used for the medium access control similar to USB and traditional remote terminal system. Each EGM is assigned a link address of 1 to 127. ‘0’ is used for broadcasting. The polling rate ranges from 200ms to 5s but can be reduced to 40ms when EGM support RTE (real time event) mode. SMIB uses two different types of polls, GP (general poll) and LP (long poll). GP is one byte EGM address with wakeup bit set, and polls events generated in the receiving EGM system. The receiving EGM should respond with 1 byte event code, which is called ‘exception’ in the specification. The exceptions include non-priority exceptions such as no event, game start, game end, system tilt, and priority exceptions such as handpay condition, ticket out, ticket in, and fund-transfer request. LP is used for SMIB to send command to EGMs and classified into R(read)-type, S(set)-type, M(multi-game)-type, and G (global)-type. LP starts with 1 byte command value with wakeup bit set, and the lengths of LP are fixed or variable depending on the command. All LP except R-type contains CRC-16 (cyclic redundancy check) for bit error detection. The response frame uses the similar format but with wakeup bit off. SMIB use implied ACK mechanism for confirming the receipt.
+All the latest documentation regarding this library can be found on [Read The Docs](https://saspy.readthedocs.io/)
-SAS Protocol Application Functions: ROM signature request, Metric, Progressive broadcast, Tournament operation, RTE (real time event), Bonus Controller, Jackpot hand-pay, TITO(Ticket in/out), Multi-denomination, AFT, Component Authentication
+In the meantime feel free to checkout the [WiKi of this project](https://github.com/zacharytomlinson/saspy/wiki) where you are gonna find response and solutions to common problems we faced during our journey
+
+# Contribute
+Not many rules. Fork the repo, write your code, create pull request. Remember to use black for formatting and give a shot to pylint.
+
+## Issue & Implementation
+Please, use the [Issues tab](https://github.com/zacharytomlinson/saspy/issues) to propose an eventual enhancement or to submit an issue.
+
+# Authors
+This project was initiated by [thomas-pythonas](https://github.com/thomas-pythonas) in 2018 and has no update since then.
+
+Current author and mantainer are:
+
+- [Zachary Tomlinson](https://github.com/zacharytomlinson)
+- [Antonio D'Angelo](https://github.com/well-it-wasnt-me)
+
+# Thanks to
+* [Grigor Kolev](https://github.com/dedalgr) for his precious help
diff --git a/config.yml b/config.yml
new file mode 100644
index 0000000..fe5e759
--- /dev/null
+++ b/config.yml
@@ -0,0 +1,21 @@
+connection:
+ serial_port: /dev/ttyUSB0
+ timeout: 2
+ baudrate: 19200
+ infinite: False
+
+events:
+ poll_timeout: 0.5
+ poll_address: 0x82 # Standard Poll Address - On most the machines | Try 0x80 if 82 dont work
+
+debug:
+ level: DEBUG # CRITICAL | ERROR | WARNING | INFO | DEBUG | NOTSET
+
+security:
+ key: 44
+
+machine:
+ pos_id: "B374A402"
+ reg_key: "0000000000000000000000000000000000000000"
+ asset_number: "15cd5b07" # Asset Magic Number - Works on every machine !
+ denomination: 0.01
diff --git a/config_handler.py b/config_handler.py
new file mode 100644
index 0000000..027396d
--- /dev/null
+++ b/config_handler.py
@@ -0,0 +1,17 @@
+import yaml
+
+
+class ConfigHandler:
+ def __init__(self, file_path="config.yml"):
+ self.config_file_path = file_path
+ self.config = None
+
+ def read_config_file(self):
+ with open(self.config_file_path, "r") as yaml_file:
+ self.config = yaml.safe_load(yaml_file)
+
+ def get_config_value(self, section, key):
+ if self.config:
+ return self.config.get(section, {}).get(key)
+ else:
+ raise ValueError("Configuration not loaded. Call read_config_file first.")
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 0000000..d4bb2cb
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS ?=
+SPHINXBUILD ?= sphinx-build
+SOURCEDIR = .
+BUILDDIR = _build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 0000000..16563e3
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1,58 @@
+# Configuration file for the Sphinx documentation builder.
+#
+# This file only contains a selection of the most common options. For a full
+# list see the documentation:
+# https://www.sphinx-doc.org/en/master/usage/configuration.html
+
+# -- Path setup --------------------------------------------------------------
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+import os
+import sys
+sys.path.insert(0, os.path.abspath('..'))
+
+
+# -- Project information -----------------------------------------------------
+
+project = 'SASPy'
+copyright = "2024, Zachary Tomlinson, Antonio D'Angelo"
+author = "Zachary Tomlinson, Antonio D'Angelo"
+
+# The full version, including alpha/beta/rc tags
+release = '2.0.0'
+
+
+# -- General configuration ---------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+ 'sphinx.ext.autodoc',
+ 'sphinx.ext.coverage',
+ 'sphinx.ext.napoleon'
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This pattern also affects html_static_path and html_extra_path.
+exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+
+
+# -- Options for HTML output -------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+#
+html_theme = 'sphinx_rtd_theme'
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
\ No newline at end of file
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644
index 0000000..a14a3c8
--- /dev/null
+++ b/docs/index.rst
@@ -0,0 +1,27 @@
+Welcome to SASPy's documentation!
+===================================
+
+.. image:: saspy-logo.jpeg
+ :align: center
+ :width: 300
+
+|
+| **SASPy** is a Python library for handling your beloved VLTs via SAS Protocol.
+
+At moment is compatible with SAS v6.02+
+
+Check out the :ref:`wiki-home`. section for more information about connections and common problems.
+
+.. note::
+ This project is under active development.
+
+
+.. toctree::
+ :maxdepth: 1
+ :caption: Table of Contents
+ :name: mastertoc
+ :glob:
+
+ Home
+ Wiki
+ The Library
\ No newline at end of file
diff --git a/docs/make.bat b/docs/make.bat
new file mode 100644
index 0000000..922152e
--- /dev/null
+++ b/docs/make.bat
@@ -0,0 +1,35 @@
+@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=.
+set BUILDDIR=_build
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+ echo.
+ echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+ echo.installed, then set the SPHINXBUILD environment variable to point
+ echo.to the full path of the 'sphinx-build' executable. Alternatively you
+ echo.may add the Sphinx directory to PATH.
+ echo.
+ echo.If you don't have Sphinx installed, grab it from
+ echo.http://sphinx-doc.org/
+ exit /b 1
+)
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+
+:end
+popd
diff --git a/docs/requirements.txt b/docs/requirements.txt
new file mode 100644
index 0000000..afc587c
--- /dev/null
+++ b/docs/requirements.txt
@@ -0,0 +1,3 @@
+sphinx
+sphinx_rtd_theme
+readthedocs-sphinx-search
\ No newline at end of file
diff --git a/docs/sas-lib/error_handler.rst b/docs/sas-lib/error_handler.rst
new file mode 100644
index 0000000..e0211f6
--- /dev/null
+++ b/docs/sas-lib/error_handler.rst
@@ -0,0 +1,7 @@
+Error Handling
+=====================
+
+.. automodule:: error_handler
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/docs/sas-lib/main.rst b/docs/sas-lib/main.rst
new file mode 100644
index 0000000..82f45e7
--- /dev/null
+++ b/docs/sas-lib/main.rst
@@ -0,0 +1,9 @@
+Library Main Doc
+================
+
+.. toctree::
+ :maxdepth: 2
+
+ sas
+ error_handler
+ models/models
\ No newline at end of file
diff --git a/docs/sas-lib/models/models.AftLockStatus.rst b/docs/sas-lib/models/models.AftLockStatus.rst
new file mode 100644
index 0000000..c25d5cf
--- /dev/null
+++ b/docs/sas-lib/models/models.AftLockStatus.rst
@@ -0,0 +1,7 @@
+AFT Lock Status
+===========================
+
+.. automodule:: models.AftLockStatus
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/docs/sas-lib/models/models.AftReceiptStatus.rst b/docs/sas-lib/models/models.AftReceiptStatus.rst
new file mode 100644
index 0000000..ad56573
--- /dev/null
+++ b/docs/sas-lib/models/models.AftReceiptStatus.rst
@@ -0,0 +1,7 @@
+AFT Receipt Status
+==============================
+
+.. automodule:: models.AftReceiptStatus
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/docs/sas-lib/models/models.AftRegistrationStatus.rst b/docs/sas-lib/models/models.AftRegistrationStatus.rst
new file mode 100644
index 0000000..e11189b
--- /dev/null
+++ b/docs/sas-lib/models/models.AftRegistrationStatus.rst
@@ -0,0 +1,7 @@
+AFT Rgistration Status
+===================================
+
+.. automodule:: models.AftRegistrationStatus
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/docs/sas-lib/models/models.AftStatements.rst b/docs/sas-lib/models/models.AftStatements.rst
new file mode 100644
index 0000000..c5fb75c
--- /dev/null
+++ b/docs/sas-lib/models/models.AftStatements.rst
@@ -0,0 +1,7 @@
+AFT Statements
+===========================
+
+.. automodule:: models.AftStatements
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/docs/sas-lib/models/models.AftTransferStatus.rst b/docs/sas-lib/models/models.AftTransferStatus.rst
new file mode 100644
index 0000000..2520918
--- /dev/null
+++ b/docs/sas-lib/models/models.AftTransferStatus.rst
@@ -0,0 +1,7 @@
+AFT Transfer Status
+===============================
+
+.. automodule:: models.AftTransferStatus
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/docs/sas-lib/models/models.AftTransferType.rst b/docs/sas-lib/models/models.AftTransferType.rst
new file mode 100644
index 0000000..c9d60ad
--- /dev/null
+++ b/docs/sas-lib/models/models.AftTransferType.rst
@@ -0,0 +1,7 @@
+AFT Transfer Type
+=============================
+
+.. automodule:: models.AftTransferType
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/docs/sas-lib/models/models.Denomination.rst b/docs/sas-lib/models/models.Denomination.rst
new file mode 100644
index 0000000..0e91433
--- /dev/null
+++ b/docs/sas-lib/models/models.Denomination.rst
@@ -0,0 +1,7 @@
+Denominations
+==========================
+
+.. automodule:: models.Denomination
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/docs/sas-lib/models/models.EftStatement.rst b/docs/sas-lib/models/models.EftStatement.rst
new file mode 100644
index 0000000..7a68502
--- /dev/null
+++ b/docs/sas-lib/models/models.EftStatement.rst
@@ -0,0 +1,7 @@
+EFT Statement (deprecated)
+==========================
+
+.. automodule:: models.EftStatement
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/docs/sas-lib/models/models.GPoll.rst b/docs/sas-lib/models/models.GPoll.rst
new file mode 100644
index 0000000..e19ce70
--- /dev/null
+++ b/docs/sas-lib/models/models.GPoll.rst
@@ -0,0 +1,7 @@
+General Poll
+===================
+
+.. automodule:: models.GPoll
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/docs/sas-lib/models/models.GameFeatures.rst b/docs/sas-lib/models/models.GameFeatures.rst
new file mode 100644
index 0000000..c694904
--- /dev/null
+++ b/docs/sas-lib/models/models.GameFeatures.rst
@@ -0,0 +1,7 @@
+Game Features
+==========================
+
+.. automodule:: models.GameFeatures
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/docs/sas-lib/models/models.Meters.rst b/docs/sas-lib/models/models.Meters.rst
new file mode 100644
index 0000000..997729c
--- /dev/null
+++ b/docs/sas-lib/models/models.Meters.rst
@@ -0,0 +1,7 @@
+Meters
+====================
+
+.. automodule:: models.Meters
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/docs/sas-lib/models/models.TitoStatement.rst b/docs/sas-lib/models/models.TitoStatement.rst
new file mode 100644
index 0000000..0b64f5b
--- /dev/null
+++ b/docs/sas-lib/models/models.TitoStatement.rst
@@ -0,0 +1,7 @@
+TiTo Statements
+===========================
+
+.. automodule:: models.TitoStatement
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/docs/sas-lib/models/models.rst b/docs/sas-lib/models/models.rst
new file mode 100644
index 0000000..6c8b7d7
--- /dev/null
+++ b/docs/sas-lib/models/models.rst
@@ -0,0 +1,21 @@
+Models
+==============
+Models are where to Dictionaries of some commands response are stored.
+
+.. toctree::
+ :maxdepth: 1
+ :caption: Models List
+ :name: modelstoc
+
+ models.AftLockStatus
+ models.AftReceiptStatus
+ models.AftRegistrationStatus
+ models.AftStatements
+ models.AftTransferStatus
+ models.AftTransferType
+ models.Denomination
+ models.EftStatement
+ models.GPoll
+ models.GameFeatures
+ models.Meters
+ models.TitoStatement
diff --git a/docs/sas-lib/sas.rst b/docs/sas-lib/sas.rst
new file mode 100644
index 0000000..a1ee138
--- /dev/null
+++ b/docs/sas-lib/sas.rst
@@ -0,0 +1,7 @@
+SAS Class
+==========
+
+.. automodule:: sas
+ :members:
+ :undoc-members:
+ :show-inheritance:
\ No newline at end of file
diff --git a/docs/saspy-logo.jpeg b/docs/saspy-logo.jpeg
new file mode 100644
index 0000000..40792b6
Binary files /dev/null and b/docs/saspy-logo.jpeg differ
diff --git a/docs/wiki/WikiHome.rst b/docs/wiki/WikiHome.rst
new file mode 100644
index 0000000..69aa0a4
--- /dev/null
+++ b/docs/wiki/WikiHome.rst
@@ -0,0 +1,16 @@
+.. _wiki-home:
+
+WiKI
+===================================
+
+Welcome to the SASPy wiki!
+
+We hope that on those pages you will find a reply to your questions on how to use this library
+
+.. toctree::
+ :maxdepth: 2
+ :caption: Wiki Content Menu
+ :name: wikitoc
+ :glob:
+
+ pages/*
\ No newline at end of file
diff --git a/docs/wiki/pages/1.-Raspberry-Limitation-&-How-to-solve-it.rst b/docs/wiki/pages/1.-Raspberry-Limitation-&-How-to-solve-it.rst
new file mode 100644
index 0000000..db7feae
--- /dev/null
+++ b/docs/wiki/pages/1.-Raspberry-Limitation-&-How-to-solve-it.rst
@@ -0,0 +1,80 @@
+.. _1-raspbery:
+
+Raspberry Limitation & How To Solve
+===================================
+The Raspberry present some challenges when comes to UART and Serial connections. In the specific:
+
+
+
+ The mini UART is a secondary low throughput UART intended to be used as a console. The mini Uart has the following features:
+
+ * 7 or 8 bit operation.
+ * 1 start and 1 stop bit.
+ * No parities.
+ * Break generation.
+ * 8 symbols deep FIFOs for receive and transmit.
+ * SW controlled RTS, SW readable CTS.
+ * Auto flow control with programmable FIFO level.
+ * 16550 like registers.
+ * Baudrate derived from system clock.
+
+
+As per SAS documentation, for the connection, you need:
+
+ 19.2 KBaud in a "wakeup" mode. The 11-bit data packet consists of one start bit, eight data bits, a
+ ninth ‘wakeup’ bit, and one stop bit.
+
+
+As you can see, you will never be able to connect successfully to a machine using ONLY the Raspberry. Here how to solve this pickle.
+
+Solution 1
+----------
+
+Use an RS-232 to TTL logic level converter. We tested the `MAX3323 `_
+
+In case you decide to buy and use the MAX3323 remember to:
+1) Update your machine:
+
+.. code-block::
+
+ user@host $ sudo apt-get update
+ user@host $ sudo apt-get dist-upgrade
+ user@host $ sudo apt-get clean
+
+2) Enable UART in config and disable bluetooth
+
+.. code-block::
+
+ user@host $ echo "enable_uart=1" >> /boot/firmware/config.txt
+ user@host $ echo "dtoverlay=disable-bt" >> /boot/firmware/config.txt
+
+3) Disable getty (serial agent for console login)
+
+.. code-block::
+
+ user@host $ sudo systemctl disable serial-getty@ttyS0.service
+
+4) Ensure console is not set in cmdline.txt
+
+.. code-block::
+
+ user@host $ sudo nano /boot/firmware/cmdline.txt # Remove "console=serial0,115200" from text if applicable
+
+
+5) Reboot for changes to take effect
+
+.. code-block::
+
+ user@host $ sudo reboot
+
+6) Configure serial connection to "serial0" port in config.yml (If you are using the default tx and rx pins on pi (8,10))
+
+.. code-block::
+
+ connection:
+ serial_port: /dev/serial0
+
+Solution 2
+----------
+
+Buy and use an USB to serial adapter, we tested this `one from Prolific `_ and works like a charm and nothing has to be done on raspberry.
diff --git a/docs/wiki/pages/2.-Machines-Coverage.rst b/docs/wiki/pages/2.-Machines-Coverage.rst
new file mode 100644
index 0000000..28125d3
--- /dev/null
+++ b/docs/wiki/pages/2.-Machines-Coverage.rst
@@ -0,0 +1,33 @@
+.. _2-machines:
+
+Machine Coverage
+===================================
+
+Preamble
+++++++++
+
+During our journey in building this library we came across few problems that we weren't able to understand.
+
+Until we came to a proven fact that **EVERY vendor implements the SAS Protocol as they wish**. Basically, event tho this should be a STANDARD, many manufacturer do not follow this standard in it's entirety....giving you a lot of headache
+
+Vendor Covered
+++++++++
+
+
+* `EGT `_
+* `CTGaming `_
+* `Novomatic `_
+* `Amatic `_
+* `Merkur `_
+
+Small Notes
+++++++++
+
+Even with machine from same vendor you could still have some trouble and you will be in need to adjust your code in order to have everything working. The reason for this is explained above in the preamble chapter.
+
+Machine Tested
+++++++++
+
+Make and Model on which this library has been tested on. Feel free to help expanding this list by creating a Pull Request :)
+
+(Coming Soon)
diff --git a/docs/wiki/pages/3.-How-To.rst b/docs/wiki/pages/3.-How-To.rst
new file mode 100644
index 0000000..f952527
--- /dev/null
+++ b/docs/wiki/pages/3.-How-To.rst
@@ -0,0 +1,21 @@
+.. _3-howto:
+
+How To
+===================================
+
+Requirements
+++++++++++++++
+
+* Python 3.10+
+* PIP
+
+Clone and Run
+++++++++++++++
+
+.. code-block:: bash
+
+ $ git clone https://github.com/zacharytomlinson/saspy.git
+ $ cd saspy
+ $ pip install -r requirements.txt
+ $ nano config.yml # Adjust according your needs
+ $ python example.py # To check that everything works
diff --git a/docs/wiki/pages/4.-Important-To-Know.rst b/docs/wiki/pages/4.-Important-To-Know.rst
new file mode 100644
index 0000000..3405d45
--- /dev/null
+++ b/docs/wiki/pages/4.-Important-To-Know.rst
@@ -0,0 +1,54 @@
+.. _4-important:
+
+Very Important to Know
+===================================
+
+AFT IN - Loading Credits
++++++++++++++++++++++++++
+
+On some machines you can call the function `aft_in `_ without problem and charge money on your VLT.
+
+On others you might need to call `aft_clean_transaction_poll `_ AFTER you send the ``aft_in`` command.
+
+..
+
+ A proper use of aft_clean_transaction_poll is to loop it. If it raises an error or returns 'Transfer pending (not complete)' you continue to execute until 'Full transfer successful'. Otherwise, you break the cycle and make the request invalid.
+
+
+Event Reporting
++++++++++++++++++++++++++
+
+Basically you have 2 ways: Standard event poll and real time event poll.
+
+The standard event poll works with a FIFO memory and in real world use cases is kinda useless (unless you need to store the history of this machine status). You can call this event simply using the ``event_poll`` method in the code.
+
+When you start the real time event reporting the machine, no matter what you ask, will always reply with the current event in the machine...leading to badcrc error and whatnot...plus the real time event responses are not mapped in the code (don't worry...im working on it). The function, in the code, to abilitate this is ``en_dis_rt_event_reporting``.
+
+Of course i needed the real time event reporting (to bind some actions) and at same time use some of the ``AFT_*`` functions in the code.
+
+To solve this issues i had to use the operator page on the machine to abilitate a second channel and buy a second prolific usb rs232 cable....
+
+In this way on a channel i abilitate the real time event reporting and on the second channel i could use the script normally (and the AFT functions) without problems.....
+
+Of course...this is a way created out of "no time"....if somebody has a better idea im all ear !
+
+Model Classes Notes
++++++++++++++++++++++++++
+
+Inside the classes in the folder ``models`` you will find all the machine responses mapped. So you know what the machine is telling you.
+
+Here some notes that is important to know.
+
+GPOLL
++++++++++++++++++++++++++
+
+**"4f": "Bill accepted"**
+
+
+* Non-RTE mode: use this for all bills without explicit denomination.
+* RTE mode: use for all bill denominations.
+
+**"50": "$200.00 bill accepted"**
+
+
+* Non-RTE only
diff --git a/error_handler.py b/error_handler.py
new file mode 100644
index 0000000..e703ffa
--- /dev/null
+++ b/error_handler.py
@@ -0,0 +1,35 @@
+class ErrorHandler(Exception):
+ def __init__(self, message, error_code=None):
+ self.message = message
+ self.error_code = error_code
+ super().__init__(self.message)
+
+
+class BadCRC(ErrorHandler):
+ def __init__(self, message="Bad CRC", error_code=None):
+ super().__init__(message, error_code)
+
+
+class AFTBadAmount(ErrorHandler):
+ def __init__(self, message="AFT Bad Amount", error_code=None):
+ super().__init__(message, error_code)
+
+
+class BadTransactionID(ErrorHandler):
+ def __init__(self, message="Bad Transaction ID", error_code=None):
+ super().__init__(message, error_code)
+
+
+class NoSasConnection(ErrorHandler):
+ def __init__(self, message="No SAS Connection", error_code=None):
+ super().__init__(message, error_code)
+
+
+class SASOpenError(ErrorHandler):
+ def __init__(self, message="SAS Open Error", error_code=None):
+ super().__init__(message, error_code)
+
+
+class EMGGpollBadResponse(ErrorHandler):
+ def __init__(self, message="EMGGPoll bad response", error_code=None):
+ super().__init__(message, error_code)
diff --git a/example.py b/example.py
new file mode 100644
index 0000000..5ce4786
--- /dev/null
+++ b/example.py
@@ -0,0 +1,28 @@
+from sas import Sas
+from config_handler import *
+
+# Let's init the configuration file
+config_handler = ConfigHandler()
+config_handler.read_config_file()
+
+
+sas = Sas(
+ port=config_handler.get_config_value("connection", "serial_port"),
+ timeout=config_handler.get_config_value("connection", "timeout"),
+ poll_address=config_handler.get_config_value("events", "poll_address"),
+ denom=config_handler.get_config_value("machine", "denomination"),
+ asset_number=config_handler.get_config_value("machine", "asset_number"),
+ reg_key=config_handler.get_config_value("machine", "reg_key"),
+ pos_id=config_handler.get_config_value("machine", "pos_id"),
+ key=config_handler.get_config_value("security", "key"),
+ debug_level="DEBUG",
+ perpetual=config_handler.get_config_value("connection", "infinite"),
+)
+
+print(sas.start())
+print(sas.en_dis_rt_event_reporting(False))
+print(sas.sas_version_gaming_machine_serial_id())
+print(sas.gaming_machine_id())
+print(sas.aft_in(15.00))
+print(sas.aft_clean_transaction_poll())
+print(sas.current_credits())
diff --git a/models/AftLockStatus.py b/models/AftLockStatus.py
new file mode 100644
index 0000000..eadb2bf
--- /dev/null
+++ b/models/AftLockStatus.py
@@ -0,0 +1,21 @@
+class AftLockStatus:
+ """Class representing the lock status for AFT"""
+
+ STATUS_MAP = {
+ "00": "Game locked",
+ "40": "Game lock pending",
+ "ff": "Game not locked",
+ }
+
+ @classmethod
+ def get_status(cls, key):
+ """Get the status value for the given key.
+
+ Args:
+ key (str): The key for the status.
+
+ Returns:
+ str: The corresponding status value or an error message if the key is not found.
+ """
+ # Use get() method to retrieve the value, or return an error message.
+ return cls.STATUS_MAP.get(key, f"Unknown key: {key}")
diff --git a/models/AftReceiptStatus.py b/models/AftReceiptStatus.py
new file mode 100644
index 0000000..7f2187a
--- /dev/null
+++ b/models/AftReceiptStatus.py
@@ -0,0 +1,22 @@
+class AftReceiptStatus:
+ """Class representing the receipt status for AFT"""
+
+ STATUS_MAP = {
+ "00": "Receipt printed",
+ "20": "Receipt printing in progress (not complete)",
+ "40": "Receipt pending (not complete)",
+ "ff": "No receipt requested or receipt not printed",
+ }
+
+ @classmethod
+ def get_status(cls, key):
+ """Get the status value for the given key.
+
+ Args:
+ key (str): The key for the status.
+
+ Returns:
+ str: The corresponding status value or an error message if the key is not found.
+ """
+ # Use get() method to retrieve the value, or return an error message.
+ return cls.STATUS_MAP.get(key, f"Unknown key: {key}")
diff --git a/models/AftRegistrationStatus.py b/models/AftRegistrationStatus.py
new file mode 100644
index 0000000..25cfd9e
--- /dev/null
+++ b/models/AftRegistrationStatus.py
@@ -0,0 +1,22 @@
+class AftRegistrationStatus:
+ """Class representing the registration status for AFT"""
+
+ STATUS_MAP = {
+ "00": "Gaming machine registration ready",
+ "01": "Gaming machine registered",
+ "40": "Gaming machine registration pending",
+ "80": "Gaming machine not registered",
+ }
+
+ @classmethod
+ def get_status(cls, key):
+ """Get the status value for the given key.
+
+ Args:
+ key (str): The key for the status.
+
+ Returns:
+ str: The corresponding status value or an error message if the key is not found.
+ """
+ # Use get() method to retrieve the value, or return an error message.
+ return cls.STATUS_MAP.get(key, f"Unknown key: {key}")
diff --git a/models/AftStatements.py b/models/AftStatements.py
new file mode 100644
index 0000000..fac538b
--- /dev/null
+++ b/models/AftStatements.py
@@ -0,0 +1,58 @@
+class AftStatements:
+ """Class representing the statements for AFT"""
+
+ STATUS_MAP = {
+ "registration_status": [],
+ "asset_number": [],
+ "registration_key": [],
+ "POS_ID": [],
+ "transaction_buffer_position": [],
+ "transfer_status": [],
+ "receipt_status": [],
+ "transfer_type": [],
+ "cashable_amount": [],
+ "restricted_amount": [],
+ "nonrestricted_amount": [],
+ "transfer_flags": [],
+ "transaction_ID_lenght": [],
+ "transaction_ID": [],
+ "transaction_date": [],
+ "transaction_time": [],
+ "expiration": [],
+ "pool_ID": [],
+ "cumulative_casable_amount_meter_size": [],
+ "cumulative_casable_amount_meter": [],
+ "cumulative_restricted_amount_meter_size": [],
+ "cumulative_restricted_amount_meter": [],
+ "cumulative_nonrestricted_amount_meter_size": [],
+ "cumulative_nonrestricted_amount_meter": [],
+ "game_lock_status": [],
+ "avilable_transfers": [],
+ "host_cashout_status": [],
+ "AFT_status": [],
+ "max_buffer_index": [],
+ "current_cashable_amount": [],
+ "current_restricted_amount": [],
+ "current_non_restricted_amount": [],
+ "restricted_expiration": [],
+ "restricted_pool_ID": [],
+ }
+
+ @classmethod
+ def get_status(cls, key):
+ """Get the status value for the given key.
+
+ Args:
+ key (str): The key for the status.
+
+ Returns:
+ str: The corresponding status value or an error message if the key is not found.
+ """
+ # Use get() method to retrieve the value, or return an error message.
+ return cls.STATUS_MAP.get(key, f"Unknown key: {key}")
+
+ @classmethod
+ def get_non_empty_status_map(cls):
+ """Return a dictionary containing only keys with non-empty values."""
+ non_empty_map = {key: value for key, value in cls.STATUS_MAP.items() if value}
+ return non_empty_map
diff --git a/models/AftTransferStatus.py b/models/AftTransferStatus.py
new file mode 100644
index 0000000..1d00c4e
--- /dev/null
+++ b/models/AftTransferStatus.py
@@ -0,0 +1,47 @@
+class AftTransferStatus:
+ """Class representing the transfer status for AFT"""
+
+ STATUS_MAP = {
+ "00": "Full transfer successful",
+ "01": "Partial transfer successful Binary codes 010xxxxx indicate transfer pending",
+ "40": "Transfer pending (not complete)",
+ "80": "Transfer cancelled by host",
+ "81": "Transaction ID not unique (same as last successful transfer logged in history)",
+ "82": "Not a valid transfer function (unsupported type, amount, index, etc.)",
+ "83": "Not a valid transfer amount or expiration (non-BCD, etc.)",
+ "84": "Transfer amount exceeds the gaming machine transfer limit",
+ "85": "Transfer amount not an even multiple of gaming machine denomination",
+ "86": "Gaming machine unable to perform partial transfers to the host",
+ "87": "Gaming machine unable to perform transfers at this time (door open, tilt, disabled, cashout in progress, etc.)",
+ "88": "Gaming machine not registered (required for debit transfers)",
+ "89": "Registration key does not match",
+ "8a": "No POS ID (required for debit transfers)",
+ "8b": "No won credits available for cashout",
+ "8c": "No gaming machine denomination set (unable to perform cents to credits conversion)",
+ "8d": "Expiration not valid for transfer to ticket (already expired)",
+ "8e": "Transfer to ticket device not available",
+ "8f": "Unable to accept transfer due to existing restricted amounts from different pool",
+ "90": "Unable to print transaction receipt (receipt device not currently available)",
+ "91": "Insufficient data to print transaction receipt (required fields missing)",
+ "92": "Transaction receipt not allowed for specified transfer type",
+ "93": "Asset number zero or does not match",
+ "94": "Gaming machine not locked (transfer specified lock required)",
+ "95": "Transaction ID not valid",
+ "9f": "Unexpected error Binary codes 110xxxxx indicate incompatible or unsupported poll",
+ "c0": "Not compatible with current transfer in progress",
+ "c1": "Unsupported transfer code Binary codes 111xxxxx indicate no transfer information available",
+ "ff": "No transfer information available",
+ }
+
+ @classmethod
+ def get_status(cls, key):
+ """Get the status value for the given key.
+
+ Args:
+ key (str): The key for the status.
+
+ Returns:
+ str: The corresponding status value or an error message if the key is not found.
+ """
+ # Use get() method to retrieve the value, or return an error message.
+ return cls.STATUS_MAP.get(key, f"Unknown key: {key}")
diff --git a/models/AftTransferType.py b/models/AftTransferType.py
new file mode 100644
index 0000000..780670b
--- /dev/null
+++ b/models/AftTransferType.py
@@ -0,0 +1,26 @@
+class AftTransferType:
+ """Class representing the transfer type for AFT"""
+
+ STATUS_MAP = {
+ "00": "Transfer in-house amount from host to gaming machine",
+ "10": "Transfer bonus coin out win amount from host to gaming machine",
+ "11": "Transfer bonus jackpot win amount from host to gaming machine (force attendant pay lockup)",
+ "20": "Transfer in-house amount from host to ticket (only one amount type allowed per transfer)",
+ "40": "Transfer debit amount from host to gaming machine",
+ "60": "Transfer debit amount from host to ticket",
+ "80": "Transfer in-house amount from gaming machine to host",
+ "90": "Transfer win amount (in-house) from gaming machine to host",
+ }
+
+ @classmethod
+ def get_status(cls, key):
+ """Get the status value for the given key.
+
+ Args:
+ key (str): The key for the status.
+
+ Returns:
+ str: The corresponding status value or an error message if the key is not found.
+ """
+ # Use get() method to retrieve the value, or return an error message.
+ return cls.STATUS_MAP.get(key, f"Unknown key: {key}")
diff --git a/models/Denomination.py b/models/Denomination.py
new file mode 100644
index 0000000..9590e81
--- /dev/null
+++ b/models/Denomination.py
@@ -0,0 +1,29 @@
+class Denomination:
+ """Class representing the Denominations"""
+
+ STATUS_MAP = {
+ "00": None,
+ "01": 0.01,
+ "17": 0.02,
+ "02": 0.05,
+ "03": 0.10,
+ "04": 0.25,
+ "05": 0.50,
+ "06": 1.00,
+ "07": 5.00,
+ "08": 10.00,
+ "09": 20.00,
+ }
+
+ @classmethod
+ def get_status(cls, key):
+ """Get the status value for the given key.
+
+ Args:
+ key (str): The key for the status.
+
+ Returns:
+ str: The corresponding status value or an error message if the key is not found.
+ """
+ # Use get() method to retrieve the value, or return an error message.
+ return cls.STATUS_MAP.get(key, f"Unknown key: {key}")
diff --git a/models/EftStatement.py b/models/EftStatement.py
new file mode 100644
index 0000000..1f7ca1a
--- /dev/null
+++ b/models/EftStatement.py
@@ -0,0 +1,9 @@
+from dataclasses import dataclass
+
+
+@dataclass
+class EftStatement:
+ eft_status: str
+ promo_amount: str
+ cashable_amount: str
+ eft_transfer_counter: str
diff --git a/models/GPoll.py b/models/GPoll.py
new file mode 100644
index 0000000..bb32852
--- /dev/null
+++ b/models/GPoll.py
@@ -0,0 +1,133 @@
+class GPoll:
+ """Class representing the GPoll Events"""
+
+ STATUS_MAP = {
+ "00": "No activity",
+ "01": "No Response",
+ "11": "Slot door was opened",
+ "12": "Slot door was closed",
+ "13": "Drop door was opened",
+ "14": "Drop door was closed",
+ "15": "Card cage was opened",
+ "16": "Card cage was closed",
+ "17": "AC power was applied to gaming machine",
+ "18": "AC power was lost from gaming machine",
+ "19": "Cashbox door was opened",
+ "1a": "Cashbox door was closed",
+ "1b": "Cashbox was removed",
+ "1c": "Cashbox was installed",
+ "1d": "Belly door was opened",
+ "1e": "Belly door was closed",
+ "1f": "No activity and waiting for player input (obsolete)",
+ "20": "General tilt (Use this tilt when other exception tilt codes do not apply or when the tilt condition cannot be determined.)",
+ "21": "Coin in tilt",
+ "22": "Coin out tilt",
+ "23": "Hopper empty detected",
+ "24": "Extra coin paid",
+ "25": "Diverter malfunction (controls coins to drop or hopper)",
+ "27": "Cashbox full detected",
+ "28": "Bill jam",
+ "29": "Bill acceptor hardware failure",
+ "2a": "Reverse bill detected",
+ "2b": "Bill rejected",
+ "2c": "Counterfeit bill detected",
+ "2d": "Reverse coin in detected",
+ "2e": "Cashbox near full detected",
+ "31": "CMOS RAM error (data recovered from EEPROM)",
+ "32": "CMOS RAM error (no data recovered from EEPROM)",
+ "33": "CMOS RAM error (bad device)",
+ "34": "EEPROM error (data error)",
+ "35": "EEPROM error (bad device)",
+ "36": "EPROM error (different checksum – version changed)",
+ "37": "EPROM error (bad checksum compare)",
+ "38": "Partitioned EPROM error (checksum – version changed)",
+ "39": "Partitioned EPROM error (bad checksum compare)",
+ "3a": "Memory error reset (operator used self test switch)",
+ "3b": "Low backup battery detected",
+ "3c": "Operator changed options (This is sent whenever the operator changes configuration options. This includes, but is not limited to, denomination, gaming machine address, or any option that affects the response to long polls 1F, 53, 54, 56, A0, B2, B3, B4, or B5.)",
+ "3d": "A cash out ticket has been printed",
+ "3e": "A handpay has been validated",
+ "3f": "Validation ID not configured",
+ "40": "Reel Tilt (Which reel is not specified.)",
+ "41": "Reel 1 tilt",
+ "42": "Reel 2 tilt",
+ "43": "Reel 3 tilt",
+ "44": "Reel 4 tilt",
+ "45": "Reel 5 tilt",
+ "46": "Reel mechanism disconnected",
+ "47": "$1.00 bill accepted (non-RTE only)",
+ "48": "$5.00 bill accepted (non-RTE only)",
+ "49": "$10.00 bill accepted (non-RTE only)",
+ "4a": "$20.00 bill accepted (non-RTE only)",
+ "4b": "$50.00 bill accepted (non-RTE only)",
+ "4c": "$100.00 bill accepted (non-RTE only)",
+ "4d": "$2.00 bill accepted (non-RTE only)",
+ "4e": "$500.00 bill accepted (non-RTE only)",
+ "4f": "Bill accepted",
+ "50": "$200.00 bill accepted",
+ "51": "Handpay is pending (Progressive, non-progressive or cancelled credits)",
+ "52": "Handpay was reset (Jackpot reset switch activated)",
+ "53": "No progressive information has been received for 5 seconds",
+ "54": "Progressive win (cashout device/credit paid)",
+ "55": "Player has cancelled the handpay request",
+ "56": "SAS progressive level hit",
+ "57": "System validation request",
+ "60": "Printer communication error",
+ "61": "Printer paper out error",
+ "66": "Cash out button pressed",
+ "67": "Ticket has been inserted",
+ "68": "Ticket transfer complete",
+ "69": "AFT transfer complete",
+ "6a": "AFT request for host cashout",
+ "6b": "AFT request for host to cash out win",
+ "6c": "AFT request to register",
+ "6d": "AFT registration acknowledged",
+ "6e": "AFT registration cancelled",
+ "6f": "Game locked",
+ "70": "Exception buffer overflow",
+ "71": "Change lamp on",
+ "72": "Change lamp off",
+ "74": "Printer paper low",
+ "75": "Printer power off",
+ "76": "Printer power on",
+ "77": "Replace printer ribbon",
+ "78": "Printer carriage jammed",
+ "79": "Coin in lockout malfunction (coin accepted while coin mech disabled)",
+ "7a": "Gaming machine soft (lifetime-to-date) meters reset to zero",
+ "7b": "Bill validator (period) totals have been reset by an attendant/operator",
+ "7c": "A legacy bonus pay awarded and/or a multiplied jackpot occurred",
+ "7e": "Game has started",
+ "7f": "Game has ended",
+ "80": "Hopper full detected",
+ "81": "Hopper level low detected",
+ "82": "Display meters or attendant menu has been entered",
+ "83": "Display meters or attendant menu has been exited",
+ "84": "Self test or operator menu has been entered",
+ "85": "Self test or operator menu has been exited",
+ "86": "Gaming machine is out of service (by attendant)",
+ "87": "Player has requested draw cards (only send when in RTE mode)",
+ "88": "Reel N has stopped (only send when in RTE mode)",
+ "89": "Coin/credit wagered (only send when in RTE mode, and only send if the configured max bet is 10 or less)",
+ "8a": "Game recall entry has been displayed",
+ "8b": "Card held/not held (only send when in RTE mode)",
+ "8c": "Game selected",
+ "8e": "Component list changed",
+ "8f": "Authentication complete",
+ "98": "Power off card cage access",
+ "99": "Power off slot door access",
+ "9a": "Power off cashbox door access",
+ "9b": "Power off drop door access",
+ }
+
+ @classmethod
+ def get_status(cls, key):
+ """Get the status value for the given key.
+
+ Args:
+ key (str): The key for the status.
+
+ Returns:
+ str: The corresponding status value or an error message if the key is not found.
+ """
+ # Use get() method to retrieve the value, or return an error message.
+ return cls.STATUS_MAP.get(key, f"Unknown key: {key}")
diff --git a/models/GameFeatures.py b/models/GameFeatures.py
new file mode 100644
index 0000000..630df6c
--- /dev/null
+++ b/models/GameFeatures.py
@@ -0,0 +1,32 @@
+class GameFeatures:
+ """Class representing the Game Features"""
+
+ STATUS_MAP = {
+ "game_number": [],
+ "jackpot_multiplier": [],
+ "AFT_bonus_awards": [],
+ "legacy_bonus_awards": [],
+ "tournament": [],
+ "validation_extensions": [],
+ "validation_style": [],
+ "ticket_redemption": [],
+ }
+
+ @classmethod
+ def get_status(cls, key):
+ """Get the status value for the given key.
+
+ Args:
+ key (str): The key for the status.
+
+ Returns:
+ str: The corresponding status value or an error message if the key is not found.
+ """
+ # Use get() method to retrieve the value, or return an error message.
+ return cls.STATUS_MAP.get(key, f"Unknown key: {key}")
+
+ @classmethod
+ def get_non_empty_status_map(cls):
+ """Return a dictionary containing only keys with non-empty values."""
+ non_empty_map = {key: value for key, value in cls.STATUS_MAP.items() if value}
+ return non_empty_map
diff --git a/models/Meters.py b/models/Meters.py
new file mode 100644
index 0000000..184599d
--- /dev/null
+++ b/models/Meters.py
@@ -0,0 +1,142 @@
+class Meters:
+ """Class representing the Meters"""
+
+ STATUS_MAP = {
+ "total_cancelled_credits_meter": [],
+ "total_in_meter": [],
+ "total_out_meter": [],
+ "total_jackpot_meter": [],
+ "games_played_meter": [],
+ "games_won_meter": [],
+ "games_lost_meter": [],
+ "games_last_power_up": [],
+ "games_last_slot_door_close": [],
+ "slot_door_opened_meter": [],
+ "power_reset_meter": [],
+ "s1_bills_accepted_meter": [],
+ "s5_bills_accepted_meter": [],
+ "s10_bills_accepted_meter": [],
+ "s20_bills_accepted_meter": [],
+ "s50_bills_accepted_meter": [],
+ "s100_bills_accepted_meter": [],
+ "s500_bills_accepted_meter": [],
+ "s1000_bills_accepted_meter": [],
+ "s200_bills_accepted_meter": [],
+ "s25_bills_accepted_meter": [],
+ "s2000_bills_accepted_meter": [],
+ "s2500_bills_accepted_meter": [],
+ "s5000_bills_accepted_meter": [],
+ "s10000_bills_accepted_meter": [],
+ "s20000_bills_accepted_meter": [],
+ "s25000_bills_accepted_meter": [],
+ "s50000_bills_accepted_meter": [],
+ "s100000_bills_accepted_meter": [],
+ "s250_bills_accepted_meter": [],
+ "cashout_ticket_number": [],
+ "cashout_amount_in_cents": [],
+ "ASCII_game_ID": [],
+ "ASCII_additional_ID": [],
+ "bin_denomination": [],
+ "bin_max_bet": [],
+ "bin_progressive_mode": [],
+ "bin_game_options": [],
+ "ASCII_paytable_ID": [],
+ "ASCII_base_percentage": [],
+ "bill_meter_in_dollars": [],
+ "ROM_signature": [],
+ "current_credits": [],
+ "bin_level": [],
+ "amount": [],
+ "partial_pay_amount": [],
+ "bin_reset_ID": [],
+ "true_coin_in": [],
+ "true_coin_out": [],
+ "current_hopper_level": [],
+ "credit_amount_of_all_bills_accepted": [],
+ "coin_amount_accepted_from_external_coin_acceptor": [],
+ "country_code": [],
+ "bill_denomination": [],
+ "meter_for_accepted_bills": [],
+ "number_bills_in_stacker": [],
+ "credits_SAS_in_stacker": [],
+ "machine_ID": [],
+ "sequence_number": [],
+ "validation_type": [],
+ "index_number": [],
+ "date_validation_operation": [],
+ "time_validation_operation": [],
+ "validation_number": [],
+ "ticket_amount": [],
+ "ticket_number": [],
+ "validation_system_ID": [],
+ "expiration_date_printed_on_ticket": [],
+ "pool_id": [],
+ "current_hopper_length": [],
+ "current_hopper_status": [],
+ "current_hopper_percent_full": [],
+ "bin_validation_type": [],
+ "total_validations": [],
+ "cumulative_amount": [],
+ "total_number_of_games_implemented": [],
+ "game_n_number": [],
+ "game_n_coin_in_meter": [],
+ "game_n_coin_out_meter": [],
+ "game_n_jackpot_meter": [],
+ "geme_n_games_played_meter": [],
+ "game_n_number_config": [],
+ "game_n_ASCII_game_ID": [],
+ "game_n_ASCII_additional_id": [],
+ "game_n_bin_denomination": [],
+ "game_n_bin_max_bet": [],
+ "game_n_bin_progressive_group": [],
+ "game_n_bin_game_options": [],
+ "game_n_ASCII_paytable_ID": [],
+ "game_n_ASCII_base_percentage": [],
+ "ASCII_SAS_version": [],
+ "ASCII_serial_number": [],
+ "selected_game_number": [],
+ "number_of_enabled_games": [],
+ "enabled_games_numbers": [],
+ "cashout_type": [],
+ "cashout_amount": [],
+ "ticket_status": [],
+ "parsing_code": [],
+ "validation_data": [],
+ "registration_status": [],
+ "asset_number": [],
+ "registration_key": [],
+ "POS_ID": [],
+ "game_lock_status": [],
+ "available_transfers": [],
+ "host_cashout_status": [],
+ "AFT_status": [],
+ "max_buffer_index": [],
+ "current_cashable_amount": [],
+ "current_restricted_amount": [],
+ "current_non_restricted_amount": [],
+ "restricted_expiration": [],
+ "restricted_pool_ID": [],
+ "game_number": [],
+ "features_1": [],
+ "features_2": [],
+ "features_3": [],
+ }
+
+ @classmethod
+ def get_status(cls, key):
+ """Get the status value for the given key.
+
+ Args:
+ key (str): The key for the status.
+
+ Returns:
+ str: The corresponding status value or an error message if the key is not found.
+ """
+ # Use get() method to retrieve the value, or return an error message.
+ return cls.STATUS_MAP.get(key, f"Unknown key: {key}")
+
+ @classmethod
+ def get_non_empty_status_map(cls):
+ """Return a dictionary containing only keys with non-empty values."""
+ non_empty_map = {key: value for key, value in cls.STATUS_MAP.items() if value}
+ return non_empty_map
diff --git a/models/TitoStatement.py b/models/TitoStatement.py
new file mode 100644
index 0000000..a3cfb73
--- /dev/null
+++ b/models/TitoStatement.py
@@ -0,0 +1,42 @@
+class Tito:
+ """Class representing the TITO"""
+
+ STATUS_MAP = {
+ "asset_number": [],
+ "status_bits": [],
+ "cashable_ticket_receipt_exp": [],
+ "restricted_ticket_exp": [],
+ "cashout_ticket_number": [],
+ "cashout_amount_in_cents": [],
+ "machine_ID": [],
+ "sequence_numbercashout_type": [],
+ "cashout_amount": [],
+ "validation_type": [],
+ "index_number": [],
+ "date_validation_operation": [],
+ "time_validation_operation": [],
+ "validation_number": [],
+ "ticket_amount": [],
+ "ticket_number": [],
+ "validation_system_ID": [],
+ "expiration_date_printed_on_ticketpool_id": [],
+ }
+
+ @classmethod
+ def get_status(cls, key):
+ """Get the status value for the given key.
+
+ Args:
+ key (str): The key for the status.
+
+ Returns:
+ str: The corresponding status value or an error message if the key is not found.
+ """
+ # Use get() method to retrieve the value, or return an error message.
+ return cls.STATUS_MAP.get(key, f"Unknown key: {key}")
+
+ @classmethod
+ def get_non_empty_status_map(cls):
+ """Return a dictionary containing only keys with non-empty values."""
+ non_empty_map = {key: value for key, value in cls.STATUS_MAP.items() if value}
+ return non_empty_map
diff --git a/models/__init__.py b/models/__init__.py
new file mode 100644
index 0000000..951a590
--- /dev/null
+++ b/models/__init__.py
@@ -0,0 +1,13 @@
+__all__ = [
+ "AftLockStatus",
+ "AftReceiptStatus",
+ "AftRegistrationStatus",
+ "AftTransferStatus",
+ "AftTransferType",
+ "Denomination",
+ "GameFeatures",
+ "GPoll",
+ "Meters",
+ "TitoStatement",
+ "AftStatements",
+]
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..47a00b6
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,5 @@
+pythoncrc
+pyserial
+pytest
+pytest-sugar
+pyyaml
\ No newline at end of file
diff --git a/sas.py b/sas.py
index f82db52..dac2fd7 100644
--- a/sas.py
+++ b/sas.py
@@ -1,1953 +1,3510 @@
#!/usr/bin/python
# -*- coding: utf8 -*-
-import bcd
import serial
import time
import binascii
-#import string
-from PyCRC.CRC16Kermit import CRC16Kermit
-from array import array
-#ser = serial.Serial('/dev/ttyS3','19200', timeout=1) # open first serial port
-data_to_sent=[0x01, 0x21, 0x00, 0x00]
-#adress=1
-#print "OK"
-meters = dict.fromkeys(('total_cancelled_credits_meter',
- 'total_in_meter',
- 'total_out_meter',
- 'total_in_meter',
- 'total_jackpot_meter',
- 'games_played_meter',
- 'games_won_meter',
- 'games_lost_meter',
- 'games_last_power_up',
- 'games_last_slot_door_close',
- 'slot_door_opened_meter',
- 'power_reset_meter',
- 's1_bills_accepted_meter',
- 's5_bills_accepted_meter',
- 's10_bills_accepted_meter',
- 's20_bills_accepted_meter',
- 's50_bills_accepted_meter',
- 's100_bills_accepted_meter',
- 's500_bills_accepted_meter',
- 's1000_bills_accepted_meter',
- 's200_bills_accepted_meter',
- 's25_bills_accepted_meter',
- 's2000_bills_accepted_meter',
- 's2500_bills_accepted_meter',
- 's5000_bills_accepted_meter',
- 's10000_bills_accepted_meter',
- 's20000_bills_accepted_meter',
- 's25000_bills_accepted_meter',
- 's50000_bills_accepted_meter',
- 's100000_bills_accepted_meter',
- 's250_bills_accepted_meter',
- 'cashout_ticket_number',
- 'cashout_amount_in_cents',
- 'ASCII_game_ID',
- 'ASCII_additional_ID',
- 'bin_denomination',
- 'bin_max_bet',
- 'bin_progressive_mode',
- 'bin_game_options',
- 'ASCII_paytable_ID',
- 'ASCII_base_percentage',
- 'bill_meter_in_dollars',
- 'ROM_signature',
- 'current_credits',
- 'bin_level',
- 'amount',
- 'partial_pay_amount',
- 'bin_reset_ID',
- 'bill_meter_in_dollars',
- 'true_coin_in',
- 'true_coin_out',
- 'current_hopper_level',
- 'credit_amount_of_all_bills_accepted',
- 'coin_amount_accepted_from_external_coin_acceptor',
- 'country_code',
- 'bill_denomination',
- 'meter_for_accepted_bills',
- 'number_bills_in_stacker',
- 'credits_SAS_in_stacker',
- 'machine_ID',
- 'sequence_number',
- 'validation_type',
- 'index_number',
- 'date_validation_operation',
- 'time_validation_operation',
- 'validation_number',
- 'ticket_amount',
- 'ticket_number',
- 'validation_system_ID',
- 'expiration_date_printed_on_ticket',
- 'pool_id',
- 'current_hopper_lenght',
- 'current_hopper_ststus',
- 'current_hopper_percent_full',
- 'current_hopper_level',
- 'bin_validation_type',
- 'total_validations',
- 'cumulative_amount',
- 'total_number_of_games_impemented',
- 'game_n_number',
- 'game_n_coin_in_meter',
- 'game_n_coin_out_meter',
- 'game_n_jackpot_meter',
- 'geme_n_games_played_meter',
- 'game_n_number_config',
- 'game_n_ASCII_game_ID',
- 'game_n_ASCII_additional_id',
- 'game_n_bin_denomination',
- 'game_n_bin_max_bet',
- 'game_n_bin_progressive_group',
- 'game_n_bin_game_options',
- 'game_n_ASCII_paytable_ID',
- 'game_n_ASCII_base_percentage',
- 'ASCII_SAS_version',
- 'ASCII_serial_number',
- 'selected_game_number',
- 'number_of_enabled_games',
- 'enabled_games_numbers',
- 'cashout_type',
- 'cashout_amount',
- 'ticket_status',
- 'ticket_amount',
- 'parsing_code',
- 'validation_data',
- 'registration_status',
- 'asset_number',
- 'registration_key',
- 'POS_ID',
- 'game_lock_status',
- 'avilable_transfers',
- 'host_cashout_status',
- 'AFT_ststus',
- 'max_buffer_index',
- 'current_cashable_amount',
- 'current_restricted_amount',
- 'current_non_restricted_amount',
- 'restricted_expiration',
- 'restricted_pool_ID',
- 'game_number',
- 'features_1',
- 'features_2',
- 'features_3'
-
-
- ),[])
-aft_statement=dict.fromkeys((
- 'registration_status',
- 'asset_number',
- 'registration_key',
- 'POS_ID',
- 'transaction_buffer_position',
- 'transfer_status',
- 'receipt_status',
- 'transfer_type',
- 'cashable_amount',
- 'restricted_amount',
- 'nonrestricted_amount',
- 'transfer_flags',
- 'asset_number',
- 'transaction_ID_lenght',
- 'transaction_ID',
- 'transaction_date',
- 'transaction_time',
- 'expiration',
- 'pool_ID',
- 'cumulative_casable_amount_meter_size',
- 'cumulative_casable_amount_meter',
- 'cumulative_restricted_amount_meter_size',
- 'cumulative_restricted_amount_meter',
- 'cumulative_nonrestricted_amount_meter_size',
- 'cumulative_nonrestricted_amount_meter',
- 'asset_number',
- 'game_lock_status',
- 'avilable_transfers',
- 'host_cashout_status',
- 'AFT_status',
- 'max_buffer_index',
- 'current_cashable_amount',
- 'current_restricted_amount',
- 'current_non_restricted_amount',
- 'restricted_expiration',
- 'restricted_pool_ID',
-
-
-
-
- ),[])
-tito_statement=dict.fromkeys((
- 'asset_number',
- 'status_bits',
- 'cashable_ticket_reciept_exp',
- 'restricted_ticket_exp',
- 'cashout_ticket_number',
- 'cashout_amount_in_cents',
- 'machine_ID',
- 'sequence_number'
- 'cashout_type',
- 'cashout_amount',
- 'validation_type',
- 'index_number',
- 'date_validation_operation',
- 'time_validation_operation',
- 'validation_number',
- 'ticket_amount',
- 'ticket_number',
- 'validation_system_ID',
- 'expiration_date_printed_on_ticket'
- 'pool_id'
- ),[])
-
-
-eft_statement=dict.fromkeys((
- 'eft_status',
- 'promo_amount',
- 'cashable_amount',
- 'eft_transfer_counter'
-
- ),[])
-game_features=dict.fromkeys((
- 'game_number',
- 'jackpot_multiplier',
- 'AFT_bonus_avards',
- 'legacy_bonus_awards',
- 'tournament',
- 'validation_extensions',
- 'validation_style',
- 'ticket_redemption'
- ),[])
-class sas(object):
- adress=1
-
- def __init__(self, port):
- try:
- #print 1
- self.connection=serial.Serial(port=port,baudrate=19200, timeout=2)
- except:
- print "port error"
- return
- def start(self):
- print 'Connecting SAS...'
- while True:
- response =self.connection.read(1)
- if (response<>''):
- self.adress=int(binascii.hexlify(response))
- if self.adress>=1:
- print 'adress recognised '+str(self.adress)
- break
-
- #print str(binascii.hexlifyself.adress))
- time.sleep(.5)
-
- self.gaming_machine_ID()
- print meters.get('ASCII_game_ID')
- print meters.get('ASCII_additional_ID')
- print meters.get('bin_denomination')
- print meters.get('bin_max_bet')
- print meters.get('bin_progressive_mode')
- print meters.get('bin_game_options')
- print meters.get('ASCII_paytable_ID')
- print meters.get('ASCII_base_percentage')
- self.SAS_version_gaming_machine_serial_ID()
- print meters.get('ASCII_SAS_version')
- print meters.get('ASCII_serial_number')
- self.enabled_features() #todo
-
- # 7e date_time_add
- self.AFT_register_gaming_machine(reg_code=0xff)
- print aft_statement.get('registration_status')
- print aft_statement.get('asset_number')
- print aft_statement.get('registration_key')
- print aft_statement.get('POS_ID')
-
-
-
-
- return True
-
- def __send_command( self, command, no_response=False, timeout=3, crc_need=True):
- busy = True
- response=b''
- try:
- buf_header=[self.adress]
- buf_header.extend(command)
- buf_count=len(command)
- #buf_header[2]=buf_count+2
- if (crc_need==True):
- crc=CRC16Kermit().calculate(str(bytearray(buf_header)))
- buf_header.extend([((crc>>8)&0xFF),(crc&0xFF)])
- #print buf_header
- print buf_header
- #print self.connection.portstr
- #self.connection.write([0x31, 0x32,0x33,0x34,0x35])
- self.connection.write((buf_header))
-
- except Exception as e:
- print e
+import logging
+import datetime
+
+from utils import Crc
+from utils.Decorators import deprecated
+from multiprocessing import log_to_stderr
+
+from models import *
+from error_handler import *
+
+__author__ = "Zachary Tomlinson, Antonio D'Angelo"
+__credits__ = ["Thomas Pythonas", "Grigor Kolev"]
+__license__ = "MIT"
+__version__ = "2.0.0"
+__maintainer__ = "Zachary Tomlinson, Antonio D'Angelo"
+__status__ = "Staging"
+
+
+class Sas:
+ """Main SAS Library Class"""
+
+ def __init__(
+ self,
+ port, # Serial Port full Address
+ timeout=2, # Connection timeout
+ poll_address=0x82, # Poll Address
+ denom=0.01, # Denomination
+ asset_number="01000000", # Asset Number
+ reg_key="0000000000000000000000000000000000000000", # Reg Key
+ pos_id="B374A402", # Pos ID
+ key="44", # Key
+ debug_level="DEBUG", # Debug Level
+ perpetual=False, # When this is true the lib will try forever to connect to the serial
+ check_last_transaction = True
+ ):
+ # Let's address some internal var
+ self.poll_timeout = timeout
+ self.address = None
+ self.machine_n = None
+ self.check_last_transaction = check_last_transaction
+ self.denom = denom
+ self.asset_number = asset_number
+ self.reg_key = reg_key
+ self.pos_id = pos_id
+ self.transaction = None
+ self.my_key = key
+ self.poll_address= poll_address
+ self.perpetual = perpetual
+
+ # Init the Logging system
+ self.log = log_to_stderr()
+ self.log.setLevel(logging.getLevelName(debug_level))
+ self.last_gpoll_event = None
+
+ # Open the serial connection
+ while 1:
+ try:
+ self.connection = serial.Serial(
+ port=port,
+ baudrate=19200,
+ timeout=timeout,
+ )
+ self.close()
+ self.timeout = timeout
+ self.log.info("Connection Successful")
+ break
+ except:
+ if not self.perpetual:
+ self.log.critical("Error while connecting to the machine....Quitting...")
+ exit(1) # Make a graceful exit since it's expected behaviour
+
+ self.log.critical("Error while connecting to the machine....")
+ time.sleep(1)
- try:
- buffer = []
- self.connection.flushInput()
- t=time.time()
- while time.time()-tFalse):
- break
-
- if time.time()-t>=timeout:
- print "timeout waiting response"
- #buffer.append(response)
- #print binascii.hexlify(bytearray(response))
- return None
-
- busy = False
- return self.checkResponse(response)
- #return None
- except Exception as e:
- print e
-
- busy = False
- return None
-
- def checkResponse(self, rsp):
- if (rsp==''):
- print 'not response'
- return False
-
- resp = bytearray(rsp)
- #print resp
- if (resp[0]<>self.adress):
- print "wrong ardess or NACK"
- return False
-
- CRC = binascii.hexlify(resp[-2:])
-
-
- command = resp[0:-2]
-
- crc1=crc=CRC16Kermit().calculate(str(bytearray(command)))
-
- data = resp[1:-2]
-
- crc1 = hex(crc1).split('x')[-1]
-
- while len(crc1)<4:
- crc1 = "0"+crc1
-
- #print crc1
- #print CRC
- if(CRC != crc1):
-
- #print "Wrong response command hash " + str(CRC)
- #print "////" + str(hex(crc1).split('x')[-1])
- #print "////" + str(binascii.hexlify(command))
- return False
- print binascii.hexlify(data)
- return data
-## def check_crc(self):
-## cmd=[0x01, 0x50, 0x81]
-## cmd=bytearray(cmd)
-## #print self.sas_CRC([0x01, 0x50, 0x81])
-## #print ('\\'+'\\'.join(hex(e)[1:] for e in cmd))
-##
-## print (CRC16Kermit().calculate(str(cmd)))
-## return
-
- def events_poll(self, timeout=1):
- event=''
- cmd=[0x80+self.adress]
- try:
- self.connection.write(cmd)
- t=time.time()
- while time.time()-t> 8) & 0xFF), (game & 0xFF)])
+ cmd.extend(bytearray(en_dis))
+
+ if self._send_command(cmd, True, crc_need=True) == self.address:
+ return True
+
+ return False
+
+ def enter_maintenance_mode(self):
+ """Put the VLT in a state of maintenance mode
+ Returns
+ -------
+ bool
+ True if successful, False otherwise.
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ if self._send_command([0x0A], True, crc_need=True) == self.address:
+ return True
+
+ return False
+
+ def exit_maintenance_mode(self):
+ """Recover the VLT from a state of maintenance mode
+ Returns
+ -------
+ bool
+ True if successful, False otherwise.
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ if self._send_command([0x0B], True, crc_need=True) == self.address:
+ return True
+
+ return False
+
+ def en_dis_rt_event_reporting(self, enable=False):
+ """For situations where real time event reporting is desired, the gaming machine can be configured to report events in response to long polls as well as general polls. This allows events such as reel stops, coins in, game end, etc., to be reported in a timely manner
+ Returns
+ -------
+ bool
+ True if successful, False otherwise.
+
+ See Also
+ --------
+ WiKi : https://github.com/zacharytomlinson/saspy/wiki/4.-Important-To-Know#event-reporting
+ """
+ if not enable:
+ enable = [0]
+ else:
+ enable = [1]
+
+ cmd = [0x0E]
+ cmd.extend(bytearray(enable))
+
+ if self._send_command(cmd, True, crc_need=True) == self.address:
+ return True
+
+ return False
+
+ def send_meters_10_15(self, denom=True):
+ """Send meters 10 through 15
+
+ Parameters
+ ----------
+ denom : bool
+ If True will return the values of the meters in float format (i.e. 123.23)
+ otherwise as int (i.e. 12323)
+
+ Returns
+ -------
+ Mixed
+ Object containing the translated meters or None
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ cmd = [0x0F]
+ data = self._send_command(cmd, crc_need=False, size=28)
+ if data:
+ meters = {}
+ if denom:
+ Meters.Meters.STATUS_MAP["total_cancelled_credits_meter"] = round(
+ int((binascii.hexlify(bytearray(data[1:5])))) * self.denom, 2
+ )
+ Meters.Meters.STATUS_MAP["total_in_meter"] = round(
+ int(binascii.hexlify(bytearray(data[5:9]))) * self.denom, 2
+ )
+ Meters.Meters.STATUS_MAP["total_out_meter"] = round(
+ int(binascii.hexlify(bytearray(data[9:13]))) * self.denom, 2
+ )
+ Meters.Meters.STATUS_MAP["total_droup_meter"] = round(
+ int(binascii.hexlify(bytearray(data[13:17]))) * self.denom, 2
+ )
+ Meters.Meters.STATUS_MAP["total_jackpot_meter"] = round(
+ int(binascii.hexlify(bytearray(data[17:21]))) * self.denom, 2
+ )
+ Meters.Meters.STATUS_MAP["games_played_meter"] = int(
+ binascii.hexlify(bytearray(data[21:25]))
+ )
+ else:
+ Meters.Meters.STATUS_MAP["total_cancelled_credits_meter"] = int(
+ (binascii.hexlify(bytearray(data[1:5])))
+ )
+ Meters.Meters.STATUS_MAP["total_in_meter"] = int(
+ binascii.hexlify(bytearray(data[5:9]))
+ )
+ Meters.Meters.STATUS_MAP["total_out_meter"] = int(
+ binascii.hexlify(bytearray(data[9:13]))
+ )
+ Meters.Meters.STATUS_MAP["total_droup_meter"] = int(
+ binascii.hexlify(bytearray(data[13:17]))
+ )
+ Meters.Meters.STATUS_MAP["total_jackpot_meter"] = int(
+ binascii.hexlify(bytearray(data[17:21]))
+ )
+ Meters.Meters.STATUS_MAP["games_played_meter"] = int(
+ binascii.hexlify(bytearray(data[21:25]))
+ )
+
+ return Meters.Meters.get_non_empty_status_map()
+
+ return None
+
+ def total_cancelled_credits(self, denom=True):
+ """Send total cancelled credits meter
+
+ Parameters
+ ----------
+ denom : bool
+ If True will return the values of the meters in float format (i.e. 123.23)
+ otherwise as int (i.e. 12323)
+
+ Returns
+ -------
+ Mixed
+ Round | INT | None
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ cmd = [0x10]
+ data = self._send_command(cmd, crc_need=False, size=8)
+ if data:
+ if denom:
+ return round(
+ int(binascii.hexlify(bytearray(data[1:5]))) * self.denom, 2
+ )
+ else:
+ return int(binascii.hexlify(bytearray(data[1:5])))
+
+ return None
+
+ def total_bet_meter(self, denom=True):
+ """Send total coin in meter
+ Parameters
+ ----------
+ denom : bool
+ If True will return the values of the meters in float format (i.e. 123.23)
+ otherwise as int (i.e. 12323)
+
+ Returns
+ -------
+ Mixed
+ Round | INT | None
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND - Pretty sure that the param should not be used @todo CHECK ME
+ """
+ cmd = [0x11]
+ data = self._send_command(cmd, crc_need=False, size=8)
+ if data:
+ if denom:
+ return round(
+ int(binascii.hexlify(bytearray(data[1:5]))) * self.denom, 2
+ )
+ else:
+ return int(binascii.hexlify(bytearray(data[1:5])))
+
+ return None
+
+ def total_win_meter(self, denom=True):
+ """Send total coin out meter
+ Parameters
+ ----------
+ denom : bool
+ If True will return the values of the meters in float format (i.e. 123.23)
+ otherwise as int (i.e. 12323)
+
+ Returns
+ -------
+ Mixed
+ Round | INT | None
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND - Pretty sure that the param should not be used @todo CHECK ME
+ """
+ cmd = [0x12]
+ data = self._send_command(cmd, crc_need=False, size=8)
+ if data:
+ if denom:
+ return round(
+ int(binascii.hexlify(bytearray(data[1:5]))) * self.denom, 2
+ )
+ else:
+ return int(binascii.hexlify(bytearray(data[1:5])))
+
+ return None
+
+ def total_drop_meter(self, denom=True):
+ """Send total drop meter
+ Parameters
+ ----------
+ denom : bool
+ If True will return the values of the meters in float format (i.e. 123.23)
+ otherwise as int (i.e. 12323)
+
+ Returns
+ -------
+ Mixed
+ Round | INT | None
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND - Pretty sure that the param should not be used @todo CHECK ME
+ """
+ cmd = [0x13]
+ data = self._send_command(cmd, crc_need=False, size=8)
+ if data:
+ if denom:
+ return round(
+ int(binascii.hexlify(bytearray(data[1:5]))) * self.denom, 2
+ )
+ else:
+ return int(binascii.hexlify(bytearray(data[1:5])))
+
+ return None
+
+ def total_jackpot_meter(self, denom=True):
+ """Send total jackpot meter
+ Parameters
+ ----------
+ denom : bool
+ If True will return the values of the meters in float format (i.e. 123.23)
+ otherwise as int (i.e. 12323)
+
+ Returns
+ -------
+ Mixed
+ Round | INT | None
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND - Pretty sure that the param should not be used @todo CHECK ME
+ """
+ cmd = [0x14]
+ data = self._send_command(cmd, crc_need=False, size=8)
+ if data:
+ if denom:
+ return round(
+ int(binascii.hexlify(bytearray(data[1:5]))) * self.denom, 2
+ )
+ else:
+ return int(binascii.hexlify(bytearray(data[1:5])))
+
+ return None
+
+ def games_played_meter(self):
+ """Send games played meter
+
+ Returns
+ -------
+ Mixed
+ INT | None
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ cmd = [0x15]
+ data = self._send_command(cmd, crc_need=False, size=8)
+ if data:
+ return int(binascii.hexlify(bytearray(data[1:5])))
+
+ return None
+
+ def games_won_meter(self, denom=True):
+ """Send games won meter
+ Parameters
+ ----------
+ denom : bool
+ If True will return the values of the meters in float format (i.e. 123.23)
+ otherwise as int (i.e. 12323)
+
+ Returns
+ -------
+ Mixed
+ Round | INT | None
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND - Pretty sure that the param should not be used @todo CHECK ME
+ """
+ cmd = [0x16]
+ data = self._send_command(cmd, crc_need=False, size=8)
+ if data:
+ if denom:
+ return round(
+ int(binascii.hexlify(bytearray(data[1:5]))) * self.denom, 2
+ )
+ else:
+ return int(binascii.hexlify(bytearray(data[1:5])))
+
+ return None
+
+ def games_lost_meter(self):
+ """Send games won meter
+ Returns
+ -------
+ Mixed
+ INT | None
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ cmd = [0x17]
+ data = self._send_command(cmd, crc_need=False, size=8)
+ if data:
+ return int(binascii.hexlify(bytearray(data[1:5])))
+
+ return None
+
+ def games_powerup_door_opened(self):
+ """Send meters 10 through 15
+
+ Returns
+ -------
+ Mixed
+ Object containing the translated meters or None
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ cmd = [0x18]
+ data = self._send_command(cmd, crc_need=False, size=8)
+ if data:
+ Meters.Meters.STATUS_MAP["games_last_power_up"] = int(
+ binascii.hexlify(bytearray(data[1:3]))
+ )
+ Meters.Meters.STATUS_MAP["games_last_slot_door_close"] = int(
+ binascii.hexlify(bytearray(data[1:5]))
+ )
+ return Meters.Meters.get_non_empty_status_map()
+
+ return None
+
+ def meters_11_15(self, denom=True):
+ """Send meters 11 through 15
+
+ Parameters
+ ----------
+ denom : bool
+ If True will return the values of the meters in float format (i.e. 123.23)
+ otherwise as int (i.e. 12323)
+
+ Returns
+ -------
+ Mixed
+ Object containing the translated meters or None
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ cmd = [0x19]
+ data = self._send_command(cmd, crc_need=False, size=24)
+ if data:
+ if not denom:
+ Meters.Meters.STATUS_MAP["total_bet_meter"] = int(
+ binascii.hexlify(bytearray(data[1:5]))
+ )
+ Meters.Meters.STATUS_MAP["total_win_meter"] = int(
+ binascii.hexlify(bytearray(data[5:9]))
+ )
+ Meters.Meters.STATUS_MAP["total_in_meter"] = int(
+ binascii.hexlify(bytearray(data[9:13]))
+ )
+ Meters.Meters.STATUS_MAP["total_jackpot_meter"] = int(
+ binascii.hexlify(bytearray(data[13:17]))
+ )
+ Meters.Meters.STATUS_MAP["games_played_meter"] = int(
+ binascii.hexlify(bytearray(data[17:21]))
+ )
+ else:
+ Meters.Meters.STATUS_MAP["total_bet_meter"] = round(
+ int(binascii.hexlify(bytearray(data[1:5]))) * self.denom, 2
+ )
+ Meters.Meters.STATUS_MAP["total_win_meter"] = round(
+ int(binascii.hexlify(bytearray(data[5:9]))) * self.denom, 2
+ )
+ Meters.Meters.STATUS_MAP["total_in_meter"] = round(
+ int(binascii.hexlify(bytearray(data[9:13]))) * self.denom, 2
+ )
+ Meters.Meters.STATUS_MAP["total_jackpot_meter"] = round(
+ int(binascii.hexlify(bytearray(data[13:17]))) * self.denom, 2
+ )
+ Meters.Meters.STATUS_MAP["games_played_meter"] = int(
+ binascii.hexlify(bytearray(data[17:21]))
+ )
+ return Meters.Meters.get_non_empty_status_map()
+
+ return None
+
+ def current_credits(self, denom=True):
+ """Send current credits
+
+ Parameters
+ ----------
+ denom : bool
+ If True will return the value in float format (i.e. 123.23)
+ otherwise as int (i.e. 12323)
+
+ Returns
+ -------
+ Mixed
+ round | int | None
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ cmd = [0x1A]
+ data = self._send_command(cmd, crc_need=False, size=8)
+ if data:
+ if denom:
+ return round(
+ int(binascii.hexlify(bytearray(data[1:5]))) * self.denom, 2
+ )
+ else:
+ return int(binascii.hexlify(bytearray(data[1:5])))
+
+ return None
+
+ def handpay_info(self):
+ """Send handpay information
+
+ Returns
+ -------
+ Mixed
+ Object containing the translated meters or None
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND - Warning: is missing 2-byte BCD Partial pay amount @todo FIX ME !
+ """
+ cmd = [0x1B]
+ data = self._send_command(cmd, crc_need=False)
+ if data:
+ Meters.Meters.STATUS_MAP["bin_progressive_group"] = int(
+ binascii.hexlify(bytearray(data[1:2]))
+ )
+ Meters.Meters.STATUS_MAP["bin_level"] = int(
+ binascii.hexlify(bytearray(data[2:3]))
+ )
+ Meters.Meters.STATUS_MAP["amount"] = int(
+ binascii.hexlify(bytearray(data[3:8]))
+ )
+ Meters.Meters.STATUS_MAP["bin_reset_ID"] = int(
+ binascii.hexlify(bytearray(data[8:]))
+ )
+ return Meters.Meters.get_non_empty_status_map()
+
+ return None
+
+ def meters(self, denom=True):
+ """Send Meters
+
+ Parameters
+ ----------
+ denom : bool
+ If True will return the value in float format (i.e. 123.23)
+ otherwise as int (i.e. 12323)
+
+ Returns
+ -------
+ Mixed
+ Object containing the translated meters (in int or float) or None
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ cmd = [0x1C]
+ data = self._send_command(cmd, crc_need=False, size=36)
+ if data:
+ if not denom:
+ Meters.Meters.STATUS_MAP["total_bet_meter"] = int(
+ binascii.hexlify(bytearray(data[1:5]))
+ )
+ Meters.Meters.STATUS_MAP["total_win_meter"] = int(
+ binascii.hexlify(bytearray(data[5:9]))
+ )
+ Meters.Meters.STATUS_MAP["total_drop_meter"] = int(
+ binascii.hexlify(bytearray(data[9:13]))
+ )
+ Meters.Meters.STATUS_MAP["total_jackpot_meter"] = int(
+ binascii.hexlify(bytearray(data[13:17]))
+ )
+ Meters.Meters.STATUS_MAP["games_played_meter"] = int(
+ binascii.hexlify(bytearray(data[17:21]))
+ )
+ Meters.Meters.STATUS_MAP["games_won_meter"] = int(
+ binascii.hexlify(bytearray(data[21:25]))
+ )
+ Meters.Meters.STATUS_MAP["slot_door_opened_meter"] = int(
+ binascii.hexlify(bytearray(data[25:29]))
+ )
+ Meters.Meters.STATUS_MAP["power_reset_meter"] = int(
+ binascii.hexlify(bytearray(data[29:33]))
+ )
+ else:
+ Meters.Meters.STATUS_MAP["total_bet_meter"] = round(
+ int(binascii.hexlify(bytearray(data[1:5]))) * self.denom, 2
+ )
+ Meters.Meters.STATUS_MAP["total_win_meter"] = round(
+ int(binascii.hexlify(bytearray(data[5:9]))) * self.denom, 2
+ )
+ Meters.Meters.STATUS_MAP["total_drop_meter"] = round(
+ int(binascii.hexlify(bytearray(data[9:13]))) * self.denom, 2
+ )
+ Meters.Meters.STATUS_MAP["total_jackpot_meter"] = round(
+ int(binascii.hexlify(bytearray(data[13:17]))) * self.denom, 2
+ )
+ Meters.Meters.STATUS_MAP["games_played_meter"] = int(
+ binascii.hexlify(bytearray(data[17:21]))
+ )
+ Meters.Meters.STATUS_MAP["games_won_meter"] = round(
+ int(binascii.hexlify(bytearray(data[21:25]))) * self.denom, 2
+ )
+ Meters.Meters.STATUS_MAP["slot_door_opened_meter"] = int(
+ binascii.hexlify(bytearray(data[25:29]))
+ )
+ Meters.Meters.STATUS_MAP["power_reset_meter"] = int(
+ binascii.hexlify(bytearray(data[29:33]))
+ )
+
+ return Meters.Meters.get_non_empty_status_map()
+
+ return None
+
+ def total_bill_meters(self):
+ """Send total bill meters (# of bills)
+
+ Returns
+ -------
+ Mixed
+ Object containing the translated meters or None
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ cmd = [0x1E]
+ data = self._send_command(cmd, crc_need=False, size=28)
+ if data:
+ Meters.Meters.STATUS_MAP["s1_bills_accepted_meter"] = int(
+ binascii.hexlify(bytearray(data[1:5]))
+ )
+ Meters.Meters.STATUS_MAP["s5_bills_accepted_meter"] = int(
+ binascii.hexlify(bytearray(data[5:9]))
+ )
+ Meters.Meters.STATUS_MAP["s10_bills_accepted_meter"] = int(
+ binascii.hexlify(bytearray(data[9:13]))
+ )
+ Meters.Meters.STATUS_MAP["s20_bills_accepted_meter"] = int(
+ binascii.hexlify(bytearray(data[13:17]))
+ )
+ Meters.Meters.STATUS_MAP["s50_bills_accepted_meter"] = int(
+ binascii.hexlify(bytearray(data[17:21]))
+ )
+ Meters.Meters.STATUS_MAP["s100_bills_accepted_meter"] = int(
+ binascii.hexlify(bytearray(data[21:25]))
+ )
+
+ return Meters.Meters.get_non_empty_status_map()
+
+ return None
+
+ def gaming_machine_id(self):
+ """Gaming machine information command
+ @todo Check this one...something smell bad
+
+ According to doc:
+ ===================== ====== ================= ========================================================================================================================================
+ Field Bytes Value Description
+ ===================== ====== ================= ========================================================================================================================================
+ Address 1 binary 01-7F Address of gaming machine responding
+ Command 1 binary 1F Gaming machine information command
+ Game ID 2 ASCII ?? Game ID in ASCII. (see Table C-1 in Appendix C)
+ Additional ID 3 ASCII ??? Additional game ID in ASCII. If the gaming machine does not support an additional ID, this field should be padded with ASCII "0"s.
+ Denomination 1 binary 00-FF Binary number representing the SAS accounting denomination of this gaming machine
+ Max bet 1 binary 01-FF Largest configured max bet for the gaming machine, or FF if largest configured max bet greater than or equal to 255
+ Progressive Group 1 binary 00-FF Current configured progressive group for the gaming machine
+ Game options 2 binary 0000-FFFF Game options selected by the operator. The bit configurations are dependent upon the type of gaming machine.
+ Paytable ID 6 ASCII ?????? Paytable ID in ASCII
+ Base % 4 ASCII ??.?? Theoretical base pay back percentage for maximum bet in ASCII. The decimal is implied and NOT transmitted.
+ CRC 2 binary 0000-FFFF 16-bit CRC
+ ===================== ====== ================= ========================================================================================================================================
+
+ """
+ # 1F
+ cmd = [0x1F]
+ data = self._send_command(cmd, crc_need=False, size=24)
+ if data is not None:
+ denom = Denomination.Denomination.get_status(data[6:7].hex())
+ self.log.info("Recognized " + str(denom))
+ self.denom = denom
+ return denom
+ # meters['ASCII_game_ID']=(((data[1:3])))
+ # meters['ASCII_additional_ID']=(((data[3:6])))
+ # meters['bin_denomination']=int(self.hexlify(self.bytearray(data[4:5])))
+ # meters['bin_max_bet']=(self.hexlify(self.bytearray(data[7:8])))
+ # meters['bin_progressive_mode']=int(self.hexlify(self.bytearray(data[8:9])))
+ # meters['bin_game_options']=(self.hexlify(self.bytearray(data[9:11])))
+ # meters['ASCII_paytable_ID']=(((data[11:17])))
+ # meters['ASCII_base_percentage']=(((data[17:21])))
+
+ # return data
+ return None
+
+ def total_dollar_value_of_bills_meter(self):
+ """Send total dollar value of bills meter
+
+ Returns
+ -------
+ Mixed
+ int | none
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ cmd = [0x20]
+ data = self._send_command(cmd, crc_need=False, size=8)
+
+ if data:
+ return int(binascii.hexlify(bytearray(data[1:])))
+
+ return None
+
+ def rom_signature_verification(self):
+ """ROM Signature Verification
+
+ Returns
+ -------
+ Mixed
+ int | none
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ cmd = [0x21, 0x00, 0x00]
+ data = self._send_command(cmd, crc_need=True)
+ if data:
+ return int(binascii.hexlify(bytearray(data[1:3])))
+
+ return None
+
+ def true_coin_in(self):
+ """Send true coin in
+
+ Returns
+ -------
+ Mixed
+ int (meter in # of coins/tokens) | none
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ cmd = [0x2A]
+ data = self._send_command(cmd, crc_need=False)
+ if data:
+ return int(binascii.hexlify(bytearray(data[1:5])))
+
+ return None
+
+ def true_coin_out(self):
+ """Send true coin out
+
+ Returns
+ -------
+ Mixed
+ int (meter in # of coins/tokens) | none
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ cmd = [0x2B]
+ data = self._send_command(cmd, crc_need=False)
+ if data:
+ return int(binascii.hexlify(bytearray(data[1:5])))
+
+ return None
+
+ def curr_hopper_level(self):
+ """Send current hopper level
+
+ Returns
+ -------
+ Mixed
+ int (meter in # of coins/tokens) | none
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ cmd = [0x2C]
+ data = self._send_command(cmd, crc_need=False)
+ if data:
+ return int(binascii.hexlify(bytearray(data[1:5])))
+
+ return None
+
+ def total_hand_paid_cancelled_credit(self):
+ """Send total hand paid cancelled credits
+
+ Notes
+ -------
+ WARNING ! @todo i return: 2-byte BCD game number and 4-byte BCD meter in SAS accounting denom units. Therefore this code is WRONG
+
+ This is a LONG POLL COMMAND
+ """
+ cmd = [0x2D]
+ data = self._send_command(cmd, crc_need=False, size=8)
+ if data:
+ return int(binascii.hexlify(bytearray(data[1:5])))
+
+ return None
+
+ def delay_game(self, delay_time=100):
+ """Delay Game
+ Parameters
+ ----------
+ delay_time : int
+ How long in ms to delay a game
+
+ Returns
+ -------
+ bool
+ True for a successful operation, False otherwise
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ delay_time = str(delay_time)
+ delay_fmt = "" + ("0" * (4 - len(delay_time)) + delay_time)
+ cmd = [0x2E]
+ count = 0
+ for i in range(len(delay_fmt) // 2):
+ cmd.append(int(delay_fmt[count: count + 2], 16))
+ count += 2
+ if self._send_command(cmd, True, crc_need=True) == self.address:
+ return True
+ else:
+ return False
+
+ @staticmethod
+ def selected_meters_for_game():
+ # 2F
+ # TODO: selected_meters_for_game
+ # As per above...NOT ME ! @well-it-wasnt-me
+ return None
+
+ def send_1_bills_in_meters(self):
+ """Send 1$ bills in meters
+
+ Returns
+ -------
+ mixed
+ int (# of bills) or None
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ cmd = [0x31]
+ data = self._send_command(cmd, crc_need=False, size=8)
+ if data:
+ return int(binascii.hexlify(bytearray(data[1:5])))
+
+ return None
+
+ def send_2_bills_in_meters(self):
+ """Send 2$ bills in meters
+
+ Returns
+ -------
+ mixed
+ int (# of bills) or None
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ cmd = [0x32]
+ data = self._send_command(cmd, crc_need=False, size=8)
+ if data:
+ return int(binascii.hexlify(bytearray(data[1:5])))
+
+ return None
+
+ def send_5_bills_in_meters(self):
+ """Send 5$ bills in meters
+
+ Returns
+ -------
+ mixed
+ int (# of bills) or None
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ cmd = [0x33]
+ data = self._send_command(cmd, crc_need=False, size=8)
+ if data:
+ return int(binascii.hexlify(bytearray(data[1:5])))
+
+ return None
+
+ def send_10_bills_in_meters(self):
+ """Send 10$ bills in meters
+
+ Returns
+ -------
+ mixed
+ int (# of bills) or None
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ cmd = [0x34]
+ data = self._send_command(cmd, crc_need=False, size=8)
+ if data:
+ return int(binascii.hexlify(bytearray(data[1:5])))
+
+ return None
+
+ def send_20_bills_in_meters(self):
+ """Send 20$ bills in meters
+
+ Returns
+ -------
+ mixed
+ int (# of bills) or None
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ cmd = [0x35]
+ data = self._send_command(cmd, crc_need=False, size=8)
+ if data:
+ return int(binascii.hexlify(bytearray(data[1:5])))
+
+ return None
+
+ def send_50_bills_in_meters(self):
+ """Send 50$ bills in meters
+
+ Returns
+ -------
+ mixed
+ int (# of bills) or None
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ cmd = [0x36]
+ data = self._send_command(cmd, crc_need=False, size=8)
+ if data:
+ return int(binascii.hexlify(bytearray(data[1:5])))
+
+ return None
+
+ def send_100_bills_in_meters(self):
+ """Send 100$ bills in meters
+
+ Returns
+ -------
+ mixed
+ int (# of bills) or None
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ cmd = [0x37]
+ data = self._send_command(cmd, crc_need=False, size=8)
+ if data:
+ return int(binascii.hexlify(bytearray(data[1:5])))
+
+ return None
+
+ def send_500_bills_in_meters(self):
+ """Send 500$ bills in meters
+
+ Returns
+ -------
+ mixed
+ int (# of bills) or None
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ cmd = [0x38]
+ data = self._send_command(cmd, crc_need=False, size=8)
+ if data:
+ return int(binascii.hexlify(bytearray(data[1:5])))
+
+ return None
+
+ def send_1000_bills_in_meters(self):
+ """Send 1.000$ bills in meters
+
+ Returns
+ -------
+ mixed
+ int (# of bills) or None
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ cmd = [0x39]
+ data = self._send_command(cmd, crc_need=False, size=8)
+ if data:
+ return int(binascii.hexlify(bytearray(data[1:5])))
+
+ return None
+
+ def send_200_bills_in_meters(self):
+ """Send 200$ bills in meters
+
+ Returns
+ -------
+ mixed
+ int (# of bills) or None
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ cmd = [0x3A]
+ data = self._send_command(cmd, crc_need=False, size=8)
+ if data:
+ return int(binascii.hexlify(bytearray(data[1:5])))
+
+ return None
+
+ def send_25_bills_in_meters(self):
+ """Send 25$ bills in meters
+
+ Returns
+ -------
+ mixed
+ int (# of bills) or None
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ cmd = [0x3B]
+ data = self._send_command(cmd, crc_need=False, size=8)
+ if data:
+ return int(binascii.hexlify(bytearray(data[1:5])))
+
+ return None
+
+ def send_2000_bills_in_meters(self):
+ """Send 2.000$ bills in meters
+
+ Returns
+ -------
+ mixed
+ int (# of bills) or None
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ cmd = [0x3C]
+ data = self._send_command(cmd, crc_need=False, size=8)
+ if data:
+ return int(binascii.hexlify(bytearray(data[1:5])))
+ return None
+
+ def cash_out_ticket_info(self):
+ """Send cash out ticket information
+
+ Returns
+ -------
+ mixed
+ dict or None
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ cmd = [0x3D]
+ data = self._send_command(cmd, crc_need=False)
+ if data:
+ return {
+ "cashout_ticket_number": int(binascii.hexlify(bytearray(data[1:3]))),
+ "cashout_amount_in_cents": int(binascii.hexlify(bytearray(data[3:]))),
+ }
+
+ return None
+
+ def send_2500_bills_in_meters(self):
+ """Send 2.500$ bills in meters
+
+ Returns
+ -------
+ mixed
+ int (# of bills) or None
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ cmd = [0x3E]
+ data = self._send_command(cmd, crc_need=False, size=8)
+ if data:
+ return int(binascii.hexlify(bytearray(data[1:5])))
+
+ return None
+
+ def send_5000_bills_in_meters(self):
+ """Send 5.000$ bills in meters
+
+ Returns
+ -------
+ mixed
+ int (# of bills) or None
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ cmd = [0x3F]
+ data = self._send_command(cmd, crc_need=False, size=8)
+ if data:
+ return int(binascii.hexlify(bytearray(data[1:5])))
+
+ return None
+
+ def send_10000_bills_in_meters(self):
+ """Send 10.000$ bills in meters
+
+ Returns
+ -------
+ mixed
+ int (# of bills) or None
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ cmd = [0x40]
+ data = self._send_command(cmd, crc_need=False, size=8)
+ if data:
+ return int(binascii.hexlify(bytearray(data[1:5])))
+
+ return None
+
+ def send_20000_bills_in_meters(self):
+ """Send 20.000$ bills in meters
+
+ Returns
+ -------
+ mixed
+ int (# of bills) or None
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ cmd = [0x41]
+ data = self._send_command(cmd, crc_need=False, size=8)
+ if data:
+ return int(binascii.hexlify(bytearray(data[1:5])))
+
+ return None
+
+ def send_25000_bills_in_meters(self):
+ """Send 25.000$ bills in meters
+
+ Returns
+ -------
+ mixed
+ int (# of bills) or None
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ cmd = [0x42]
+ data = self._send_command(cmd, crc_need=False, size=8)
+ if data:
+ return int(binascii.hexlify(bytearray(data[1:5])))
+
+ return None
+
+ def send_50000_bills_in_meters(self):
+ """Send 50.000$ bills in meters
+
+ Returns
+ -------
+ mixed
+ int (# of bills) or None
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ cmd = [0x43]
+ data = self._send_command(cmd, crc_need=False, size=8)
+ if data:
+ return int(binascii.hexlify(bytearray(data[1:5])))
+
+ return None
+
+ def send_100000_bills_in_meters(self):
+ """Send 100.000$ bills in meters
+
+ Returns
+ -------
+ mixed
+ int (# of bills) or None
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ cmd = [0x44]
+ data = self._send_command(cmd, crc_need=False, size=8)
+ if data:
+ return int(binascii.hexlify(bytearray(data[1:5])))
+
+ return None
+
+ def send_250_bills_in_meters(self):
+ """Send 250$ bills in meters
+
+ Returns
+ -------
+ mixed
+ int (# of bills) or None
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ cmd = [0x45]
+ data = self._send_command(cmd, crc_need=False, size=8)
+ if data:
+ return int(binascii.hexlify(bytearray(data[1:5])))
+
+ return None
+
+ def credit_amount_of_all_bills_accepted(self):
+ """Send credit amount of all bills accepted
+
+ Returns
+ -------
+ mixed
+ meter in SAS accounting denom units or None
+
+ """
+ cmd = [0x46]
+ data = self._send_command(cmd, crc_need=False, size=8)
+ if data:
+ return int(binascii.hexlify(bytearray(data[1:5])))
+
+ return None
+
+ def coin_amount_accepted_from_external_coin_acceptor(self):
+ """Send coin amount accepted from an external coin acceptor
+
+ Returns
+ -------
+ mixed
+ meter in SAS accounting denom units or None
+
+ """
+ cmd = [0x47]
+ data = self._send_command(cmd, crc_need=False, size=8)
+ if data:
+ return int(binascii.hexlify(bytearray(data[1:5])))
+
+ return None
+
+ def last_accepted_bill_info(self):
+ """ Send last accepted bill information
+ Returns
+ -------
+ mixed
+ dict or None
+ """
+ cmd = [0x48]
+ data = self._send_command(cmd, crc_need=False)
+ if data:
+ Meters.Meters.STATUS_MAP["country_code"] = int(
+ binascii.hexlify(bytearray(data[1:2]))
+ )
+ Meters.Meters.STATUS_MAP["bill_denomination"] = int(
+ binascii.hexlify(bytearray(data[2:3]))
+ )
+ Meters.Meters.STATUS_MAP["meter_for_accepted_bills"] = int(
+ binascii.hexlify(bytearray(data[3:6]))
+ )
+ return Meters.Meters.get_non_empty_status_map()
+
+ return None
+
+ def number_of_bills_currently_in_stacker(self):
+ """ Send number of bills currently in the stacker
+ Returns
+ -------
+ mixed
+ int ( meter in # of bills ) or None
+ """
+ cmd = [0x49]
+ data = self._send_command(cmd, crc_need=False, size=8)
+ if data:
+ return int(binascii.hexlify(bytearray(data[1:5])))
+
+ return None
+
+ def total_credit_amount_of_all_bills_in_stacker(self):
+ """Send total credit amount of all bills currently in the stacker
+
+ Returns
+ -------
+ mixed
+ int (# of bills) or None
+
+ Notes
+ -------
+ This is a LONG POLL COMMAND
+ """
+ cmd = [0x4A]
+ data = self._send_command(cmd, crc_need=False, size=8)
+ if data:
+ return int(binascii.hexlify(bytearray(data[1:5])))
+
+ return None
+
+ def set_secure_enhanced_validation_id(
+ self, machine_id=[0x01, 0x01, 0x01], seq_num=[0x00, 0x00, 0x01]
+ ):
+ """
+ For a gaming machine to perform secure enhanced ticket/receipt/handpay validation, the host must use
+ the type S long poll. The host may also use this long poll to retrieve the current gaming
+ machine validation ID and validation sequence number by issuing the 4C command with a gaming
+ machine validation ID of zero. If a gaming machine is not configured to perform secure enhanced
+ validation, or is responding to a host that is not the validation controller, it ignores this long poll
+
+ :param machine_id: 3 binary - Gaming machine validation ID number
+ :param seq_num: 3 binary - Starting sequence number (incremented before being assigned to each event)
+ :return:
+ """
+ # 4C
+ # FIXME: set_secure_enhanced_validation_ID @todo... im beat...@well-it-wasnt-me
+ cmd = [0x4C, machine_id, seq_num]
+ data = self._send_command(cmd, True, crc_need=True)
+ if data:
+ TitoStatement.Tito.STATUS_MAP["machine_ID"] = int(
+ binascii.hexlify(bytearray(data[1:4]))
+ )
+ TitoStatement.Tito.STATUS_MAP["sequence_number"] = int(
+ binascii.hexlify(bytearray(data[4:8]))
+ )
+ return data
+
+ return None
+
+ def enhanced_validation_information(self, curr_validation_info=0):
+ """Send Enhanced Validation Information Command
+
+ Parameters
+ ----------
+ curr_validation_info :
+ Function code; 00 = read current validation info | 01-1F = validation info from buffer index n | FF = look ahead at current validation info
+
+ Returns
+ -------
+ mixed :
+ dict | none
+ """
+ # FIXME: enhanced_validation_information
+ cmd = [0x4D, curr_validation_info]
+ data = self._send_command(cmd, True, crc_need=True)
+ if data:
+ TitoStatement.Tito.STATUS_MAP["validation_type"] = int(
+ binascii.hexlify(bytearray(data[1:2]))
+ )
+ TitoStatement.Tito.STATUS_MAP["index_number"] = int(
+ binascii.hexlify(bytearray(data[2:3]))
+ )
+ TitoStatement.Tito.STATUS_MAP["date_validation_operation"] = str(
+ binascii.hexlify(bytearray(data[3:7]))
+ )
+ TitoStatement.Tito.STATUS_MAP["time_validation_operation"] = str(
+ binascii.hexlify(bytearray(data[7:10]))
+ )
+ TitoStatement.Tito.STATUS_MAP["validation_number"] = str(
+ binascii.hexlify(bytearray(data[10:18]))
+ )
+ TitoStatement.Tito.STATUS_MAP["amount"] = int(
+ binascii.hexlify(bytearray(data[18:23]))
+ )
+ TitoStatement.Tito.STATUS_MAP["ticket_number"] = int(
+ binascii.hexlify(bytearray(data[23:25]))
+ )
+ TitoStatement.Tito.STATUS_MAP["validation_system_ID"] = int(
+ binascii.hexlify(bytearray(data[25:26]))
+ )
+ TitoStatement.Tito.STATUS_MAP["expiration_date_printed_on_ticket"] = str(
+ binascii.hexlify(bytearray(data[26:30]))
+ )
+ TitoStatement.Tito.STATUS_MAP["pool_id"] = int(
+ binascii.hexlify(bytearray(data[30:32]))
+ )
+
+ return TitoStatement.Tito.get_non_empty_status_map()
+
+ return None
+
+ def current_hopper_status(self):
+ """Send Current Hopper Status
+ Returns
+ -------
+ mixed :
+ dict | none
+
+ Notes
+ ------
+ Understanding the values:
+
+ - current_hopper_length
+ ============== =====
+ Code (Binary) Description
+ ============== =====
+ 02 Only status and % full
+ 06 Status, % full and level
+ ============== =====
+
+ - current_hopper_status
+ ============== =====
+ Code (Binary) Status
+ ============== =====
+ 00 Hopper OK
+ 01 Flooded Optics
+ 02 Reverse Coin
+ 03 Coin too short
+ 04 Coin Jam
+ 05 Hopper runaway
+ 06 Optics Disconnected
+ 07 Hopper Empty
+ 08-FE Reserved
+ FF Other
+ ============== =====
+
+ - current_hopper_percent_full :
+ Current hopper level as 0-100%, or FF if unable to detect hopper level percentage
+
+ - current_hopper_level :
+ 4 BCD | Current hopper level in number of coins/tokens, only if EGM able to detect
+ """
+ # FIXME: current_hopper_status
+ cmd = [0x4F]
+ data = self._send_command(cmd, True, crc_need=False)
+ if data:
+ Meters.Meters.STATUS_MAP["current_hopper_length"] = int(
+ binascii.hexlify(bytearray(data[1:2]))
+ )
+ Meters.Meters.STATUS_MAP["current_hopper_status"] = int(
+ binascii.hexlify(bytearray(data[2:3]))
+ )
+ Meters.Meters.STATUS_MAP["current_hopper_percent_full"] = int(
+ binascii.hexlify(bytearray(data[3:4]))
+ )
+ Meters.Meters.STATUS_MAP["current_hopper_level"] = int(
+ binascii.hexlify(bytearray(data[4:]))
+ )
+ return Meters.Meters.get_non_empty_status_map()
+
+ return None
+
+ def validation_meters(self, type_of_validation=0x00):
+ """Send validation meters
+ Parameters
+ ----------
+ type_of_validation : int
+ Type of validation
+
+ ============== =====
+ Code (Binary) Validation type
+ ============== =====
+ 00 Cashable ticket from cashout or win, no handpay lockup
+ 01 Restricted promotional ticket from cashout
+ 02 Cashable ticket from AFT transfer
+ 03 Restricted ticket from AFT transfer
+ 04 Debit ticket from AFT transfer
+ 10 Cancelled credit handpay (receipt printed
+ 20 Jackpot handpay (receipt printed)
+ 40 Cancelled credit handpay (no receipt)
+ 60 Jackpot handpay (no receipt)
+ 80 Cashable ticket redeemed
+ 81 Restricted promotional ticket redeemed
+ 82 Nonrestricted promotional ticket redeemed
+ ============== =====
+
+
+ Returns
+ -------
+ mixed :
+ dict | none
+
+ Notes
+ -------
+ Understanding the response:
+ - bin_validation_type :
+ See the table "Type of validation"
+ - total_validations : 4 BCD
+ Total number of validations of type
+ - cumulative_amount : 5 BCD
+ Cumulative validation amount in units of cents
+ """
+ # FIXME: validation_meters
+ cmd = [0x50, type_of_validation]
+ data = self._send_command(cmd, True, crc_need=True)
+ if data:
+ Meters.Meters.STATUS_MAP["bin_validation_type"] = int(
+ binascii.hexlify(bytearray(data[1]))
+ )
+ Meters.Meters.STATUS_MAP["total_validations"] = int(
+ binascii.hexlify(bytearray(data[2:6]))
+ )
+ Meters.Meters.STATUS_MAP["cumulative_amount"] = str(
+ binascii.hexlify(bytearray(data[6:]))
+ )
+ return Meters.Meters.get_non_empty_status_map()
+
+ return None
+
+ def total_number_of_games_implemented(self):
+ # 51
+ cmd = [0x51]
+ # FIXME: cmd.extend(type_of_validation)
+ data = self._send_command(cmd, crc_need=False, size=6)
+ if data:
+ return str(binascii.hexlify(bytearray(data[1:])))
+
+ return None
+
+ def game_meters(self, n=None, denom=True):
+ # 52
+ cmd = [0x52]
+
+ if not n:
+ n = self.selected_game_number(in_hex=False)
+ cmd.extend([((n >> 8) & 0xFF), (n & 0xFF)])
+
+ data = self._send_command(cmd, crc_need=True, size=22)
+ if data:
+ meters = {}
+ if not denom:
+ Meters.Meters.STATUS_MAP["game_n_number"] = str(
+ binascii.hexlify(bytearray(data[1:3]))
+ )
+ Meters.Meters.STATUS_MAP["game_n_coin_in_meter"] = int(
+ binascii.hexlify(bytearray(data[3:7]))
+ )
+ Meters.Meters.STATUS_MAP["game_n_coin_out_meter"] = int(
+ binascii.hexlify(bytearray(data[7:11]))
+ )
+ Meters.Meters.STATUS_MAP["game_n_jackpot_meter"] = int(
+ binascii.hexlify(bytearray(data[11:15]))
+ )
+ Meters.Meters.STATUS_MAP["geme_n_games_played_meter"] = int(
+ binascii.hexlify(bytearray(data[15:]))
+ )
+ else:
+ Meters.Meters.STATUS_MAP["game_n_number"] = str(
+ binascii.hexlify(bytearray(data[1:3]))
+ )
+ Meters.Meters.STATUS_MAP["game_n_coin_in_meter"] = round(
+ int(binascii.hexlify(bytearray(data[3:7]))) * self.denom, 2
+ )
+ Meters.Meters.STATUS_MAP["game_n_coin_out_meter"] = round(
+ int(binascii.hexlify(bytearray(data[7:11]))) * self.denom, 2
+ )
+ Meters.Meters.STATUS_MAP["game_n_jackpot_meter"] = round(
+ int(binascii.hexlify(bytearray(data[11:15]))) * self.denom, 2
+ )
+ Meters.Meters.STATUS_MAP["geme_n_games_played_meter"] = int(
+ binascii.hexlify(bytearray(data[15:]))
+ )
+
+ return Meters.Meters.get_non_empty_status_map()
+
+ return None
+
+ def game_configuration(self, n=None):
+ # 53
+ cmd = [0x53]
+ # FIXME: game_configuration
+
+ if not n:
+ n = self.selected_game_number(in_hex=False)
+ cmd.extend([(n & 0xFF), ((n >> 8) & 0xFF)])
+
+ data = self._send_command(cmd, True, crc_need=True)
+ if data:
+ Meters.Meters.STATUS_MAP["game_n_number_config"] = int(
+ binascii.hexlify(bytearray(data[1:3]))
+ )
+ Meters.Meters.STATUS_MAP["game_n_ASCII_game_ID"] = str(
+ binascii.hexlify(bytearray(data[3:5]))
+ )
+ Meters.Meters.STATUS_MAP["game_n_ASCII_additional_id"] = str(
+ binascii.hexlify(bytearray(data[5:7]))
+ )
+ Meters.Meters.STATUS_MAP["game_n_bin_denomination"] = str(
+ binascii.hexlify(bytearray(data[7]))
+ )
+ Meters.Meters.STATUS_MAP["game_n_bin_progressive_group"] = str(
+ binascii.hexlify(bytearray(data[8]))
+ )
+ Meters.Meters.STATUS_MAP["game_n_bin_game_options"] = str(
+ binascii.hexlify(bytearray(data[9:11]))
+ )
+ Meters.Meters.STATUS_MAP["game_n_ASCII_paytable_ID"] = str(
+ binascii.hexlify(bytearray(data[11:17]))
+ )
+ Meters.Meters.STATUS_MAP["game_n_ASCII_base_percentage"] = str(
+ binascii.hexlify(bytearray(data[17:]))
+ )
+ return Meters.Meters.get_non_empty_status_map()
+
+ return None
+
+ def sas_version_gaming_machine_serial_id(self):
+ # 54
+ """
+ This function should be checked at begin in order to address
+ the changes from sas v6.02 and 6.03
+ - Antonio
+ @todo...one day i'll see to this....
+ """
+ cmd = [0x54, 0x00]
+ data = self._send_command(cmd, crc_need=False, size=20)
+ if data:
+ Meters.Meters.STATUS_MAP["ASCII_SAS_version"] = (
+ int(binascii.hexlify(bytearray(data[2:5]))) * 0.01
+ )
+ Meters.Meters.STATUS_MAP["ASCII_serial_number"] = str(bytearray(data[5:]))
+ return Meters.Meters.get_non_empty_status_map()
+
+ return None
+
+ def selected_game_number(self, in_hex=True):
+ # 55
+ cmd = [0x55]
+ data = self._send_command(cmd, crc_need=False, size=6)
+ if data:
+ if not in_hex:
+ return int(binascii.hexlify(bytearray(data[1:])))
+ else:
+ return binascii.hexlify(bytearray(data[1:]))
+
+ return None
+
+ def enabled_game_numbers(self):
+ # 56
+ cmd = [0x56]
+ data = self._send_command(cmd, crc_need=False)
+ if data:
+ Meters.Meters.STATUS_MAP["number_of_enabled_games"] = int(
+ binascii.hexlify(bytearray(data[2]))
+ )
+ Meters.Meters.STATUS_MAP["enabled_games_numbers"] = int(
+ binascii.hexlify(bytearray(data[3:]))
+ )
+ return Meters.Meters.get_non_empty_status_map()
+
+ return None
+
+ def pending_cashout_info(self):
+ # 57
+ cmd = [0x57]
+ data = self._send_command(cmd, crc_need=False)
+ if data:
+ TitoStatement.Tito.STATUS_MAP["cashout_type"] = int(
+ binascii.hexlify(bytearray(data[1:2]))
+ )
+ TitoStatement.Tito.STATUS_MAP["cashout_amount"] = str(
+ binascii.hexlify(bytearray(data[2:]))
+ )
+ return TitoStatement.Tito.get_non_empty_status_map()
+
+ return None
+
+ def rcv_validation_number(self, validation_id=1, valid_number=0):
+ """Receive Validation number
+ Parameters
+ ----------
+ validation_id : int
+ Validation System ID Code (00 = system validation denied)
+
+ valid_number : int
+ validation number to use for cashout (not used if validation denied)
+
+ Returns
+ -------
+ Mixed
+ str | none - 00 = command ack | 80 = Not in cashout | 81 = Improper validation rejected
+ """
+ cmd = [0x58, self._bcd_coder_array(validation_id, 1), self._bcd_coder_array(valid_number, 8)]
+ data = self._send_command(cmd, crc_need=True)
+ if data:
+ return str(binascii.hexlify(bytearray(data[1])))
+
+ return None
+
+ def authentication_info(
+ self,
+ action=0,
+ addressing_mode=0,
+ component_name="",
+ auth_method=b"\x00\x00\x00\x00",
+ seed="",
+ seed_length=0,
+ offset="",
+ offset_length=0,
+ ):
+ """Authentication Info
+
+ Parameters
+ ----------
+ action : 1 binary
+ Requested authentication action:
+
+ ===== =====
+ Value Description
+ ===== =====
+ 00 Interrogate number of installed components
+ 01 Read status of component (address required)
+ 02 Authenticate component (address required)
+ 03 Interrogate authentication status
+ ===== =====
+
+ addressing_mode : 1 binary
+ ===== =====
+ Value Description
+ ===== =====
+ 00 Addressing by component index number
+ 01 Addressing by component name
+ ===== =====
+
+ component_name : x bytes
+ ASCII component name if addressing mode = 01
+
+ auth_method : 4 binary
+ ============== ============ ====================== =======================
+ Code (Binary) Method Seed size (max bytes) Result Size (max bytes)
+ ============== ============ ====================== =======================
+ 00000000 None n/a n/a
+ 00000001 CRC16 2 binary 2 binary
+ 00000002 CRC32 4 binary 4 binary
+ 00000004 MD5 16 bytes 16 bytes
+ 00000008 Kobetron I 4 ASCII 4 ASCII
+ 00000010 Kobetron II 4 ASCII 4 ASCII
+ 00000020 SHA1 20 Bytes 20 Bytes
+ ============== ============ ====================== =======================
+
+ Returns
+ -------
+ bytearray
+ Response ACK/NACK
+
+ Notes
+ -------
+ Actually the real response is way more long and complex. Planning to map and implement it in the future
+
+ """
+ # 6E
+ # FIXME: authentication_info
+ cmd = [0x6E, 0x00, action]
+ if action == 0:
+ cmd[1] = 1
+ else:
+ if action == 1 or action == 3:
+ cmd.append(addressing_mode)
+ cmd.append(len(bytearray(component_name)))
+ cmd.append(bytearray(component_name))
+ cmd[1] = len(bytearray(component_name)) + 3
+ else:
+ if action == 2:
+ cmd.append(addressing_mode)
+ cmd.append(len(bytearray(component_name)))
+ cmd.append(bytearray(component_name))
+ cmd.append(auth_method)
+ cmd.append(seed_length)
+ cmd.append(bytearray(seed))
+ cmd.append(offset_length)
+ cmd.append(bytearray(offset))
+
+ cmd[1] = (
+ len(bytearray(offset))
+ + len(bytearray(seed))
+ + len(bytearray(component_name))
+ + 6
+ )
+
+ data = self._send_command(cmd, True, crc_need=True)
+ if data:
+ return data[1]
+
+ return None
+
+ @staticmethod
+ def extended_meters_for_game():
+ # TODO: extended_meters_for_game
+ # 6F
+ return None
+
+ def ticket_validation_data(self):
+ # 70
+ # FIXME: ticket_validation_data
+ # @todo This is wrong for sure....this should reply 9 BCD BCD-encoded 18 digit decimal validation number. The first two digits are a 2
+ # digit system ID code indicating how to interpret the following 16 digits.
+ # System ID code 00 indicates that the following 16 digits represent a SAS
+ # secure enhanced validation number. Other system ID codes and parsing codes
+ # will be assigned by IGT as needed
+ cmd = [0x70]
+ data = self._send_command(cmd, True, crc_need=False)
+ if data:
+ Meters.Meters.STATUS_MAP["ticket_status"] = int(
+ binascii.hexlify(bytearray(data[2:3]))
+ )
+ Meters.Meters.STATUS_MAP["ticket_amount"] = str(
+ binascii.hexlify(bytearray(data[3:8]))
+ )
+ Meters.Meters.STATUS_MAP["parsing_code"] = int(
+ binascii.hexlify(bytearray(data[8:9]))
+ )
+ Meters.Meters.STATUS_MAP["validation_data"] = str(
+ binascii.hexlify(bytearray(data[9:]))
+ )
+ return Meters.Meters.get_non_empty_status_map()
+
+ return None
+
+ def redeem_ticket(
+ self,
+ transfer_code=0,
+ transfer_amount=0,
+ parsing_code=0,
+ validation_data=0,
+ restricted_expiration=0,
+ pool_id=0
+ ):
+ # 71
+ # FIXME: redeem_ticket
+ cmd = [
+ 0x71,
+ 0x21,
+ transfer_code,
+ self._bcd_coder_array(transfer_amount, 5),
+ parsing_code,
+ self._bcd_coder_array(validation_data, 8),
+ self._bcd_coder_array(restricted_expiration, 4),
+ self._bcd_coder_array(pool_id, 2),
+ ]
+ data = self._send_command(cmd, True, crc_need=True)
+ if data:
+ Meters.Meters.STATUS_MAP["machine_status"] = int(
+ binascii.hexlify(bytearray(data[2:3]))
+ )
+ Meters.Meters.STATUS_MAP["transfer_amount"] = int(
+ binascii.hexlify(bytearray(data[3:8]))
+ )
+ Meters.Meters.STATUS_MAP["parsing_code"] = int(
+ binascii.hexlify(bytearray(data[8:9]))
+ )
+ Meters.Meters.STATUS_MAP["validation_data"] = str(
+ binascii.hexlify(bytearray(data[9:]))
+ )
+ return Meters.Meters.get_non_empty_status_map()
+
+ return None
+
+ def aft_jp(self, money, amount=1, lock_timeout=0, games=None):
+ # FIXME: make logically coherent
+ # self.lock_emg(lock_time=500, condition=1)
+ money_1 = money_2 = money_3 = "0000000000"
+ if self.denom > 0.01:
+ return None
+
+ if not games:
+ for i in range(3):
+ games = self.selected_game_number(in_hex=False)
+ if not games:
+ time.sleep(0.04)
else:
- return "False"
-
- return
- def enter_maintenance_mode(self):
- #0A
- return
- def exit_maintanance_mode(self):
- #0B
- return
- def en_dis_rt_event_reporting(self):
- #0E
- return
- def send_meters_10_15(self):
- #0F
- cmd=[0x0f]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
- meters['total_cancelled_credits_meter']=int((binascii.hexlify(bytearray(data[1:5]))))
- meters['total_in_meter']=int(binascii.hexlify(bytearray(data[5:9])))
- meters['total_out_meter']=int(binascii.hexlify(bytearray(data[9:13])))
- meters['total_droup_meter']=int(binascii.hexlify(bytearray(data[13:17])))
- meters['total_jackpot_meter']=int(binascii.hexlify(bytearray(data[17:21])))
- meters['games_played_meter']=int(binascii.hexlify(bytearray(data[21:25])))
- return data
- return ''
- def total_cancelled_credits(self):
- #10
- cmd=[0x10]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
- meters['total_cancelled_credits_meter']=int(binascii.hexlify(bytearray(data[1:5])))
- return data
- return ''
- def total_bet_meter(self):
- #11
- cmd=[0x11]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
-
- meters['total_bet_meter']=int(binascii.hexlify(bytearray(data[1:5])))
-
- return data
- return ''
- def total_win_meter(self):
- #12
- cmd=[0x12]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
-
- meters['total_win_meter']=int(binascii.hexlify(bytearray(data[1:5])))
-
- return data
- return ''
- def total_in_meter(self):
- #13
- cmd=[0x13]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
-
- meters['total_in_meter']=int(binascii.hexlify(bytearray(data[1:5])))
-
- return data
- return ''
- def total_jackpot_meter(self):
- #14
- cmd=[0x14]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
-
- meters['total_jackpot_meter']=int(binascii.hexlify(bytearray(data[1:5])))
-
- return data
- return ''
- def games_played_meter(self):
- #15
- cmd=[0x15]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
-
- meters['games_played_meter']=int(binascii.hexlify(bytearray(data[1:5])))
-
- return data
- return ''
- def games_won_meter(self):
- #16
- cmd=[0x16]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
-
- meters['games_won_meter']=int(binascii.hexlify(bytearray(data[1:5])))
-
- return data
- return ''
- def games_lost_meter(self):
- #17
- cmd=[0x17]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
-
- meters['games_lost_meter']=int(binascii.hexlify(bytearray(data[1:5])))
-
- return data
- return ''
- def games_powerup_door_opened(self):
- #18
- cmd=[0x18]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
-
- meters['games_last_power_up']=int(binascii.hexlify(bytearray(data[1:3])))
- meters['games_last_slot_door_close']=int(binascii.hexlify(bytearray(data[1:5])))
-
- return data
- return ''
- def meters_11_15(self):
- #19
- cmd=[0x19]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
- meters['total_bet_meter']=int(binascii.hexlify(bytearray(data[1:5])))
- meters['total_win_meter']=int(binascii.hexlify(bytearray(data[5:9])))
- meters['total_in_meter']=int(binascii.hexlify(bytearray(data[9:13])))
- meters['total_jackpot_meter']=int(binascii.hexlify(bytearray(data[13:17])))
- meters['games_played_meter']=int(binascii.hexlify(bytearray(data[17:21])))
- return data
- return ''
- def current_credits(self):
- #1A
- cmd=[0x1A]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
- meters['current_credits']=int(binascii.hexlify(bytearray(data[1:5])))
-
- return data
- return ''
- def handpay_info(self):
- #1B
- cmd=[0x1B]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
-
- meters['bin_progressive_group']=int(binascii.hexlify(bytearray(data[1:2])))
- meters['bin_level']=int(binascii.hexlify(bytearray(data[2:3])))
- meters['amount']=int(binascii.hexlify(bytearray(data[3:8])))
- meters['bin_reset_ID']=int(binascii.hexlify(bytearray(data[8:])))
- return data
- return ''
- def meters(self):
- #1C
- cmd=[0x1C]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
- meters['total_bet_meter']=int(binascii.hexlify(bytearray(data[1:5])))
- meters['total_win_meter']=int(binascii.hexlify(bytearray(data[5:9])))
- meters['total_in_meter']=int(binascii.hexlify(bytearray(data[9:13])))
- meters['total_jackpot_meter']=int(binascii.hexlify(bytearray(data[13:17])))
- meters['games_played_meter']=int(binascii.hexlify(bytearray(data[17:21])))
- meters['games_won_meter']=int(binascii.hexlify(bytearray(data[21:25])))
- meters['slot_door_opened_meter']=int(binascii.hexlify(bytearray(data[25:29])))
- meters['power_reset_meter']=int(binascii.hexlify(bytearray(data[29:33])))
-
- return data
- return ''
- def total_bill_meters(self):
- #1E
- cmd=[0x1E]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
- meters['s1_bills_accepted_meter']=int(binascii.hexlify(bytearray(data[1:5])))
- meters['s5_bills_accepted_meter']=int(binascii.hexlify(bytearray(data[5:9])))
- meters['s10_bills_accepted_meter']=int(binascii.hexlify(bytearray(data[9:13])))
- meters['s20_bills_accepted_meter']=int(binascii.hexlify(bytearray(data[13:17])))
- meters['s50_bills_accepted_meter']=int(binascii.hexlify(bytearray(data[17:21])))
- meters['s100_bills_accepted_meter']=int(binascii.hexlify(bytearray(data[21:25])))
-
- return data
- return ''
- def gaming_machine_ID(self):
- #1F
- cmd=[0x1F]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
-
- meters['ASCII_game_ID']=(((data[1:3])))
- meters['ASCII_additional_ID']=(((data[3:6])))
- meters['bin_denomination']=int(binascii.hexlify(bytearray(data[6])))
- meters['bin_max_bet']=(binascii.hexlify(bytearray(data[7:8])))
- meters['bin_progressive_mode']=int(binascii.hexlify(bytearray(data[8:9])))
- meters['bin_game_options']=(binascii.hexlify(bytearray(data[9:11])))
- meters['ASCII_paytable_ID']=(((data[11:17])))
- meters['ASCII_base_percentage']=(((data[17:21])))
-
- return data
- return ''
- def total_dollar_value_of_bills_meter(self):
- #20
-
- cmd=[0x20]
- data=self.__send_command(cmd,True, crc_need=False)
-
- if(data<>''):
-
- meters['bill_meter_in_dollars']=int(binascii.hexlify(bytearray(data[1:])))
-
- return data
- return ''
- def ROM_signature_verification(self):
- #21
-
- cmd=[0x21, 0x00, 0x00]
- data=self.__send_command(cmd,True, crc_need=True)
- print data
- if(data<>None):
-
- meters['ROM_signature']= int(binascii.hexlify(bytearray(data[1:3])))
- print (str(meters.get('ROM_signature')))
- return data
- return False
-
- def eft_button_pressed(self, state=0):
- #24
-
- cmd=[0x24, 0x03]
- cmd.append(state)
-
- data=self.__send_command(cmd,True, crc_need=True)
- print data
- if(data<>None):
-
- return data
- return ''
-
- def true_coin_in(self):
- #2A
- cmd=[0x2A]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
-
- meters['true_coin_in']=int(binascii.hexlify(bytearray(data[1:5])))
-
- return data
- return ''
- def true_coin_out(self):
- #2B
- cmd=[0x2B]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
-
- meters['true_coin_out']=int(binascii.hexlify(bytearray(data[1:5])))
-
- return data
- return ''
- def curr_hopper_level(self):
- #2C
- cmd=[0x2C]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
-
- meters['current_hopper_level']=int(binascii.hexlify(bytearray(data[1:5])))
-
- return data
- return ''
- def total_hand_paid_cancelled_credit(self):
- #2D
- return
- def delay_game(self, delay=0x01):
- #2E
- cmd=[0x2E]
-## if (delay[0]<=0xff):
-## cmd.extend([0x00])
- cmd.append(delay)
-
-
- #print cmd
- if (self.__send_command(cmd,True, crc_need=True)[0]==self.adress):
- return "True"
+ break
+
+ if not games or games == 0 or games < 1:
+ return "NoGame"
+
+ if not money:
+ money = str(self.current_credits(denom=False))
+ else:
+ money = str(int((money / self.denom)))
+ money = money.replace(".", "")
+
+ money = "0" * (10 - len(money)) + money
+
+ match amount:
+ case 1:
+ money_1 = money
+ case 2:
+ money_2 = money
+ case 3:
+ money_3 = money
+ case _:
+ raise AFTBadAmount
+
+ last_transaction = self.aft_format_transaction()
+ len_transaction_id = hex(len(last_transaction) // 2)[
+ 2:
+ ] # the division result should be converted to an integer before using hex, added extra / to solve this
+ if len(len_transaction_id) < 2:
+ len_transaction_id = "0" + len_transaction_id
+ elif len(len_transaction_id) % 2 == 1:
+ len_transaction_id = "0" + len_transaction_id
+
+ cmd = """72{my_key}{index}00{transfer_code}{money_1}{money_2}{money_3}
+ 00{asset}{key}{len_transaction}{transaction}{times}0C0000""".format(
+ transfer_code="11",
+ index="00",
+ money_1=money_1,
+ money_2=money_2,
+ money_3=money_3,
+ asset=self.asset_number,
+ key=self.reg_key,
+ len_transaction=len_transaction_id,
+ transaction=last_transaction,
+ times=datetime.datetime.strftime(datetime.datetime.now(), "%m%d%Y"),
+ my_key=self.my_key,
+ )
+
+ new_cmd = []
+ count = 0
+ for i in range(
+ len(cmd) // 2
+ ): # Python3...not my fault...might be better using range(0, len(cmd), 2) ?
+ new_cmd.append(int(cmd[count: count + 2], 16))
+ count += 2
+
+ response = None
+ self.aft_register()
+ if lock_timeout > 0:
+ self.aft_game_lock(lock_timeout, condition=1)
+
+ data = self._send_command(new_cmd, crc_need=True, size=82)
+
+ if data:
+ a = int(binascii.hexlify(bytearray(data[26:27])), 16)
+ response = {
+ "Length": int(binascii.hexlify(bytearray(data[26:27])), 16),
+ "Transaction buffer position": int(
+ binascii.hexlify(bytearray(data[2:3]))
+ ),
+ "Transfer status": AftTransferStatus.AftTransferStatus.get_status(
+ binascii.hexlify(bytearray(data[3:4]))
+ ),
+ "Receipt status": AftTransferStatus.AftTransferStatus.get_status(
+ binascii.hexlify(bytearray(data[4:5]))
+ ),
+ "Transfer type": AftTransferStatus.AftTransferStatus.get_status(
+ binascii.hexlify(bytearray(data[5:6]))
+ ),
+ "Cashable amount": int(binascii.hexlify(bytearray(data[6:11])))
+ * self.denom,
+ "Restricted amount": int(binascii.hexlify(bytearray(data[11:16])))
+ * self.denom,
+ "Nonrestricted amount": int(binascii.hexlify(bytearray(data[16:21])))
+ * self.denom,
+ "Transfer flags": binascii.hexlify(bytearray(data[21:22])),
+ "Asset number": binascii.hexlify(bytearray(data[22:26])),
+ "Transaction ID length": binascii.hexlify(bytearray(data[26:27])),
+ "Transaction ID": binascii.hexlify(
+ bytearray(data[27: (27 + a)])
+ ), # WARNING: technically should be (27 + 2 * a) due to an off error.... @todo...somebody see me !
+ }
+ try:
+ self.aft_unregister()
+ except:
+ self.log.warning("AFT UNREGISTER ERROR: won to host")
+
+ return response
+
+ def aft_out(self, money=None, amount=1, lock_timeout=0):
+ """
+ aft_out is a function to make a machine cashout (effectively removes the credit in the machine)
+ :param money:
+ :param amount:
+ :param lock_timeout:
+ :param kwargs:
+ :return:
+ """
+ # self.lock_emg(lock_time=500, condition=1)
+ money_1 = money_2 = money_3 = "0000000000"
+ if self.denom > 0.01:
+ return None
+
+ if not money:
+ money = str(self.current_credits(denom=False))
+ else:
+ money = str(int((money / self.denom))).replace(".", "")
+
+ money = "0" * (10 - len(money)) + money
+
+ match amount:
+ case 1:
+ money_1 = money
+ case 2:
+ money_2 = money
+ case 3:
+ money_3 = money
+ case _:
+ raise AFTBadAmount
+
+ last_transaction = self.aft_format_transaction()
+ len_transaction_id = hex(len(last_transaction) // 2)[2:]
+ if len(len_transaction_id) < 2:
+ len_transaction_id = "0" + len_transaction_id
+ elif len(len_transaction_id) % 2 == 1:
+ len_transaction_id = "0" + len_transaction_id
+
+ cmd = """72{my_key}{index}00{transfer_code}{money_1}{money_2}{money_3}
+ 00{asset}{key}{len_transaction}{transaction}{times}0C0000""".format(
+ transfer_code="80",
+ index="00",
+ money_1=money_1,
+ money_2=money_2,
+ money_3=money_3,
+ asset=self.asset_number,
+ key=self.reg_key,
+ len_transaction=len_transaction_id,
+ transaction=last_transaction,
+ times=datetime.datetime.strftime(datetime.datetime.now(), "%m%d%Y"),
+ my_key=self.my_key,
+ )
+
+ new_cmd = []
+ count = 0
+ for i in range(len(cmd) // 2):
+ new_cmd.append(int(cmd[count: count + 2], 16))
+ count += 2
+
+ response = None
+ self.aft_register()
+ if lock_timeout > 0:
+ self.aft_game_lock(lock_timeout, condition=1)
+ try:
+ data = self._send_command(new_cmd, crc_need=True, size=82)
+ if data:
+ a = int(binascii.hexlify(bytearray(data[26:27])), 16)
+ response = {
+ "Length": int(binascii.hexlify(bytearray(data[26:27])), 16),
+ "Transaction buffer position": int(
+ binascii.hexlify(bytearray(data[2:3]))
+ ),
+ "Transfer status": AftTransferStatus.AftTransferStatus.get_status(
+ binascii.hexlify(bytearray(data[3:4]))
+ ),
+ "Receipt status": AftTransferStatus.AftTransferStatus.get_status(
+ binascii.hexlify(bytearray(data[4:5]))
+ ),
+ "Transfer type": AftTransferStatus.AftTransferStatus.get_status(
+ binascii.hexlify(bytearray(data[5:6]))
+ ),
+ "Cashable amount": int(binascii.hexlify(bytearray(data[6:11])))
+ * self.denom,
+ "Restricted amount": int(binascii.hexlify(bytearray(data[11:16])))
+ * self.denom,
+ "Nonrestricted amount": int(
+ binascii.hexlify(bytearray(data[16:21]))
+ )
+ * self.denom,
+ "Transfer flags": binascii.hexlify(bytearray(data[21:22])),
+ "Asset number": binascii.hexlify(bytearray(data[22:26])),
+ "Transaction ID length": binascii.hexlify(bytearray(data[26:27])),
+ "Transaction ID": binascii.hexlify(bytearray(data[27: (27 + a)])),
+ }
+ except Exception as e:
+ self.log.error(e, exc_info=True)
+
+ self.aft_unregister()
+
+ return response
+
+ def aft_cashout_enable(self, amount=1, money="0000000000"):
+ money_1 = money_2 = money_3 = "0000000000"
+
+ match amount:
+ case 1:
+ money_1 = money
+ case 2:
+ money_2 = money
+ case 3:
+ money_3 = money
+ case _:
+ raise AFTBadAmount
+
+ last_transaction = self.aft_format_transaction()
+ len_transaction_id = hex(len(last_transaction) // 2)[2:]
+ if len(len_transaction_id) < 2:
+ len_transaction_id = "0" + len_transaction_id
+ elif len(len_transaction_id) % 2 == 1:
+ len_transaction_id = "0" + len_transaction_id
+
+ cmd = """72{my_key}00{index}{transfer_code}{money_1}{money_2}{money_3}
+ 02{asset}{key}{len_transaction}{transaction}{times}0C0000""".format(
+ transfer_code="80",
+ index="00",
+ money_1=money_1,
+ money_2=money_2,
+ money_3=money_3,
+ asset=self.asset_number,
+ key=self.reg_key,
+ len_transaction=len_transaction_id,
+ transaction=last_transaction,
+ times=datetime.datetime.strftime(datetime.datetime.now(), "%m%d%Y"),
+ my_key=self.my_key,
+ )
+
+ new_cmd = []
+ count = 0
+ for i in range(len(cmd) // 2):
+ new_cmd.append(int(cmd[count: count + 2], 16))
+ count += 2
+
+ self.aft_register()
+
+ response = None
+ try:
+ data = self._send_command(new_cmd, crc_need=True, size=82)
+ if data:
+ a = int(binascii.hexlify(bytearray(data[26:27])), 16)
+ response = {
+ "Length": int(binascii.hexlify(bytearray(data[26:27])), 16),
+ "Transaction buffer position": int(
+ binascii.hexlify(bytearray(data[2:3]))
+ ),
+ "Transfer status": AftTransferStatus.AftTransferStatus.get_status(
+ binascii.hexlify(bytearray(data[3:4]))
+ ),
+ "Receipt status": AftTransferStatus.AftTransferStatus.get_status(
+ binascii.hexlify(bytearray(data[4:5]))
+ ),
+ "Transfer type": AftTransferStatus.AftTransferStatus.get_status(
+ binascii.hexlify(bytearray(data[5:6]))
+ ),
+ "Cashable amount": int(binascii.hexlify(bytearray(data[6:11])))
+ * self.denom,
+ "Restricted amount": int(binascii.hexlify(bytearray(data[11:16])))
+ * self.denom,
+ "Nonrestricted amount": int(
+ binascii.hexlify(bytearray(data[16:21]))
+ )
+ * self.denom,
+ "Transfer flags": binascii.hexlify(bytearray(data[21:22])),
+ "Asset number": binascii.hexlify(bytearray(data[22:26])),
+ "Transaction ID length": binascii.hexlify(bytearray(data[26:27])),
+ "Transaction ID": binascii.hexlify(bytearray(data[27: (27 + a)])),
+ }
+ except Exception as e:
+ self.log.critical(e, exc_info=True)
+
+ self.aft_unregister()
+ try:
+ self.aft_clean_transaction_poll()
+ except:
+ self.log.critical("Triggered unknown exception in aft_cashout_enable")
+ return False
+
+ return True
+
+ def aft_won(
+ self, money="0000000000", amount=1, games=None, lock_timeout=0
+ ):
+ money_1 = money_2 = money_3 = "0000000000"
+ if self.denom > 0.01:
+ return None
+
+ if not games:
+ for i in range(3):
+ try:
+ games = self.selected_game_number(in_hex=False)
+ except:
+ time.sleep(0.04)
else:
- return "False"
-
- return
- def selected_meters_for_game(self):
- #2F
- return
- def send_1_bills_in_meters(self):
- #31
- cmd=[0x31]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
-
- meters['s1_bills_in_meter']=int(binascii.hexlify(bytearray(data[1:5])))
-
- return data
- return ''
- def send_2_bills_in_meters(self):
- #32
- cmd=[0x32]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
-
- meters['s2_bills_in_meter']=int(binascii.hexlify(bytearray(data[1:5])))
-
- return data
- return ''
- def send_5_bills_in_meters(self):
- #33
- cmd=[0x33]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
-
- meters['s5_bills_in_meter']=int(binascii.hexlify(bytearray(data[1:5])))
-
- return data
- return ''
- def send_10_bills_in_meters(self):
- #34
- cmd=[0x34]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
-
- meters['s10_bills_in_meter']=int(binascii.hexlify(bytearray(data[1:5])))
-
- return data
- return ''
- def send_20_bills_in_meters(self):
- #35
- cmd=[0x35]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
-
- meters['s20_bills_in_meter']=int(binascii.hexlify(bytearray(data[1:5])))
-
- return data
- return ''
- def send_50_bills_in_meters(self):
- #36
- cmd=[0x36]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
-
- meters['s50_bills_in_meter']=int(binascii.hexlify(bytearray(data[1:5])))
-
- return data
- return ''
- def send_100_bills_in_meters(self):
- #37
- cmd=[0x37]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
-
- meters['s100_bills_in_meter']=int(binascii.hexlify(bytearray(data[1:5])))
-
- return data
- return ''
- def send_500_bills_in_meters(self):
- #38
- cmd=[0x38]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
-
- meters['s500_bills_in_meter']=int(binascii.hexlify(bytearray(data[1:5])))
-
- return data
- return ''
- def send_1000_bills_in_meters(self):
- #39
- cmd=[0x39]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
-
- meters['s1000_bills_in_meter']=int(binascii.hexlify(bytearray(data[1:5])))
-
- return data
- return ''
- def send_200_bills_in_meters(self):
- #3A
- cmd=[0x3a]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
-
- meters['s200_bills_in_meter']=int(binascii.hexlify(bytearray(data[1:5])))
-
- return data
- return ''
- def send_25_bills_in_meters(self):
- #3B
- cmd=[0x3B]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
-
- meters['s25_bills_in_meter']=int(binascii.hexlify(bytearray(data[1:5])))
-
- return data
- return ''
- def send_2000_bills_in_meters(self):
- #3C
- cmd=[0x3C]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
-
- meters['s2000_bills_in_meter']=int(binascii.hexlify(bytearray(data[1:5])))
-
- return data
- return ''
- def cash_out_ticket_info(self):
- #3D
- cmd=[0x3D]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
-
- tito_statement['cashout_ticket_number']=int(binascii.hexlify(bytearray(data[1:3])))
- tito_statement['cashout_amount_in_cents']=int(binascii.hexlify(bytearray(data[3:])))
-
- return data
- return ''
- def send_2500_bills_in_meters(self):
- #3E
- cmd=[0x3E]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
-
- meters['s2500_bills_in_meter']=int(binascii.hexlify(bytearray(data[1:5])))
-
- return data
- return ''
- def send_5000_bills_in_meters(self):
- #3F
- cmd=[0x3F]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
-
- meters['s5000_bills_in_meter']=int(binascii.hexlify(bytearray(data[1:5])))
-
- return data
- return ''
- def send_10000_bills_in_meters(self):
- #40
- cmd=[0x40]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
-
- meters['s10000_bills_in_meter']=int(binascii.hexlify(bytearray(data[1:5])))
-
- return data
- return ''
- def send_20000_bills_in_meters(self):
- #41
- cmd=[0x41]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
-
- meters['s20000_bills_in_meter']=int(binascii.hexlify(bytearray(data[1:5])))
-
- return data
- return ''
- def send_25000_bills_in_meters(self):
- #42
- cmd=[0x42]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
-
- meters['s25000_bills_in_meter']=int(binascii.hexlify(bytearray(data[1:5])))
-
- return data
- return ''
- def send_50000_bills_in_meters(self):
- #43
- cmd=[0x43]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
-
- meters['s50000_bills_in_meter']=int(binascii.hexlify(bytearray(data[1:5])))
-
- return data
- return ''
- def send_100000_bills_in_meters(self):
- #44
- cmd=[0x44]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
-
- meters['s100000_bills_in_meter']=int(binascii.hexlify(bytearray(data[1:5])))
-
- return data
- return ''
- def send_250_bills_in_meters(self):
- #45
- cmd=[0x45]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
-
- meters['s250_bills_in_meter']=int(binascii.hexlify(bytearray(data[1:5])))
-
- return data
- return ''
- def credit_amount_of_all_bills_accepted(self):
- #46
-
- cmd=[0x46]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>''):
-
- meters['credit_amount_of_all_bills_accepted']=int(binascii.hexlify(bytearray(data[1:5])))
-
- return data
- return ''
- def coin_amount_accepted_from_external_coin_acceptor(self):
- #47
- cmd=[0x47]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>None):
-
- meters['coin_amount_accepted_from_external_coin_acceptor']=int(binascii.hexlify(bytearray(data[1:5])))
-
- return data
- return ''
- def last_accepted_bill_info(self):
- #48
- cmd=[0x48]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>None):
- meters['country_code']=int(binascii.hexlify(bytearray(data[1:2])))
- meters['bill_denomination']=int(binascii.hexlify(bytearray(data[2:3])))
- meters['meter_for_accepted_bills']=int(binascii.hexlify(bytearray(data[3:6])))
- return data
- return ''
- def number_of_bills_currently_in_stacker(self):
- #49
- cmd=[0x49]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>None):
- meters['number_bills_in_stacker']=int(binascii.hexlify(bytearray(data[1:5])))
- return data
- return ''
- def total_credit_amount_of_all_bills_in_stacker(self):
- #4A
- cmd=[0x49]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>None):
- meters['credits_SAS_in_stacker']=int(binascii.hexlify(bytearray(data[1:5])))
- return data
- return ''
- def set_secure_enhanced_validation_ID(self, MachineID=[0x01,0x01,0x01], seq_num=[0x00,0x00,0x01]):
- #4C
- cmd=[0x4C]
-
- cmd.extend(MachineID)
- cmd.extend(seq_num)
- cmd=bytearray(cmd)
- #print str(binascii.hexlify((cmd)))
- data=self.__send_command(cmd,True, crc_need=True)
- if(data<>None):
- tito_statement['machine_ID']=int(binascii.hexlify(bytearray(data[1:4])))
- tito_statement['sequence_number']=int(binascii.hexlify(bytearray(data[4:8])))
-
- return data
- return ''
- def enhanced_validation_information(self, curr_validation_info=0):
- #4D
-
- cmd=[0x4D]
-
- #cmd.append(transfer_code)
- #cmd=cmd.extend(0)
- #rint str(binascii.hexlify(bytearray(cmd)))
- cmd.append((curr_validation_info))
- data=self.__send_command(cmd,True, crc_need=True)
- if(data<>None):
- tito_statement['validation_type']=int(binascii.hexlify(bytearray(data[1:2])))
- tito_statement['index_number']=int(binascii.hexlify(bytearray(data[2:3])))
- tito_statement['date_validation_operation']=str(binascii.hexlify(bytearray(data[3:7])))
- tito_statement['time_validation_operation']=str(binascii.hexlify(bytearray(data[7:10])))
- tito_statement['validation_number']=str(binascii.hexlify(bytearray(data[10:18])))
- tito_statement['ticket_amount']=int(binascii.hexlify(bytearray(data[18:23])))
- tito_statement['ticket_number']=int(binascii.hexlify(bytearray(data[23:25])))
- tito_statement['validation_system_ID']=int(binascii.hexlify(bytearray(data[25:26])))
- tito_statement['expiration_date_printed_on_ticket']=str(binascii.hexlify(bytearray(data[26:30])))
- tito_statement['pool_id']=int(binascii.hexlify(bytearray(data[30:32])))
-
-
- return data
- return ''
- def current_hopper_status(self):
- #4F
-
- cmd=[0x4F]
-
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>None):
- meters['current_hopper_lenght']=int(binascii.hexlify(bytearray(data[1:2])))
- meters['current_hopper_ststus']=int(binascii.hexlify(bytearray(data[2:3])))
- meters['current_hopper_percent_full']=int(binascii.hexlify(bytearray(data[3:4])))
- meters['current_hopper_level']=int(binascii.hexlify(bytearray(data[4:])))
- return data
- return ''
- def validation_meters(self, type_of_validation=0x00):
- #50
-
- cmd=[0x50]
- cmd.append(type_of_validation)
- data=self.__send_command(cmd,True, crc_need=True)
- if(data<>None):
- meters['bin_validation_type']=int(binascii.hexlify(bytearray(data[1])))
- meters['total_validations']=int(binascii.hexlify(bytearray(data[2:6])))
- meters['cumulative_amount']=str(binascii.hexlify(bytearray(data[6:])))
-
- return data
- return ''
- def total_number_of_games_impimented(self):
- #51
- cmd=[0x51]
- cmd.extend(type_of_validation)
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>None):
- meters['total_number_of_games_impemented']=str(binascii.hexlify(bytearray(data[1:])))
-
-
- return data
- return ''
- def game_meters(self, n=1):
- #52
-
- cmd=[0x52]
- cmd.extend([(n&0xFF), ((n>>8)&0xFF)])
-
- data=self.__send_command(cmd,True, crc_need=True)
- if(data<>None):
- meters['game_n_number']=str(binascii.hexlify(bytearray(data[1:3])))
- meters['game_n_coin_in_meter']=str(binascii.hexlify(bytearray(data[3:7])))
- meters['game_n_coin_out_meter']=str(binascii.hexlify(bytearray(data[7:11])))
- meters['game_n_jackpot_meter']=str(binascii.hexlify(bytearray(data[11:15])))
- meters['geme_n_games_played_meter']=str(binascii.hexlify(bytearray(data[15:])))
-
-
- return data
- return ''
- def game_configuration(self, n=1):
- #53
-
- cmd=[0x53]
- cmd.extend([(n&0xFF), ((n>>8)&0xFF)])
-
- data=self.__send_command(cmd,True, crc_need=True)
- if(data<>None):
- meters['game_n_number_config']=int(binascii.hexlify(bytearray(data[1:3])))
- meters['game_n_ASCII_game_ID']=str(binascii.hexlify(bytearray(data[3:5])))
- meters['game_n_ASCII_additional_id']=str(binascii.hexlify(bytearray(data[5:7])))
- meters['game_n_bin_denomination']=str(binascii.hexlify(bytearray(data[7])))
- meters['game_n_bin_progressive_group']=str(binascii.hexlify(bytearray(data[8])))
- meters['game_n_bin_game_options']=str(binascii.hexlify(bytearray(data[9:11])))
- meters['game_n_ASCII_paytable_ID']=str(binascii.hexlify(bytearray(data[11:17])))
- meters['game_n_ASCII_base_percentage']=str(binascii.hexlify(bytearray(data[17:])))
-
-
- return data
- return ''
- def SAS_version_gaming_machine_serial_ID(self):
- #54
- cmd=[0x54]
-
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>None):
- meters['ASCII_SAS_version']=data[2:5]
- meters['ASCII_serial_number']=data[5:]
- return data
- return ''
- def selected_game_number(self):
- #55
- cmd=[0x55]
-
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>None):
- meters['selected_game_number']=int(binascii.hexlify(bytearray(data[1:])))
- return data
- return ''
- def enabled_game_numbers(self):
- #56
-
- cmd=[0x56]
-
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>None):
- meters['number_of_enabled_games']=int(binascii.hexlify(bytearray(data[2])))
- meters['enabled_games_numbers']=int(binascii.hexlify(bytearray(data[3:])))
-
- return data
- return ''
- def pending_cashout_info(self):
- #57
-
- cmd=[0x57]
-
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>None):
- tito_statement['cashout_type']=int(binascii.hexlify(bytearray(data[1:2])))
- tito_statement['cashout_amount']=str(binascii.hexlify(bytearray(data[2:])))
-
- return data
- return ''
- def validation_number(self, validationID=1, valid_number=0):
- #58
-
- cmd=[0x58]
- cmd.append(validationID)
- cmd.extend(self.bcd_coder_array( valid_number,8))
- print cmd
- data=self.__send_command(cmd,True, crc_need=True)
- if(data<>None):
-
- return data[1]
- return ''
- def eft_send_promo_to_machine(self, amount=0, count=1, status=0):
- #63
- cmd=[0x63, count, ]
- #status 0-init 1-end
- cmd.append(status)
- cmd.extend(self.bcd_coder_array(amount, 4))
- data=self.__send_command(cmd,True, crc_need=True)
-
- if(data<>None):
- eft_statement['eft_status']=str(binascii.hexlify(bytearray(data[1:])))
- eft_statement['promo_amount']=str(binascii.hexlify(bytearray(data[4:])))
- # eft_statement['eft_transfer_counter']=int(binascii.hexlify(bytearray(data[3:4])))
-
-
- return data[3]
- return ''
- def eft_load_cashable_credits(self, amount=0, count=1, status=0):
- #69
- cmd=[0x69, count, ]
- cmd.append(status)
- cmd.extend(self.bcd_coder_array(amount, 4))
- data=self.__send_command(cmd,True, crc_need=True)
-
- if(data<>None):
- meters['eft_status']=str(binascii.hexlify(bytearray(data[1:2])))
- meters['cashable_amount']=str(binascii.hexlify(bytearray(data[2:5])))
-
-
- return data[3]
- return ''
-
- def eft_avilable_transfers(self):
- #6A
- cmd=[0x6A]
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>None):
- #meters['number_bills_in_stacker']=int(binascii.hexlify(bytearray(data[1:5])))
- return data
- return ''
-
-
- def autentification_info(self, action=0, adressing_mode=0, component_name='', auth_method=b'\x00\x00\x00\x00', seed_lenght=0, seed='', offset_lenght=0, offset=''):
- #6E
- cmd=[0x6E, 0x00]
- cmd.append(action)
- if action==0:
- #cmd.append(action)
- cmd[1]=1
+ break
+
+ if not games or games < 1:
+ return "NoGame"
+
+ money = str(int(money / self.denom)).replace(".", "")
+ money = "0" * (10 - len(money)) + money
+
+ match amount:
+ case 1:
+ money_1 = money
+ case 2:
+ money_2 = money
+ case 3:
+ money_3 = money
+ case _:
+ raise AFTBadAmount
+
+ last_transaction = self.aft_format_transaction()
+ len_transaction_id = hex(len(last_transaction) // 2)[2:]
+ if len(len_transaction_id) < 2:
+ len_transaction_id = "0" + len_transaction_id
+ elif len(len_transaction_id) % 2 == 1:
+ len_transaction_id = "0" + len_transaction_id
+
+ cmd = """72{my_key}{transfer_code}{index}{money_1}{money_2}{money_3}"
+ "00{asset}{key}{len_transaction}{transaction}{times}0C0000""".format(
+ transfer_code="0000",
+ index="10",
+ money_1=money_1,
+ money_2=money_2,
+ money_3=money_3,
+ asset=self.asset_number,
+ key=self.reg_key,
+ len_transaction=len_transaction_id,
+ transaction=last_transaction,
+ times=datetime.datetime.strftime(datetime.datetime.now(), "%m%d%Y"),
+ my_key=self.my_key,
+ )
+
+ new_cmd = []
+ count = 0
+ for i in range(len(cmd) // 2):
+ new_cmd.append(int(cmd[count: count + 2], 16))
+ count += 2
+
+ response = None
+ self.aft_register()
+ if lock_timeout > 0:
+ self.aft_game_lock(lock_timeout, condition=3)
+ try:
+ data = self._send_command(new_cmd, crc_need=True, size=82)
+ if data:
+ a = int(binascii.hexlify(bytearray(data[26:27])), 16)
+ response = {
+ "Length": int(binascii.hexlify(bytearray(data[26:27])), 16),
+ "Transaction buffer position": int(
+ binascii.hexlify(bytearray(data[2:3]))
+ ),
+ "Transfer status": AftTransferStatus.AftTransferStatus.get_status(
+ binascii.hexlify(bytearray(data[3:4]))
+ ),
+ "Receipt status": AftTransferStatus.AftTransferStatus.get_status(
+ binascii.hexlify(bytearray(data[4:5]))
+ ),
+ "Transfer type": AftTransferStatus.AftTransferStatus.get_status(
+ binascii.hexlify(bytearray(data[5:6]))
+ ),
+ "Cashable amount": int(binascii.hexlify(bytearray(data[6:11])))
+ * self.denom,
+ "Restricted amount": int(binascii.hexlify(bytearray(data[11:16])))
+ * self.denom,
+ "Nonrestricted amount": int(
+ binascii.hexlify(bytearray(data[16:21]))
+ )
+ * self.denom,
+ "Transfer flags": binascii.hexlify(bytearray(data[21:22])),
+ "Asset number": binascii.hexlify(bytearray(data[22:26])),
+ "Transaction ID length": binascii.hexlify(bytearray(data[26:27])),
+ "Transaction ID": binascii.hexlify(bytearray(data[27: (27 + a)])),
+ }
+ except Exception as e:
+ self.log.error(e, exc_info=True)
+
+ self.aft_unregister()
+
+ return response
+
+ def aft_in(self, money, amount=1):
+ """
+ aft_in is the function you want to use to charge money into your machine
+
+ :param money:
+ :param amount:
+ :param lock_timeout:
+ :param kwargs:
+ :return:
+ """
+ money_1 = money_2 = money_3 = "0000000000"
+ if self.denom > 0.01:
+ return None
+
+ money = str(int(money / self.denom)).replace(".", "")
+ money = "0" * (10 - len(money)) + money
+
+ match amount:
+ case 1:
+ money_1 = money
+ case 2:
+ money_2 = money
+ case 3:
+ money_3 = money
+ case _:
+ raise AFTBadAmount
+
+ last_transaction = self.aft_format_transaction()
+ len_transaction_id = hex(len(last_transaction) // 2)[2:]
+ if len(len_transaction_id) < 2:
+ len_transaction_id = "0" + len_transaction_id
+ elif len(len_transaction_id) % 2 == 1:
+ len_transaction_id = "0" + len_transaction_id
+
+ cmd = """72{my_key}{transfer_code}{index}00{money_1}{money_2}{money_3}
+ 00{asset}{key}{len_transaction}{transaction}{times}0C0000""".format(
+ transfer_code="00",
+ index="00",
+ money_1=money_1,
+ money_2=money_2,
+ money_3=money_3,
+ asset=self.asset_number,
+ key=self.reg_key,
+ len_transaction=len_transaction_id,
+ transaction=last_transaction,
+ times=datetime.datetime.strftime(datetime.datetime.now(), "%m%d%Y"),
+ my_key=self.my_key,
+ )
+
+ new_cmd = []
+ count = 0
+ for i in range(len(cmd) // 2):
+ new_cmd.append(int(cmd[count: count + 2], 16))
+ count += 2
+
+ try:
+ data = self._send_command(new_cmd, crc_need=True, size=82)
+ if data:
+ a = int(binascii.hexlify(bytearray(data[26:27])), 16)
+ response = {
+ "Length": int(binascii.hexlify(bytearray(data[26:27])), 16),
+ "Transaction buffer position": int(
+ binascii.hexlify(bytearray(data[2:3]))
+ ),
+ "Transfer status": AftTransferStatus.AftTransferStatus.get_status(
+ [binascii.hexlify(bytearray(data[3:4]))]
+ ),
+ "Receipt status": AftReceiptStatus.AftReceiptStatus.get_status(
+ [binascii.hexlify(bytearray(data[4:5]))]
+ ),
+ "Transfer type": AftTransferType.AftTransferType.get_status(
+ [binascii.hexlify(bytearray(data[5:6]))]
+ ),
+ "Cashable amount": int(binascii.hexlify(bytearray(data[6:11])))
+ * self.denom,
+ "Restricted amount": int(binascii.hexlify(bytearray(data[11:16])))
+ * self.denom,
+ "Nonrestricted amount": int(
+ binascii.hexlify(bytearray(data[16:21]))
+ )
+ * self.denom,
+ "Transfer flags": binascii.hexlify(bytearray(data[21:22])),
+ "Asset number": binascii.hexlify(bytearray(data[22:26])),
+ "Transaction ID length": binascii.hexlify(bytearray(data[26:27])),
+ "Transaction ID": binascii.hexlify(bytearray(data[27: (27 + a)])),
+ }
+
+ self.aft_unregister()
+ return response
+
+ except Exception as e:
+ self.aft_unregister()
+ self.log.error(e, exc_info=True)
+
+ def aft_clean_transaction_poll(self, register=False):
+ """Remember to loop this function AFTER calling aft_in.
+ If it raises an error or returns 'Transfer pending (not complete)'
+ you continue to execute until 'Full transfer successful'.
+ Otherwise, you break the cycle and make the request invalid.
+ """
+ if register:
+ self.aft_register()
+
+ if not self.transaction:
+ self.aft_get_last_trx()
+
+ cmd = "7202FF00"
+ count = 0
+ new_cmd = []
+ for i in range(len(cmd) // 2):
+ new_cmd.append(int(cmd[count: count + 2], 16))
+ count += 2
+
+ response = None
+ try:
+ data = self._send_command(new_cmd, crc_need=True, size=90)
+ if data:
+ a = int(binascii.hexlify(bytearray(data[26:27])), 16)
+ response = {
+ "Length": int(binascii.hexlify(bytearray(data[26:27])), 16),
+ "Transfer status": AftTransferStatus.AftTransferStatus.get_status(
+ binascii.hexlify(bytearray(data[3:4]))
+ ),
+ "Receipt status": AftTransferStatus.AftTransferStatus.get_status(
+ binascii.hexlify(bytearray(data[4:5]))
+ ),
+ "Transfer type": AftTransferStatus.AftTransferStatus.get_status(
+ binascii.hexlify(bytearray(data[5:6]))
+ ),
+ "Cashable amount": int(binascii.hexlify(bytearray(data[6:11])))
+ * self.denom,
+ "Restricted amount": int(binascii.hexlify(bytearray(data[11:16])))
+ * self.denom,
+ "Nonrestricted amount": int(
+ binascii.hexlify(bytearray(data[16:21]))
+ )
+ * self.denom,
+ "Transfer flags": binascii.hexlify(bytearray(data[21:22])),
+ "Asset number": binascii.hexlify(bytearray(data[22:26])),
+ "Transaction ID length": binascii.hexlify(bytearray(data[26:27])),
+ "Transaction ID": binascii.hexlify(bytearray(data[27: (27 + a)])),
+ }
+
+ if register:
+ try:
+ self.aft_unregister()
+ except:
+ self.log.warning("AFT UNREGISTER ERROR: clean poll")
+
+ if hex(self.transaction)[2:-1] == response["Transaction ID"]:
+ return response
+ else:
+ if self.check_last_transaction:
+ raise BadTransactionID(
+ "last: %s, new:%s "
+ % (hex(self.transaction)[2:-1], response["Transaction ID"])
+ )
else:
- if (action==1 or action==3):
- cmd.append(adressing_mode)
- cmd.append(len(bytearray(component_name)))
- cmd.append (bytearray(component_name))
- cmd[1]=len(bytearray(component_name))+3
- else:
- if action==2:
- cmd.append(adressing_mode)
- cmd.append(len(bytearray(component_name)))
- cmd.append (bytearray(component_name))
- cmd.append(auth_metod)
- cmd.append(seed_lenght)
- cmd.append(bytearray(seed))
- cmd.append(offset_lenght)
- cmd.append(bytearray(offset))
-
- cmd[1]=len(bytearray(offset))+len(bytearray(seed))+len(bytearray(component_name))+6
-
-
- data=self.__send_command(cmd,True, crc_need=True)
- if(data<>None):
-
- return data[1]
- return ''
- def extended_meters_for_game(self, n=1):
- #6F
- return
- def ticket_validation_data(self):
- #70
-
- cmd=[0x70]
-
- data=self.__send_command(cmd,True, crc_need=False)
- if(data<>None):
- meters['ticket_status']=int(binascii.hexlify(bytearray(data[2:3])))
- meters['ticket_amount']=str(binascii.hexlify(bytearray(data[3:8])))
- meters['parsing_code']=int(binascii.hexlify(bytearray(data[8:9])))
- meters['validation_data']=str(binascii.hexlify(bytearray(data[9:])))
-
-
- return data[1]
- return ''
- def redeem_ticket(self, transfer_code=0, transfer_amount=0, parsing_code=0, validation_data=0, rescticted_expiration=0, pool_ID=0):
- #71
-
- cmd=[0x71, 0x00]
- cmd.append(transfer_code)
- cmd.extend(self.bcd_coder_array(transfer_amount, 5))
- cmd.append(parsing_code)
-
- cmd.extend(self.bcd_coder_array(validation_data, 8))
- cmd.extend(self.bcd_coder_array(rescticted_expiration, 4))
- cmd.extend(self.bcd_coder_array(pool_ID,2))
- cmd[1]=8+13
-
- data=self.__send_command(cmd,True, crc_need=True)
- if(data<>None):
- meters['ticket_status']=int(binascii.hexlify(bytearray(data[2:3])))
- meters['ticket_amount']=int(binascii.hexlify(bytearray(data[3:8])))
- meters['parsing_code']=int(binascii.hexlify(bytearray(data[8:9])))
- meters['validation_data']=str(binascii.hexlify(bytearray(data[9:])))
-
-
- return data[1]
- return ''
- def AFT_transfer_funds(self, transfer_code=0x00, transaction_index=0x00, transfer_type=0x00, cashable_amount=0, restricted_amount=0, non_restricted_amount=0, transfer_flags=0x00, asset_number=b'\x00\x00\x00\x00\x00', registration_key=0, transaction_ID_lenght=0x00, transaction_ID='', expiration=0, pool_ID=0, reciept_data='', lock_timeout=0):
- #72
-#sas.AFT_transfer_funds(0, 1, 0x60, 10000, 0, 0, 0b00000000,)
- cmd=[0x72, 0x00]
- cmd.append(transfer_code)
- cmd.append(transaction_index)
- cmd.append(transfer_type)
- cmd.extend(self.bcd_coder_array(cashable_amount, 5))
- cmd.extend(self.bcd_coder_array(restricted_amount, 5))
- cmd.extend(self.bcd_coder_array(non_restricted_amount, 5))
- cmd.append(transfer_flags)
- cmd.extend((asset_number))
- cmd.extend(self.bcd_coder_array(registration_key, 20))
- cmd.append(len(transaction_ID))
- cmd.extend(transaction_ID)
- cmd.extend(self.bcd_coder_array(expiration, 4))
- cmd.extend(self.bcd_coder_array(pool_ID, 2))
-
- cmd.append(len(reciept_data))
- cmd.extend(reciept_data)
- cmd.extend(self.bcd_coder_array(lock_timeout,2))
-
- cmd[1]=len(transaction_ID)+len(transaction_ID)+53
-
- data=self.__send_command(cmd,True, crc_need=True)
- if(data<>None):
- aft_statement['transaction_buffer_position']=int(binascii.hexlify(bytearray(data[2:3])))
- aft_statement['transfer_status']=int(binascii.hexlify(bytearray(data[3:4])))
- aft_statement['receipt_status']=int(binascii.hexlify(bytearray(data[4:5])))
- aft_statement['transfer_type']=int(binascii.hexlify(bytearray(data[5:6])))
- aft_statement['cashable_amount']=int(binascii.hexlify(bytearray(data[6:11])))
- aft_statement['restricted_amount']=int(binascii.hexlify(bytearray(data[11:16])))
- aft_statement['nonrestricted_amount']=int(binascii.hexlify(bytearray(data[16:21])))
- aft_statement['transfer_flags']=int(binascii.hexlify(bytearray(data[21:22])))
- aft_statement['asset_number']=(binascii.hexlify(bytearray(data[22:26])))
- aft_statement['transaction_ID_lenght']=int(binascii.hexlify(bytearray(data[26:27])))
- a=int(binascii.hexlify(bytearray(data[26:27])))
- aft_statement['transaction_ID']=str(binascii.hexlify(bytearray(data[27:(27+a+1)])))
- a=27+a+1
- aft_statement['transaction_date']=str(binascii.hexlify(bytearray(data[a:a+5])))
- a=a+5
- aft_statement['transaction_time']=str(binascii.hexlify(bytearray(data[a:a+4])))
- aft_statement['expiration']=str(binascii.hexlify(bytearray(data[a+4:a+9])))
- aft_statement['pool_ID']=str(binascii.hexlify(bytearray(data[a+9:a+11])))
- aft_statement['cumulative_casable_amount_meter_size']=(binascii.hexlify(bytearray(data[a+11:a+12])))
- b=a+int(binascii.hexlify(bytearray(data[a+11:a+12])))
- aft_statement['cumulative_casable_amount_meter']=(binascii.hexlify(bytearray(data[a+12:b+1])))
- aft_statement['cumulative_restricted_amount_meter_size']=(binascii.hexlify(bytearray(data[b+1:b+2])))
- c=b+2+int(binascii.hexlify(bytearray(data[b+1:b+2])))
- aft_statement['cumulative_restricted_amount_meter']=(binascii.hexlify(bytearray(data[b+2:c])))
- aft_statement['cumulative_nonrestricted_amount_meter_size']=(binascii.hexlify(bytearray(data[c:c+1])))
- b=int(binascii.hexlify(bytearray(data[c:c+1])))+c
- aft_statement['cumulative_nonrestricted_amount_meter']=(binascii.hexlify(bytearray(data[c+1:])))
-
-
- return data[1]
- return ''
- def AFT_register_gaming_machine(self, reg_code=0xff, asset_number=0, reg_key=0, POS_ID=b'\x00\x00\x00\x00'):
- #73
- cmd=[0x73, 0x00]
- if reg_code==0xFF:
- cmd.append(reg_code)
- cmd[1]=1
-
+ self.log.info(
+ "last: %s, new:%s "
+ % (hex(self.transaction)[2:-1], response["Transaction ID"])
+ )
+ except BadCRC:
+ pass
+
+ return False
+
+ def aft_transfer_funds(
+ self,
+ transfer_code=0x00,
+ transaction_index=0x00,
+ transfer_type=0x00,
+ cashable_amount=0,
+ restricted_amount=0,
+ non_restricted_amount=0,
+ transfer_flags=0x00,
+ asset_number=b"\x00\x00\x00\x00\x00",
+ registration_key=0,
+ transaction_id="",
+ expiration=0,
+ pool_id=0,
+ receipt_data="",
+ lock_timeout=0
+ ):
+ # 72
+ cmd = [
+ 0x72,
+ 2 * len(transaction_id) + 53,
+ transfer_code,
+ transaction_index,
+ transfer_type,
+ self._bcd_coder_array(cashable_amount, 5),
+ self._bcd_coder_array(restricted_amount, 5),
+ self._bcd_coder_array(non_restricted_amount, 5),
+ transfer_flags,
+ asset_number,
+ self._bcd_coder_array(registration_key, 20),
+ len(transaction_id),
+ self._bcd_coder_array(expiration, 4),
+ self._bcd_coder_array(pool_id, 2),
+ len(receipt_data),
+ receipt_data,
+ self._bcd_coder_array(lock_timeout, 2)
+ ]
+
+ data = self._send_command(cmd, crc_need=True)
+ if data:
+ AftStatements.AftStatements.STATUS_MAP["transaction_buffer_position"] = int(
+ binascii.hexlify(bytearray(data[2:3]))
+ )
+ AftStatements.AftStatements.STATUS_MAP["transfer_status"] = int(
+ binascii.hexlify(bytearray(data[3:4]))
+ )
+ AftStatements.AftStatements.STATUS_MAP["receipt_status"] = int(
+ binascii.hexlify(bytearray(data[4:5]))
+ )
+ AftStatements.AftStatements.STATUS_MAP["transfer_type"] = int(
+ binascii.hexlify(bytearray(data[5:6]))
+ )
+ AftStatements.AftStatements.STATUS_MAP["cashable_amount"] = int(
+ binascii.hexlify(bytearray(data[6:11]))
+ )
+ AftStatements.AftStatements.STATUS_MAP["restricted_amount"] = int(
+ binascii.hexlify(bytearray(data[11:16]))
+ )
+ AftStatements.AftStatements.STATUS_MAP["nonrestricted_amount"] = int(
+ binascii.hexlify(bytearray(data[16:21]))
+ )
+ AftStatements.AftStatements.STATUS_MAP["transfer_flags"] = int(
+ binascii.hexlify(bytearray(data[21:22]))
+ )
+ AftStatements.AftStatements.STATUS_MAP["asset_number"] = binascii.hexlify(
+ bytearray(data[22:26])
+ )
+ AftStatements.AftStatements.STATUS_MAP["transaction_id_length"] = int(
+ binascii.hexlify(bytearray(data[26:27]))
+ )
+ a = int(binascii.hexlify(bytearray(data[26:27])))
+ AftStatements.AftStatements.STATUS_MAP["transaction_id"] = str(
+ binascii.hexlify(bytearray(data[27: (27 + a + 1)]))
+ )
+ a = 27 + a + 1
+ AftStatements.AftStatements.STATUS_MAP["transaction_date"] = str(
+ binascii.hexlify(bytearray(data[a: a + 5]))
+ )
+ a = a + 5
+ AftStatements.AftStatements.STATUS_MAP["transaction_time"] = str(
+ binascii.hexlify(bytearray(data[a: a + 4]))
+ )
+ AftStatements.AftStatements.STATUS_MAP["expiration"] = str(
+ binascii.hexlify(bytearray(data[a + 4: a + 9]))
+ )
+ AftStatements.AftStatements.STATUS_MAP["pool_id"] = str(
+ binascii.hexlify(bytearray(data[a + 9: a + 11]))
+ )
+ AftStatements.AftStatements.STATUS_MAP[
+ "cumulative_cashable_amount_meter_size"
+ ] = binascii.hexlify(bytearray(data[a + 11: a + 12]))
+ b = a + int(binascii.hexlify(bytearray(data[a + 11: a + 12])))
+ AftStatements.AftStatements.STATUS_MAP[
+ "cumulative_cashable_amount_meter"
+ ] = binascii.hexlify(bytearray(data[a + 12: b + 1]))
+ AftStatements.AftStatements.STATUS_MAP[
+ "cumulative_restricted_amount_meter_size"
+ ] = binascii.hexlify(bytearray(data[b + 1: b + 2]))
+ c = b + 2 + int(binascii.hexlify(bytearray(data[b + 1: b + 2])))
+ AftStatements.AftStatements.STATUS_MAP[
+ "cumulative_restricted_amount_meter"
+ ] = binascii.hexlify(bytearray(data[b + 2: c]))
+ AftStatements.AftStatements.STATUS_MAP[
+ "cumulative_nonrestricted_amount_meter_size"
+ ] = binascii.hexlify(bytearray(data[c: c + 1]))
+ b = int(binascii.hexlify(bytearray(data[c: c + 1]))) + c
+ AftStatements.AftStatements.STATUS_MAP[
+ "cumulative_nonrestricted_amount_meter"
+ ] = binascii.hexlify(bytearray(data[c + 1:]))
+
+ return AftStatements.AftStatements.get_non_empty_status_map()
+
+ return None
+
+ def aft_get_last_trx(self):
+ cmd = [0x72, 0x02, 0xFF, 0x00]
+ data = self._send_command(cmd, crc_need=True, size=90)
+ if data:
+ try:
+ if not self.aft_get_last_transaction:
+ raise ValueError
+
+ count = int(binascii.hexlify(data[26:27]), 16)
+ transaction = binascii.hexlify(data[27: 27 + count])
+ if transaction == "2121212121212121212121212121212121":
+ transaction = "2020202020202020202020202020202021"
+ self.transaction = int(transaction, 16)
+
+ return self.transaction
+
+ except ValueError as e:
+ self.log.warning(e, exc_info=True)
+ self.transaction = int("2020202020202020202020202020202021", 16)
+ self.log.warning("AFT no transaction")
+
+ except Exception as e:
+ self.log.error(e, exc_info=True)
+ self.transaction = int("2020202020202020202020202020202021", 16)
+ self.log.warning("AFT no transaction")
+
+ else:
+ self.transaction = int("2020202020202020202020202020202021", 16)
+ self.log.warning("AFT no transaction")
+
+ return self.transaction
+
+ def aft_format_transaction(self, from_egm=False):
+ if from_egm:
+ self.aft_get_last_trx()
+
+ if self.transaction is None:
+ self.aft_get_last_trx()
+
+ self.transaction += 1
+ transaction = hex(self.transaction)[2:-1]
+ count = 0
+ tmp = []
+ for i in range(len(transaction) // 2):
+ tmp.append(transaction[count: count + 2])
+ count += 2
+
+ tmp.reverse()
+ for i in range(len(tmp)):
+ if int(tmp[i], 16) >= 124:
+ tmp[i] = "20"
+ tmp[i + 1] = hex(int(tmp[i + 1], 16) + 1)[2:]
+
+ tmp.reverse()
+ response = ""
+ for i in tmp:
+ response += i
+ if response == "2121212121212121212121212121212121":
+ response = "2020202020202020202020202020202021"
+
+ self.transaction = int(response, 16)
+ return response
+
+ def aft_register(self, reg_code=0x01):
+ try:
+ return self.aft_register_gaming_machine(reg_code=reg_code)
+ except Exception as e:
+ self.log.error(e, exc_info=True)
+ return None
+
+ def aft_unregister(self, reg_code=0x80):
+ try:
+ return self.aft_register_gaming_machine(reg_code=reg_code)
+ except Exception as e:
+ self.log.error(e, exc_info=True)
+ return None
+
+ def aft_register_gaming_machine(self, reg_code=0xFF):
+ # 73
+ cmd = [0x73, 0x00, reg_code]
+
+ if reg_code == 0xFF:
+ cmd[1] = 0x01
+ else:
+ tmp = self.asset_number + self.reg_key + self.pos_id
+ cmd[1] = 0x1D
+ count = 0
+ for i in range(len(tmp) // 2):
+ cmd.append(int(tmp[count: count + 2], 16))
+ count += 2
+
+ data = self._send_command(cmd, crc_need=True, size=34)
+
+ if data:
+ AftStatements.AftStatements.STATUS_MAP[
+ "registration_status"
+ ] = binascii.hexlify(data[3:7])
+
+ AftStatements.AftStatements.STATUS_MAP["registration_key"] = str(
+ binascii.hexlify(data[7:27])
+ )
+ AftStatements.AftStatements.STATUS_MAP["POS_ID"] = str(
+ binascii.hexlify((data[27:]))
+ )
+ return AftStatements.AftStatements.get_non_empty_status_map()
+
+ return None
+
+ def aft_game_lock(self, lock_timeout=100, condition=00):
+ return self.aft_game_lock_and_status_request(
+ lock_code=0x00, lock_timeout=lock_timeout, transfer_condition=condition
+ )
+
+ def aft_game_unlock(self):
+ return self.aft_game_lock_and_status_request(lock_code=0x80)
+
+ def aft_game_lock_and_status_request(
+ self, lock_code=0x00, transfer_condition=00, lock_timeout=0
+ ):
+ # 74
+ cmd = [
+ 0x74,
+ lock_code,
+ transfer_condition,
+ self._bcd_coder_array(lock_timeout, 2),
+ ]
+
+ data = self._send_command(cmd, crc_need=True, size=40)
+ if data:
+ AftStatements.AftStatements.STATUS_MAP["asset_number"] = str(
+ binascii.hexlify(bytearray(data[2:6]))
+ )
+ AftStatements.AftStatements.STATUS_MAP["game_lock_status"] = str(
+ binascii.hexlify(bytearray(data[6:7]))
+ )
+ AftStatements.AftStatements.STATUS_MAP["avilable_transfers"] = str(
+ binascii.hexlify(bytearray(data[7:8]))
+ )
+ AftStatements.AftStatements.STATUS_MAP["host_cashout_status"] = str(
+ binascii.hexlify(bytearray(data[8:9]))
+ )
+ AftStatements.AftStatements.STATUS_MAP["AFT_status"] = str(
+ binascii.hexlify(bytearray(data[9:10]))
+ )
+ AftStatements.AftStatements.STATUS_MAP["max_buffer_index"] = str(
+ binascii.hexlify(bytearray(data[10:11]))
+ )
+ AftStatements.AftStatements.STATUS_MAP["current_cashable_amount"] = str(
+ binascii.hexlify(bytearray(data[11:16]))
+ )
+ AftStatements.AftStatements.STATUS_MAP["current_restricted_amount"] = str(
+ binascii.hexlify(bytearray(data[16:21]))
+ )
+ AftStatements.AftStatements.STATUS_MAP[
+ "current_non_restricted_amount"
+ ] = str(binascii.hexlify(bytearray(data[21:26])))
+ AftStatements.AftStatements.STATUS_MAP["restricted_expiration"] = str(
+ binascii.hexlify(bytearray(data[26:29]))
+ )
+ AftStatements.AftStatements.STATUS_MAP["restricted_pool_ID"] = str(
+ binascii.hexlify(bytearray(data[29:31]))
+ )
+
+ return AftStatements.AftStatements.get_non_empty_status_map()
+
+ return None
+
+ def aft_cancel_request(self):
+ cmd = [0x72, 0x01, 0x80]
+ self.aft_register()
+ response = None
+ data = self._send_command(cmd, crc_need=True, size=90)
+ if data:
+ a = int(binascii.hexlify(bytearray(data[26:27])), 16)
+ response = {
+ "Length": int(binascii.hexlify(bytearray(data[26:27])), 16),
+ "Transfer status": AftTransferStatus.AftTransferStatus.get_status(
+ binascii.hexlify(bytearray(data[3:4]))
+ ),
+ "Receipt status": AftReceiptStatus.AftReceiptStatus.get_status(
+ binascii.hexlify(bytearray(data[4:5]))
+ ),
+ "Transfer type": AftTransferType.AftTransferType.get_status(
+ binascii.hexlify(bytearray(data[5:6]))
+ ),
+ "Cashable amount": int(binascii.hexlify(bytearray(data[6:11])))
+ * self.denom,
+ "Restricted amount": int(binascii.hexlify(bytearray(data[11:16])))
+ * self.denom,
+ "Nonrestricted amount": int(binascii.hexlify(bytearray(data[16:21])))
+ * self.denom,
+ "Transfer flags": binascii.hexlify(bytearray(data[21:22])),
+ "Asset number": binascii.hexlify(bytearray(data[22:26])),
+ "Transaction ID length": binascii.hexlify(bytearray(data[26:27])),
+ "Transaction ID": binascii.hexlify(bytearray(data[27: (27 + a)])),
+ }
+ try:
+ self.aft_unregister()
+ except:
+ self.log.warning("AFT UNREGISTER ERROR")
+
+ if response["Transaction ID"] == hex(self.transaction)[2:-1]:
+ return response
+
+ return False
+
+ def aft_receipt_data(self):
+ # TODO: 75
+ return NotImplemented
+
+ def aft_set_custom_ticket_data(self):
+ # TODO: 76
+ return NotImplemented
+
+ def extended_validation_status(
+ self,
+ control_mask=[0, 0],
+ status_bits=[0, 0],
+ cashable_ticket_receipt_exp=0,
+ restricted_ticket_exp=0
+ ):
+ # 7B
+ cmd = [
+ 0x7B,
+ 0x08,
+ control_mask,
+ status_bits,
+ self._bcd_coder_array(cashable_ticket_receipt_exp, 2),
+ self._bcd_coder_array(restricted_ticket_exp, 2),
+ ]
+
+ data = self._send_command(cmd, True, crc_need=True)
+ if data:
+ AftStatements.AftStatements.STATUS_MAP["asset_number"] = str(
+ binascii.hexlify(bytearray(data[2:6]))
+ )
+ AftStatements.AftStatements.STATUS_MAP["status_bits"] = str(
+ binascii.hexlify(bytearray(data[6:8]))
+ )
+ AftStatements.AftStatements.STATUS_MAP["cashable_ticket_receipt_exp"] = str(
+ binascii.hexlify(bytearray(data[8:10]))
+ )
+ AftStatements.AftStatements.STATUS_MAP["restricted_ticket_exp"] = str(
+ binascii.hexlify(bytearray(data[10:]))
+ )
+
+ return AftStatements.AftStatements.get_non_empty_status_map()
+
+ return None
+
+ def set_extended_ticket_data(self):
+ # TODO: 7C
+ return NotImplemented
+
+ def set_ticket_data(self):
+ # TODO: 7D
+ return NotImplemented
+
+ def current_date_time(self):
+ # 7E
+ cmd = [0x7E]
+ data = self._send_command(cmd, crc_need=False, size=11)
+ if data:
+ data = str(binascii.hexlify(bytearray(data[1:8])))
+ return datetime.datetime.strptime(data, "%m%d%Y%H%M%S")
+
+ return None
+
+ def receive_date_time(self, dates, times):
+ # 7F
+ cmd = [0x7F]
+ fmt_cmd = "" + dates.replace(".", "") + times.replace(":", "") + "00"
+ count = 0
+ for i in range(len(fmt_cmd) // 2):
+ cmd.append(int(fmt_cmd[count: count + 2], 16))
+ count += 2
+
+ if self._send_command(cmd, True, crc_need=True) == self.address:
+ return True
+
+ return False
+
+ @staticmethod
+ def receive_progressive_amount():
+ # TODO: 80
+ return NotImplemented
+
+ @staticmethod
+ def cumulative_progressive_wins():
+ # TODO: 83
+ return NotImplemented
+
+ @staticmethod
+ def progressive_win_amount():
+ # TODO: 84
+ return NotImplemented
+
+ @staticmethod
+ def sas_progressive_win_amount():
+ # TODO: 85
+ return NotImplemented
+
+ @staticmethod
+ def receive_multiple_progressive_levels():
+ # TODO: 86
+ return NotImplemented
+
+ @staticmethod
+ def multiple_sas_progressive_win_amounts():
+ # TODO: 87
+ return NotImplemented
+
+ def initiate_legacy_bonus_pay(self, money, tax="00", games=None, ):
+ # 8A
+ if not games:
+ for i in range(3):
+ try:
+ games = self.selected_game_number(in_hex=False)
+ except:
+ pass
+ if not games:
+ time.sleep(0.04)
else:
- cmd.append(reg_code)
- cmd.extend(self.bcd_coder_array(asset_number, 4))
- cmd.extend(self.bcd_coder_array(reg_key, 20))
- cmd.extend(self.bcd_coder_array(POS_ID, 4))
- cmd[1]=0x1D
- data=self.__send_command(cmd,True, crc_need=True)
- if(data<>None):
- print len(data)
- aft_statement['registration_status']=(binascii.hexlify((data[2:3])))
- aft_statement['asset_number']=bytearray(data[3:7])
- aft_statement['registration_key']=bytearray(data[7:27])
- aft_statement['POS_ID']=str(binascii.hexlify((data[27:])))
- return data[1]
- return ''
- def AFT_game_lock_and_status_request(self, lock_code=0x00, transfer_condition=0b00000000, lock_timeout=0):
- #74
- cmd=[0x74]
-
- cmd.append(lock_code)
- cmd.append(transfer_condition)
- cmd.extend(self.bcd_coder_array(lock_timeout, 2))
- #cmd.addend(0x23)
-
- data=self.__send_command(cmd,True, crc_need=True)
- if(data<>None):
- aft_statement['asset_number']=str(binascii.hexlify(bytearray(data[2:6])))
- aft_statement['game_lock_status']=str(binascii.hexlify(bytearray(data[6:7])))
- aft_statement['avilable_transfers']=str(binascii.hexlify(bytearray(data[7:8])))
- aft_statement['host_cashout_status']=str(binascii.hexlify(bytearray(data[8:9])))
- aft_statement['AFT_status']=str(binascii.hexlify(bytearray(data[9:10])))
- aft_statement['max_buffer_index']=str(binascii.hexlify(bytearray(data[10:11])))
- aft_statement['current_cashable_amount']=str(binascii.hexlify(bytearray(data[11:16])))
- aft_statement['current_restricted_amount']=str(binascii.hexlify(bytearray(data[16:21])))
- aft_statement['current_non_restricted_amount']=str(binascii.hexlify(bytearray(data[21:26])))
- aft_statement['restricted_expiration']=str(binascii.hexlify(bytearray(data[26:29])))
- aft_statement['restricted_pool_ID']=str(binascii.hexlify(bytearray(data[29:31])))
-
- return data[1]
- return ''
- def set_AFT_reciept_data(self):
- #75
- return
- def set_custom_AFT_ticket_data(self):
- #76
- return
- def exnended_validation_status(self, control_mask=[0,0], status_bits=[0,0], cashable_ticket_reciept_exp=0, restricted_ticket_exp=0):
- #7B
- cmd=[0x7B, 0x08]
-
- cmd.extend(control_mask)
- cmd.extend(status_bits)
- cmd.extend(self.bcd_coder_array(cashable_ticket_reciept_exp, 2))
- cmd.extend(self.bcd_coder_array(restricted_ticket_exp, 2))
-
- #cmd.addend(0x23)
-
-
- data=self.__send_command(cmd,True, crc_need=True)
- if(data<>None):
- aft_statement['asset_number']=str(binascii.hexlify(bytearray(data[2:6])))
- aft_statement['status_bits']=str(binascii.hexlify(bytearray(data[6:8])))
- aft_statement['cashable_ticket_reciept_exp']=str(binascii.hexlify(bytearray(data[8:10])))
- aft_statement['restricted_ticket_exp']=str(binascii.hexlify(bytearray(data[10:])))
-
- return data[1]
- return ''
- def set_extended_ticket_data(self):
- #7C
- return
- def set_ticket_data(self):
- #7D
- return
- def current_date_time(self):
- #7E
- return
- def recieve_date_time(self):
- #7F
- return
- def recieve_progressive_amount(self):
- #80
- return
- def cumulative_progressive_wins(self):
- #83
- return
- def progressive_win_amount(self):
- #84
- return
- def SAS_progressive_win_amount(self):
- #85
- return
- def recieve_multiple_progressive_levels(self):
- #86
- return
- def multiple_SAS_progresive_win_amounts(self):
- #87
- return
- def initiate_legacy_bonus_pay(self):
- #8A
- return
- def initiate_multiplied_jackpot_mode(self):
- #8B
- return
- def enter_exit_tournament_mode(self):
- #8C
- return
- def card_info(self):
- #8E
- return
- def physical_reel_stop_info(self):
- #8F
- return
- def legacy_bonus_win_info(self):
- #90
- return
- def remote_handpay_reset(self):
- #94
- return
- def tournament_games_played(self):
- #95
- return
- def tournament_games_won(self):
- #96
- return
- def tournament_credits_wagered(self):
- #97
- return
- def tournament_credits_won(self):
- #98
- return
- def meters_95_98(self):
- #99
- return
- def legacy_bonus_meters(self):
- #9A
- return
- def enabled_features(self, game_nimber=0):
- #A0
- cmd=[0xA0]
-
- cmd.extend(self.bcd_coder_array(game_nimber, 2))
-
- data=self.__send_command(cmd,True, crc_need=True)
- if(data<>None):
- aft_statement['game_number']=str(binascii.hexlify(bytearray(data[1:3])))
- aft_statement['features_1']=data[3]
- aft_statement['features_2']=data[4]
- aft_statement['features_3']=data[5]
-
- game_features['game_number']=aft_statement.get('game_number')
- if (data[3]&0b00000001):
- game_features['jackpot_multiplier']=1
- else:
- game_features['jackpot_multiplier']=0
-
- if (data[3]&0b00000010):
- game_features['AFT_bonus_avards']=1
- else:
- game_features['AFT_bonus_avards']=0
- if (data[3]&0b00000100):
- game_features['legacy_bonus_awards']=1
- else:
- game_features['legacy_bonus_awards']=0
- if (data[3]&0b00001000):
- game_features['tournament']=1
- else:
- game_features['tournament']=0
- if (data[3]&0b00010000):
- game_features['validation_extensions']=1
- else:
- game_features['validation_extensions']=0
-
- game_features['validation_style']=data[3]&0b01100000>>5
-
- if (data[3]&0b10000000):
- game_features['ticket_redemption']=1
- else:
- game_features['ticket_redemption']=0
-
-
-
-
- return data[1]
- return ''
- def cashout_limit(self):
- #A4
- return
- def enable_jackpot_handpay_reset_method(self):
- #A8
- return
- def en_dis_game_auto_rebet(self):
- #AA
- return
- def extended_meters_game_alt(self,n=1):
- #AF
- return
- def multi_denom_preamble(self):
- #B0
- return
- def current_player_denomination(self):
- #B1
- return
- def enabled_player_denominations(self):
- #B2
- return
- def token_denomination(self):
- #B3
- return
- def wager_category_info(self):
- #B4
- return
- def extended_game_info(self,n=1):
- #B5
- return
- def event_response_to_long_poll(self):
- #FF
- return
- def bcd_coder_array(self, value=0, lenght=4):
- return self.int_to_bcd(value, lenght)
-
-
- def int_to_bcd(self, number=0, lenght=5):
- n=0
- m=0
- bval=0
- p=lenght-1
- result=[]
- for i in range(0, lenght):
- result.extend([0x00])
- while (p>=0):
- if (number!=0):
- digit=number%10
- number=number/10
- m=m+1
- else:
- digit=0
- if (n&1):
- bval |= digit<<4
- result[p]=bval
- p=p-1
- bval=0
- else:
- bval=digit
- n=n+1
- return result
-
-
-if __name__ =="__main__":
- print "OK"
- sas=sas('/dev/ttyS3')
- #print ( bcd.bcd_to_int(100))
- #print int(bcd.int_to_bcd(0x1467))
- #a=sas.bcd_coder_array(value=100, lenght=10)
- #print ((a))
- print sas.int_to_bcd(1234567890365421,8)
- #sas.start()
- #sas.ROM_signature_verification()
- #sas.total_cancelled_credits()
- #sas.send_meters_10_15()
- #sas.total_bet_meter()
- #sas.total_win_meter()
- #sas.total_in_meter()
- #sas.total_jackpot_meter()
-
-
-
- #sas.SAS_version_gaming_machine_serial_ID()
-
-## sas.start( )
-##
-##
-##
-##
- # print sas.events_poll( timeout=1)
-##
-##
-##
-## sas.shutdown( )
-##
-## sas.startup( )
-##
-## sas.sound_off( )
-##
-## sas.sound_on( )
-##
-## sas.reel_spin_game_sounds_disabled( )
-##
-## sas.enable_bill_acceptor( )
-##
-## sas.disable_bill_acceptor( )
-##
-## sas.configure_bill_denom( , bill_denom=[0xFF,0xFF,0xFF], action_flag=[0xff])
-##
-## sas.en_dis_game( , game_number=[1], en_dis=[1])
-##
-## sas.enter_maintenance_mode( )
-##
-## sas.exit_maintanance_mode( )
-##
-## sas.en_dis_rt_event_reporting( )
-##
-## sas.send_meters_10_15( )
-##
-## sas.total_cancelled_credits( )
-##
-## sas.total_bet_meter( )
-##
-## sas.total_win_meter( )
-##
-## sas.total_in_meter( )
-##
-## sas.total_jackpot_meter( )
-##
-
-
-
-# sas.games_played_meter( )
-##
- # sas.games_won_meter( )
-##
-# sas.games_lost_meter( )
-##
- # sas.games_powerup_door_opened( )
-##
- # sas.meters_11_15( )
-##
- # sas.current_credits( )
-##
- # sas.handpay_info( )
-##
- # sas.meters( )
-##
- # sas.total_bill_meters( )
-##
-# sas.gaming_machine_ID( )
-##
-# sas.total_dollar_value_of_bills_meter( )
-##
- # sas.ROM_signature_verification( ) # test usage?
-##
- # sas.true_coin_in( )
-##
-# sas.true_coin_out( )
-##
-# sas.curr_hopper_level( )
-##
- # sas.total_hand_paid_cancelled_credit( ) #need for maid
-##
-# sas.delay_game( delay=1) # need to test
-##
- # sas.selected_meters_for_game( ) #need to maid
-##
-# sas.send_1_bills_in_meters( )
-##
- # sas.send_2_bills_in_meters( )
-##
- # sas.send_5_bills_in_meters( )
-##
-# sas.send_10_bills_in_meters( )
-##
- # sas.send_20_bills_in_meters( )
-##
-# sas.send_50_bills_in_meters( )
-##
- # sas.send_100_bills_in_meters( )
-##
- # sas.send_500_bills_in_meters( )
-##
- # sas.send_1000_bills_in_meters( )
-##
- # sas.send_200_bills_in_meters( )
-##
-# sas.send_25_bills_in_meters( )
-##
- # sas.send_2000_bills_in_meters( )
-##
- # sas.cash_out_ticket_info( )
-##
-# sas.send_2500_bills_in_meters( )
-##
-# sas.send_5000_bills_in_meters( )
-##
-# sas.send_10000_bills_in_meters( )
-##
-# sas.send_20000_bills_in_meters( )
-##
- # sas.send_25000_bills_in_meters( )
-##
-# sas.send_50000_bills_in_meters( )
-##
-# sas.send_100000_bills_in_meters( )
-##
-# sas.send_250_bills_in_meters( )
-##
-# sas.credit_amount_of_all_bills_accepted( )
-##
- # sas.coin_amount_accepted_from_external_coin_acceptor( )
-##
- # sas.last_accepted_bill_info( )
-##
-# sas.number_of_bills_currently_in_stacker( )
-##
- # sas.total_credit_amount_of_all_bills_in_stacker( )
-##
- # sas.set_secure_enhanced_validation_ID( MachineID=b'\x01\x00\x01', seq_num=b'\x00\x01\x00') # read manual
-##
- # sas.enhanced_validation_information( curr_validation_info=0x00) # asc Lena (append)
-##
-# sas.current_hopper_status( )
-##
- # sas.validation_meters( type_of_validation=0x01)
-##
-## sas.total_number_of_games_impimented( )
-##
-## sas.game_meters( , n=1)
-##
-## sas.game_configuration( , n=1)
-##
-## sas.SAS_version_gaming_machine_serial_ID( )
-##
-## sas.selected_game_number( )
-##
-## sas.enabled_game_numbers( )
-##
-## sas.pending_cashout_info( )
-##
- # sas.validation_number( 11, 123456)
-##
-## sas.autentification_info( )
-##
-## sas.extended_meters_for_game( , n=1)
-## #6F
-##
-## sas.ticket_validation_data( )
-## #70
-##
- # sas.redeem_ticket( )
-## #71
-##
- # sas.AFT_transfer_funds(0x00, 0,0x00, 10000, 0, 0, 0, b'\xea\x03\x00\x00', b'\x67\x68\x6a\x68\x62\x76\x79\x64\x6a\x6c\x66\x79\x76\x6d\x6b\x64\x79\x79\x64\x72', transaction_ID_lenght=0x01, transaction_ID='1', expiration=b'\x03\x25\x20\x18', pool_ID=0x1010, reciept_data='fgh', lock_timeout=1)
-#transfer_code=0x00, transaction_index=0x00, transfer_type=0x00, cashable_amount=0, restricted_amount=0, non_restricted_amount=0, transfer_flags=0x00, asset_number=b'\x00\x00\x00\x00\x00', registration_key=b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', transaction_ID_lenght=0x00, transaction_ID='', expiration=b'\x00\x00\x00\x00', pool_ID=0, reciept_data='', lock_timeout=0):
-
-## #72
-##
-## sas.AFT_register_gaming_machine( )
-## #73
-##
-## sas.AFT_game_lock_and_status_request( )
-## #74
- # sas.AFT_register_gaming_machine(reg_code=0x01, asset_number=b'\xea\x03\x00\x00', reg_key='ghjhbvydjlfyvmkdyydr', POS_ID=b'\x03\x04\x05\x06')
-##
-## sas.set_AFT_reciept_data( )
-## #75
-##
-## sas.set_custom_AFT_ticket_data( )
-## #76
-##
- # sas.exnended_validation_status(control_mask=[0b00000011,0b00000000], status_bits=[0b00000011,0b00000000], cashable_ticket_reciept_exp=0, restricted_ticket_exp=0)
-
-## #7B
-##
-## sas.set_extended_ticket_data( )
-## #7C
-##
-## sas.set_ticket_data( )
-## #7D
-##
-## sas.current_date_time( )
-## #7E
-##
-## sas.recieve_date_time( )
-## #7F
-##
-## sas.recieve_progressive_amount( )
-## #80
-##
-## sas.cumulative_progressive_wins( )
-## #83
-##
-## sas.progressive_win_amount( )
-## #84
-##
-## sas.SAS_progressive_win_amount( )
-## #85
-##
-## sas.recieve_multiple_progressive_levels( )
-## #86
-##
-## sas.multiple_SAS_progresive_win_amounts( )
-## #87
-##
-## sas.initiate_legacy_bonus_pay( )
-## #8A
-##
-## sas.initiate_multiplied_jackpot_mode( )
-## #8B
-##
-## sas.enter_exit_tournament_mode( )
-## #8C
-##
-## sas.card_info( )
-## #8E
-##
-## sas.physical_reel_stop_info( )
-## #8F
-##
-## sas.legacy_bonus_win_info( )
-## #90
-##
-## sas.remote_handpay_reset( )
-## #94
-##
-## sas.tournament_games_played( )
-## #95
-##
-## sas.tournament_games_won( )
-## #96
-##
-## sas.tournament_credits_wagered( )
-## #97
-##
-## sas.tournament_credits_won( )
-## #98
-##
-## sas.meters_95_98( )
-## #99
-##
-## sas.legacy_bonus_meters( )
-## #9A
-##
-## sas.enabled_features( )
-## #A0
-##
-## sas.cashout_limit( )
-## #A4
-##
-## sas.enable_jackpot_handpay_reset_method( )
-## #A8
-##
-## sas.en_dis_game_auto_rebet( )
-## #AA
-##
-## sas.extended_meters_game_alt( ,n=1)
-## #AF
-##
-## sas.multi_denom_preamble( )
-## #B0
-##
-## sas.current_player_denomination( )
-## #B1
-##
-## sas.enabled_player_denominations( )
-## #B2
-##
-## sas.token_denomination( )
-## #B3
-##
-## sas.wager_category_info( )
-## #B4
-##
-## sas.extended_game_info( ,n=1)
-## #B5
-##
-## sas.event_response_to_long_poll( )
-## #FF
-
-
-
-
-##
-## for keys, values in aft_statement.items():
-## print(keys)
-## print(values)
-
- #sas.enhanced_validation_information(0)
-
- #sas.set_secure_enhanced_validation_ID( MachineID=[0x01,0x01,0x01], seq_num=[0x00,0x00,0x01])
-##
- while True:
- state= binascii.hexlify(bytearray(sas.events_poll()))
- print state
- if (state=='57'):
- sas.pending_cashout_info()
- sas.validation_number( validationID=1, valid_number=1234567890365421)
-
- #sas.cash_out_ticket_info()
-
- if (state=='67'): #cashin
- sas.ticket_validation_data()
- sas.redeem_ticket( transfer_code=0, transfer_amount=10000, parsing_code=0, validation_data=1234567891234567, rescticted_expiration=3, pool_ID=0)
- time.sleep(.3)
- sas.redeem_ticket( transfer_code=0xff, transfer_amount=10000, parsing_code=0, validation_data=1234567891234567, rescticted_expiration=3, pool_ID=0)
-
- #71
-
- time.sleep(1)
-
- for keys, values in tito_statement.items():
- print(keys)
- print(values)
-
-
-
+ break
+
+ if not games or games <= 0:
+ return None
+
+ t_cmd = str(int(round(money / self.denom, 2)))
+ t_cmd = ("0" * (8 - len(t_cmd)) + t_cmd) + tax
+
+ cmd = [0x8A]
+ count = 0
+ for i in range(len(t_cmd) // 2):
+ cmd.append(int(t_cmd[count: count + 2], 16))
+ count += 2
+
+ if self._send_command(cmd, True, crc_need=True) == self.address:
+ return True
+
+ return False
+
+ @staticmethod
+ def initiate_multiplied_jackpot_mode():
+ # TODO: 8B
+ return NotImplemented
+
+ @staticmethod
+ def enter_exit_tournament_mode():
+ # TODO: 8C
+ return NotImplemented
+
+ @staticmethod
+ def card_info():
+ # TODO: 8E
+ return NotImplemented
+
+ @staticmethod
+ def physical_reel_stop_info():
+ # TODO: 8F
+ return NotImplemented
+
+ @staticmethod
+ def legacy_bonus_win_info():
+ # TODO: 90
+ return NotImplemented
+
+ def remote_handpay_reset(self, ):
+ # 94
+ cmd = [0x94]
+ if self._send_command(cmd, True, crc_need=True) == self.address:
+ return True
+
+ return False
+
+ @staticmethod
+ def tournament_games_played():
+ # TODO: 95
+ return NotImplemented
+
+ @staticmethod
+ def tournament_games_won():
+ # TODO: 96
+ return NotImplemented
+
+ @staticmethod
+ def tournament_credits_wagered():
+ # TODO: 97
+ return NotImplemented
+
+ @staticmethod
+ def tournament_credits_won():
+ # TODO: 98
+ return NotImplemented
+
+ @staticmethod
+ def meters_95_98():
+ # TODO: 99
+ return NotImplemented
+
+ def legacy_bonus_meters(self, denom=True, n=0):
+ # 9A
+ cmd = [0x9A, ((n >> 8) & 0xFF), (n & 0xFF)]
+ data = self._send_command(cmd, crc_need=True, size=18)
+ if data:
+ if not denom:
+ Meters.Meters.STATUS_MAP["game number"] = int(
+ binascii.hexlify(bytearray(data[2:3]))
+ )
+ Meters.Meters.STATUS_MAP["deductible"] = int(
+ binascii.hexlify(bytearray(data[3:7]))
+ )
+ Meters.Meters.STATUS_MAP["non-deductible"] = int(
+ binascii.hexlify(bytearray(data[7:11]))
+ )
+ Meters.Meters.STATUS_MAP["wager match"] = int(
+ binascii.hexlify(bytearray(data[11:15]))
+ )
+ else:
+ Meters.Meters.STATUS_MAP["game number"] = int(
+ binascii.hexlify(bytearray(data[2:3]))
+ )
+ Meters.Meters.STATUS_MAP["deductible"] = round(
+ int(binascii.hexlify(bytearray(data[3:7]))) * self.denom, 2
+ )
+ Meters.Meters.STATUS_MAP["non-deductible"] = round(
+ int(binascii.hexlify(bytearray(data[7:11]))) * self.denom, 2
+ )
+ Meters.Meters.STATUS_MAP["wager match"] = round(
+ int(binascii.hexlify(bytearray(data[11:15]))) * self.denom, 2
+ )
+ return Meters.Meters.get_non_empty_status_map()
+
+ return None
+
+ def toggle_autorebet(self, val=True):
+ cmd = [0xAA]
+ if not val:
+ # AA00
+ cmd.append(0x00)
+ else:
+ # AA01
+ cmd.append(0x01)
+
+ if self._send_command(cmd, True, crc_need=True) == self.address:
+ return True
+
+ return False
+
+ def enabled_features(self, game_number=0):
+ # A0
+ # FIXME: This makes no sense
+ # Basically tells which feature a selected games has. - Antonio
+
+ cmd = [0xA0, self._bcd_coder_array(game_number, 2)]
+ data = self._send_command(cmd, True, crc_need=True)
+ if data:
+ AftStatements.AftStatements.STATUS_MAP["game_number"] = str(
+ binascii.hexlify(bytearray(data[1:3]))
+ )
+ AftStatements.AftStatements.STATUS_MAP["features_1"] = data[3]
+ AftStatements.AftStatements.STATUS_MAP["features_2"] = data[4]
+ AftStatements.AftStatements.STATUS_MAP["features_3"] = data[5]
+ GameFeatures.GameFeatures.STATUS_MAP[
+ "game_number"
+ ] = AftStatements.AftStatements.get_status("game_number")
+ if data[3] & 0b00000001:
+ GameFeatures.GameFeatures.STATUS_MAP["jackpot_multiplier"] = 1
+ else:
+ GameFeatures.GameFeatures.STATUS_MAP["jackpot_multiplier"] = 0
+
+ if data[3] & 0b00000010:
+ GameFeatures.GameFeatures.STATUS_MAP["AFT_bonus_awards"] = 1
+ else:
+ GameFeatures.GameFeatures.STATUS_MAP["AFT_bonus_awards"] = 0
+
+ if data[3] & 0b00000100:
+ GameFeatures.GameFeatures.STATUS_MAP["legacy_bonus_awards"] = 1
+ else:
+ GameFeatures.GameFeatures.STATUS_MAP["legacy_bonus_awards"] = 0
+
+ if data[3] & 0b00001000:
+ GameFeatures.GameFeatures.STATUS_MAP["tournament"] = 1
+ else:
+ GameFeatures.GameFeatures.STATUS_MAP["tournament"] = 0
+
+ if data[3] & 0b00010000:
+ GameFeatures.GameFeatures.STATUS_MAP["validation_extensions"] = 1
+ else:
+ GameFeatures.GameFeatures.STATUS_MAP["validation_extensions"] = 0
+
+ GameFeatures.GameFeatures.STATUS_MAP["validation_style"] = (
+ data[3] & 0b01100000 >> 5
+ )
+
+ if data[3] & 0b10000000:
+ GameFeatures.GameFeatures.STATUS_MAP["ticket_redemption"] = 1
+ else:
+ GameFeatures.GameFeatures.STATUS_MAP["ticket_redemption"] = 0
+
+ return GameFeatures.GameFeatures.get_non_empty_status_map()
+
+ return None
+
+ @staticmethod
+ def cashout_limit():
+ # TODO: A4
+ return NotImplemented
+
+ @staticmethod
+ def enable_jackpot_handpay_reset_method():
+ # TODO: A8
+ return NotImplemented
+
+ @staticmethod
+ def extended_meters_game_alt():
+ # TODO: AF
+ return NotImplemented
+
+ @staticmethod
+ def multi_denom_preamble():
+ # TODO: B0
+ return NotImplemented
+
+ @staticmethod
+ def current_player_denomination():
+ # TODO: B1
+ return NotImplemented
+
+ @staticmethod
+ def enabled_player_denominations():
+ # TODO: B2
+ return NotImplemented
+
+ @staticmethod
+ def token_denomination():
+ # TODO: B3
+ return NotImplemented
+
+ @staticmethod
+ def wager_category_info():
+ # TODO: B4
+ return NotImplemented
+
+ @staticmethod
+ def extended_game_info(n=1):
+ # TODO: B5
+ return NotImplemented
+
+ @staticmethod
+ def event_response_to_long_poll():
+ # TODO: FF
+ return NotImplemented
+
+ def _bcd_coder_array(self, value=0, length=4):
+ return self._int_to_bcd(value, length)
+
+ @staticmethod
+ def _int_to_bcd(number=0, length=5, ):
+ n = m = bval = 0
+ p = length - 1
+ result = []
+ for i in range(0, length):
+ result.extend([0x00])
+
+ while p >= 0:
+ if number != 0:
+ digit = number % 10
+ number = number / 10
+ m = m + 1
+ else:
+ digit = 0
+
+ if n & 1:
+ bval |= digit << 4
+ result[p] = bval
+ p = p - 1
+ bval = 0
+ else:
+ bval = digit
+
+ n = n + 1
+
+ return result
diff --git a/test_sas.py b/test_sas.py
deleted file mode 100644
index 66ceefd..0000000
--- a/test_sas.py
+++ /dev/null
@@ -1,110 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf8 -*-
-import sas as main_sas
-import time
-import binascii
-import random
-import string
-
-sas=main_sas.sas('/dev/ttyS3')
-sas.start()
-#print sas.int_to_bcd(1234567890365421,8)
-### EFT to machine
-##for i in range(1,6):
-## a= sas.eft_load_cashable_credits( amount=10, count=i, status=0)
-##
-## print main_sas.aft_statement.get('eft_status')
-## print main_sas.aft_statement.get('eft_transfer_counter')
-## sas.eft_load_cashable_credits( amount=10, count=i+1, status=0)
-##
-## sas.eft_load_cashable_credits( amount=10, count=i+1, status=1)
-## sas.eft_avilable_transfers()
-## time.sleep(5)
-
-#time.sleep(3)
-##
-
-
-#sas.startup()
-
-
-########## AFT
-###register
-##sas.AFT_register_gaming_machine( reg_code=0x00,
-## asset_number=200,
-## reg_key=0x1234573657236575,
-## POS_ID=0xffffffff)
-##
-##sas.AFT_register_gaming_machine( reg_code=0x01,
-## asset_number=200,
-## reg_key=0x1234573657236575,
-## POS_ID=0xffffffff)
-###lock
-##sas.AFT_game_lock_and_status_request( lock_code=0x80,
-## transfer_condition=0b00000000,
-## lock_timeout=1)
-##
-###transfer
-##sas.AFT_transfer_funds( transfer_code=0x00,
-## transaction_index=0x00,
-## transfer_type=0x00,
-## cashable_amount=10000,
-## restricted_amount=0,
-## non_restricted_amount=0,
-## transfer_flags=0b00000011,
-## asset_number=b'\xea\x03\x00\x00' ,
-## registration_key=0x1234573657236575,
-## transaction_ID_lenght=0x01,
-## transaction_ID='1',
-## expiration=1,
-## pool_ID=1,
-## reciept_data=b'\x00'+'hey',
-## lock_timeout=1)
-
-
-##for keys, values in main_sas.aft_statement.items():
-## print(keys)
-## print(values)
-##
-##
-while True:
-
- sas.send_meters_10_15()
- sas.current_credits()
- sas.eft_avilable_transfers()
- sas.AFT_game_lock_and_status_request( lock_code=0xff)
- state= binascii.hexlify(bytearray(sas.events_poll()))
- print state
- if (state=='51'): #cashout
- sas.eft_send_promo_to_machine( amount=25, status=a)
-
-
-
-############cashout-cashin
-
- if (state=='57'): #cashout
- sas.pending_cashout_info()
- time.sleep(.5)
- sas.validation_number( validationID=1, valid_number=int(''.join([random.choice(string.digits) for n in xrange(16)])))
-
- sas.send_meters_10_15()
- print main_sas.meters.get('total_in_meter')
- print main_sas.meters.get('total_out_meter')
- sas.current_credits()
- print main_sas.meters.get('current_credits')
-
- #sas.cash_out_ticket_info()
-
- if (state=='67'): #cashin
- sas.ticket_validation_data()
- sas.redeem_ticket( transfer_code=0, transfer_amount=10000, parsing_code=0, validation_data=1234567891234567, rescticted_expiration=3, pool_ID=0)
- time.sleep(.3)
- sas.redeem_ticket( transfer_code=0xff, transfer_amount=10000, parsing_code=0, validation_data=1234567891234567, rescticted_expiration=3, pool_ID=0)
-
- #71
-
- time.sleep(1)
-
-for keys, values in tito_statement.items():
- print(keys)
- print(values)
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..25a50f8
--- /dev/null
+++ b/tests/__init__.py
@@ -0,0 +1,20 @@
+import subprocess
+import sys
+from pathlib import Path
+import importlib
+
+
+def get_required_packages():
+ project_root = Path(__file__).parent.parent
+ main_module_path = project_root
+
+ with open(main_module_path / "sas.py", "r") as file:
+ content = file.read()
+
+ # Extract import statements from the file content
+ imports = [
+ line.split()[1]
+ for line in content.splitlines()
+ if line.startswith("import") or line.startswith("from")
+ ]
+ return imports
diff --git a/tests/test_sas.py b/tests/test_sas.py
new file mode 100644
index 0000000..e11b5c8
--- /dev/null
+++ b/tests/test_sas.py
@@ -0,0 +1,29 @@
+import pytest
+from unittest.mock import MagicMock, patch
+
+from sas import Sas
+from error_handler import *
+
+
+@pytest.fixture
+def sas_instance():
+ with patch("serial.Serial"), patch("time.sleep"):
+ sas = Sas(port="/dev/ttyUSB0")
+ yield sas
+
+
+@patch("serial.Serial")
+def test_start_method(mock_serial):
+ mock_serial_instance = mock_serial.return_value
+ mock_serial_instance.read.return_value = b"\x01"
+
+ with patch.object(mock_serial_instance, "isOpen", return_value=False):
+ result = mock_serial_instance.start()
+
+ assert result is not None
+ assert mock_serial_instance.isOpen.called_once()
+ assert mock_serial_instance.open.called_once()
+ assert mock_serial_instance.flushOutput.called_once()
+ assert mock_serial_instance.flushInput.called_once()
+ assert mock_serial_instance.read.called_once_with(1)
+
diff --git a/utils/Crc.py b/utils/Crc.py
new file mode 100644
index 0000000..adc4c51
--- /dev/null
+++ b/utils/Crc.py
@@ -0,0 +1,79 @@
+from enum import Enum
+from ctypes import c_ushort
+from error_handler import NoSasConnection, BadCRC
+
+MAGIC_SEED = 0x8408
+table = []
+
+
+class Endianness(Enum):
+ LITTLE_ENDIAN = 0
+ BIG_ENDIAN = 1
+
+
+for i in range(0, 256):
+ val = c_ushort(i).value
+ for j in range(0, 8):
+ val = c_ushort(val >> 1).value ^ MAGIC_SEED if val & 0x0001 else c_ushort(val >> 1).value
+ table.append(hex(val))
+
+
+def calculate(payload=None, init=0, sigbit=Endianness.LITTLE_ENDIAN):
+ _crc = init
+
+ for c in payload:
+ q = _crc ^ c
+ _crc = c_ushort(_crc >> 8).value ^ int(table[(q & 0x00ff)], 0)
+
+ if sigbit == Endianness.BIG_ENDIAN:
+ _crc = (_crc & 0x00ff) << 8 | (_crc & 0xff00) >> 8
+ else:
+ _crc = (_crc & 0xff00) >> 8 | (_crc & 0x00ff) << 8
+
+ return [((_crc >> 8) & 0xFF), (_crc & 0xFF)]
+
+def validate(check=None, init=0, sigbit=Endianness.LITTLE_ENDIAN):
+ """Function in charge of the CRC Check"""
+ if check == "":
+ raise NoSasConnection
+
+ rcvd_crc = [int.from_bytes(check[-2:-1]), int.from_bytes(check[-1:])]
+ my_crc = calculate(check[0:-2], init=init, sigbit=sigbit)
+
+ if rcvd_crc != my_crc:
+ raise BadCRC(hex(check))
+ else:
+ return check[1:-2]
+
+
+''' Tableless algo in Python and Rust
+def calculate(payload: bytes, init=0, sigbit=Endianness.LITTLE_ENDIAN):
+ crc, y = init, 0
+
+ for byte in payload:
+ x = c_ushort(byte)
+ y = (crc ^ int(x)) & 0o17
+ crc = (crc >> 8) ^ (y * MAGIC_SEED)
+ y = (crc ^ (x >> 8)) & 0o17
+ crc = (crc >> 8) ^ (y * MAGIC_SEED)
+
+ if sigbit == Endianness.BIG_ENDIAN:
+ return crc & 0xFF, (crc >> 8) & 0xFF
+
+ return (crc >> 8) & 0xFF, crc & 0xFF
+
+fn crc16(msg: &[u8]) -> u16 {
+ let mut crc: u16 = 0;
+ let (mut c, mut q): (u16, u16);
+
+ for byte in msg.iter() {
+ c = *byte as u16;
+ q = (crc ^ c) & 0o17;
+ crc = (crc >> 4) ^ (q * 0o10201);
+ q = (crc ^ (c >> 4)) & 0o17;
+ crc = (crc >> 4) ^ (q * 0o10201);
+ }
+
+ crc
+}
+'''
\ No newline at end of file
diff --git a/utils/Decorators.py b/utils/Decorators.py
new file mode 100644
index 0000000..d22f884
--- /dev/null
+++ b/utils/Decorators.py
@@ -0,0 +1,78 @@
+import functools
+import inspect
+import warnings
+
+string_types = (type(b''), type(u''))
+
+
+def deprecated(reason):
+ """
+ This is a decorator which can be used to mark functions
+ as deprecated. It will result in a warning being emitted
+ when the function is used.
+ """
+
+ if isinstance(reason, string_types):
+
+ # The @deprecated is used with a 'reason'.
+ #
+ # .. code-block:: python
+ #
+ # @deprecated("please, use another function")
+ # def old_function(x, y):
+ # pass
+
+ def decorator(func1):
+
+ if inspect.isclass(func1):
+ fmt1 = "Call to deprecated class {name} ({reason})."
+ else:
+ fmt1 = "Call to deprecated function {name} ({reason})."
+
+ @functools.wraps(func1)
+ def new_func1(*args, **kwargs):
+ warnings.simplefilter('always', DeprecationWarning)
+ warnings.warn(
+ fmt1.format(name=func1.__name__, reason=reason),
+ category=DeprecationWarning,
+ stacklevel=2
+ )
+ warnings.simplefilter('default', DeprecationWarning)
+ return func1(*args, **kwargs)
+
+ return new_func1
+
+ return decorator
+
+ elif inspect.isclass(reason) or inspect.isfunction(reason):
+
+ # The @deprecated is used without any 'reason'.
+ #
+ # .. code-block:: python
+ #
+ # @deprecated
+ # def old_function(x, y):
+ # pass
+
+ func2 = reason
+
+ if inspect.isclass(func2):
+ fmt2 = "Call to deprecated class {name}."
+ else:
+ fmt2 = "Call to deprecated function {name}."
+
+ @functools.wraps(func2)
+ def new_func2(*args, **kwargs):
+ warnings.simplefilter('always', DeprecationWarning)
+ warnings.warn(
+ fmt2.format(name=func2.__name__),
+ category=DeprecationWarning,
+ stacklevel=2
+ )
+ warnings.simplefilter('default', DeprecationWarning)
+ return func2(*args, **kwargs)
+
+ return new_func2
+
+ else:
+ raise TypeError(repr(type(reason)))
\ No newline at end of file