pymscada 0.1.11b8__tar.gz → 0.1.11b10__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.
Potentially problematic release.
This version of pymscada might be problematic. Click here for more details.
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/PKG-INFO +2 -2
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/pyproject.toml +2 -2
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/demo/accuweather.yaml +1 -1
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/demo/openweather.yaml +1 -1
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/iodrivers/openweather.py +4 -2
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/tests/iodrivers/test_logix.py +12 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/tests/iodrivers/test_modbus.py +47 -13
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/tests/test_bus_server.py +14 -14
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/tests/test_openweather.py +2 -2
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/tests/test_validate.py +2 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/LICENSE +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/README.md +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/__init__.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/__main__.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/bus_client.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/bus_server.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/checkout.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/config.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/console.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/demo/README.md +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/demo/__init__.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/demo/bus.yaml +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/demo/files.yaml +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/demo/history.yaml +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/demo/logixclient.yaml +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/demo/modbus_plc.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/demo/modbusclient.yaml +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/demo/modbusserver.yaml +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/demo/opnotes.yaml +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/demo/ping.yaml +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/demo/pymscada-bus.service +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/demo/pymscada-demo-modbus_plc.service +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/demo/pymscada-files.service +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/demo/pymscada-history.service +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/demo/pymscada-io-accuweather.service +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/demo/pymscada-io-logixclient.service +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/demo/pymscada-io-modbusclient.service +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/demo/pymscada-io-modbusserver.service +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/demo/pymscada-io-ping.service +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/demo/pymscada-io-snmpclient.service +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/demo/pymscada-opnotes.service +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/demo/pymscada-wwwserver.service +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/demo/snmpclient.yaml +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/demo/tags.yaml +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/demo/wwwserver.yaml +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/files.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/history.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/iodrivers/__init__.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/iodrivers/accuweather.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/iodrivers/logix_client.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/iodrivers/logix_map.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/iodrivers/modbus_client.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/iodrivers/modbus_map.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/iodrivers/modbus_server.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/iodrivers/ping_client.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/iodrivers/ping_map.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/iodrivers/snmp_client.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/iodrivers/snmp_map.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/main.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/misc.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/module_config.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/opnotes.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/pdf/__init__.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/pdf/one.pdf +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/pdf/two.pdf +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/periodic.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/protocol_constants.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/samplers.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/tag.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/tools/snmp_client2.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/tools/walk.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/validate.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/src/pymscada/www_server.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/tests/__init__.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/tests/bus_echo.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/tests/test_assets/busserver.yaml +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/tests/test_assets/db.sqlite +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/tests/test_assets/hist_tag_0_0.dat +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/tests/test_assets/hist_tag_0_10_2.dat +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/tests/test_assets/hist_tag_0_15.dat +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/tests/test_assets/hist_tag_0_26.dat +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/tests/test_assets/hist_tag_0_50.dat +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/tests/test_config.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/tests/test_history.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/tests/test_misc.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/tests/test_opnotes.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/tests/test_periodic.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/tests/test_samplers.py +0 -0
- {pymscada-0.1.11b8 → pymscada-0.1.11b10}/tests/test_tag.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pymscada
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.11b10
|
|
4
4
|
Summary: Shared tag value SCADA with python backup and Angular UI
|
|
5
5
|
Author-Email: Jamie Walton <jamie@walton.net.nz>
|
|
6
6
|
License: GPL-3.0-or-later
|
|
@@ -15,7 +15,7 @@ Project-URL: Bug Tracker, https://github.com/jamie0walton/pymscada/issues
|
|
|
15
15
|
Requires-Python: >=3.9
|
|
16
16
|
Requires-Dist: PyYAML>=6.0.1
|
|
17
17
|
Requires-Dist: aiohttp>=3.8.5
|
|
18
|
-
Requires-Dist: pymscada-html>=0.1.
|
|
18
|
+
Requires-Dist: pymscada-html>=0.1.10b5
|
|
19
19
|
Requires-Dist: cerberus>=1.3.5
|
|
20
20
|
Requires-Dist: pycomm3>=1.2.14
|
|
21
21
|
Requires-Dist: pysnmplib>=5.0.24
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pymscada"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.11b10"
|
|
4
4
|
description = "Shared tag value SCADA with python backup and Angular UI"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Jamie Walton", email = "jamie@walton.net.nz" },
|
|
@@ -8,7 +8,7 @@ authors = [
|
|
|
8
8
|
dependencies = [
|
|
9
9
|
"PyYAML>=6.0.1",
|
|
10
10
|
"aiohttp>=3.8.5",
|
|
11
|
-
"pymscada-html>=0.1.
|
|
11
|
+
"pymscada-html>=0.1.10b5",
|
|
12
12
|
"cerberus>=1.3.5",
|
|
13
13
|
"pycomm3>=1.2.14",
|
|
14
14
|
"pysnmplib>=5.0.24",
|
|
@@ -73,6 +73,7 @@ class OpenWeatherClient:
|
|
|
73
73
|
|
|
74
74
|
async def fetch_current_data(self):
|
|
75
75
|
"""Fetch current weather data for all locations."""
|
|
76
|
+
logging.info('fetching current')
|
|
76
77
|
if self.session is None:
|
|
77
78
|
self.session = aiohttp.ClientSession()
|
|
78
79
|
for location, coords in self.locations.items():
|
|
@@ -92,6 +93,7 @@ class OpenWeatherClient:
|
|
|
92
93
|
|
|
93
94
|
async def fetch_forecast_data(self):
|
|
94
95
|
"""Fetch forecast weather data for all locations."""
|
|
96
|
+
logging.info('fetching forecast')
|
|
95
97
|
if self.session is None:
|
|
96
98
|
self.session = aiohttp.ClientSession()
|
|
97
99
|
for location, coords in self.locations.items():
|
|
@@ -113,9 +115,9 @@ class OpenWeatherClient:
|
|
|
113
115
|
"""Poll OpenWeather APIs every 10 minutes."""
|
|
114
116
|
now = int(time())
|
|
115
117
|
if now % 600 == 0: # Every 10 minutes
|
|
116
|
-
|
|
118
|
+
asyncio.create_task(self.fetch_current_data())
|
|
117
119
|
if now % 10800 == 60: # Every 3 hours, offset by 1 minute
|
|
118
|
-
|
|
120
|
+
asyncio.create_task(self.fetch_forecast_data())
|
|
119
121
|
|
|
120
122
|
async def start(self):
|
|
121
123
|
"""Start bus connection and API polling."""
|
|
@@ -63,6 +63,18 @@ def tag_callback(tag: Tag):
|
|
|
63
63
|
@pytest.mark.asyncio
|
|
64
64
|
async def test_connect():
|
|
65
65
|
"""Test Logix."""
|
|
66
|
+
# Check if PLC is reachable
|
|
67
|
+
ip = CLIENT['rtus'][0]['ip']
|
|
68
|
+
try:
|
|
69
|
+
proc = await asyncio.create_subprocess_exec(
|
|
70
|
+
'ping', '-c', '1', '-W', '1', ip,
|
|
71
|
+
stdout=asyncio.subprocess.DEVNULL,
|
|
72
|
+
stderr=asyncio.subprocess.DEVNULL)
|
|
73
|
+
await proc.communicate()
|
|
74
|
+
if proc.returncode != 0:
|
|
75
|
+
pytest.skip(f"PLC at {ip} not reachable")
|
|
76
|
+
except Exception:
|
|
77
|
+
pytest.skip(f"Error checking connectivity to PLC at {ip}")
|
|
66
78
|
global queue
|
|
67
79
|
lc = LogixClient(**CLIENT)
|
|
68
80
|
# PLC code maps 'in' tags to 'out' tags to close the loop
|
|
@@ -44,8 +44,7 @@ CLIENT = {
|
|
|
44
44
|
'port': 1512,
|
|
45
45
|
'tcp_udp': 'tcp',
|
|
46
46
|
'rate': 0.1,
|
|
47
|
-
'
|
|
48
|
-
'writeok': [{'unit': 1, 'file': '4x', 'start': 1, 'end': 30}]
|
|
47
|
+
'poll': [{'unit': 1, 'file': '4x', 'start': 1, 'end': 30}]
|
|
49
48
|
},
|
|
50
49
|
{
|
|
51
50
|
'name': 'RTU_UDP',
|
|
@@ -53,20 +52,55 @@ CLIENT = {
|
|
|
53
52
|
'port': 1513,
|
|
54
53
|
'tcp_udp': 'udp',
|
|
55
54
|
'rate': 0.1,
|
|
56
|
-
'
|
|
57
|
-
'writeok': [{'unit': 1, 'file': '4x', 'start': 1, 'end': 30}]
|
|
55
|
+
'poll': [{'unit': 1, 'file': '4x', 'start': 1, 'end': 30}]
|
|
58
56
|
}
|
|
59
57
|
],
|
|
60
58
|
'tags': {
|
|
61
|
-
'c_int16Tag': {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
'
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
59
|
+
'c_int16Tag': {
|
|
60
|
+
'type': 'int16',
|
|
61
|
+
'read': 'RTU:1:4x:1',
|
|
62
|
+
'write': 'RTU:1:4x:1'
|
|
63
|
+
},
|
|
64
|
+
'c_uint16Tag': {
|
|
65
|
+
'type': 'uint16',
|
|
66
|
+
'read': 'RTU:1:4x:2',
|
|
67
|
+
'write': 'RTU:1:4x:2'
|
|
68
|
+
},
|
|
69
|
+
'c_int32Tag': {
|
|
70
|
+
'type': 'int32',
|
|
71
|
+
'read': 'RTU:1:4x:3',
|
|
72
|
+
'write': 'RTU:1:4x:3'
|
|
73
|
+
},
|
|
74
|
+
'c_uint32Tag': {
|
|
75
|
+
'type': 'uint32',
|
|
76
|
+
'read': 'RTU:1:4x:5',
|
|
77
|
+
'write': 'RTU:1:4x:5'
|
|
78
|
+
},
|
|
79
|
+
'c_int64Tag': {
|
|
80
|
+
'type': 'int64',
|
|
81
|
+
'read': 'RTU:1:4x:7',
|
|
82
|
+
'write': 'RTU:1:4x:7'
|
|
83
|
+
},
|
|
84
|
+
'c_uint64Tag': {
|
|
85
|
+
'type': 'uint64',
|
|
86
|
+
'read': 'RTU:1:4x:11',
|
|
87
|
+
'write': 'RTU:1:4x:11'
|
|
88
|
+
},
|
|
89
|
+
'c_float32Tag': {
|
|
90
|
+
'type': 'float32',
|
|
91
|
+
'read': 'RTU:1:4x:15',
|
|
92
|
+
'write': 'RTU:1:4x:15'
|
|
93
|
+
},
|
|
94
|
+
'c_float64Tag': {
|
|
95
|
+
'type': 'float64',
|
|
96
|
+
'read': 'RTU:1:4x:17',
|
|
97
|
+
'write': 'RTU:1:4x:17'
|
|
98
|
+
},
|
|
99
|
+
'c_udp_int16Tag': {
|
|
100
|
+
'type': 'int16',
|
|
101
|
+
'read': 'RTU_UDP:1:4x:1',
|
|
102
|
+
'write': 'RTU_UDP:1:4x:1'
|
|
103
|
+
}
|
|
70
104
|
}
|
|
71
105
|
}
|
|
72
106
|
queue = asyncio.Queue()
|
|
@@ -8,7 +8,7 @@ import sys
|
|
|
8
8
|
import gc
|
|
9
9
|
import weakref
|
|
10
10
|
from struct import pack, unpack_from
|
|
11
|
-
from pymscada import
|
|
11
|
+
from pymscada.protocol_constants import COMMAND, TYPE
|
|
12
12
|
from pymscada.bus_server import BusTags, BusTag, BusServer
|
|
13
13
|
from pymscada.tag import Tag
|
|
14
14
|
from pymscada.bus_client import BusClient
|
|
@@ -69,24 +69,24 @@ async def bus_echo(bus_server):
|
|
|
69
69
|
|
|
70
70
|
PROTOCOL_TESTS = [
|
|
71
71
|
{'desc': 'create a new bus tag with ID request',
|
|
72
|
-
'send': (
|
|
73
|
-
'recv': [(
|
|
74
|
-
(
|
|
72
|
+
'send': (COMMAND.ID, 170, 18_446_744_073_709_551_615, b't_0'),
|
|
73
|
+
'recv': [(COMMAND.ERR, None, None, b"ID b't_0' undefined"),
|
|
74
|
+
(COMMAND.ID, 'ID', None, b'')]},
|
|
75
75
|
{'desc': 'SET the value of an existing tag',
|
|
76
|
-
'send': (
|
|
76
|
+
'send': (COMMAND.SET, 'ID', 6_744_073_709_551_615, b'VAL'),
|
|
77
77
|
'recv': [None]},
|
|
78
78
|
{'desc': 'GET the value just SET',
|
|
79
|
-
'send': (
|
|
80
|
-
'recv': [(
|
|
79
|
+
'send': (COMMAND.GET, 'ID', 0, b''),
|
|
80
|
+
'recv': [(COMMAND.SET, 'ID', 6_744_073_709_551_615, b'VAL')]},
|
|
81
81
|
{'desc': 'SUB to the created tag',
|
|
82
|
-
'send': (
|
|
83
|
-
'recv': [(
|
|
82
|
+
'send': (COMMAND.SUB, 'ID', 0, b''),
|
|
83
|
+
'recv': [(COMMAND.SET, 'ID', 6_744_073_709_551_615, b'VAL')]},
|
|
84
84
|
{'desc': 'SET the subscribed tag',
|
|
85
|
-
'send': (
|
|
85
|
+
'send': (COMMAND.SET, 'ID', 615, b'New value'),
|
|
86
86
|
'recv': [None]}, # SUB does not return as this is sending bus
|
|
87
87
|
{'desc': 'LIST bus tags',
|
|
88
|
-
'send': (
|
|
89
|
-
'recv': [(
|
|
88
|
+
'send': (COMMAND.LIST, 0, 0, b'^t_0'),
|
|
89
|
+
'recv': [(COMMAND.LIST, 0, None, b't_0')]},
|
|
90
90
|
]
|
|
91
91
|
|
|
92
92
|
|
|
@@ -125,7 +125,7 @@ async def test_protocol_message(bus_server):
|
|
|
125
125
|
_v, cmd, tag_id, size, time_us = unpack_from(
|
|
126
126
|
'!BBHHQ', data, offset=0)
|
|
127
127
|
data = data[14:]
|
|
128
|
-
if cmd ==
|
|
128
|
+
if cmd == COMMAND.ID:
|
|
129
129
|
t_id = tag_id
|
|
130
130
|
if r_tag_id == 'ID':
|
|
131
131
|
r_tag_id = t_id
|
|
@@ -170,7 +170,7 @@ async def test_client_init(bus_server):
|
|
|
170
170
|
global queue
|
|
171
171
|
# Hack the tag value straight into the bus
|
|
172
172
|
mybustag = BusTag(b'myinit')
|
|
173
|
-
mybustag.value = pack('!B4s',
|
|
173
|
+
mybustag.value = pack('!B4s', TYPE.STR, b'init')
|
|
174
174
|
# standard bus client tag setup
|
|
175
175
|
mytag = Tag('myinit', str)
|
|
176
176
|
# callback to see when the tag updates
|
|
@@ -7,9 +7,9 @@ from pymscada.iodrivers.openweather import OpenWeatherClient
|
|
|
7
7
|
@pytest.mark.asyncio
|
|
8
8
|
async def test_openweather_temperature():
|
|
9
9
|
"""Test reading current temperature from OpenWeather API."""
|
|
10
|
-
api_key = os.getenv('
|
|
10
|
+
api_key = os.getenv('OPENWEATHERMAP_API_KEY')
|
|
11
11
|
if not api_key:
|
|
12
|
-
pytest.skip("
|
|
12
|
+
pytest.skip("OPENWEATHERMAP_API_KEY environment variable not set")
|
|
13
13
|
api_config = {
|
|
14
14
|
'api_key': api_key,
|
|
15
15
|
'locations': {'London': {'lat': 51.5074,'lon': -0.1278}},
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|