Connection works under ping_computer_from_arduino.ino
This commit is contained in:
parent
db993cae11
commit
863e486944
381
Arduino/libraries/ICMPPing/ICMPPing.cpp
Normal file
381
Arduino/libraries/ICMPPing/ICMPPing.cpp
Normal file
@ -0,0 +1,381 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2010 by Blake Foster <blfoster@vassar.edu>
|
||||||
|
*
|
||||||
|
* This file is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of either the GNU General Public License version 2
|
||||||
|
* or the GNU Lesser General Public License version 2.1, both as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ICMPPing.h"
|
||||||
|
#include <util.h>
|
||||||
|
|
||||||
|
#ifdef ICMPPING_INSERT_YIELDS
|
||||||
|
#define ICMPPING_DOYIELD() delay(2)
|
||||||
|
#else
|
||||||
|
#define ICMPPING_DOYIELD()
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
inline uint16_t _makeUint16(const uint8_t& highOrder, const uint8_t& lowOrder)
|
||||||
|
{
|
||||||
|
// make a 16-bit unsigned integer given the low order and high order bytes.
|
||||||
|
// lowOrder first because the Arduino is little endian.
|
||||||
|
uint8_t value [] = {lowOrder, highOrder};
|
||||||
|
return *(uint16_t *)&value;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t _checksum(const ICMPEcho& echo)
|
||||||
|
{
|
||||||
|
// calculate the checksum of an ICMPEcho with all fields but icmpHeader.checksum populated
|
||||||
|
unsigned long sum = 0;
|
||||||
|
|
||||||
|
// add the header, bytes reversed since we're using little-endian arithmetic.
|
||||||
|
sum += _makeUint16(echo.icmpHeader.type, echo.icmpHeader.code);
|
||||||
|
|
||||||
|
// add id and sequence
|
||||||
|
sum += echo.id + echo.seq;
|
||||||
|
|
||||||
|
// add time, one half at a time.
|
||||||
|
uint16_t const * time = (uint16_t const *)&echo.time;
|
||||||
|
sum += *time + *(time + 1);
|
||||||
|
|
||||||
|
// add the payload
|
||||||
|
for (uint8_t const * b = echo.payload; b < echo.payload + sizeof(echo.payload); b+=2)
|
||||||
|
{
|
||||||
|
sum += _makeUint16(*b, *(b + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ones complement of ones complement sum
|
||||||
|
sum = (sum >> 16) + (sum & 0xffff);
|
||||||
|
sum += (sum >> 16);
|
||||||
|
return ~sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
ICMPEcho::ICMPEcho(uint8_t type, uint16_t _id, uint16_t _seq, uint8_t * _payload)
|
||||||
|
: seq(_seq), id(_id), time(millis())
|
||||||
|
{
|
||||||
|
memcpy(payload, _payload, REQ_DATASIZE);
|
||||||
|
icmpHeader.type = type;
|
||||||
|
icmpHeader.code = 0;
|
||||||
|
icmpHeader.checksum = _checksum(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
ICMPEcho::ICMPEcho()
|
||||||
|
: seq(0), id(0), time(0)
|
||||||
|
{
|
||||||
|
memset(payload, 0, sizeof(payload));
|
||||||
|
icmpHeader.code = 0;
|
||||||
|
icmpHeader.type = 0;
|
||||||
|
icmpHeader.checksum = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ICMPEcho::serialize(uint8_t * binData) const
|
||||||
|
{
|
||||||
|
*(binData++) = icmpHeader.type;
|
||||||
|
*(binData++) = icmpHeader.code;
|
||||||
|
|
||||||
|
*(uint16_t *)binData = htons(icmpHeader.checksum); binData += 2;
|
||||||
|
*(uint16_t *)binData = htons(id); binData += 2;
|
||||||
|
*(uint16_t *)binData = htons(seq); binData += 2;
|
||||||
|
*(icmp_time_t *) binData = htonl(time); binData += 4;
|
||||||
|
|
||||||
|
memcpy(binData, payload, sizeof(payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ICMPEcho::deserialize(uint8_t const * binData)
|
||||||
|
{
|
||||||
|
icmpHeader.type = *(binData++);
|
||||||
|
icmpHeader.code = *(binData++);
|
||||||
|
|
||||||
|
icmpHeader.checksum = ntohs(*(uint16_t *)binData); binData += 2;
|
||||||
|
id = ntohs(*(uint16_t *)binData); binData += 2;
|
||||||
|
seq = ntohs(*(uint16_t *)binData); binData += 2;
|
||||||
|
|
||||||
|
if (icmpHeader.type != TIME_EXCEEDED)
|
||||||
|
{
|
||||||
|
time = ntohl(*(icmp_time_t *)binData); binData += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(payload, binData, sizeof(payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uint16_t ICMPPing::ping_timeout = PING_TIMEOUT;
|
||||||
|
|
||||||
|
ICMPPing::ICMPPing(SOCKET socket, uint8_t id) :
|
||||||
|
#ifdef ICMPPING_ASYNCH_ENABLE
|
||||||
|
_curSeq(0), _numRetries(0), _asyncstart(0), _asyncstatus(BAD_RESPONSE),
|
||||||
|
#endif
|
||||||
|
_id(id), _nextSeq(0), _socket(socket), _attempt(0)
|
||||||
|
{
|
||||||
|
memset(_payload, 0x1A, REQ_DATASIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ICMPPing::setPayload(uint8_t * payload)
|
||||||
|
{
|
||||||
|
memcpy(_payload, payload, REQ_DATASIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ICMPPing::openSocket()
|
||||||
|
{
|
||||||
|
|
||||||
|
W5100.execCmdSn(_socket, Sock_CLOSE);
|
||||||
|
W5100.writeSnIR(_socket, 0xFF);
|
||||||
|
W5100.writeSnMR(_socket, SnMR::IPRAW);
|
||||||
|
W5100.writeSnPROTO(_socket, IPPROTO::ICMP);
|
||||||
|
W5100.writeSnPORT(_socket, 0);
|
||||||
|
W5100.execCmdSn(_socket, Sock_OPEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void ICMPPing::operator()(const IPAddress& addr, int nRetries, ICMPEchoReply& result)
|
||||||
|
{
|
||||||
|
openSocket();
|
||||||
|
|
||||||
|
ICMPEcho echoReq(ICMP_ECHOREQ, _id, _nextSeq++, _payload);
|
||||||
|
|
||||||
|
for (_attempt=0; _attempt<nRetries; ++_attempt)
|
||||||
|
{
|
||||||
|
|
||||||
|
ICMPPING_DOYIELD();
|
||||||
|
|
||||||
|
result.status = sendEchoRequest(addr, echoReq);
|
||||||
|
if (result.status == SUCCESS)
|
||||||
|
{
|
||||||
|
byte replyAddr [4];
|
||||||
|
ICMPPING_DOYIELD();
|
||||||
|
receiveEchoReply(echoReq, addr, result);
|
||||||
|
}
|
||||||
|
if (result.status == SUCCESS)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
W5100.execCmdSn(_socket, Sock_CLOSE);
|
||||||
|
W5100.writeSnIR(_socket, 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
ICMPEchoReply ICMPPing::operator()(const IPAddress& addr, int nRetries)
|
||||||
|
{
|
||||||
|
ICMPEchoReply reply;
|
||||||
|
operator()(addr, nRetries, reply);
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
|
||||||
|
Status ICMPPing::sendEchoRequest(const IPAddress& addr, const ICMPEcho& echoReq)
|
||||||
|
{
|
||||||
|
// I wish there were a better way of doing this, but if we use the uint32_t
|
||||||
|
// cast operator, we're forced to (1) cast away the constness, and (2) deal
|
||||||
|
// with an endianness nightmare.
|
||||||
|
uint8_t addri [] = {addr[0], addr[1], addr[2], addr[3]};
|
||||||
|
W5100.writeSnDIPR(_socket, addri);
|
||||||
|
W5100.writeSnTTL(_socket, 128);
|
||||||
|
// The port isn't used, becuause ICMP is a network-layer protocol. So we
|
||||||
|
// write zero. This probably isn't actually necessary.
|
||||||
|
W5100.writeSnDPORT(_socket, 0);
|
||||||
|
|
||||||
|
uint8_t serialized [sizeof(ICMPEcho)];
|
||||||
|
echoReq.serialize(serialized);
|
||||||
|
|
||||||
|
W5100.send_data_processing(_socket, serialized, sizeof(ICMPEcho));
|
||||||
|
W5100.execCmdSn(_socket, Sock_SEND);
|
||||||
|
|
||||||
|
while ((W5100.readSnIR(_socket) & SnIR::SEND_OK) != SnIR::SEND_OK)
|
||||||
|
{
|
||||||
|
if (W5100.readSnIR(_socket) & SnIR::TIMEOUT)
|
||||||
|
{
|
||||||
|
W5100.writeSnIR(_socket, (SnIR::SEND_OK | SnIR::TIMEOUT));
|
||||||
|
return SEND_TIMEOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
ICMPPING_DOYIELD();
|
||||||
|
}
|
||||||
|
W5100.writeSnIR(_socket, SnIR::SEND_OK);
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ICMPPing::receiveEchoReply(const ICMPEcho& echoReq, const IPAddress& addr, ICMPEchoReply& echoReply)
|
||||||
|
{
|
||||||
|
icmp_time_t start = millis();
|
||||||
|
while (millis() - start < ping_timeout)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (W5100.getRXReceivedSize(_socket) < 1)
|
||||||
|
{
|
||||||
|
// take a break, maybe let platform do
|
||||||
|
// some background work (like on ESP8266)
|
||||||
|
ICMPPING_DOYIELD();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ah! we did receive something... check it out.
|
||||||
|
|
||||||
|
uint8_t ipHeader[6];
|
||||||
|
uint8_t buffer = W5100.readSnRX_RD(_socket);
|
||||||
|
W5100.read_data(_socket, (uint16_t) buffer, ipHeader, sizeof(ipHeader));
|
||||||
|
buffer += sizeof(ipHeader);
|
||||||
|
for (int i = 0; i < 4; ++i)
|
||||||
|
echoReply.addr[i] = ipHeader[i];
|
||||||
|
uint8_t dataLen = ipHeader[4];
|
||||||
|
dataLen = (dataLen << 8) + ipHeader[5];
|
||||||
|
|
||||||
|
uint8_t serialized[sizeof(ICMPEcho)];
|
||||||
|
if (dataLen > sizeof(ICMPEcho))
|
||||||
|
dataLen = sizeof(ICMPEcho);
|
||||||
|
W5100.read_data(_socket, (uint16_t) buffer, serialized, dataLen);
|
||||||
|
echoReply.data.deserialize(serialized);
|
||||||
|
|
||||||
|
buffer += dataLen;
|
||||||
|
W5100.writeSnRX_RD(_socket, buffer);
|
||||||
|
W5100.execCmdSn(_socket, Sock_RECV);
|
||||||
|
|
||||||
|
echoReply.ttl = W5100.readSnTTL(_socket);
|
||||||
|
|
||||||
|
// Since there aren't any ports in ICMP, we need to manually inspect the response
|
||||||
|
// to see if it originated from the request we sent out.
|
||||||
|
switch (echoReply.data.icmpHeader.type) {
|
||||||
|
case ICMP_ECHOREP: {
|
||||||
|
if (echoReply.data.id == echoReq.id
|
||||||
|
&& echoReply.data.seq == echoReq.seq) {
|
||||||
|
echoReply.status = SUCCESS;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TIME_EXCEEDED: {
|
||||||
|
uint8_t * sourceIpHeader = echoReply.data.payload;
|
||||||
|
unsigned int ipHeaderSize = (sourceIpHeader[0] & 0x0F) * 4u;
|
||||||
|
uint8_t * sourceIcmpHeader = echoReply.data.payload + ipHeaderSize;
|
||||||
|
|
||||||
|
// The destination ip address in the originating packet's IP header.
|
||||||
|
IPAddress sourceDestAddress(sourceIpHeader + ipHeaderSize - 4);
|
||||||
|
|
||||||
|
if (!(sourceDestAddress == addr))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
uint16_t sourceId = ntohs(*(uint16_t * )(sourceIcmpHeader + 4));
|
||||||
|
uint16_t sourceSeq = ntohs(*(uint16_t * )(sourceIcmpHeader + 6));
|
||||||
|
|
||||||
|
if (sourceId == echoReq.id && sourceSeq == echoReq.seq) {
|
||||||
|
echoReply.status = BAD_RESPONSE;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
echoReply.status = NO_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef ICMPPING_ASYNCH_ENABLE
|
||||||
|
/*
|
||||||
|
* When ICMPPING_ASYNCH_ENABLE is defined, we have access to the
|
||||||
|
* asyncStart()/asyncComplete() methods from the API.
|
||||||
|
*/
|
||||||
|
bool ICMPPing::asyncSend(ICMPEchoReply& result)
|
||||||
|
{
|
||||||
|
ICMPEcho echoReq(ICMP_ECHOREQ, _id, _curSeq, _payload);
|
||||||
|
|
||||||
|
Status sendOpResult(NO_RESPONSE);
|
||||||
|
bool sendSuccess = false;
|
||||||
|
for (uint8_t i=_attempt; i<_numRetries; ++i)
|
||||||
|
{
|
||||||
|
_attempt++;
|
||||||
|
|
||||||
|
ICMPPING_DOYIELD();
|
||||||
|
sendOpResult = sendEchoRequest(_addr, echoReq);
|
||||||
|
if (sendOpResult == SUCCESS)
|
||||||
|
{
|
||||||
|
sendSuccess = true; // it worked
|
||||||
|
sendOpResult = ASYNC_SENT; // we're doing this async-style, force the status
|
||||||
|
_asyncstart = millis(); // not the start time, for timeouts
|
||||||
|
break; // break out of this loop, 'cause we're done.
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_asyncstatus = sendOpResult; // keep track of this, in case the ICMPEchoReply isn't re-used
|
||||||
|
result.status = _asyncstatus; // set the result, in case the ICMPEchoReply is checked
|
||||||
|
return sendSuccess; // return success of send op
|
||||||
|
}
|
||||||
|
bool ICMPPing::asyncStart(const IPAddress& addr, int nRetries, ICMPEchoReply& result)
|
||||||
|
{
|
||||||
|
openSocket();
|
||||||
|
|
||||||
|
// stash our state, so we can access
|
||||||
|
// in asynchSend()/asyncComplete()
|
||||||
|
_numRetries = nRetries;
|
||||||
|
_attempt = 0;
|
||||||
|
_curSeq = _nextSeq++;
|
||||||
|
_addr = addr;
|
||||||
|
|
||||||
|
return asyncSend(result);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ICMPPing::asyncComplete(ICMPEchoReply& result)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (_asyncstatus != ASYNC_SENT)
|
||||||
|
{
|
||||||
|
// we either:
|
||||||
|
// - didn't start an async request;
|
||||||
|
// - failed to send; or
|
||||||
|
// - are no longer waiting on this packet.
|
||||||
|
// either way, we're done
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (W5100.getRXReceivedSize(_socket))
|
||||||
|
{
|
||||||
|
// ooooh, we've got a pending reply
|
||||||
|
ICMPEcho echoReq(ICMP_ECHOREQ, _id, _curSeq, _payload);
|
||||||
|
receiveEchoReply(echoReq, _addr, result);
|
||||||
|
_asyncstatus = result.status; // make note of this status, whatever it is.
|
||||||
|
return true; // whatever the result of the receiveEchoReply(), the async op is done.
|
||||||
|
}
|
||||||
|
|
||||||
|
// nothing yet... check if we've timed out
|
||||||
|
if ( (millis() - _asyncstart) > ping_timeout)
|
||||||
|
{
|
||||||
|
|
||||||
|
// yep, we've timed out...
|
||||||
|
if (_attempt < _numRetries)
|
||||||
|
{
|
||||||
|
// still, this wasn't our last attempt, let's try again
|
||||||
|
if (asyncSend(result))
|
||||||
|
{
|
||||||
|
// another send has succeeded
|
||||||
|
// we'll wait for that now...
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this send has failed. too bad,
|
||||||
|
// we are done.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we timed out and have no more attempts left...
|
||||||
|
// hello? is anybody out there?
|
||||||
|
// guess not:
|
||||||
|
result.status = NO_RESPONSE;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// have yet to time out, will wait some more:
|
||||||
|
return false; // results still not in
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* ICMPPING_ASYNCH_ENABLE */
|
||||||
|
|
||||||
|
|
||||||
|
|
298
Arduino/libraries/ICMPPing/ICMPPing.h
Normal file
298
Arduino/libraries/ICMPPing/ICMPPing.h
Normal file
@ -0,0 +1,298 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2010 by Blake Foster <blfoster@vassar.edu>
|
||||||
|
*
|
||||||
|
* This file is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of either the GNU General Public License version 2
|
||||||
|
* or the GNU Lesser General Public License version 2.1, both as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <SPI.h>
|
||||||
|
#include <Ethernet.h>
|
||||||
|
#include <utility/w5100.h>
|
||||||
|
|
||||||
|
#define REQ_DATASIZE 64
|
||||||
|
#define ICMP_ECHOREPLY 0
|
||||||
|
#define ICMP_ECHOREQ 8
|
||||||
|
#define ICMP_ECHOREP 0
|
||||||
|
#define TIME_EXCEEDED 11
|
||||||
|
#define PING_TIMEOUT 1000
|
||||||
|
|
||||||
|
|
||||||
|
// ICMPPING_ASYNCH_ENABLE -- define this to enable asynch operations
|
||||||
|
// #define ICMPPING_ASYNCH_ENABLE
|
||||||
|
|
||||||
|
// ICMPPING_INSERT_YIELDS -- some platforms, such as ESP8266, like
|
||||||
|
// (read: need) to do background work so control must be yielded
|
||||||
|
// back to the main system periodically when you are doing something
|
||||||
|
// that takes a good while.
|
||||||
|
// Define (uncomment the following line) on these platforms, which
|
||||||
|
// will call a short delay() at critical junctures.
|
||||||
|
// #define ICMPPING_INSERT_YIELDS
|
||||||
|
|
||||||
|
typedef unsigned long icmp_time_t;
|
||||||
|
|
||||||
|
class ICMPHeader;
|
||||||
|
class ICMPPing;
|
||||||
|
|
||||||
|
typedef enum Status
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
Indicates whether a ping succeeded or failed due to one of various error
|
||||||
|
conditions. These correspond to error conditions that occur in this
|
||||||
|
library, not anything defined in the ICMP protocol.
|
||||||
|
*/
|
||||||
|
SUCCESS = 0,
|
||||||
|
SEND_TIMEOUT = 1, // Timed out sending the request
|
||||||
|
NO_RESPONSE = 2, // Died waiting for a response
|
||||||
|
BAD_RESPONSE = 3, // we got back the wrong type
|
||||||
|
ASYNC_SENT = 4
|
||||||
|
} Status;
|
||||||
|
|
||||||
|
|
||||||
|
struct ICMPHeader
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
Header for an ICMP packet. Does not include the IP header.
|
||||||
|
*/
|
||||||
|
uint8_t type;
|
||||||
|
uint8_t code;
|
||||||
|
uint16_t checksum;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct ICMPEcho
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
Contents of an ICMP echo packet, including the ICMP header. Does not
|
||||||
|
include the IP header.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
This constructor sets all fields and calculates the checksum. It is used
|
||||||
|
to create ICMP packet data when we send a request.
|
||||||
|
@param type: ICMP_ECHOREQ or ICMP_ECHOREP.
|
||||||
|
@param _id: Some arbitrary id. Usually set once per process.
|
||||||
|
@param _seq: The sequence number. Usually started at zero and incremented
|
||||||
|
once per request.
|
||||||
|
@param payload: An arbitrary chunk of data that we expect to get back in
|
||||||
|
the response.
|
||||||
|
*/
|
||||||
|
ICMPEcho(uint8_t type, uint16_t _id, uint16_t _seq, uint8_t * _payload);
|
||||||
|
|
||||||
|
/*
|
||||||
|
This constructor leaves everything zero. This is used when we receive a
|
||||||
|
response, since we nuke whatever is here already when we copy the packet
|
||||||
|
data out of the W5100.
|
||||||
|
*/
|
||||||
|
ICMPEcho();
|
||||||
|
|
||||||
|
ICMPHeader icmpHeader;
|
||||||
|
uint16_t id;
|
||||||
|
uint16_t seq;
|
||||||
|
icmp_time_t time;
|
||||||
|
uint8_t payload [REQ_DATASIZE];
|
||||||
|
|
||||||
|
/*
|
||||||
|
Serialize the header as a byte array, in big endian format.
|
||||||
|
*/
|
||||||
|
void serialize(byte * binData) const;
|
||||||
|
/*
|
||||||
|
Serialize the header as a byte array, in big endian format.
|
||||||
|
*/
|
||||||
|
void deserialize(byte const * binData);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct ICMPEchoReply
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
Struct returned by ICMPPing().
|
||||||
|
@param data: The packet data, including the ICMP header.
|
||||||
|
@param ttl: Time to live
|
||||||
|
@param status: SUCCESS if the ping succeeded. One of various error codes
|
||||||
|
if it failed.
|
||||||
|
@param addr: The ip address that we received the response from. Something
|
||||||
|
is borked if this doesn't match the IP address we pinged.
|
||||||
|
*/
|
||||||
|
ICMPEcho data;
|
||||||
|
uint8_t ttl;
|
||||||
|
Status status;
|
||||||
|
IPAddress addr;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class ICMPPing
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
Function-object for sending ICMP ping requests.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*
|
||||||
|
Construct an ICMP ping object.
|
||||||
|
@param socket: The socket number in the W5100.
|
||||||
|
@param id: The id to put in the ping packets. Can be pretty much any
|
||||||
|
arbitrary number.
|
||||||
|
*/
|
||||||
|
ICMPPing(SOCKET s, uint8_t id);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Control the ping timeout (ms). Defaults to PING_TIMEOUT (1000ms) but can
|
||||||
|
be set using setTimeout(MS).
|
||||||
|
@param timeout_ms: Timeout for ping replies, in milliseconds.
|
||||||
|
@note: this value is static -- i.e. system-wide for all ICMPPing objects.
|
||||||
|
*/
|
||||||
|
static void setTimeout(uint16_t setTo) { ping_timeout = setTo;}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Fetch the current setting for ping timeouts (in ms).
|
||||||
|
@return: timeout for all ICMPPing requests, in milliseconds.
|
||||||
|
*/
|
||||||
|
static uint16_t timeout() { return ping_timeout;}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Pings the given IP address.
|
||||||
|
@param addr: IP address to ping, as an array of four octets.
|
||||||
|
@param nRetries: Number of times to rety before giving up.
|
||||||
|
@return: An ICMPEchoReply containing the response. The status field in
|
||||||
|
the return value indicates whether the echo request succeeded or
|
||||||
|
failed. If the request failed, the status indicates the reason for
|
||||||
|
failure on the last retry.
|
||||||
|
*/
|
||||||
|
ICMPEchoReply operator()(const IPAddress&, int nRetries);
|
||||||
|
|
||||||
|
/*
|
||||||
|
This overloaded version of the () operator takes a (hopefully blank)
|
||||||
|
ICMPEchoReply as parameter instead of constructing one internally and
|
||||||
|
then copying it on return. This creates a very small improvement in
|
||||||
|
efficiency at the cost of making your code uglier.
|
||||||
|
@param addr: IP address to ping, as an array of four octets.
|
||||||
|
@param nRetries: Number of times to rety before giving up.
|
||||||
|
@param result: ICMPEchoReply that will hold the result.
|
||||||
|
*/
|
||||||
|
void operator()(const IPAddress& addr, int nRetries, ICMPEchoReply& result);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Use setPayload to set custom data for all ICMP packets
|
||||||
|
by passing it an array of [REQ_DATASIZE]. E.g.
|
||||||
|
uint8_t myPayload[REQ_DATASIZE] = { ... whatever ...};
|
||||||
|
ICMPPing ping(pingSocket, (uint16_t)random(0, 255));
|
||||||
|
ping.setPayload(myPayload);
|
||||||
|
// ... as usual ...
|
||||||
|
|
||||||
|
@param payload: pointer to start of REQ_DATASIZE array of bytes to use as payload
|
||||||
|
|
||||||
|
*/
|
||||||
|
void setPayload(uint8_t * payload);
|
||||||
|
|
||||||
|
#ifdef ICMPPING_ASYNCH_ENABLE
|
||||||
|
/*
|
||||||
|
Asynchronous ping methods -- only enabled if ICMPPING_ASYNCH_ENABLE is defined, above.
|
||||||
|
|
||||||
|
These methods are used to start a ping request, go do something else, and
|
||||||
|
come back later to check if the results are in. A complete example is in the
|
||||||
|
examples directory but the gist of it is E.g.
|
||||||
|
|
||||||
|
|
||||||
|
// say we're in some function, to simplify things...
|
||||||
|
IPAddress pingAddr(74,125,26,147); // ip address to ping
|
||||||
|
|
||||||
|
ICMPPing ping(0, (uint16_t)random(0, 255));
|
||||||
|
ICMPEchoReply theResult;
|
||||||
|
|
||||||
|
if (! asyncStart(pingAddr, 3, theResult))
|
||||||
|
{
|
||||||
|
// well, this didn't start off on the right foot
|
||||||
|
Serial.print("Echo request send failed; ");
|
||||||
|
Serial.println((int)theResult.status);
|
||||||
|
|
||||||
|
//
|
||||||
|
return; // forget about this
|
||||||
|
}
|
||||||
|
|
||||||
|
// ok, ping has started...
|
||||||
|
while (! ping.asyncComplete(theResult)) {
|
||||||
|
|
||||||
|
// whatever needs handling while we wait on results
|
||||||
|
doSomeStuff();
|
||||||
|
doSomeOtherStuff();
|
||||||
|
delay(30);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// we get here means we either got a response, or timed out...
|
||||||
|
if (theResult.status == SUCCESS)
|
||||||
|
{
|
||||||
|
// yay... do something.
|
||||||
|
} else {
|
||||||
|
// boooo... do something else.
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
asyncStart -- begins a new ping request, asynchronously. Parameters are the
|
||||||
|
same as for regular ping, but the method returns false on error.
|
||||||
|
|
||||||
|
@param addr: IP address to ping, as an array of four octets.
|
||||||
|
@param nRetries: Number of times to rety before giving up.
|
||||||
|
@param result: ICMPEchoReply that will hold a status == ASYNC_SENT on success.
|
||||||
|
@return: true on async request sent, false otherwise.
|
||||||
|
@author: Pat Deegan, http://psychogenic.com
|
||||||
|
*/
|
||||||
|
bool asyncStart(const IPAddress& addr, int nRetries, ICMPEchoReply& result);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
asyncComplete -- check if the asynchronous ping is done.
|
||||||
|
This can be either because of a successful outcome (reply received)
|
||||||
|
or because of an error/timeout.
|
||||||
|
|
||||||
|
@param result: ICMPEchoReply that will hold the result.
|
||||||
|
@return: true if the result ICMPEchoReply contains the status/other data,
|
||||||
|
false if we're still waiting for it to complete.
|
||||||
|
@author: Pat Deegan, http://psychogenic.com
|
||||||
|
*/
|
||||||
|
bool asyncComplete(ICMPEchoReply& result);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
// holds the timeout, in ms, for all objects of this class.
|
||||||
|
static uint16_t ping_timeout;
|
||||||
|
|
||||||
|
void openSocket();
|
||||||
|
|
||||||
|
Status sendEchoRequest(const IPAddress& addr, const ICMPEcho& echoReq);
|
||||||
|
void receiveEchoReply(const ICMPEcho& echoReq, const IPAddress& addr, ICMPEchoReply& echoReply);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef ICMPPING_ASYNCH_ENABLE
|
||||||
|
// extra internal state/methods used when asynchronous pings
|
||||||
|
// are enabled.
|
||||||
|
bool asyncSend(ICMPEchoReply& result);
|
||||||
|
uint8_t _curSeq;
|
||||||
|
uint8_t _numRetries;
|
||||||
|
icmp_time_t _asyncstart;
|
||||||
|
Status _asyncstatus;
|
||||||
|
IPAddress _addr;
|
||||||
|
#endif
|
||||||
|
uint8_t _id;
|
||||||
|
uint8_t _nextSeq;
|
||||||
|
SOCKET _socket;
|
||||||
|
uint8_t _attempt;
|
||||||
|
|
||||||
|
uint8_t _payload[REQ_DATASIZE];
|
||||||
|
};
|
||||||
|
|
||||||
|
#pragma pack(1)
|
31
Arduino/libraries/ICMPPing/keywords.txt
Normal file
31
Arduino/libraries/ICMPPing/keywords.txt
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#######################################
|
||||||
|
# Syntax Coloring Map For Ethernet
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Datatypes (KEYWORD1)
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
ICMPPing KEYWORD1
|
||||||
|
ICMPHeader KEYWORD1
|
||||||
|
ICMPEcho KEYWORD1
|
||||||
|
ICMPEchoReply KEYWORD1
|
||||||
|
Status KEYWORD1
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Methods and Functions (KEYWORD2)
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Constants (LITERAL1)
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
SUCCESS LITERAL1
|
||||||
|
SEND_TIMEOUT LITERAL1
|
||||||
|
NO_RESPONSE LITERAL1
|
||||||
|
BAD_RESPONSE LITERAL1
|
||||||
|
REQ_DATASIZE LITERAL1
|
||||||
|
ICMP_ECHOREPLY LITERAL1
|
||||||
|
ICMP_ECHOREQ LITERAL1
|
||||||
|
ICMP_ECHOREP LITERAL1
|
||||||
|
PING_TIMEOUT LITERAL1
|
14
Arduino/libraries/ICMPPing/util.h
Normal file
14
Arduino/libraries/ICMPPing/util.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#ifndef UTIL_H
|
||||||
|
#define UTIL_H
|
||||||
|
|
||||||
|
#define htons(x) ( ((x)<< 8 & 0xFF00) | \
|
||||||
|
((x)>> 8 & 0x00FF) )
|
||||||
|
#define ntohs(x) htons(x)
|
||||||
|
|
||||||
|
#define htonl(x) ( ((x)<<24 & 0xFF000000UL) | \
|
||||||
|
((x)<< 8 & 0x00FF0000UL) | \
|
||||||
|
((x)>> 8 & 0x0000FF00UL) | \
|
||||||
|
((x)>>24 & 0x000000FFUL) )
|
||||||
|
#define ntohl(x) htonl(x)
|
||||||
|
|
||||||
|
#endif
|
@ -15,10 +15,10 @@ void resetSensor() {
|
|||||||
//
|
//
|
||||||
// W5500 configuration
|
// W5500 configuration
|
||||||
#include <SPI.h>
|
#include <SPI.h>
|
||||||
#include <Ethernet3.h>
|
#include <Ethernet.h>
|
||||||
|
|
||||||
// Configure MAC address and IP:
|
// Configure MAC address and IP:
|
||||||
byte mac[] = { 0x61, 0x2C, 0xF2, 0x09, 0x73, 0xBE };
|
byte mac[] = { 0x97, 0x8A, 0xC5, 0x86, 0xA4, 0xEF };
|
||||||
char T[8];
|
char T[8];
|
||||||
char H[8];
|
char H[8];
|
||||||
char message[20];
|
char message[20];
|
||||||
@ -28,10 +28,17 @@ char message[20];
|
|||||||
|
|
||||||
|
|
||||||
// Define CS and RST pins:
|
// Define CS and RST pins:
|
||||||
#define W5500_CS_PIN 10 // 8 in E LEGO
|
#define W5500_CS_PIN 10 // 8 in E LEGO
|
||||||
#define W5500_RST_PIN 9 //10 in E LEGO
|
#define W5500_RST_PIN 9 //10 in E LEGO
|
||||||
IPAddress serverIP(10, 44, 1, 238); // Computer
|
//IPAddress serverIP(10, 44, 1, 238); // Computer
|
||||||
IPAddress W5500_ip(10, 44, 1, 22); // Change the last digit
|
//IPAddress W5500_ip(10, 44, 1, 22); // Change the last digit
|
||||||
|
IPAddress serverIP(10, 44, 1, 238);
|
||||||
|
IPAddress W5500_ip(10, 11, 1, 46);
|
||||||
|
IPAddress gateway(10, 11, 1, 1);
|
||||||
|
IPAddress DNS(147,142,19,254);
|
||||||
|
IPAddress subnet(255, 255, 255, 0);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Don't change
|
// Don't change
|
||||||
const int port = 5005;
|
const int port = 5005;
|
||||||
@ -78,9 +85,9 @@ void setup() {
|
|||||||
|
|
||||||
// Initialize Ethernet:
|
// Initialize Ethernet:
|
||||||
Ethernet.init(W5500_CS_PIN);
|
Ethernet.init(W5500_CS_PIN);
|
||||||
Ethernet.begin(mac, W5500_ip);
|
Ethernet.begin(mac, W5500_ip, DNS, gateway, subnet);
|
||||||
delay(1500);
|
|
||||||
|
|
||||||
|
delay(1500);
|
||||||
Serial.print("W5500 IP: ");
|
Serial.print("W5500 IP: ");
|
||||||
Serial.println(Ethernet.localIP());
|
Serial.println(Ethernet.localIP());
|
||||||
|
|
||||||
@ -93,12 +100,13 @@ void setup() {
|
|||||||
Serial.println("Test Message sent");
|
Serial.println("Test Message sent");
|
||||||
} else {
|
} else {
|
||||||
Serial.println("Connection failed");
|
Serial.println("Connection failed");
|
||||||
|
while(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
// put your main code here, to run repeatedly:
|
// put your main code here, to run repeatedly:
|
||||||
// Measure temp. and humidity every 5 seconds
|
// Measure temp. and humidity every 30 seconds
|
||||||
delay(15000);
|
delay(15000);
|
||||||
float t = sht31.readTemperature();
|
float t = sht31.readTemperature();
|
||||||
float h = sht31.readHumidity();
|
float h = sht31.readHumidity();
|
||||||
|
@ -20,5 +20,5 @@ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|||||||
with conn:
|
with conn:
|
||||||
data = conn.recv(1024)
|
data = conn.recv(1024)
|
||||||
if data:
|
if data:
|
||||||
print(f"Received from {addr} at {datetime.now()}: {data}")
|
print(f"Received from {addr} at {datetime.now()}: {data.decode()}")
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ def write_data_to_influxDB(influx_client: InfluxDBClient, write_api: WriteApi, r
|
|||||||
def setup_logger(logName):
|
def setup_logger(logName):
|
||||||
|
|
||||||
# creat logger
|
# creat logger
|
||||||
log_dir = "logs"
|
log_dir = "../../Data/logs"
|
||||||
os.makedirs(log_dir, exist_ok=True)
|
os.makedirs(log_dir, exist_ok=True)
|
||||||
|
|
||||||
# setup logging
|
# setup logging
|
||||||
@ -117,7 +117,7 @@ if __name__ == "__main__":
|
|||||||
if (message is not None and temp_or_hum == "T"):
|
if (message is not None and temp_or_hum == "T"):
|
||||||
print (str (datetime.now () )[:19] + ": Temp is " + message)
|
print (str (datetime.now () )[:19] + ": Temp is " + message)
|
||||||
T = float (message)
|
T = float (message)
|
||||||
p1 = influxdb_client.Point ("FerDy").tag("Table", "Philipp's").field ("temp_on_desk", T)
|
p1 = influxdb_client.Point ("FerDy").tag("Climate_control", "temperature").field ("temp_Eilons_desk", T)
|
||||||
temp_or_hum = "a"
|
temp_or_hum = "a"
|
||||||
influx_client, write_api = write_data_to_influxDB (influx_client, write_api, p1)
|
influx_client, write_api = write_data_to_influxDB (influx_client, write_api, p1)
|
||||||
time.sleep (update_interval/2)
|
time.sleep (update_interval/2)
|
||||||
@ -125,7 +125,7 @@ if __name__ == "__main__":
|
|||||||
elif (message is not None and temp_or_hum == "H"):
|
elif (message is not None and temp_or_hum == "H"):
|
||||||
print (str (datetime.now () )[:19] + ": humidity is " + message)
|
print (str (datetime.now () )[:19] + ": humidity is " + message)
|
||||||
H = float (message)
|
H = float (message)
|
||||||
p1 = influxdb_client.Point ("FerDy").tag("Table", "Philipp's").field ("hum_on_desk", H)
|
p1 = influxdb_client.Point ("FerDy").tag("Climate_control", "humidity").field ("hum_Eilons_desk", H)
|
||||||
temp_or_hum = "a"
|
temp_or_hum = "a"
|
||||||
influx_client, write_api = write_data_to_influxDB (influx_client, write_api, p1)
|
influx_client, write_api = write_data_to_influxDB (influx_client, write_api, p1)
|
||||||
time.sleep (update_interval/2)
|
time.sleep (update_interval/2)
|
||||||
|
@ -0,0 +1,111 @@
|
|||||||
|
#include <SPI.h>
|
||||||
|
#include <Ethernet.h>
|
||||||
|
#include "Adafruit_SHT31.h"
|
||||||
|
|
||||||
|
Adafruit_SHT31 sht31 = Adafruit_SHT31();
|
||||||
|
|
||||||
|
#define SHT31_RST_PIN 16
|
||||||
|
|
||||||
|
void reset_SHT31() {
|
||||||
|
pinMode(SHT31_RST_PIN, OUTPUT);
|
||||||
|
digitalWrite(SHT31_RST_PIN, LOW);
|
||||||
|
delay(10);
|
||||||
|
digitalWrite(SHT31_RST_PIN, HIGH);
|
||||||
|
delay(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
// MAC and IP configuration
|
||||||
|
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
|
||||||
|
IPAddress ip(10, 11, 1, 46);
|
||||||
|
IPAddress gateway(10, 11, 1, 1);
|
||||||
|
IPAddress subnet(255, 255, 255, 0);
|
||||||
|
|
||||||
|
// Server to connect to
|
||||||
|
IPAddress server(10, 44, 1, 238);
|
||||||
|
const int serverPort = 5005; // Change to the port your server listens on
|
||||||
|
|
||||||
|
EthernetClient client;
|
||||||
|
|
||||||
|
char T[8];
|
||||||
|
char H[8];
|
||||||
|
char message[20];
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
|
||||||
|
// Initialize Ethernet
|
||||||
|
Serial.begin(9600);
|
||||||
|
while (!Serial); // Wait for Serial (only needed for native USB boards)
|
||||||
|
|
||||||
|
Ethernet.begin(mac, ip, gateway, gateway, subnet);
|
||||||
|
delay(1000); // Give Ethernet shield time to initialize
|
||||||
|
|
||||||
|
Serial.println("Connecting to server");
|
||||||
|
|
||||||
|
if (client.connect(server, serverPort)) {
|
||||||
|
client.write("Connection check");
|
||||||
|
Serial.println("Connection successful");
|
||||||
|
client.stop();
|
||||||
|
} else {
|
||||||
|
Serial.println("Connection failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Initialize SHT31
|
||||||
|
if (!sht31.begin(0X44)) {
|
||||||
|
Serial.println("Couldn't find SHT31");
|
||||||
|
while (1) delay(100);
|
||||||
|
}
|
||||||
|
Serial.println("SHT31 Found");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
// put your main code here, to run repeatedly:
|
||||||
|
// Measure temp. and humidity every 30 seconds
|
||||||
|
delay(15000);
|
||||||
|
float t = sht31.readTemperature();
|
||||||
|
float h = sht31.readHumidity();
|
||||||
|
|
||||||
|
// Report temperature to Eilon's computer
|
||||||
|
if (client.connect(server, serverPort)) {
|
||||||
|
client.write("Temp");
|
||||||
|
client.stop();
|
||||||
|
} else {
|
||||||
|
Serial.println("Connection failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client.connect(server, serverPort)) {
|
||||||
|
if (!isnan(t)) {
|
||||||
|
dtostrf(t, 6, 2, T);
|
||||||
|
snprintf(message, sizeof(message), "T %s", T);
|
||||||
|
client.write(T);
|
||||||
|
client.stop();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Serial.println("Connection failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report humidity to Eilon's computer
|
||||||
|
delay(15000);
|
||||||
|
if (client.connect(server, serverPort)) {
|
||||||
|
client.write("Humidity");
|
||||||
|
client.stop();
|
||||||
|
} else {
|
||||||
|
Serial.println("Connection failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client.connect(server, serverPort)) {
|
||||||
|
if (!isnan(h)) {
|
||||||
|
dtostrf(h, 6, 2, H);
|
||||||
|
snprintf(message, sizeof(message), "H %s", H);
|
||||||
|
client.write(H);
|
||||||
|
client.stop();
|
||||||
|
Serial.println("message sent");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Serial.println("Connection failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,24 +0,0 @@
|
|||||||
2025-07-01 12:04:21,940 - INFO - Logger set up complete
|
|
||||||
2025-07-01 12:04:21,940 - INFO - Reading the temperature on Phillip's desk
|
|
||||||
2025-07-01 12:05:54,673 - INFO - Logger set up complete
|
|
||||||
2025-07-01 12:05:54,673 - INFO - Reading the temperature on Phillip's desk
|
|
||||||
2025-07-01 12:09:29,668 - INFO - Logger set up complete
|
|
||||||
2025-07-01 12:09:29,668 - INFO - Reading the temperature on Phillip's desk
|
|
||||||
2025-07-01 16:42:01,247 - INFO - Logger set up complete
|
|
||||||
2025-07-01 16:42:01,247 - INFO - Reading the temperature on Phillip's desk
|
|
||||||
2025-07-01 16:45:29,238 - INFO - Logger set up complete
|
|
||||||
2025-07-01 16:45:29,239 - INFO - Reading the temperature on Phillip's desk
|
|
||||||
2025-07-01 16:46:28,621 - INFO - Logger set up complete
|
|
||||||
2025-07-01 16:46:28,621 - INFO - Reading the temperature on Phillip's desk
|
|
||||||
2025-07-01 16:49:50,243 - INFO - Logger set up complete
|
|
||||||
2025-07-01 16:49:50,243 - INFO - Reading the temperature on Phillip's desk
|
|
||||||
2025-07-01 16:50:28,107 - INFO - Logger set up complete
|
|
||||||
2025-07-01 16:50:28,108 - INFO - Reading the temperature on Phillip's desk
|
|
||||||
2025-07-01 16:50:53,880 - INFO - Logger set up complete
|
|
||||||
2025-07-01 16:50:53,881 - INFO - Reading the temperature on Phillip's desk
|
|
||||||
2025-07-01 17:20:51,260 - INFO - Logger set up complete
|
|
||||||
2025-07-01 17:20:51,260 - INFO - Reading the temperature on Phillip's desk
|
|
||||||
2025-07-01 17:21:00,372 - INFO - Logger set up complete
|
|
||||||
2025-07-01 17:21:00,372 - INFO - Reading the temperature on Phillip's desk
|
|
||||||
2025-07-01 17:21:36,585 - INFO - Logger set up complete
|
|
||||||
2025-07-01 17:21:36,585 - INFO - Reading the temperature on Phillip's desk
|
|
Loading…
Reference in New Issue
Block a user