We aim to automate the card sign in process for HEZ, and more!
This project is highly specific. It only solves one problem. I'm sure you know what I am talking about if you already know about our situation.
That's because it was wrong when the target program was developed.
- Python 3.8 or 3.10, it might work on other versions, but it's not tested.
- Python's Tkinter support (some installations don't come with one).
- Windows 7 (x64) (Unsupported on other platforms, untested on other versions). But anyway, why would you care about the server side's OS? There's no choice.
- The target program might update, breaking this program. But I've already warned you, this project is highly specific.
- The tested compiler is MinGW-w64/GCC 8.2.0 and 12.1.0, with mingw32-make.
- CMake version 3.23.2
- This project uses library Better SQLite3 Multiple Cipher. Tested version is v1.4.8.
- Nlohmann JSON, version 3.10.5
- The icon for the executable was provided by Aha soft.
- Boost C++ libraries, version 1.80.0.
Thanks for all the tools, libraries and resources the community provides so generously!
-
On the build machine, clone the source code into a directory.
-
Check and double check that your client can access the server.
-
Make sure that your editor supports UTF-8. Specifically, avoid using
notepad
to edit files because it might introduce strange BOMs, breaking the config file. -
On a terminal,
cd
into that directory. -
Create a
include/
directory containing headers of the external library. Place SQL headers ininclude/
and JSON headers ininclude/nlohmann
. Kindly putsqlite3mc_x64.dll
in the root directory of the project. -
Configure and compile using cmake. If your linker complains about spirit.rc.res is not an object, try explicitly telling cmake that you're using
windres
as the RC compiler. Also check boost path. Because Boost.asio is quite large a library, we recommend precompiling it to accelerate the build. However, this isn't strictly necessary. -
Create config files as shown below.
-
You will need to copy the following files into your removable disk:
cppser/spiritd.exe
man.json
, the server config file.- All the DLLs in the same directory as your
g++.exe
(or the compiler you use) sqlite3mc_x64.dll
andcppsper/libspirit.dll
-
Plug in your media into the server (You will need some techniques for this.)
-
Open task manager, kill
LockMouse.exe
and restartexplorer.exe
. -
Copy the files mentioned above into a folder.
-
Go to task scheduler, add a task that starts
spiritd.exe
2 minutes after system boot. -
Reboot to check.
-
When asked about the firewall, allow all access.
The file is named man.json
. A template looks like this:
{
"serv_port": 8303,
"gs_port": 10031,
"dbname": "D:/singin/localData.db",
"passwd": "123",
"url_stu_new": "http://127.0.0.1//Services/SmartBoard/SmartBoardLoadSingInStudentNew/json",
"intro": "Spirit version 3.1 is up!",
"watchdog_poll": 15,
"retry_wait": 3,
"keep_logs": 3,
"timeout": 5,
"auto_watchdog": true,
"simul_limit": 90,
"local_limit": 60
}
- serv_port: The port that we should use.
- gs_port: The port of the GS executable
- dbname: The path to the database.
- passwd: The password required to access the database.
- url_stu_new: The URL to post for student info.
- intro: The first line to appear in singer.log, customizable.
- watchdog_poll: The seconds watchdog waits before checking for lessons, quit, etc.
- retry_wait: If the previous attempt to auto sign in failed because of network or database error,
wait for
retry_wait
seconds before retrying. - keep_logs: The number of logs to keep before rotating over to an older one.
- timeout: The timeout in seconds for the request to
url_stu_new
. - auto_watchdog: Specifies the state of the watchdog when the program launches. If set to false,
watchdog will be paused. You can control the state with
dbgui
at any time. - simul_limit: Controls the threshold for web-based sign in attempts. The first lesson ending in less than
simul_limit
seconds but more thanlocal_limit
will be subjected to web-based sign in. - local_limit: Lessons ending in less than
local_limit
seconds will be resorted to local DB based sign in.
Note that the program requires simul_limit >= local_limit
, because local sign in is supposed to be a kind
of last resort.
This file is called cli.json
, with a template given below:
{
"port": 8303,
"defhost": "127.0.0.1",
"buffsize": 2048,
"defmachine": "BJ303",
"timeout": 10,
"yearbook": {
"sp": "spirit",
"dp": "deophius"
}
}
- port: The port that we use to communicate with the server.
- defhost: The default host to inquire for information in the GUI.
- buffsize: The size of the buffer into which the response will be received. This shouldn't be too small (<1024) and would preferentially be a power of 2.
- defmachine: The default machine ID in the GUI.
- timeout: Timeout before the GUI decides that the server is not responding.
- yearbook: An optional object holding the abbreviation mapping used by the quick find box.
For example, if the config looks exactly like the one shown above, you can type
sp
in the quick find box and press enter. If the namespirit
is in the list of absent people, it will automatically be selected. This object is optional! If not present, the quick find function will be disabled.
Press enter
to submit the machine name and host IP info.
If you want to use a non-default machine IP or machine name, you can press
tab
to switch to different input boxes.
- For the first 9 lessons that appear on the list, you can press
1
to9
to choose them.1
is for the one on the top of the list. - Press
enter
should have the same effect as pressing the OK button. - Press
p
to trigger "pause watchdog". - Press
r
to trigger "resume watchdog". - Press
g
to trigger "get latest news".
-
To quickly search through the list of absent people, use the quickfind input box. The box grabs the focus on default, so this is completely keyboard friendly.
You need to specify the abbreviation mapping in the config file. See the
cli.json
section for details. -
Different from the previous page, if you want to trigger the buttons with your keyboard, you need to use a alt key combination. See the in-program hints.
We currently use green, orange and red to indicate the status of our program.
- Green means that the last operation was successful.
- Orange means that a lengthy operation, usually a request to the server, is being performed.
- Red means that the last operation resulted in an error.
This is a simple, stateless, UDP-based protocol that uses JSON as the "mime type".
The client sends a request to the server. The request must include a command
param, with
additional data.
The server's response always contains a success
param, set to true
if operation succeeds and
false
otherwise. If success
is false
, then there will always be a what
param providing
a brief explanation of the error.
The names of the commands should be self-explaining.
Here we make a listing of the implemented commands and their syntax:
{"command": "report_absent", "sessid": 1}
-> {"success": true, "name": ["xxx", "xxx", ...]} on success
-> {"success": false, "what": "error description"} on failure
sessid
is a non-negative integer. The order of lessons is assigned according to the sequence they
appear in the database. On the client side, this can be determined from the response of today_info
.
The server implements range checks on sessid
.
{"command": "write_record", "name": ["xxx", "yyy"], "sessid":1}
-> {"success": true}
-> {"success": false, "what": "error description"}
name
is a list of Chinese names. sessid
is same as above.
The server catches all SQL errors and returns them as what
if database operations fail.
Because we use JSON as the "mime type", the name strings should be UTF-8 encoded.
{"command": "restart_gs"}
-> {"success": true}
-> {"success": false, "what": "error description"}
Restarts GS. If the GS machine is not up, we will inform you in the what string. If GS doesn't recognize our command, you will also be notified. In this case, maybe it's up to you to figure out the new command string to use.
{"command":"today_info", "machine": "NJ303"}
-> {"success": true, "end": ["07:20:00", "08:30:00"]}
-> {"success": false, "what":"Wrong machine", "machine":"BJ303"}
-> {"success": false, "what": "error description"}
Gets the lessons there are today. Different from versions 2.x, broadcast is no longer used!
The client must know the IP of the server and its machine ID to access the server.
If the machine
in the request matches that of the DB, returns the list of lessons represented
as their "endtimes" in the format shown above.
If these two don't match, returns the machine ID in the database along with an error message.
{"command": "quit_spirit"}
-> {"success": true}
Exits the server. Normally, this will always return true, as reporting errors in the destructors is kind of difficult.
{"command": "flush_notice"}
-> {"success": true}
-> {"success": false, "what": "error description"}
Using GS's port, we can inform it to get the latest notice immediately. This might be useful if too much network analysis breaks the network interface configuration and you cannot receive notifications at all.
For the error scenarios, see restart_gs
.
{"command": "doggie_stick", "pause": true or false}
-> {"success": true}
-> {"success": false, "what": "..."} on errors, like a missing pause argument.
Tells the watchdog to pause or resume, respectively. Usually atomic bool operations are noexcept, so we will simply return a success.
Good luck!