miniminitel 1.0.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- miniminitel-1.0.3/PKG-INFO +46 -0
- miniminitel-1.0.3/README.md +32 -0
- miniminitel-1.0.3/pyproject.toml +26 -0
- miniminitel-1.0.3/setup.cfg +4 -0
- miniminitel-1.0.3/src/miniminitel/C0.py +39 -0
- miniminitel-1.0.3/src/miniminitel/C1.py +37 -0
- miniminitel-1.0.3/src/miniminitel/__init__.py +1 -0
- miniminitel-1.0.3/src/miniminitel/miniminitel.py +5 -0
- miniminitel-1.0.3/src/miniminitel/minitel_controller.py +183 -0
- miniminitel-1.0.3/src/miniminitel/minitel_csi.py +142 -0
- miniminitel-1.0.3/src/miniminitel/minitel_dialog_code.py +38 -0
- miniminitel-1.0.3/src/miniminitel/minitel_image.py +234 -0
- miniminitel-1.0.3/src/miniminitel/minitel_videotex.py +75 -0
- miniminitel-1.0.3/src/miniminitel/minitel_videotex_attributes.py +68 -0
- miniminitel-1.0.3/src/miniminitel/minitelinterfacing.py +91 -0
- miniminitel-1.0.3/src/miniminitel/serial_connection.py +30 -0
- miniminitel-1.0.3/src/miniminitel.egg-info/PKG-INFO +46 -0
- miniminitel-1.0.3/src/miniminitel.egg-info/SOURCES.txt +19 -0
- miniminitel-1.0.3/src/miniminitel.egg-info/dependency_links.txt +1 -0
- miniminitel-1.0.3/src/miniminitel.egg-info/requires.txt +2 -0
- miniminitel-1.0.3/src/miniminitel.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: miniminitel
|
|
3
|
+
Version: 1.0.3
|
|
4
|
+
Summary: Simple Minitel interface in Python
|
|
5
|
+
Author: Portevent
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Portevent/MiniMinitel
|
|
8
|
+
Project-URL: Issues, https://github.com/Portevent/MiniMinitel/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: pillow
|
|
13
|
+
Requires-Dist: pyserial
|
|
14
|
+
|
|
15
|
+
# MiniMinitel
|
|
16
|
+
MiniMinitel is a simple Minitel interface in python. It has been created with the help of :
|
|
17
|
+
- http://543210.free.fr/TV/stum1b.pdf
|
|
18
|
+
- https://wiki-ima.plil.fr/mediawiki/images/b/b1/Minitel.pdf
|
|
19
|
+
- https://github.com/eserandour/Minitel1B_Hard
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from miniminitel import MiniMinitel
|
|
23
|
+
|
|
24
|
+
with MiniMinitel("COM4") as minitel:
|
|
25
|
+
minitel.clear()
|
|
26
|
+
|
|
27
|
+
minitel.cursor_move_to(4, 4)
|
|
28
|
+
minitel.write_double_grandeur("Hello world")
|
|
29
|
+
|
|
30
|
+
minitel.write_at(6, 6, "I'm a MiniMinitel")
|
|
31
|
+
minitel.set_blinking()
|
|
32
|
+
minitel.write_at(6, 7, "And I look cool")
|
|
33
|
+
minitel.set_non_blinking()
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
# Setup
|
|
38
|
+
## Python
|
|
39
|
+
MiniMinitel is a work in progress, no version are published to PyPI yet.
|
|
40
|
+
Library is written with Python 3.10, no support for other version are guaranteed (should be compatible Python 3.11+)
|
|
41
|
+
```commandline
|
|
42
|
+
pip install miniminitel
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Minitel
|
|
46
|
+
Minitel need to be pluggged to computer throught serial port. Documentation need to be written
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# MiniMinitel
|
|
2
|
+
MiniMinitel is a simple Minitel interface in python. It has been created with the help of :
|
|
3
|
+
- http://543210.free.fr/TV/stum1b.pdf
|
|
4
|
+
- https://wiki-ima.plil.fr/mediawiki/images/b/b1/Minitel.pdf
|
|
5
|
+
- https://github.com/eserandour/Minitel1B_Hard
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
from miniminitel import MiniMinitel
|
|
9
|
+
|
|
10
|
+
with MiniMinitel("COM4") as minitel:
|
|
11
|
+
minitel.clear()
|
|
12
|
+
|
|
13
|
+
minitel.cursor_move_to(4, 4)
|
|
14
|
+
minitel.write_double_grandeur("Hello world")
|
|
15
|
+
|
|
16
|
+
minitel.write_at(6, 6, "I'm a MiniMinitel")
|
|
17
|
+
minitel.set_blinking()
|
|
18
|
+
minitel.write_at(6, 7, "And I look cool")
|
|
19
|
+
minitel.set_non_blinking()
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
# Setup
|
|
24
|
+
## Python
|
|
25
|
+
MiniMinitel is a work in progress, no version are published to PyPI yet.
|
|
26
|
+
Library is written with Python 3.10, no support for other version are guaranteed (should be compatible Python 3.11+)
|
|
27
|
+
```commandline
|
|
28
|
+
pip install miniminitel
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Minitel
|
|
32
|
+
Minitel need to be pluggged to computer throught serial port. Documentation need to be written
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "miniminitel"
|
|
3
|
+
version = "1.0.3"
|
|
4
|
+
authors = [
|
|
5
|
+
{ name="Portevent" },
|
|
6
|
+
]
|
|
7
|
+
description = "Simple Minitel interface in Python"
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Programming Language :: Python :: 3"
|
|
12
|
+
]
|
|
13
|
+
license = "MIT"
|
|
14
|
+
license-files = ["LICEN[CS]E*"]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"pillow",
|
|
17
|
+
"pyserial"
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.urls]
|
|
21
|
+
Homepage = "https://github.com/Portevent/MiniMinitel"
|
|
22
|
+
Issues = "https://github.com/Portevent/MiniMinitel/issues"
|
|
23
|
+
|
|
24
|
+
[build-system]
|
|
25
|
+
requires = ["setuptools >= 80.9.0"]
|
|
26
|
+
build-backend = "setuptools.build_meta"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# C0 grid
|
|
2
|
+
|
|
3
|
+
# http://543210.free.fr/TV/stum1b.pdf - page 92 (95 in pdf)
|
|
4
|
+
|
|
5
|
+
NUL : bytes = b'\x00' # Documentation required
|
|
6
|
+
SCH : bytes = b'\x01' # Documentation required
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
EOT : bytes = b'\x04' # Documentation required
|
|
10
|
+
ENQ : bytes = b'\x05' # Documentation required
|
|
11
|
+
|
|
12
|
+
BEL : bytes = b'\x07' # Ring a bell
|
|
13
|
+
BS : bytes = b'\x08' # Ctrl + H Move cursor left
|
|
14
|
+
HT : bytes = b'\x09' # Ctrl + I Move cursor right
|
|
15
|
+
TAB : bytes = HT # HT is sometimes called TAB
|
|
16
|
+
LF : bytes = b'\x0A' # Ctrl + J Move cursor down
|
|
17
|
+
VT : bytes = b'\x0B' # Ctrl + K Move cursor up
|
|
18
|
+
FF : bytes = b'\x0C' # Return cursor to start of first line. Clear all. Explicit article separator
|
|
19
|
+
CR : bytes = b'\x0D' # <--' Move cursor to origin
|
|
20
|
+
SO : bytes = b'\x0E' # Switch to G1 characters
|
|
21
|
+
SI : bytes = b'\x0F' # Revert to standart alphanum characters (G0)
|
|
22
|
+
|
|
23
|
+
DLE : bytes = b'\x10' # Documentation required
|
|
24
|
+
Con : bytes = b'\x11' # Cursor on
|
|
25
|
+
Rep : bytes = b'\x12' # Documentation required
|
|
26
|
+
Sep : bytes = b'\x13' # Documentation required
|
|
27
|
+
Coff : bytes = b'\x14' # Cursor off
|
|
28
|
+
NACK : bytes = b'\x15' # Documentation required
|
|
29
|
+
SYN : bytes = b'\x16' # Documentation required
|
|
30
|
+
|
|
31
|
+
CAN : bytes = b'\x18' # Ctrl + X Add space till end of line
|
|
32
|
+
SS2 : bytes = b'\x19' # Documentation required
|
|
33
|
+
SUB : bytes = b'\x1A' # Documentation required
|
|
34
|
+
ESC : bytes = b'\x1B' # Esc code (used to access C1 grid)
|
|
35
|
+
SS3 : bytes = b'\x1D' # Documentation required
|
|
36
|
+
RS : bytes = b'\x1E' # Return cursor to start of first line. Explicit article separator
|
|
37
|
+
|
|
38
|
+
US : bytes = b'\x1F' # Explicit article separator
|
|
39
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# C1 grid
|
|
2
|
+
|
|
3
|
+
# http://543210.free.fr/TV/stum1b.pdf - page 92 (95 in pdf)
|
|
4
|
+
|
|
5
|
+
NOIR : bytes = b'\x40'
|
|
6
|
+
ROUGE : bytes = b'\x41'
|
|
7
|
+
VERT : bytes = b'\x42'
|
|
8
|
+
JAUNE : bytes = b'\x43'
|
|
9
|
+
BLEU : bytes = b'\x44'
|
|
10
|
+
MAJENTA : bytes = b'\x45'
|
|
11
|
+
CYAN : bytes = b'\x46'
|
|
12
|
+
BLANC : bytes = b'\x47'
|
|
13
|
+
CLIGNOTEMENT : bytes = b'\x48'
|
|
14
|
+
FIXE : bytes = b'\x49'
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
GRANDEUR_NORMALE : bytes = b'\x4C'
|
|
18
|
+
DOUBLE_HAUTEUR : bytes = b'\x4D'
|
|
19
|
+
DOUBLE_LARGEUR : bytes = b'\x4E'
|
|
20
|
+
DOUBLE_GRANDEUR : bytes = b'\x4F'
|
|
21
|
+
|
|
22
|
+
FOND_NOIR : bytes = b'\x50'
|
|
23
|
+
FOND_ROUGE : bytes = b'\x51'
|
|
24
|
+
FOND_VERT : bytes = b'\x52'
|
|
25
|
+
FOND_JAUNE : bytes = b'\x53'
|
|
26
|
+
FOND_BLEU : bytes = b'\x54'
|
|
27
|
+
FOND_MAJENTA : bytes = b'\x55'
|
|
28
|
+
FOND_CYAN : bytes = b'\x56'
|
|
29
|
+
FOND_BLANC : bytes = b'\x57'
|
|
30
|
+
|
|
31
|
+
MASQUAGE : bytes = b'\x58'
|
|
32
|
+
LIGNAGE_FIN : bytes = b'\x59'
|
|
33
|
+
LIGNAGE_DEBUT : bytes = b'\x5A'
|
|
34
|
+
FOND_NORMAL : bytes = b'\x5C'
|
|
35
|
+
FOND_INVERSE : bytes = b'\x5D'
|
|
36
|
+
DEMASQUAGE : bytes = b'\x5F'
|
|
37
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .miniminitel import MiniMinitel
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
from typing import Callable
|
|
2
|
+
|
|
3
|
+
from .C0 import ESC, BS, LF, CAN, TAB, Sep
|
|
4
|
+
from .minitel_dialog_code import MinitelInput, MinitelCode
|
|
5
|
+
from .minitel_videotex_attributes import MinitelVideotexAttributes
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class MinitelController(MinitelVideotexAttributes):
|
|
9
|
+
|
|
10
|
+
def write(self, text: str):
|
|
11
|
+
"""
|
|
12
|
+
Write text on Minitel
|
|
13
|
+
:param text: text
|
|
14
|
+
"""
|
|
15
|
+
self._write_byte(bytes(text, encoding="ascii"))
|
|
16
|
+
|
|
17
|
+
def write_at(self, x: int, y: int, text: str) -> None:
|
|
18
|
+
"""
|
|
19
|
+
Move cursor to position and write to
|
|
20
|
+
:param x: Column
|
|
21
|
+
:param y: Row
|
|
22
|
+
:param text: Text
|
|
23
|
+
"""
|
|
24
|
+
self.cursor_move_to(x, y)
|
|
25
|
+
self.write(text)
|
|
26
|
+
|
|
27
|
+
def write_double_grandeur(self, text: str) -> None:
|
|
28
|
+
"""
|
|
29
|
+
Write with double grandeur
|
|
30
|
+
:param text: Text
|
|
31
|
+
"""
|
|
32
|
+
self.set_double_grandeur()
|
|
33
|
+
self.write(text)
|
|
34
|
+
self.set_grandeur_normale()
|
|
35
|
+
|
|
36
|
+
def write_double_largeur(self, text: str):
|
|
37
|
+
"""
|
|
38
|
+
Write with double largeur
|
|
39
|
+
:param text: Text
|
|
40
|
+
"""
|
|
41
|
+
self.set_double_largeur()
|
|
42
|
+
self.write(text)
|
|
43
|
+
self.set_grandeur_normale()
|
|
44
|
+
|
|
45
|
+
def write_double_hauteur(self, text: str):
|
|
46
|
+
"""
|
|
47
|
+
Write with double hauteur
|
|
48
|
+
:param text: Text
|
|
49
|
+
"""
|
|
50
|
+
self.set_double_hauteur()
|
|
51
|
+
self.write(text)
|
|
52
|
+
self.set_grandeur_normale()
|
|
53
|
+
|
|
54
|
+
def start_listening(self, on_read: Callable[[MinitelInput], None]):
|
|
55
|
+
"""
|
|
56
|
+
Listen to port and pass MinitelInput to callback
|
|
57
|
+
:param on_read: Action to perform on read
|
|
58
|
+
"""
|
|
59
|
+
value = self.ser.read_all() # Clear buffer before, usefull to get rid of bootup code
|
|
60
|
+
# print(f"READALL -> {value}")
|
|
61
|
+
|
|
62
|
+
while True:
|
|
63
|
+
value = self.ser.read()
|
|
64
|
+
print(f" -> {value}")
|
|
65
|
+
|
|
66
|
+
if value == ESC: # Mode téléinformatique
|
|
67
|
+
word = self.ser.read() + self.ser.read()
|
|
68
|
+
|
|
69
|
+
match word:
|
|
70
|
+
case b'OM':
|
|
71
|
+
on_read(MinitelInput(MinitelCode.ENVOI))
|
|
72
|
+
|
|
73
|
+
case b'OP':
|
|
74
|
+
on_read(MinitelInput(MinitelCode.SOMMAIRE))
|
|
75
|
+
|
|
76
|
+
case b'OQ':
|
|
77
|
+
on_read(MinitelInput(MinitelCode.ANNULATION))
|
|
78
|
+
|
|
79
|
+
case b'OR':
|
|
80
|
+
on_read(MinitelInput(MinitelCode.RETOUR))
|
|
81
|
+
|
|
82
|
+
case b'OS':
|
|
83
|
+
on_read(MinitelInput(MinitelCode.REPETITION))
|
|
84
|
+
|
|
85
|
+
case b'Om':
|
|
86
|
+
on_read(MinitelInput(MinitelCode.GUIDE))
|
|
87
|
+
|
|
88
|
+
case b'Ol':
|
|
89
|
+
on_read(MinitelInput(MinitelCode.CORRECTION))
|
|
90
|
+
|
|
91
|
+
case b'On':
|
|
92
|
+
on_read(MinitelInput(MinitelCode.SUITE))
|
|
93
|
+
|
|
94
|
+
case b'[A':
|
|
95
|
+
on_read(MinitelInput(MinitelCode.MOVE_UP))
|
|
96
|
+
|
|
97
|
+
case b'[B':
|
|
98
|
+
on_read(MinitelInput(MinitelCode.MOVE_DOWN))
|
|
99
|
+
|
|
100
|
+
case b'[C':
|
|
101
|
+
on_read(MinitelInput(MinitelCode.MOVE_RIGHT))
|
|
102
|
+
|
|
103
|
+
case b'[D':
|
|
104
|
+
on_read(MinitelInput(MinitelCode.MOVE_LEFT))
|
|
105
|
+
|
|
106
|
+
case b'[H':
|
|
107
|
+
on_read(MinitelInput(MinitelCode.GO_BACK_UP))
|
|
108
|
+
|
|
109
|
+
case b'[L':
|
|
110
|
+
on_read(MinitelInput(MinitelCode.INSERT_LINE))
|
|
111
|
+
|
|
112
|
+
case b'[M':
|
|
113
|
+
on_read(MinitelInput(MinitelCode.SUPPR_LINE))
|
|
114
|
+
|
|
115
|
+
case b'[P':
|
|
116
|
+
on_read(MinitelInput(MinitelCode.SUPPR_COLUMN))
|
|
117
|
+
|
|
118
|
+
case b'[4':
|
|
119
|
+
word += self.ser.read()
|
|
120
|
+
# print(f"INSERT COLUMN {word}")
|
|
121
|
+
on_read(MinitelInput(MinitelCode.INSERT_COLUMN, word.decode()))
|
|
122
|
+
|
|
123
|
+
case b'[2':
|
|
124
|
+
word += self.ser.read()
|
|
125
|
+
# print(f"E.Page {word}")
|
|
126
|
+
on_read(MinitelInput(MinitelCode.E_Page, word.decode()))
|
|
127
|
+
|
|
128
|
+
case _:
|
|
129
|
+
on_read(MinitelInput(MinitelCode.UNKNOWN_CODE, word.decode()))
|
|
130
|
+
|
|
131
|
+
elif value == Sep: # Mode télétel
|
|
132
|
+
word = self.ser.read()
|
|
133
|
+
|
|
134
|
+
match word:
|
|
135
|
+
case b'A':
|
|
136
|
+
on_read(MinitelInput(MinitelCode.ENVOI))
|
|
137
|
+
|
|
138
|
+
case b'F':
|
|
139
|
+
on_read(MinitelInput(MinitelCode.SOMMAIRE))
|
|
140
|
+
|
|
141
|
+
case b'E':
|
|
142
|
+
on_read(MinitelInput(MinitelCode.ANNULATION))
|
|
143
|
+
|
|
144
|
+
case b'B':
|
|
145
|
+
on_read(MinitelInput(MinitelCode.RETOUR))
|
|
146
|
+
|
|
147
|
+
case b'C':
|
|
148
|
+
on_read(MinitelInput(MinitelCode.REPETITION))
|
|
149
|
+
|
|
150
|
+
case b'D':
|
|
151
|
+
on_read(MinitelInput(MinitelCode.GUIDE))
|
|
152
|
+
|
|
153
|
+
case b'G':
|
|
154
|
+
on_read(MinitelInput(MinitelCode.CORRECTION))
|
|
155
|
+
|
|
156
|
+
case b'H':
|
|
157
|
+
on_read(MinitelInput(MinitelCode.SUITE))
|
|
158
|
+
|
|
159
|
+
case b'Y':
|
|
160
|
+
on_read(MinitelInput(MinitelCode.CONNEXION))
|
|
161
|
+
|
|
162
|
+
case _:
|
|
163
|
+
on_read(MinitelInput(MinitelCode.UNKNOWN_CODE, word.decode()))
|
|
164
|
+
else:
|
|
165
|
+
# Can't add this check in match, as match value: case BS: will map value onto BS
|
|
166
|
+
if value == BS:
|
|
167
|
+
on_read(MinitelInput(MinitelCode.BS)) # Ctrl + H
|
|
168
|
+
elif value == LF:
|
|
169
|
+
on_read(MinitelInput(MinitelCode.LF)) # Ctrl + J
|
|
170
|
+
elif value == CAN:
|
|
171
|
+
on_read(MinitelInput(MinitelCode.CAN)) # Ctrl + X
|
|
172
|
+
elif value == TAB:
|
|
173
|
+
on_read(MinitelInput(MinitelCode.TAB)) # Ctrl + I
|
|
174
|
+
else:
|
|
175
|
+
match value:
|
|
176
|
+
case b'\x00':
|
|
177
|
+
on_read(MinitelInput(MinitelCode.BRK)) # Ctrl + Connexion
|
|
178
|
+
case b'\x7f':
|
|
179
|
+
on_read(MinitelInput(MinitelCode.DELETE)) # Ctrl + Connexion
|
|
180
|
+
case b'\r':
|
|
181
|
+
on_read(MinitelInput(MinitelCode.CARRIAGE_RETURN)) # ENTER
|
|
182
|
+
case _:
|
|
183
|
+
on_read(MinitelInput(MinitelCode.TEXT, value.decode()))
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
from .minitel_videotex import MinitelVideotex
|
|
2
|
+
|
|
3
|
+
class MinitelCSI(MinitelVideotex):
|
|
4
|
+
"""
|
|
5
|
+
Minitel CSI commands
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
def move_cursor_up(self, n: int = 1):
|
|
9
|
+
"""
|
|
10
|
+
Move cursor up n time
|
|
11
|
+
Note : when n = 1, this function is similar to shiftCursorUp()
|
|
12
|
+
"""
|
|
13
|
+
self._write_csi()
|
|
14
|
+
self._write_bytes_p(n)
|
|
15
|
+
self._write_byte(b'\x41')
|
|
16
|
+
|
|
17
|
+
def move_cursor_down(self, n: int = 1):
|
|
18
|
+
"""
|
|
19
|
+
Move cursor down n time
|
|
20
|
+
Note : when n = 1, this function is similar to shiftCursorDown()
|
|
21
|
+
"""
|
|
22
|
+
self._write_csi()
|
|
23
|
+
self._write_bytes_p(n)
|
|
24
|
+
self._write_byte(b'\x42')
|
|
25
|
+
|
|
26
|
+
def move_cursor_right(self, n: int = 1):
|
|
27
|
+
"""
|
|
28
|
+
Move cursor right n time
|
|
29
|
+
Note : when n = 1, this function is similar to shiftCursorRight()
|
|
30
|
+
"""
|
|
31
|
+
self._write_csi()
|
|
32
|
+
self._write_bytes_p(n)
|
|
33
|
+
self._write_byte(b'\x43')
|
|
34
|
+
|
|
35
|
+
def move_cursor_left(self, n: int = 1):
|
|
36
|
+
"""
|
|
37
|
+
Move cursor left n time
|
|
38
|
+
Note : when n = 1, this function is similar to shiftCursorLeft()
|
|
39
|
+
"""
|
|
40
|
+
self._write_csi()
|
|
41
|
+
self._write_bytes_p(n)
|
|
42
|
+
self._write_byte(b'\x44')
|
|
43
|
+
|
|
44
|
+
def cursor_move_to(self, x: int, y: int):
|
|
45
|
+
"""
|
|
46
|
+
Move cursor to row X and column Y
|
|
47
|
+
:param x: from 01 to 24
|
|
48
|
+
:param y: from 01 to 40 (or 80)
|
|
49
|
+
"""
|
|
50
|
+
self._write_csi()
|
|
51
|
+
self._write_bytes_p(y)
|
|
52
|
+
self._write_byte(b'\x3B')
|
|
53
|
+
self._write_bytes_p(x)
|
|
54
|
+
self._write_byte(b'\x48')
|
|
55
|
+
|
|
56
|
+
def clear_all_after(self):
|
|
57
|
+
"""
|
|
58
|
+
Clear everything after the cursor till last row (doesn't move cursor)
|
|
59
|
+
"""
|
|
60
|
+
self._write_csi()
|
|
61
|
+
# self._writeByte(b'\x30') Optional, doesn't matter
|
|
62
|
+
self._write_byte(b'\x4A')
|
|
63
|
+
|
|
64
|
+
def clear_all_before(self):
|
|
65
|
+
"""
|
|
66
|
+
Clear everything before the cursor till first row (doesn't move cursor)
|
|
67
|
+
"""
|
|
68
|
+
self._write_csi()
|
|
69
|
+
self._write_byte(b'\x31')
|
|
70
|
+
self._write_byte(b'\x4A')
|
|
71
|
+
|
|
72
|
+
def clear_screen(self):
|
|
73
|
+
"""
|
|
74
|
+
Clear everything (doesn't move cursor)
|
|
75
|
+
"""
|
|
76
|
+
self._write_csi()
|
|
77
|
+
self._write_byte(b'\x32')
|
|
78
|
+
self._write_byte(b'\x4A')
|
|
79
|
+
|
|
80
|
+
def clear_row_end(self):
|
|
81
|
+
"""
|
|
82
|
+
Clear after the cursor till the end of row (doesn't move cursor)
|
|
83
|
+
"""
|
|
84
|
+
self._write_csi()
|
|
85
|
+
# self._writeByte(b'\x30') Optional
|
|
86
|
+
self._write_byte(b'\x4B')
|
|
87
|
+
|
|
88
|
+
def clear_row_start(self):
|
|
89
|
+
"""
|
|
90
|
+
Clear before the cursor till the start of row (doesn't move cursor)
|
|
91
|
+
"""
|
|
92
|
+
self._write_csi()
|
|
93
|
+
self._write_byte(b'\x31')
|
|
94
|
+
self._write_byte(b'\x4B')
|
|
95
|
+
|
|
96
|
+
def clear_row(self):
|
|
97
|
+
"""
|
|
98
|
+
Clear current row (doesn't move cursor)
|
|
99
|
+
"""
|
|
100
|
+
self._write_csi()
|
|
101
|
+
self._write_byte(b'\x32')
|
|
102
|
+
self._write_byte(b'\x4B')
|
|
103
|
+
|
|
104
|
+
def insert_rows(self, count: int):
|
|
105
|
+
"""
|
|
106
|
+
Insert n rows
|
|
107
|
+
"""
|
|
108
|
+
self._write_csi()
|
|
109
|
+
self._write_bytes_p(count)
|
|
110
|
+
self._write_byte(b'\x4C')
|
|
111
|
+
|
|
112
|
+
def clear_rows(self, count: int):
|
|
113
|
+
"""
|
|
114
|
+
Clear n rows
|
|
115
|
+
"""
|
|
116
|
+
self._write_csi()
|
|
117
|
+
self._write_bytes_p(count)
|
|
118
|
+
self._write_byte(b'\x4D')
|
|
119
|
+
|
|
120
|
+
def clear_characters(self, count: int):
|
|
121
|
+
"""
|
|
122
|
+
Clear n character
|
|
123
|
+
"""
|
|
124
|
+
self._write_csi()
|
|
125
|
+
self._write_bytes_p(count)
|
|
126
|
+
self._write_byte(b'\x50')
|
|
127
|
+
|
|
128
|
+
def insert_mode_on(self):
|
|
129
|
+
"""
|
|
130
|
+
Activate insertion mode
|
|
131
|
+
"""
|
|
132
|
+
self._write_csi()
|
|
133
|
+
self._write_byte(b'\x34')
|
|
134
|
+
self._write_byte(b'\x68')
|
|
135
|
+
|
|
136
|
+
def insert_mode_off(self):
|
|
137
|
+
"""
|
|
138
|
+
Deactivate insertion mode
|
|
139
|
+
"""
|
|
140
|
+
self._write_csi()
|
|
141
|
+
self._write_byte(b'\x34')
|
|
142
|
+
self._write_byte(b'\x6C')
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
class MinitelCode:
|
|
2
|
+
TEXT = 'TEXT'
|
|
3
|
+
ENVOI = 'ENVOI'
|
|
4
|
+
SOMMAIRE = "SOMMAIRE"
|
|
5
|
+
ANNULATION = "ANNULATION"
|
|
6
|
+
RETOUR = "RETOUR"
|
|
7
|
+
REPETITION = "REPETITION"
|
|
8
|
+
GUIDE = 'GUIDE'
|
|
9
|
+
CORRECTION = "CORRECTION"
|
|
10
|
+
SUITE = "SUITE"
|
|
11
|
+
MOVE_UP = "MOVE UP"
|
|
12
|
+
MOVE_DOWN = "MOVE DOWN"
|
|
13
|
+
MOVE_RIGHT = "MOVE RIGHT"
|
|
14
|
+
MOVE_LEFT = "MOVE LEFT"
|
|
15
|
+
GO_BACK_UP = "GO BACK UP"
|
|
16
|
+
INSERT_LINE = "INSERT LINE"
|
|
17
|
+
SUPPR_LINE = "SUPPR LINE"
|
|
18
|
+
SUPPR_COLUMN = "SUPPR COLUMN"
|
|
19
|
+
INSERT_COLUMN = "INSERT COLUMN"
|
|
20
|
+
E_Page = "E.Page"
|
|
21
|
+
CONNEXION = "CONNEXION"
|
|
22
|
+
BS = "BS"
|
|
23
|
+
LF = "LF"
|
|
24
|
+
CAN = "CAN"
|
|
25
|
+
TAB = "TAB"
|
|
26
|
+
BRK = "BRK"
|
|
27
|
+
DELETE = "DELETE"
|
|
28
|
+
CARRIAGE_RETURN = "CARRIAGE RETURN"
|
|
29
|
+
UNKNOWN_CODE = "UNKNOWN CODE"
|
|
30
|
+
|
|
31
|
+
class MinitelInput:
|
|
32
|
+
code: str
|
|
33
|
+
text: str
|
|
34
|
+
|
|
35
|
+
def __init__(self, code: str, text: str = ""):
|
|
36
|
+
self.code = code
|
|
37
|
+
self.text = text
|
|
38
|
+
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
from random import random
|
|
2
|
+
|
|
3
|
+
from PIL import Image
|
|
4
|
+
|
|
5
|
+
from .minitel_controller import MinitelController
|
|
6
|
+
|
|
7
|
+
def pseudo_random(min_value: int, max_value: int, bias = 1):
|
|
8
|
+
return min_value + int((max_value - min_value) * (random() ** bias))
|
|
9
|
+
|
|
10
|
+
# Palette Minitel (8 couleurs) en RGB
|
|
11
|
+
minitel_palette = [
|
|
12
|
+
(0, 0, 0), # Noir
|
|
13
|
+
(255, 0, 0), # Rouge
|
|
14
|
+
(0, 255, 0), # Vert
|
|
15
|
+
(255, 255, 0), # Jaune
|
|
16
|
+
(0, 0, 255), # Bleu
|
|
17
|
+
(255, 0, 255), # Magenta
|
|
18
|
+
(0, 255, 255), # Cyan
|
|
19
|
+
(255, 255, 255) # Blanc
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
pixel_to_g1_code = {
|
|
23
|
+
"000000": "20", "100000": "21", "010000": "22", "110000": "23",
|
|
24
|
+
"001000": "24", "101000": "25", "011000": "26", "111000": "27",
|
|
25
|
+
"000100": "28", "100100": "29", "010100": "2A", "110100": "2B",
|
|
26
|
+
"001100": "2C", "101100": "2D", "011100": "2E", "111100": "2F",
|
|
27
|
+
"000010": "30", "100010": "31", "010010": "32", "110010": "33",
|
|
28
|
+
"001010": "34", "101010": "35", "011010": "36", "111010": "37",
|
|
29
|
+
"000110": "38", "100110": "39", "010110": "3A", "110110": "3B",
|
|
30
|
+
"001110": "3C", "101110": "3D", "011110": "3E", "111110": "3F",
|
|
31
|
+
"000001": "60", "100001": "61", "010001": "62", "110001": "63",
|
|
32
|
+
"001001": "64", "101001": "65", "011001": "66", "111001": "67",
|
|
33
|
+
"000101": "68", "100101": "69", "010101": "6A", "110101": "6B",
|
|
34
|
+
"001101": "6C", "101101": "6D", "011101": "6E", "111101": "6F",
|
|
35
|
+
"000011": "70", "100011": "71", "010011": "72", "110011": "73",
|
|
36
|
+
"001011": "74", "101011": "75", "011011": "76", "111011": "77",
|
|
37
|
+
"000111": "78", "100111": "79", "010111": "7A", "110111": "7B",
|
|
38
|
+
"001111": "7C", "101111": "7D", "011111": "7E", "111111": "7F"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
color_codes = {
|
|
42
|
+
(0, 0, 0): ("1B40", "1B50"),
|
|
43
|
+
(255, 0, 0): ("1B41", "1B51"),
|
|
44
|
+
(0, 255, 0): ("1B42", "1B52"),
|
|
45
|
+
(255, 255, 0): ("1B43", "1B53"),
|
|
46
|
+
(0, 0, 255): ("1B44", "1B54"),
|
|
47
|
+
(255, 0, 255): ("1B45", "1B55"),
|
|
48
|
+
(0, 255, 255): ("1B46", "1B56"),
|
|
49
|
+
(255, 255, 255): ("1B47", "1B57")
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
minitel_color_names = {
|
|
54
|
+
"Black": (0, 0, 0),
|
|
55
|
+
"Red": (255, 0, 0),
|
|
56
|
+
"Green": (0, 255, 0),
|
|
57
|
+
"Yellow": (255, 255, 0),
|
|
58
|
+
"Blue": (0, 0, 255),
|
|
59
|
+
"Magenta": (255, 0, 255),
|
|
60
|
+
"Cyan": (0, 255, 255),
|
|
61
|
+
"White": (255, 255, 255)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
class MinitelImage(MinitelController):
|
|
65
|
+
|
|
66
|
+
def _convert_image_to_minitel_palette(self, image):
|
|
67
|
+
image = image.convert("RGB")
|
|
68
|
+
pixels = image.load()
|
|
69
|
+
for y in range(image.height):
|
|
70
|
+
for x in range(image.width):
|
|
71
|
+
original_color = pixels[x, y]
|
|
72
|
+
closest_color = min(minitel_palette,
|
|
73
|
+
key=lambda color: sum((color[i] - original_color[i]) ** 2 for i in range(3)))
|
|
74
|
+
pixels[x, y] = closest_color
|
|
75
|
+
return image
|
|
76
|
+
|
|
77
|
+
def _get_preview_image(self, filepath, mode="resize", bg_color=(0, 0, 0)):
|
|
78
|
+
image = Image.open(filepath)
|
|
79
|
+
image = image.convert("RGB")
|
|
80
|
+
target_width, target_height = 80, 72
|
|
81
|
+
|
|
82
|
+
if mode == "resize":
|
|
83
|
+
image = image.resize((target_width, target_height), Image.LANCZOS)
|
|
84
|
+
elif mode == "center":
|
|
85
|
+
iw, ih = image.size
|
|
86
|
+
if iw > target_width or ih > target_height:
|
|
87
|
+
scale_factor = min(target_width / iw, target_height / ih)
|
|
88
|
+
new_width = int(iw * scale_factor)
|
|
89
|
+
new_height = int(ih * scale_factor)
|
|
90
|
+
image = image.resize((new_width, new_height), Image.LANCZOS)
|
|
91
|
+
iw, ih = image.size
|
|
92
|
+
new_img = Image.new("RGB", (target_width, target_height), bg_color)
|
|
93
|
+
left = (target_width - iw) // 2
|
|
94
|
+
top = (target_height - ih) // 2
|
|
95
|
+
new_img.paste(image, (left, top))
|
|
96
|
+
image = new_img
|
|
97
|
+
|
|
98
|
+
image = self._convert_image_to_minitel_palette(image)
|
|
99
|
+
return image
|
|
100
|
+
|
|
101
|
+
def _image_to_g1_row(self, filepath, mode="resize", bg_color=(0, 0, 0)):
|
|
102
|
+
image = self._get_preview_image(filepath, mode, bg_color)
|
|
103
|
+
mosaic_hex_list = []
|
|
104
|
+
mosaic_hex = ""
|
|
105
|
+
target_width, target_height = 80, 72
|
|
106
|
+
for y in range(0, target_height, 3):
|
|
107
|
+
for x in range(0, target_width, 2):
|
|
108
|
+
block_pixels = [
|
|
109
|
+
image.getpixel((x, y)),
|
|
110
|
+
image.getpixel((x + 1, y)),
|
|
111
|
+
image.getpixel((x, y + 1)),
|
|
112
|
+
image.getpixel((x + 1, y + 1)),
|
|
113
|
+
image.getpixel((x, y + 2)),
|
|
114
|
+
image.getpixel((x + 1, y + 2))
|
|
115
|
+
]
|
|
116
|
+
color_count = {}
|
|
117
|
+
for pixel in block_pixels:
|
|
118
|
+
color_count[pixel] = color_count.get(pixel, 0) + 1
|
|
119
|
+
sorted_colors = sorted(color_count.items(), key=lambda item: item[1], reverse=True)
|
|
120
|
+
bg, fg = sorted_colors[0][0], (sorted_colors[1][0] if len(sorted_colors) > 1 else (255, 255, 255))
|
|
121
|
+
if bg == fg:
|
|
122
|
+
fg = (0, 0, 0) if bg != (0, 0, 0) else (255, 255, 255)
|
|
123
|
+
binary_string = ''.join(['1' if pixel == fg else '0' for pixel in block_pixels])
|
|
124
|
+
g1_code = pixel_to_g1_code.get(binary_string, "7F")
|
|
125
|
+
cell = color_codes[bg][1] + color_codes[fg][0] + g1_code
|
|
126
|
+
mosaic_hex += cell
|
|
127
|
+
mosaic_hex_list.append(mosaic_hex)
|
|
128
|
+
mosaic_hex = ""
|
|
129
|
+
return mosaic_hex_list
|
|
130
|
+
|
|
131
|
+
def _image_to_g1(self, filepath, mode="resize", bg_color=(0, 0, 0)):
|
|
132
|
+
image = self._get_preview_image(filepath, mode, bg_color)
|
|
133
|
+
mosaic_hex = ""
|
|
134
|
+
target_width, target_height = 80, 72
|
|
135
|
+
for y in range(0, target_height, 3):
|
|
136
|
+
for x in range(0, target_width, 2):
|
|
137
|
+
block_pixels = [
|
|
138
|
+
image.getpixel((x, y)),
|
|
139
|
+
image.getpixel((x + 1, y)),
|
|
140
|
+
image.getpixel((x, y + 1)),
|
|
141
|
+
image.getpixel((x + 1, y + 1)),
|
|
142
|
+
image.getpixel((x, y + 2)),
|
|
143
|
+
image.getpixel((x + 1, y + 2))
|
|
144
|
+
]
|
|
145
|
+
color_count = {}
|
|
146
|
+
for pixel in block_pixels:
|
|
147
|
+
color_count[pixel] = color_count.get(pixel, 0) + 1
|
|
148
|
+
sorted_colors = sorted(color_count.items(), key=lambda item: item[1], reverse=True)
|
|
149
|
+
bg, fg = sorted_colors[0][0], (sorted_colors[1][0] if len(sorted_colors) > 1 else (255, 255, 255))
|
|
150
|
+
if bg == fg:
|
|
151
|
+
fg = (0, 0, 0) if bg != (0, 0, 0) else (255, 255, 255)
|
|
152
|
+
binary_string = ''.join(['1' if pixel == fg else '0' for pixel in block_pixels])
|
|
153
|
+
g1_code = pixel_to_g1_code.get(binary_string, "7F")
|
|
154
|
+
cell = color_codes[bg][1] + color_codes[fg][0] + g1_code
|
|
155
|
+
mosaic_hex += cell
|
|
156
|
+
return mosaic_hex
|
|
157
|
+
|
|
158
|
+
def display_image(self, filepath: str, mode="resize", bg_color=(0, 0, 0)) -> None:
|
|
159
|
+
"""
|
|
160
|
+
Display image
|
|
161
|
+
:param filepath: path to image
|
|
162
|
+
:param mode: center or resize
|
|
163
|
+
:param bg_color: background color (default is black)
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
self._write_byte(b'\x1B\x3B\x60\x58\x52')
|
|
167
|
+
self.cursor_off()
|
|
168
|
+
self.clear_screen()
|
|
169
|
+
self.switch_to_g1()
|
|
170
|
+
self._write_byte(b'\x1B\x3A\x6A\x43')
|
|
171
|
+
|
|
172
|
+
bg_color = minitel_color_names.get(bg_color, (0, 0, 0)) if mode == "center" else (0, 0, 0)
|
|
173
|
+
|
|
174
|
+
mosaic_hex = self._image_to_g1(filepath, mode, bg_color)
|
|
175
|
+
data_bytes = bytes.fromhex(mosaic_hex)
|
|
176
|
+
self._write_byte(data_bytes)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def show_image_buggy(self, filepath: str, mode="resize", bg_color=(0, 0, 0)):
|
|
180
|
+
"""
|
|
181
|
+
Display image in a buggy way (will animate forever)
|
|
182
|
+
:param filepath: path to image
|
|
183
|
+
:param mode: center or resize
|
|
184
|
+
:param bg_color: background color (default is black)
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
self._write_byte(b'\x1B\x3B\x60\x58\x52')
|
|
188
|
+
self.cursor_off()
|
|
189
|
+
self.clear_screen()
|
|
190
|
+
self.switch_to_g1()
|
|
191
|
+
self._write_byte(b'\x1B\x3A\x6A\x43')
|
|
192
|
+
|
|
193
|
+
bg_color = minitel_color_names.get(bg_color, (0, 0, 0)) if mode == "center" else (0, 0, 0)
|
|
194
|
+
|
|
195
|
+
mosaic_hex = self._image_to_g1_row(filepath, mode, bg_color)
|
|
196
|
+
data_bytes = [bytes.fromhex(mosaic) for mosaic in mosaic_hex]
|
|
197
|
+
for line in data_bytes:
|
|
198
|
+
self._write_byte(line)
|
|
199
|
+
|
|
200
|
+
queue = []
|
|
201
|
+
|
|
202
|
+
empty = b'\x1bP\x1bG '
|
|
203
|
+
|
|
204
|
+
while True:
|
|
205
|
+
if len(queue) < 20:
|
|
206
|
+
x_max_length = 12
|
|
207
|
+
|
|
208
|
+
y = pseudo_random(2, len(data_bytes) - 2)
|
|
209
|
+
x_start = pseudo_random(5, 40 - x_max_length)
|
|
210
|
+
x_length = pseudo_random(4, x_max_length, 2)
|
|
211
|
+
|
|
212
|
+
offset = pseudo_random(0, x_length, 3)
|
|
213
|
+
|
|
214
|
+
glitch_instruction = (1+x_start, 1+y, y, 5*(x_start + offset), 5*(x_start+x_length), offset)
|
|
215
|
+
display_instruction = (1+x_start, 1+y, y, 5*(x_start), 5*(x_start+x_length), 0)
|
|
216
|
+
|
|
217
|
+
index = pseudo_random(min(len(queue), 5), len(queue))
|
|
218
|
+
index2 = pseudo_random(index, len(queue))
|
|
219
|
+
queue.insert(index, glitch_instruction)
|
|
220
|
+
queue.insert(index2+1, display_instruction)
|
|
221
|
+
|
|
222
|
+
dequeue_count = 0
|
|
223
|
+
|
|
224
|
+
if len(queue) > 4:
|
|
225
|
+
dequeue_count = 1
|
|
226
|
+
if len(queue) > 10:
|
|
227
|
+
dequeue_count = 4
|
|
228
|
+
|
|
229
|
+
for dequeue in range(dequeue_count):
|
|
230
|
+
x, y, line, fromByte, toByte, offset = queue.pop(0)
|
|
231
|
+
self.cursorMove(x, y)
|
|
232
|
+
for i in range(offset):
|
|
233
|
+
self._write_byte(empty)
|
|
234
|
+
self._write_byte(data_bytes[line][fromByte:toByte])
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from .C0 import LF, VT, BS, TAB, CR, RS, FF, US, CAN, BEL, ESC
|
|
2
|
+
from .minitelinterfacing import MinitelInterfacing
|
|
3
|
+
|
|
4
|
+
class MinitelVideotex(MinitelInterfacing):
|
|
5
|
+
"""
|
|
6
|
+
Basic Minitel Videotex commands
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
def write(self, text: str):
|
|
10
|
+
self._write_byte(bytes(text, encoding="ascii"))
|
|
11
|
+
|
|
12
|
+
def shift_cursor_down(self):
|
|
13
|
+
"""
|
|
14
|
+
Move cursor down one time
|
|
15
|
+
"""
|
|
16
|
+
self._write_byte(LF)
|
|
17
|
+
|
|
18
|
+
def shift_cursor_up(self):
|
|
19
|
+
"""
|
|
20
|
+
Move cursor right one time
|
|
21
|
+
"""
|
|
22
|
+
self._write_byte(VT)
|
|
23
|
+
|
|
24
|
+
def shift_cursor_left(self):
|
|
25
|
+
"""
|
|
26
|
+
Move cursor left one time
|
|
27
|
+
"""
|
|
28
|
+
self._write_byte(BS)
|
|
29
|
+
|
|
30
|
+
def shift_cursor_right(self):
|
|
31
|
+
"""
|
|
32
|
+
Move cursor left one time
|
|
33
|
+
"""
|
|
34
|
+
self._write_byte(TAB)
|
|
35
|
+
|
|
36
|
+
def cariage_return(self):
|
|
37
|
+
"""
|
|
38
|
+
Move cursor to start of the line (sometimes called Origin)
|
|
39
|
+
"""
|
|
40
|
+
self._write_byte(CR)
|
|
41
|
+
|
|
42
|
+
def return_to_top_left(self):
|
|
43
|
+
"""
|
|
44
|
+
(also called RS) Move cursor to start of first row. Explicit article separator
|
|
45
|
+
"""
|
|
46
|
+
self._write_byte(RS)
|
|
47
|
+
|
|
48
|
+
def explicit_article_separator(self):
|
|
49
|
+
"""
|
|
50
|
+
(also called US) Explicit article separator
|
|
51
|
+
"""
|
|
52
|
+
self._write_byte(US)
|
|
53
|
+
|
|
54
|
+
def complete_to_end_of_line(self):
|
|
55
|
+
"""
|
|
56
|
+
(also called CAN) Write spaces from cursor to end of line (doesn't move cursor)
|
|
57
|
+
"""
|
|
58
|
+
self._write_byte(CAN)
|
|
59
|
+
|
|
60
|
+
def clear(self): # Clear screen, go to top left, reset attributes
|
|
61
|
+
self._write_byte(FF)
|
|
62
|
+
|
|
63
|
+
def ring_bel(self) -> None:
|
|
64
|
+
"""
|
|
65
|
+
Do a quick sound
|
|
66
|
+
"""
|
|
67
|
+
self._write_byte(BEL)
|
|
68
|
+
|
|
69
|
+
def ask_cursor_pos(self):
|
|
70
|
+
"""
|
|
71
|
+
Ask current cursor position
|
|
72
|
+
"""
|
|
73
|
+
self._write_byte(ESC)
|
|
74
|
+
self._write_byte(b'\x61')
|
|
75
|
+
# TODO : listen to anwser
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from .C0 import ESC
|
|
2
|
+
from .C1 import DOUBLE_HAUTEUR, DOUBLE_GRANDEUR, DOUBLE_LARGEUR, GRANDEUR_NORMALE, CLIGNOTEMENT, FIXE, FOND_INVERSE, \
|
|
3
|
+
FOND_NORMAL
|
|
4
|
+
from .minitel_csi import MinitelCSI
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MinitelVideotexAttributes(MinitelCSI):
|
|
8
|
+
"""
|
|
9
|
+
Minitel Videotex attributes
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def _set_attribute(self, attribute) -> None:
|
|
13
|
+
"""
|
|
14
|
+
Set attribute
|
|
15
|
+
:param attribute: C1 attribute
|
|
16
|
+
"""
|
|
17
|
+
self._write_byte(ESC)
|
|
18
|
+
self._write_byte(attribute)
|
|
19
|
+
if attribute == DOUBLE_HAUTEUR or attribute == DOUBLE_GRANDEUR:
|
|
20
|
+
self.move_cursor_down()
|
|
21
|
+
|
|
22
|
+
def set_grandeur_normale(self) -> None:
|
|
23
|
+
"""
|
|
24
|
+
Switch to normal-sized characters
|
|
25
|
+
"""
|
|
26
|
+
self._set_attribute(GRANDEUR_NORMALE)
|
|
27
|
+
|
|
28
|
+
def set_double_hauteur(self) -> None:
|
|
29
|
+
"""
|
|
30
|
+
Switch to double row characters
|
|
31
|
+
"""
|
|
32
|
+
self._set_attribute(DOUBLE_HAUTEUR)
|
|
33
|
+
|
|
34
|
+
def set_double_largeur(self) -> None:
|
|
35
|
+
"""
|
|
36
|
+
Switch to double column characters
|
|
37
|
+
"""
|
|
38
|
+
self._set_attribute(DOUBLE_LARGEUR)
|
|
39
|
+
|
|
40
|
+
def set_double_grandeur(self) -> None:
|
|
41
|
+
"""
|
|
42
|
+
Switch to double row and column characters
|
|
43
|
+
"""
|
|
44
|
+
self._set_attribute(DOUBLE_GRANDEUR)
|
|
45
|
+
|
|
46
|
+
def set_blinking(self) -> None:
|
|
47
|
+
"""
|
|
48
|
+
Switch to blinking characters
|
|
49
|
+
"""
|
|
50
|
+
self._set_attribute(CLIGNOTEMENT)
|
|
51
|
+
|
|
52
|
+
def set_non_blinking(self) -> None:
|
|
53
|
+
"""
|
|
54
|
+
Revert to non-blinking characters
|
|
55
|
+
"""
|
|
56
|
+
self._set_attribute(FIXE)
|
|
57
|
+
|
|
58
|
+
def set_inverted_color(self) -> None:
|
|
59
|
+
"""
|
|
60
|
+
Switch to inverted color characters
|
|
61
|
+
"""
|
|
62
|
+
self._set_attribute(FOND_INVERSE)
|
|
63
|
+
|
|
64
|
+
def set_non_inverted_color(self) -> None:
|
|
65
|
+
"""
|
|
66
|
+
Revert to non-inverted color characters
|
|
67
|
+
"""
|
|
68
|
+
self._set_attribute(FOND_NORMAL)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from .C0 import ESC, Con, Coff, SO, SI
|
|
2
|
+
from .serial_connection import SerialConnection
|
|
3
|
+
|
|
4
|
+
MODE_TELETEL_VIDEOTEX = 0 # Teletel Videotex mode, default when booting Minitel. Use 40 columns display
|
|
5
|
+
MODE_TELETEL_MIXTE = 1 # Teletel Mixte mode, can be prompted by the server. Use 80 columns display
|
|
6
|
+
MODE_TELEINFORMATIQUE = 2 # Ascii mode, can be prompted by the server or user (Fnct + T then A). Use 80 columns display
|
|
7
|
+
|
|
8
|
+
class MinitelInterfacing(SerialConnection):
|
|
9
|
+
"""
|
|
10
|
+
Basic Minitel interface
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
mode = MODE_TELETEL_VIDEOTEX
|
|
14
|
+
|
|
15
|
+
def _write_csi(self):
|
|
16
|
+
"""
|
|
17
|
+
Send Command Sequence Introducer code, commonly used for prompting command
|
|
18
|
+
:return:
|
|
19
|
+
"""
|
|
20
|
+
self._write_byte(ESC)
|
|
21
|
+
self._write_byte(b'\x5B')
|
|
22
|
+
|
|
23
|
+
def _videotex_to_mixte(self):
|
|
24
|
+
"""
|
|
25
|
+
Switch from VideoTex to Mixte
|
|
26
|
+
"""
|
|
27
|
+
self._write_byte(ESC)
|
|
28
|
+
self._write_byte(b'\x3A')
|
|
29
|
+
self._write_byte(b'\x32')
|
|
30
|
+
self._write_byte(b'\x7D')
|
|
31
|
+
# Expect to receive \x13 \x70 ?
|
|
32
|
+
self.mode = MODE_TELETEL_MIXTE
|
|
33
|
+
|
|
34
|
+
def _mixte_to_videotex(self):
|
|
35
|
+
"""
|
|
36
|
+
Switch from Mixte to VideoTex
|
|
37
|
+
"""
|
|
38
|
+
self._write_byte(ESC)
|
|
39
|
+
self._write_byte(b'\x3A')
|
|
40
|
+
self._write_byte(b'\x32')
|
|
41
|
+
self._write_byte(b'\x7E')
|
|
42
|
+
# Expect to receive \x13 \x71 ?
|
|
43
|
+
self.mode = MODE_TELETEL_VIDEOTEX
|
|
44
|
+
|
|
45
|
+
def _teletel_to_teleinformatique(self):
|
|
46
|
+
"""
|
|
47
|
+
Switch from Teletel (Mixte or VideoTex?) to Teleinformatique
|
|
48
|
+
Can be done by user with Fnct + T then A (or then F for French ASCII)
|
|
49
|
+
"""
|
|
50
|
+
self._write_byte(ESC)
|
|
51
|
+
self._write_byte(b'\x3A')
|
|
52
|
+
self._write_byte(b'\x31')
|
|
53
|
+
self._write_byte(b'\x7D')
|
|
54
|
+
# Expect to receive \x1B \x5B \x3F \x7A ?
|
|
55
|
+
self.mode = MODE_TELEINFORMATIQUE
|
|
56
|
+
|
|
57
|
+
def _teleinformatique_to_videotex(self):
|
|
58
|
+
"""
|
|
59
|
+
Switch from Teleinformatique to VideoTex
|
|
60
|
+
Can be done by user with Fnct + T then V
|
|
61
|
+
"""
|
|
62
|
+
self._write_byte(ESC)
|
|
63
|
+
self._write_byte(b'\x5B')
|
|
64
|
+
self._write_byte(b'\x3F')
|
|
65
|
+
self._write_byte(b'\x7B')
|
|
66
|
+
# Expect to receive \x13 \x5E ?
|
|
67
|
+
self.mode = MODE_TELETEL_VIDEOTEX
|
|
68
|
+
|
|
69
|
+
def cursor_on(self):
|
|
70
|
+
"""
|
|
71
|
+
Display cursor
|
|
72
|
+
"""
|
|
73
|
+
self._write_byte(Con)
|
|
74
|
+
|
|
75
|
+
def cursor_off(self):
|
|
76
|
+
"""
|
|
77
|
+
Hide cursor
|
|
78
|
+
"""
|
|
79
|
+
self._write_byte(Coff)
|
|
80
|
+
|
|
81
|
+
def switch_to_g1(self): # Display images
|
|
82
|
+
"""
|
|
83
|
+
Switch to G1 character table (used for semi graphic display)
|
|
84
|
+
"""
|
|
85
|
+
self._write_byte(SO)
|
|
86
|
+
|
|
87
|
+
def revert_to_g0(self): # Revert to standard alphanum characters
|
|
88
|
+
"""
|
|
89
|
+
Revert to G0 character table (alphanumeric character table)
|
|
90
|
+
"""
|
|
91
|
+
self._write_byte(SI)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import serial
|
|
2
|
+
|
|
3
|
+
class SerialConnection:
|
|
4
|
+
"""
|
|
5
|
+
Raw SerialConnection interface
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
def __init__(self, port: str = '/dev/ttyUSB0', baudrate=4800):
|
|
9
|
+
self.ser = serial.Serial(port, baudrate=baudrate, bytesize=7, parity=serial.PARITY_EVEN, stopbits=serial.STOPBITS_ONE)
|
|
10
|
+
|
|
11
|
+
def __enter__(self):
|
|
12
|
+
return self
|
|
13
|
+
|
|
14
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
15
|
+
self.ser.close()
|
|
16
|
+
|
|
17
|
+
def _write_byte(self, byte: bytes):
|
|
18
|
+
"""
|
|
19
|
+
Send byte over serial connection
|
|
20
|
+
:param byte: Bytes to send
|
|
21
|
+
"""
|
|
22
|
+
self.ser.write(byte)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _write_bytes_p(self, n: int):
|
|
26
|
+
if n <= 9:
|
|
27
|
+
self._write_byte((0x30 + n).to_bytes(1, "big"))
|
|
28
|
+
else :
|
|
29
|
+
self._write_byte((0x30 + (n // 10)).to_bytes(1, "big"))
|
|
30
|
+
self._write_byte((0x30 + (n % 10)).to_bytes(1, "big"))
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: miniminitel
|
|
3
|
+
Version: 1.0.3
|
|
4
|
+
Summary: Simple Minitel interface in Python
|
|
5
|
+
Author: Portevent
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Portevent/MiniMinitel
|
|
8
|
+
Project-URL: Issues, https://github.com/Portevent/MiniMinitel/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: pillow
|
|
13
|
+
Requires-Dist: pyserial
|
|
14
|
+
|
|
15
|
+
# MiniMinitel
|
|
16
|
+
MiniMinitel is a simple Minitel interface in python. It has been created with the help of :
|
|
17
|
+
- http://543210.free.fr/TV/stum1b.pdf
|
|
18
|
+
- https://wiki-ima.plil.fr/mediawiki/images/b/b1/Minitel.pdf
|
|
19
|
+
- https://github.com/eserandour/Minitel1B_Hard
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from miniminitel import MiniMinitel
|
|
23
|
+
|
|
24
|
+
with MiniMinitel("COM4") as minitel:
|
|
25
|
+
minitel.clear()
|
|
26
|
+
|
|
27
|
+
minitel.cursor_move_to(4, 4)
|
|
28
|
+
minitel.write_double_grandeur("Hello world")
|
|
29
|
+
|
|
30
|
+
minitel.write_at(6, 6, "I'm a MiniMinitel")
|
|
31
|
+
minitel.set_blinking()
|
|
32
|
+
minitel.write_at(6, 7, "And I look cool")
|
|
33
|
+
minitel.set_non_blinking()
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
# Setup
|
|
38
|
+
## Python
|
|
39
|
+
MiniMinitel is a work in progress, no version are published to PyPI yet.
|
|
40
|
+
Library is written with Python 3.10, no support for other version are guaranteed (should be compatible Python 3.11+)
|
|
41
|
+
```commandline
|
|
42
|
+
pip install miniminitel
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Minitel
|
|
46
|
+
Minitel need to be pluggged to computer throught serial port. Documentation need to be written
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/miniminitel/C0.py
|
|
4
|
+
src/miniminitel/C1.py
|
|
5
|
+
src/miniminitel/__init__.py
|
|
6
|
+
src/miniminitel/miniminitel.py
|
|
7
|
+
src/miniminitel/minitel_controller.py
|
|
8
|
+
src/miniminitel/minitel_csi.py
|
|
9
|
+
src/miniminitel/minitel_dialog_code.py
|
|
10
|
+
src/miniminitel/minitel_image.py
|
|
11
|
+
src/miniminitel/minitel_videotex.py
|
|
12
|
+
src/miniminitel/minitel_videotex_attributes.py
|
|
13
|
+
src/miniminitel/minitelinterfacing.py
|
|
14
|
+
src/miniminitel/serial_connection.py
|
|
15
|
+
src/miniminitel.egg-info/PKG-INFO
|
|
16
|
+
src/miniminitel.egg-info/SOURCES.txt
|
|
17
|
+
src/miniminitel.egg-info/dependency_links.txt
|
|
18
|
+
src/miniminitel.egg-info/requires.txt
|
|
19
|
+
src/miniminitel.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
miniminitel
|